VSCode 语言特性 API
CodeLens、Decorations、Hover、Diagnostics 与智能补全
目录
CodeLens
概述
CodeLens 是显示在代码行上方的可点击链接,用于展示引用数量、运行测试等上下文信息。采用两阶段处理:先创建基础 CodeLens,再按需解析(resolve)以提升性能。
架构图
实现 CodeLensProvider
typescript
import * as vscode from 'vscode';
class MyCodeLensProvider implements vscode.CodeLensProvider {
private _onDidChangeCodeLenses = new vscode.EventEmitter<void>();
readonly onDidChangeCodeLenses = this._onDidChangeCodeLenses.event;
provideCodeLenses(
document: vscode.TextDocument,
token: vscode.CancellationToken
): vscode.CodeLens[] | Thenable<vscode.CodeLens[]> {
const codeLenses: vscode.CodeLens[] = [];
const text = document.getText();
// 匹配 function 关键字
const regex = /function\s+(\w+)/g;
let match;
while ((match = regex.exec(text)) !== null) {
const range = new vscode.Range(
document.positionAt(match.index),
document.positionAt(match.index + match[0].length)
);
// 创建阶段:可只提供 range,command 在 resolve 时填充
codeLenses.push(new vscode.CodeLens(range));
}
return codeLenses;
}
resolveCodeLens?(
codeLens: vscode.CodeLens,
token: vscode.CancellationToken
): vscode.CodeLens | Thenable<vscode.CodeLens> {
// 解析阶段:填充 command,可执行异步操作
codeLens.command = {
title: 'Run Function',
command: 'myExtension.runFunction',
arguments: [codeLens.range]
};
return codeLens;
}
refresh(): void {
this._onDidChangeCodeLenses.fire();
}
}
// 注册
export function activate(context: vscode.ExtensionContext) {
const provider = new MyCodeLensProvider();
context.subscriptions.push(
vscode.languages.registerCodeLensProvider(
{ scheme: 'file', language: 'javascript' },
provider
)
);
}package.json 贡献
json
{
"contributes": {
"configuration": {
"properties": {
"myExtension.codeLens.enable": {
"type": "boolean",
"default": true,
"description": "Enable CodeLens"
}
}
}
}
}Decorations
概述
Decorations 用于在编辑器中显示装饰(如高亮、下划线、图标),与 CodeLens 不同,不可点击。常用于语法高亮、错误标记、引用高亮等。
创建 DecorationType
typescript
// 范围装饰(整块高亮)
const decorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(255, 100, 100, 0.3)',
border: '1px solid red',
borderRadius: '2px'
});
// 行内装饰(行尾装饰)
const inlineDecoration = vscode.window.createTextEditorDecorationType({
after: {
contentText: ' ← deprecated',
color: new vscode.ThemeColor('editorWarning.foreground')
}
});
// 使用 gutter 图标
const iconDecoration = vscode.window.createTextEditorDecorationType({
gutterIconPath: vscode.Uri.file('/path/to/icon.png'),
gutterIconSize: 'contain'
});设置装饰
typescript
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const document = editor.document;
const ranges: vscode.Range[] = [];
// 查找要装饰的文本
const text = document.getText();
const regex = /TODO/g;
let match;
while ((match = regex.exec(text)) !== null) {
ranges.push(
new vscode.Range(
document.positionAt(match.index),
document.positionAt(match.index + match[0].length)
)
);
}
editor.setDecorations(decorationType, ranges);
// 清理
context.subscriptions.push(decorationType);Decorations vs CodeLens
| 特性 | Decorations | CodeLens |
|---|---|---|
| 可点击 | 否 | 是 |
| 显示位置 | 行内/行尾 | 行上方 |
| 典型用途 | 高亮、标记 | 引用数、运行测试 |
| 性能 | 需手动管理范围 | 两阶段解析 |
Hover
HoverProvider
typescript
class MyHoverProvider implements vscode.HoverProvider {
provideHover(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.Hover> {
const wordRange = document.getWordRangeAtPosition(position);
if (!wordRange) return null;
const word = document.getText(wordRange);
const markdown = new vscode.MarkdownString();
markdown.appendMarkdown(`**${word}**\n\n`);
markdown.appendMarkdown(`类型: \`string\`\n\n`);
markdown.appendCodeblock(`// 使用示例\nconst x = ${word};`, 'javascript');
return new vscode.Hover(markdown, wordRange);
}
}
context.subscriptions.push(
vscode.languages.registerHoverProvider(
{ scheme: 'file', language: 'javascript' },
new MyHoverProvider()
)
);Diagnostics
概述
Diagnostics 用于显示错误、警告、提示信息,出现在编辑器波浪线、Problems 面板中。
DiagnosticCollection
typescript
// 创建诊断集合
const collection = vscode.languages.createDiagnosticCollection('myExtension');
// 设置诊断
function updateDiagnostics(document: vscode.TextDocument) {
const diagnostics: vscode.Diagnostic[] = [];
const text = document.getText();
// 简单示例:检查 console.log
const regex = /console\.log/g;
let match;
while ((match = regex.exec(text)) !== null) {
const range = new vscode.Range(
document.positionAt(match.index),
document.positionAt(match.index + match[0].length)
);
diagnostics.push({
range,
message: 'Consider removing console.log in production',
severity: vscode.DiagnosticSeverity.Warning,
source: 'myExtension'
});
}
collection.set(document.uri, diagnostics);
}
// 监听文档变化
vscode.workspace.onDidChangeTextDocument((e) => {
if (e.document.fileName.endsWith('.js')) {
updateDiagnostics(e.document);
}
});
// 清理
context.subscriptions.push(collection);DiagnosticSeverity
typescript
vscode.DiagnosticSeverity.Error; // 0 红色
vscode.DiagnosticSeverity.Warning; // 1 黄色
vscode.DiagnosticSeverity.Information; // 2 蓝色
vscode.DiagnosticSeverity.Hint; // 3 灰色CompletionItem
CompletionItemProvider
typescript
class MyCompletionProvider implements vscode.CompletionItemProvider {
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
const linePrefix = document.getText(
new vscode.Range(new vscode.Position(position.line, 0), position)
);
const items: vscode.CompletionItem[] = [
new vscode.CompletionItem('myFunction', vscode.CompletionItemKind.Function),
new vscode.CompletionItem('myVariable', vscode.CompletionItemKind.Variable)
];
items[0].detail = 'Custom function';
items[0].documentation = new vscode.MarkdownString('Description of myFunction');
return items;
}
}
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
{ scheme: 'file', language: 'javascript' },
new MyCompletionProvider(),
'.', '"', "'"
)
);支持 resolve 的补全
typescript
class ResolvableCompletionProvider implements vscode.CompletionItemProvider {
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position
): vscode.CompletionItem[] {
return [
{ label: 'slowItem', kind: vscode.CompletionItemKind.Snippet }
];
}
resolveCompletionItem?(
item: vscode.CompletionItem,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.CompletionItem> {
// 延迟加载详情
item.detail = 'Loaded asynchronously';
item.documentation = new vscode.MarkdownString('Full description');
return item;
}
}DocumentSymbol
大纲视图
typescript
class MyDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
provideDocumentSymbols(
document: vscode.TextDocument,
token: vscode.CancellationToken
): vscode.ProviderResult<vscode.SymbolInformation[] | vscode.DocumentSymbol[]> {
const symbols: vscode.DocumentSymbol[] = [];
const text = document.getText();
const regex = /(function|class)\s+(\w+)/g;
let match;
while ((match = regex.exec(text)) !== null) {
const range = new vscode.Range(
document.positionAt(match.index),
document.positionAt(match.index + match[0].length)
);
symbols.push(
new vscode.DocumentSymbol(
match[2],
match[1],
vscode.SymbolKind.Function,
range,
range
)
);
}
return symbols;
}
}
context.subscriptions.push(
vscode.languages.registerDocumentSymbolProvider(
{ scheme: 'file', language: 'javascript' },
new MyDocumentSymbolProvider()
)
);高频面试题
Q1: CodeLens 的两阶段解析机制是什么?
创建阶段:provideCodeLens 快速返回 CodeLens 数组,只需 range,无需 command。
解析阶段:用户悬停或点击时触发 resolveCodeLens,此时可执行异步操作(如查询引用数),填充 command。
优势:避免打开文件时执行大量耗时操作,按需解析提升性能。
Q2: Decorations 与 CodeLens 的区别?
| 特性 | Decorations | CodeLens |
|---|---|---|
| 交互 | 不可点击 | 可点击执行命令 |
| 位置 | 行内/行尾/gutter | 行上方 |
| 典型用途 | 高亮、错误标记 | 引用数、运行测试 |
Q3: 如何实现实时诊断?
使用 vscode.languages.createDiagnosticCollection 创建集合,在 onDidChangeTextDocument 中解析文档并调用 collection.set(uri, diagnostics) 更新。注意防抖以优化性能。
Q4: CompletionItemProvider 的 resolve 何时调用?
当用户选择某个补全项时,若该补全项未包含完整 detail/documentation,VSCode 会调用 resolveCompletionItem 延迟加载,避免初始列表时执行过多计算。