如何开发VSCode扩展实现代码局部变暗效果?
实现VSCode扩展的代码选择性变暗功能
核心方案:使用VSCode文本装饰器(Text Editor Decorations)API
这是官方支持的最直接实现方式,无需修改原有语法token,只需在非焦点区域叠加自定义样式即可达到变暗效果。
具体实现步骤
1. 创建自定义装饰类型
针对Dark Mode定义变暗后的文本样式,包括深色前景色和取消加粗:
// 根据原有高亮色调定义对应深色值,比如亮蓝色关键字对应暗蓝色,亮黄色名称对应暗黄色 const dimKeywordDecoration = vscode.window.createTextEditorDecorationType({ color: '#3a4b70', // 暗蓝色,匹配原有亮蓝色关键字 fontWeight: 'normal' }); const dimNameDecoration = vscode.window.createTextEditorDecorationType({ color: '#8a7a4c', // 暗黄色,匹配原有亮黄色名称 fontWeight: 'normal' }); // 如果不需要区分token类型,可使用全局变暗装饰(复用主题内置样式,适配性更好) const globalDimDecoration = vscode.window.createTextEditorDecorationType({ color: { id: 'editor.dimmedForeground' }, fontWeight: 'normal' });
2. 计算非焦点区域范围
获取编辑器选中的焦点区域,推导文档中其余需要变暗的范围:
function getNonFocusRanges(editor: vscode.TextEditor, focusRange: vscode.Range): vscode.Range[] { const nonRanges: vscode.Range[] = []; const doc = editor.document; const docStart = new vscode.Position(0, 0); // 添加焦点区域之前的内容范围 if (docStart.isBefore(focusRange.start)) { nonRanges.push(new vscode.Range(docStart, focusRange.start)); } // 添加焦点区域之后的内容范围 const docEnd = doc.lineAt(doc.lineCount - 1).range.end; if (focusRange.end.isBefore(docEnd)) { nonRanges.push(new vscode.Range(focusRange.end, docEnd)); } return nonRanges; }
3. 应用装饰到非焦点区域
将创建的装饰类型应用到计算出的非焦点范围:
function updateDimDecorations() { const editor = vscode.window.activeTextEditor; if (!editor) return; const selection = editor.selection; const focusRange = new vscode.Range(selection.start, selection.end); const nonFocusRanges = getNonFocusRanges(editor, focusRange); // 若使用全局变暗,直接应用 editor.setDecorations(globalDimDecoration, nonFocusRanges); // 若需区分token类型,需结合语义token筛选后应用对应装饰(见下文进阶实现) }
4. 监听编辑器状态变化,动态更新装饰
监听选中区域变化、主题切换等事件,确保装饰实时生效:
// 监听选中区域变化 vscode.window.onDidChangeTextEditorSelection(event => { if (event.textEditor === vscode.window.activeTextEditor) { updateDimDecorations(); } }); // 监听主题切换,重新创建适配当前主题的装饰 vscode.window.onDidChangeActiveColorTheme(() => { // 销毁旧装饰 globalDimDecoration.dispose(); dimKeywordDecoration.dispose(); dimNameDecoration.dispose(); // 重新创建装饰(重复步骤1的装饰创建逻辑) updateDimDecorations(); });
进阶实现:区分token类型的精准变暗
如果需要针对关键字、名称等不同token类型设置不同的变暗效果,可结合vscode.languages.getDocumentSemanticTokens API获取当前文档的语义token,筛选非焦点区域的token并应用对应装饰:
async function applyTokenSpecificDim(editor: vscode.TextEditor, focusRange: vscode.Range) { const doc = editor.document; const semanticTokens = await vscode.languages.getDocumentSemanticTokens(doc); if (!semanticTokens) return; const keywordRanges: vscode.Range[] = []; const nameRanges: vscode.Range[] = []; // 解码语义token(处理token的偏移量、长度、类型索引) const data = semanticTokens.data; let prevLine = 0; let prevChar = 0; for (let i = 0; i < data.length; i += 5) { const deltaLine = data[i]; const deltaChar = data[i + 1]; const length = data[i + 2]; const tokenTypeIndex = data[i + 3]; const line = prevLine + deltaLine; const startChar = deltaLine === 0 ? prevChar + deltaChar : deltaChar; const endChar = startChar + length; const tokenRange = new vscode.Range(line, startChar, line, endChar); // 判断token是否在非焦点区域 if (!focusRange.contains(tokenRange)) { // 根据token类型索引匹配对应类型(需结合当前语言的token类型定义) const tokenType = semanticTokens.tokenTypes[tokenTypeIndex]; if (tokenType === 'keyword') { keywordRanges.push(tokenRange); } else if (tokenType === 'variable' || tokenType === 'function') { nameRanges.push(tokenRange); } } prevLine = line; prevChar = endChar; } // 应用对应装饰 editor.setDecorations(dimKeywordDecoration, keywordRanges); editor.setDecorations(dimNameDecoration, nameRanges); }
对原有思路的说明
- 修改原有token不可行:VSCode不允许直接访问或修改已有语义token provider的输出,
DocumentSemanticTokensProvider仅用于自定义token的提供,无法覆盖或修改内置provider的结果。 - 无需绘制半透明矩形:文本装饰器API直接支持设置文本的颜色、字体粗细等样式,完全可以实现代码变暗的需求,无需额外绘制图形。
内容的提问来源于stack exchange,提问作者James Koppel
相关产品推荐
相关产品推荐

