Skip to content

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

特性DecorationsCodeLens
可点击
显示位置行内/行尾行上方
典型用途高亮、标记引用数、运行测试
性能需手动管理范围两阶段解析

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 的区别?

特性DecorationsCodeLens
交互不可点击可点击执行命令
位置行内/行尾/gutter行上方
典型用途高亮、错误标记引用数、运行测试

Q3: 如何实现实时诊断?

使用 vscode.languages.createDiagnosticCollection 创建集合,在 onDidChangeTextDocument 中解析文档并调用 collection.set(uri, diagnostics) 更新。注意防抖以优化性能。

Q4: CompletionItemProvider 的 resolve 何时调用?

当用户选择某个补全项时,若该补全项未包含完整 detail/documentation,VSCode 会调用 resolveCompletionItem 延迟加载,避免初始列表时执行过多计算。

前端面试知识库