VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features

go (57) 2023-03-24 21:54

大家好,我是编程小6,很高兴遇见你,有问题可以及时留言哦。

概述

编程语言特性(Programmatic Language Features)是由 vscode.languages.* 系列接口提供的智能编辑能力,通常有两种方式来提供动态语言特性,以Hover为例:

vscode.languages.registerHoverProvider('javascript', {
    provideHover(document, position, token) {
        return {
            contents: ['Hover Content']
        };
    }
});

如上所见 vscode.languages.registerHoverProvider 接口实现了一个方便的为JavaScript文件提供hover contents的方式。本文第一章节的“语言特性列表”可以帮助你找到插件所需要的API/LSP

除此之外,另一种方式是实现一个遵循语言服务器协议(Language Server Protocol,下文简称LSP)的语言服务器(Language Server),其原理如下:

  • 插件提供一个语言客户端(Language Client,下文简称LC)和一个语言服务器(Language Server,以下简称LS)
  • LC可以看做是一个基本的VS Code插件,运行在Node.js插件上下文中,当LC被激活时它将会启动LS进程并利用LSP与之通信
  • 用户在VS Codehover了一段JavaScript代码
  • VS Code将hover告知LC
  • LCLS处查询hover的结果,让后将其返回给VS Code
  • VS Code展示hover的结果

上述过程比较繁琐,但有两个比较大的长处:

  • LS是语言无关的,可以用任何开发语言
  • LS是可以在其它编辑器里被复用

以后会有专门文章介绍更多关于LS的信息。本文将会重点介绍VS Code提供的各种编程语言特性

特性列表

下面这个列表列出了VS Code API和对应的LSP,本文后面内容皆是根据此列表而来

VS Code API LSP method
createDiagnosticCollection PublishDiagnostics
registerCompletionItemProvider Completion & Completion Resolve
registerHoverProvider Hover
registerSignatureHelpProvider SignatureHelp
registerDefinitionProvider Definition
registerTypeDefinitionProvider TypeDefinition
registerImplementationProvider Implementation
registerReferenceProvider References
registerDocumentHighlightProvider DocumentHighlight
registerDocumentSymbolProvider DocumentSymbol
registerCodeActionsProvider CodeAction
registerCodeLensProvider CodeLens & CodeLens Resolve
registerDocumentLinkProvider DocumentLink & DocumentLink Resolve
registerColorProvider DocumentColor & Color Presentation
registerDocumentFormattingEditProvider Formatting
registerDocumentRangeFormattingEditProvider RangeFormatting
registerOnTypeFormattingEditProvider OnTypeFormatting
registerRenameProvider Rename & Prepare Rename
registerFoldingRangeProvider FoldingRange

特性详情

代码诊断

我们利用代码诊断指出当前代码存在的问题

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第1张

LSP方式

LS发送textDocument/publishDiagnostics消息给LC,消息是一个数组,每一项是诊断结果。需要注意的是,消息的传递不是LC发送请求,而是由LS主动推送给LC

API方式

let diagnosticCollection: vscode.DiagnosticCollection;

export function activate(ctx: vscode.ExtensionContext): void {
    // ...
    ctx.subscriptions.push(getDisposable());
    diagnosticCollection = vscode.languages.createDiagnosticCollection('go');
    ctx.subscriptions.push(diagnosticCollection);
    // ...
}

function onChange() {
    let uri = document.uri;
    check(uri.fsPath, goConfig).then(errors => {
        diagnosticCollection.clear();
        let diagnosticMap: Map<string, vscode.Diagnostic[]> = new Map();
        errors.forEach(error => {
            let canonicalFile = vscode.Uri.file(error.file).toString();
            let range = new vscode.Range(error.line - 1, error.startColumn, error.line - 1, error.endColumn);
            let diagnostics = diagnosticMap.get(canonicalFile);
            if (!diagnostics) { diagnostics = []; }
            diagnostics.push(new vscode.Diagnostic(range, error.msg, error.severity));
            diagnosticMap.set(canonicalFile, diagnostics);
        });
        diagnosticMap.forEach((diags, file) => {
            diagnosticCollection.set(vscode.Uri.parse(file), diags);
        });
    })
}

基础应用

在每次保存的时候做最低限度的代码诊断,如果做的好一些的话,可以仅对本次保存更改的内容做诊断,仅针对打开的文件

进阶应用

给文件夹下的每一个文件无论是否打开,都做代码诊断


代码补全建议

代码补全主要是为用户提供与上下文相关的建议

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第2张

LSP方式

LSonInitialize方法中申明支持代码补全以及是否支持completionItem\resolve方法提供额外的代码补全信息

{
    ...
    "capabilities" : {
        "completionProvider" : {
            "resolveProvider": "true",
            "triggerCharacters": [ '.' ]
        }
        ...
    }
}

API方式

class GoSignatureHelpProvider implements SignatureHelpProvider {
    public provideSignatureHelp(
        document: TextDocument, position: Position, token: CancellationToken):
        Promise<SignatureHelp> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerSignatureHelpProvider(
            GO_MODE, new GoSignatureHelpProvider(), '(', ','));
    ...
}
class GoCompletionItemProvider implements vscode.CompletionItemProvider {
    public provideCompletionItems(
        document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
        Thenable<vscode.CompletionItem[]> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(getDisposable());
    ctx.subscriptions.push(
        vscode.languages.registerCompletionItemProvider(
            GO_MODE, new GoCompletionItemProvider(), '.', '\"'));
    ...
}

基础应用

不提供completionItem\resolve

进阶应用

提供completionItem\resolve支持,给用户额外的复杂建议的附加信息


展示hover

hover功能会展示当前鼠标下代码的信息,通常是描述或数据类型

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第3张

LSP方式

需要LSonInitialize方法中申明支持hover功能:

{
    ...
    "capabilities" : {
        "hoverProvider" : "true",
        ...
    }
}

此外,LS需要对textDocument/hover做出回应

API方式

class GoHoverProvider implements HoverProvider {
    public provideHover(
        document: TextDocument, position: Position, token: CancellationToken):
        Thenable<Hover> {
    // ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    // ...
    ctx.subscriptions.push(
        vscode.languages.registerHoverProvider(
            GO_MODE, new GoHoverProvider()));
    // ...
}

基础应用

展示类型信息,如果可能的话增加一些备注

进阶应用

用与代码着色相同的样式着色方法签名


展示函数、方法的帮助信息

当用户输入方法或函数时展示其信息

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第4张

LSP方式

需要LSonInitialize方法中申明支持帮助功能:

{
    ...
    "capabilities" : {
        "signatureHelpProvider" : {
            "triggerCharacters": [ '(' ]
        }
        ...
    }
}

此外,LS需要对textDocument/signatureHelp做出回应

API方式

class GoSignatureHelpProvider implements SignatureHelpProvider {
    public provideSignatureHelp(
        document: TextDocument, position: Position, token: CancellationToken):
        Promise<SignatureHelp> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerSignatureHelpProvider(
            GO_MODE, new GoSignatureHelpProvider(), '(', ','));
    ...
}

基础应用

为函数或class方法提供参数的文档


展示定义

允许用户在变量、函数、方法使用的地方查看其定义

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第5张

LSP方式

需要LSonInitialize方法中申明支持跳转到定义位置的功能:

{
    ...
    "capabilities" : {
        "definitionProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/definition做出回应

API方式

class GoDefinitionProvider implements vscode.DefinitionProvider {
    public provideDefinition(
        document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
        Thenable<vscode.Location> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerDefinitionProvider(
            GO_MODE, new GoDefinitionProvider()));
    ...
}

基础应用

支持展示多个定义


找到所有的引用

支持用户查看某个变量、函数、方法、符号的所有使用位置

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第6张

LSP方式

需要LSonInitialize方法中申明支持符号引用定位的功能:

{
    ...
    "capabilities" : {
        "referencesProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/references做出回应

API方式

class GoReferenceProvider implements vscode.ReferenceProvider {
    public provideReferences(
        document: vscode.TextDocument, position: vscode.Position,
        options: { includeDeclaration: boolean }, token: vscode.CancellationToken):
        Thenable<vscode.Location[]> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerReferenceProvider(
            GO_MODE, new GoReferenceProvider()));
    ...
}

基础应用

返回所有的引用位置(文件URI和范围)

符号高亮

方便用户查看整个文档中同一个字符的使用情况

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第7张

LSP方式

需要LS在onInitialize方法中申明支持符号文档定位的功能:

{
    ...
    "capabilities" : {
        "documentHighlightProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/documentHighlight做出回应

API方式

class GoDocumentHighlightProvider implements vscode.DocumentHighlightProvider {
    public provideDocumentHighlights(
        document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
        vscode.DocumentHighlight[] | Thenable<vscode.DocumentHighlight[]>;
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerDocumentHighlightProvider(
            GO_MODE, new GoDocumentHighlightProvider()));
    ...
}

基础应用

返回符号的引用位置


显示当前文档的全部定义

支持用户快速浏览一个打开的文档里的全部符号定义

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第8张

LSP方式

需要LSonInitialize方法中申明支持符号文档定位的功能:

{
    ...
    "capabilities" : {
        "documentSymbolProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/documentSymbol做出回应

API方式

class GoDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
    public provideDocumentSymbols(
        document: vscode.TextDocument, token: vscode.CancellationToken):
        Thenable<vscode.SymbolInformation[]> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerDocumentSymbolProvider(
            GO_MODE, new GoDocumentSymbolProvider()));
    ...
}

基础应用

返回一个文档里的全部应用,定义好符号的类型,如变量、函数、class、方法等


展示文件夹里的所有字符定义

支持用户快速浏览工作区内的所有符号定义

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第9张

LSP方式

需要LSonInitialize方法中申明支持全局符号定位的功能:

{
    ...
    "capabilities" : {
        "workspaceSymbolProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/symbol做出回应

API方式

class GoWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
    public provideWorkspaceSymbols(
        query: string, token: vscode.CancellationToken):
        Thenable<vscode.SymbolInformation[]> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerWorkspaceSymbolProvider(
            new GoWorkspaceSymbolProvider()));
    ...
}

基础应用

返回工作区内全部的字符定义,定义好符号的类型,如变量、函数、class、方法等


提供可能的错误或报警修复信息

在错误或警告旁边向用户提供可能的纠正措施,通过一个灯泡来提示用户,当用户点击了灯泡时显示一系列可用的修复措施

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第10张

LSP方式

需要LSonInitialize方法中申明支持编码指导的功能:

{
    ...
    "capabilities" : {
        "codeActionProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/codeAction做出回应

API方式

class GoCodeActionProvider implements vscode.CodeActionProvider {
    public provideCodeActions(
        document: vscode.TextDocument, range: vscode.Range,
        context: vscode.CodeActionContext, token: vscode.CancellationToken):
        Thenable<vscode.Command[]> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerCodeActionsProvider(
            GO_MODE, new GoCodeActionProvider()));
    ...
}

基础应用

提供错误或告警的修复措施

进阶应用、

提供诸如代码重构等自动化措施


CodeLens

为用户提供可操作的上下文信息,显示在源代码中

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第11张

LSP方式

需要LSonInitialize方法中申明支持CodeLens功能以及是否支持codeLens\resolve

{
    ...
    "capabilities" : {
        "codeLensProvider" : {
            "resolveProvider": "true"
        }
        ...
    }
}

此外,LS需要对textDocument/codeLens做出回应

API方式

class GoCodeLensProvider implements vscode.CodeLensProvider {
    public provideCodeLenses(document: TextDocument, token: CancellationToken):
        CodeLens[] | Thenable<CodeLens[]> {
    ...
    }

    public resolveCodeLens?(codeLens: CodeLens, token: CancellationToken):
         CodeLens | Thenable<CodeLens> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerCodeLensProvider(
            GO_MODE, new GoCodeLensProvider()));
    ...
}

基础应用

定义可用于文档的CodeLens结果

进阶应用

CodeLens结果绑定给一个命令作为对codeLens/resolve的回应


显示取色器

支持用户预览和修改文档中的颜色

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第12张

LSP方式

需要LSonInitialize方法中申明提供颜色信息的功能:

{
    ...
    "capabilities" : {
        "colorProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/documentColortextDocument/colorPresentation做出回应

API方式

class GoColorProvider implements vscode.DocumentColorProvider {
    public provideDocumentColors(
        document: vscode.TextDocument, token: vscode.CancellationToken):
        Thenable<vscode.ColorInformation[]> {
    ...
    }
    public provideColorPresentations(
        color: Color, context: { document: TextDocument, range: Range }, token: vscode.CancellationToken):
        Thenable<vscode.ColorPresentation[]> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerColorProvider(
            GO_MODE, new GoColorProvider()));
    ...
}

基础应用

返回文件中的所有颜色,为支持的颜色格式(如rgb、hsl)提供颜色表示


文档代码格式化

提供用户格式化整个文档的能力

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第13张

LSP方式

需要LSonInitialize方法中申明支持文档格式化功能:

{
    ...
    "capabilities" : {
        "documentFormattingProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/formatting做出回应

API方式

class GoDocumentFormatter implements vscode.DocumentFormattingEditProvider {
    public formatDocument(document: vscode.TextDocument):
        Thenable<vscode.TextEdit[]> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerDocumentFormattingEditProvider(
            GO_MODE, new GoDocumentFormatter()));
    ...
}

基础应用

不提供格式化支持

进阶功能

返回尽可能小的导致源代码格式化的文本编辑。以确保如诊断结果的调整是正确的,而不是丢失


格式化选中的内容

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第14张

LSP方式

需要LSonInitialize方法中申明支持格式化选中内容的功能:

{
    ...
    "capabilities" : {
        "documentRangeFormattingProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/rangeFormatting做出回应

API方式

class GoDocumentRangeFormatter implements vscode.DocumentRangeFormattingEditProvider{
    public provideDocumentRangeFormattingEdits(
        document: vscode.TextDocument, range: vscode.Range,
        options: vscode.FormattingOptions, token: vscode.CancellationToken):
        Thenable<vscode.TextEdit[]>;
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerDocumentRangeFormattingEditProvider(
            GO_MODE, new GoDocumentRangeFormatter()));
    ...
}

基础应用

不提供格式化支持

进阶功能

返回尽可能小的导致源代码格式化的文本编辑。以确保如诊断结果的调整是正确的,而不是丢失


渐进式自动格式化

支持用户在键入一行后自动格式化,用户配置的editor.formatOnType是否启用此功能

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第15张

LSP方式

需要LS在onInitialize方法中申明支持自动格式化的功能,同时还需要告知LC哪个字符会触发自动格式化,moreTriggerCharacters是可选的:

{
    ...
    "capabilities" : {
        "documentOnTypeFormattingProvider" : {
            "firstTriggerCharacter": "}",
            "moreTriggerCharacter": [";", ","]
        }
        ...
    }
}

此外,LS需要对textDocument/onTypeFormatting做出回应

API方式

class GoOnTypingFormatter implements vscode.OnTypeFormattingEditProvider{
    public provideOnTypeFormattingEdits(
        document: vscode.TextDocument, position: vscode.Position,
        ch: string, options: vscode.FormattingOptions, token: vscode.CancellationToken):
        Thenable<vscode.TextEdit[]>;
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerOnTypeFormattingEditProvider(
            GO_MODE, new GoOnTypingFormatter()));
    ...
}

基础应用

不提供格式化支持

进阶功能

返回尽可能小的导致源代码格式化的文本编辑。以确保如诊断结果的调整是正确的,而不是丢失


重命名

允许用户重命名一个符号并且自动将所有引用都更新

VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features_https://bianchenghao6.com/blog_go_第16张

LSP方式

需要LSonInitialize方法中申明支持重命名功能:

{
    ...
    "capabilities" : {
        "renameProvider" : "true"
        ...
    }
}

此外,LS需要对textDocument/references做出回应

API方式

class GoRenameProvider implements vscode.RenameProvider {
    public provideRenameEdits(
        document: vscode.TextDocument, position: vscode.Position,
        newName: string, token: vscode.CancellationToken):
        Thenable<vscode.WorkspaceEdit> {
    ...
    }
}

export function activate(ctx: vscode.ExtensionContext): void {
    ...
    ctx.subscriptions.push(
        vscode.languages.registerRenameProvider(
            GO_MODE, new GoRenameProvider()));
    ...
}

基础应用

不提供重命名

进阶应用

返回需要执行的所有工作空间编辑列表,例如,跨文件的字符引用编辑

相关文章

  • VS Code插件开发教程(1) Overview

  • VS Code插件开发教程(2) 起步 Get Started

  • VS Code插件开发教程(3) 插件能力一览 Capabilities

  • VS Code插件开发教程(4) 插件指南 Extension Guidesd

  • VS Code插件开发教程(5) 命令的使用 Command

  • VS Code插件开发教程(6) 颜色主题一览 Color Theme

  • VS Code插件开发教程(7) 树视图 Tree View

  • VS Code插件开发教程(8) Webview

  • VS Code插件开发教程(9)构建自定义编辑器 Custom Editor

  • VS Code插件开发教程(10)编程式语言特性 Programmatic Language Features

  • VS Code插件开发教程(11)语言服务器入门 Language Server Extension Guide

发表回复