程序性语言特性

程序性语言特性是由vscode.languages.*API提供的一系列智能编辑功能。在VS Code中有两种实现动态语言特性的途径。我们先以悬停提示为例:

  1. vscode.languages.registerHoverProvider('javascript', {
  2. provideHover(document, position, token) {
  3. return {
  4. contents: ['Hover Content']
  5. };
  6. }
  7. });

正如你所见,代码中vscode.languages.registerHoverProviderAPI可以很方便地在JS文件中提供悬停提示的内容。这个插件激活后,只要你悬停到了JS代码上,VS Code就会查询全部对JS注册了的HoverProvider然后在悬浮提示框中显示对应内容。你可以查看下面的语言功能列表,里面包含了VS Code API / LSP实现的语言功能。

那么一种实现就是使用了语言服务器协议的语言服务器。它的实现方式如下:

  • 一个为JS同时提供语言客户端和语言服务器的插件
  • 语言客户端就像普通插件一样,运行于Node.js插件主机环境中。这个插件激活后,会启动另一个进程——语言服务器,然后两者通过语言服务器协议进行通信。
  • 你悬停到JS代码上
  • VS Code通知语言客户端
  • 语言客户端向语言服务器发起请求,索要悬停的返回结果,最后再送回给VS Code
  • VS Code将结果展示在悬浮框中

这个过程可能看起来有些复杂,但是这么做主要有两个好处:

  • 语言服务器可以用任何语言实现
  • 语言服务器可以被多个编辑器重用,提供更加智能的编辑体验

深入指南,请移步至语言服务器插件指南

语言功能列表


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
registerColorProvider DocumentColor & Color Presentation
registerDocumentFormattingEditProvider Formatting
registerDocumentRangeFormattingEditProvider RangeFormatting
registerOnTypeFormattingEditProvider OnTypeFormatting
registerRenameProvider Rename & Prepare Rename
registerFoldingRangeProvider FoldingRange

提供诊断信息


诊断信息是提示代码问题的一种方式。

diagnostics

语言服务器协议

语言服务器需要向客户端发送textDocument/publishDiagnostics信息,这个信息中包含了诊断信息url的数组。

!> 注意:客户端不会主动向服务端请求信息,需要服务器将诊断信息推送到客户端。

直接实现
  1. let diagnosticCollection: vscode.DiagnosticCollection;
  2. export function activate(ctx: vscode.ExtensionContext): void {
  3. ...
  4. ctx.subscriptions.push(getDisposable());
  5. diagnosticCollection = vscode.languages.createDiagnosticCollection('go');
  6. ctx.subscriptions.push(diagnosticCollection);
  7. ...
  8. }
  9. function onChange() {
  10. let uri = document.uri;
  11. check(uri.fsPath, goConfig).then(errors => {
  12. diagnosticCollection.clear();
  13. let diagnosticMap: Map<string, vscode.Diagnostic[]> = new Map();
  14. errors.forEach(error => {
  15. let canonicalFile = vscode.Uri.file(error.file).toString();
  16. let range = new vscode.Range(error.line-1, error.startColumn, error.line-1, error.endColumn);
  17. let diagnostics = diagnosticMap.get(canonicalFile);
  18. if (!diagnostics) { diagnostics = []; }
  19. diagnostics.push(new vscode.Diagnostic(range, error.msg, error.severity));
  20. diagnosticMap.set(canonicalFile, diagnostics);
  21. });
  22. diagnosticMap.forEach((diags, file) => {
  23. diagnosticCollection.set(vscode.Uri.parse(file), diags);
  24. });
  25. })
  26. }

基础实现

只对打开的编辑器提供诊断,保证至少在每次保存文件时诊断一次。诊断信息最好能随编辑器的文档内容变化触发。

进阶实现

不仅仅为打开的编辑器提供诊断,而是诊断当前打开的文件目录中的所有资源,不论文件是被打开还是关闭。

提供补全建议


代码补全可以给用户提供内容感知建议。

code-completion

语言服务器协议

在接收响应的initialize方法中,你的语言服务器需要声明它是否能提供补全,以及它是否支持动态计算补全项的completionItem\resolve方法。

  1. {
  2. ...
  3. "capabilities" : {
  4. "completionProvider" : {
  5. "resolveProvider": "true",
  6. "triggerCharacters": [ '.' ]
  7. }
  8. ...
  9. }
  10. }
直接实现
  1. class GoCompletionItemProvider implements vscode.CompletionItemProvider {
  2. public provideCompletionItems(
  3. document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
  4. Thenable<vscode.CompletionItem[]> {
  5. ...
  6. }
  7. }
  8. export function activate(ctx: vscode.ExtensionContext): void {
  9. ...
  10. ctx.subscriptions.push(getDisposable());
  11. ctx.subscriptions.push(
  12. vscode.languages.registerCompletionItemProvider(
  13. GO_MODE, new GoCompletionItemProvider(), '.', '\"'));
  14. ...
  15. }

基础实现

不支持本功能

进阶实现

当用户挑选补全项时,动态计算补全项的相关信息,这条信息会浮现在补全项旁边。

显示悬浮提示


悬浮信息会展示在鼠标光标的下方,为用户提供符号/对象的相关信息,一般展示关于符号的类型和描述。

hovers

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "hoverProvider" : "true",
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/hover请求。

直接实现
  1. class GoHoverProvider implements HoverProvider {
  2. public provideHover(
  3. document: TextDocument, position: Position, token: CancellationToken):
  4. Thenable<Hover> {
  5. ...
  6. }
  7. }
  8. export function activate(ctx: vscode.ExtensionContext): void {
  9. ...
  10. ctx.subscriptions.push(
  11. vscode.languages.registerHoverProvider(
  12. GO_MODE, new GoHoverProvider()));
  13. ...
  14. }

基础实现 显示符号的类型和相关文档描述。

进阶实现 对方法名进行着色,就像你的源码一样

函数和方法签名


当用户输入函数和方法时,显示调用该方法的相关信息。

signature-help

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "signatureHelpProvider" : {
  5. "triggerCharacters": [ '(' ]
  6. }
  7. ...
  8. }
  9. }

另外,你的语言服务器还要能够响应textDocument/signatureHelp请求。

直接实现
  1. class GoSignatureHelpProvider implements SignatureHelpProvider {
  2. public provideSignatureHelp(
  3. document: TextDocument, position: Position, token: CancellationToken):
  4. Promise<SignatureHelp> {
  5. ...
  6. }
  7. }
  8. export function activate(ctx: vscode.ExtensionContext): void {
  9. ...
  10. ctx.subscriptions.push(
  11. vscode.languages.registerSignatureHelpProvider(
  12. GO_MODE, new GoSignatureHelpProvider(), '(', ','));
  13. ...
  14. }

基础实现

签名帮助需要包含参数的相关文档。

进阶实现

符号定义


允许用户查看变量/函数/方法的定义。

goto-definition

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "definitionProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/definition请求。

直接实现
  1. lass GoDefinitionProvider implements vscode.DefinitionProvider {
  2. public provideDefinition(
  3. document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
  4. Thenable<vscode.Location> {
  5. ...
  6. }
  7. }
  8. export function activate(ctx: vscode.ExtensionContext): void {
  9. ...
  10. ctx.subscriptions.push(
  11. vscode.languages.registerDefinitionProvider(
  12. GO_MODE, new GoDefinitionProvider()));
  13. ...
  14. }

基础实现

如果符号有多个定义,你可以显示多条定义。

进阶实现

查找符号的全部引用


允许用户在当前编辑器直接查看变量/函数/方法的定义的源代码。

find-references

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "referencesProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/references请求。

直接实现
  1. class GoReferenceProvider implements vscode.ReferenceProvider {
  2. public provideReferences(
  3. document: vscode.TextDocument, position: vscode.Position,
  4. options: { includeDeclaration: boolean }, token: vscode.CancellationToken):
  5. Thenable<vscode.Location[]> {
  6. ...
  7. }
  8. }
  9. export function activate(ctx: vscode.ExtensionContext): void {
  10. ...
  11. ctx.subscriptions.push(
  12. vscode.languages.registerReferenceProvider(
  13. GO_MODE, new GoReferenceProvider()));
  14. ...
  15. }

基础实现

为所有引用返回引用位置(资源的url和范围)

进阶实现

高亮匹配符号


允许用户在打开的编辑器中查看某个符号的全部匹配项。

document-highlights

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "documentHighlightProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/documentHighlight请求。

直接实现
  1. class GoDocumentHighlightProvider implements vscode.DocumentHighlightProvider {
  2. public provideDocumentHighlights(
  3. document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken):
  4. vscode.DocumentHighlight[] | Thenable<vscode.DocumentHighlight[]>;
  5. ...
  6. }
  7. }
  8. export function activate(ctx: vscode.ExtensionContext): void {
  9. ...
  10. ctx.subscriptions.push(
  11. vscode.languages.registerDocumentHighlightProvider(
  12. GO_MODE, new GoDocumentHighlightProvider()));
  13. ...
  14. }

基础实现

返回引用文档

进阶实现

显示当前文档中的符号定义


允许用户在打开的编辑器中快速跳转到任何符号定义。

document-symbols

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "documentSymbolProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/documentSymbol请求。

直接实现
  1. class GoDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
  2. public provideDocumentSymbols(
  3. document: vscode.TextDocument, token: vscode.CancellationToken):
  4. Thenable<vscode.SymbolInformation[]> {
  5. ...
  6. }
  7. }
  8. export function activate(ctx: vscode.ExtensionContext): void {
  9. ...
  10. ctx.subscriptions.push(
  11. vscode.languages.registerDocumentSymbolProvider(
  12. GO_MODE, new GoDocumentSymbolProvider()));
  13. ...
  14. }

基础实现

返回文档中的所有符号。将符号分类,如变量、函数、类、方法等。

进阶实现

显示文件夹中的符号定义


允许用户在打开的文件夹(工作区)中快速跳转到任何符号定义。

workspace-symbols

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "workspaceSymbolProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应workspace/symbol请求。

直接实现
  1. class GoWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
  2. public provideWorkspaceSymbols(
  3. query: string, token: vscode.CancellationToken):
  4. Thenable<vscode.SymbolInformation[]> {
  5. ...
  6. }
  7. }
  8. export function activate(ctx: vscode.ExtensionContext): void {
  9. ...
  10. ctx.subscriptions.push(
  11. vscode.languages.registerWorkspaceSymbolProvider(
  12. new GoWorkspaceSymbolProvider()));
  13. ...
  14. }

基础实现

返回文件夹中所有匹配的符号。将符号分类,如变量、函数、类、方法等。

进阶实现

处理错误和警告


为用户提供处理错误和警告的办法。如果有更正操作可用,就会在那个错误边上显示一个小灯泡。当用户点击灯泡的时候,会显示出操作列表。

quick-fixes

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "codeActionProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/codeAction请求。

直接实现
  1. class GoCodeActionProvider implements vscode.CodeActionProvider {
  2. public provideCodeActions(
  3. document: vscode.TextDocument, range: vscode.Range,
  4. context: vscode.CodeActionContext, token: vscode.CancellationToken):
  5. Thenable<vscode.Command[]> {
  6. ...
  7. }
  8. }
  9. export function activate(ctx: vscode.ExtensionContext): void {
  10. ...
  11. ctx.subscriptions.push(
  12. vscode.languages.registerCodeActionsProvider(
  13. GO_MODE, new GoCodeActionProvider()));
  14. ...
  15. }

基础实现

为错误/警告提供更正操作。

进阶实现

提供源码级别的操作,如重构、提取方法等。

CodeLens - 为源代码提供更多操作


为用户弹出一个可以操作、包含上下文信息的分隔弹出框。

code-lens

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能,以及它是否支持将codeLens\resolve方法绑定到CodeLens的命令上。

  1. {
  2. ...
  3. "capabilities" : {
  4. "codeLensProvider" : {
  5. "resolveProvider": "true"
  6. }
  7. ...
  8. }
  9. }

另外,你的语言服务器还要能够响应textDocument/codeLens请求。

直接实现
  1. class GoCodeLensProvider implements vscode.CodeLensProvider {
  2. public provideCodeLenses(document: TextDocument, token: CancellationToken):
  3. CodeLens[] | Thenable<CodeLens[]> {
  4. ...
  5. }
  6. public resolveCodeLens?(codeLens: CodeLens, token: CancellationToken):
  7. CodeLens | Thenable<CodeLens> {
  8. ...
  9. }
  10. }
  11. export function activate(ctx: vscode.ExtensionContext): void {
  12. ...
  13. ctx.subscriptions.push(
  14. vscode.languages.registerCodeLensProvider(
  15. GO_MODE, new GoCodeLensProvider()));
  16. ...
  17. }

基础实现

为文档提供CodeLens结果。

进阶实现

将Codelens结果绑定到响应codeLens/resolve的命令上。

颜色拾取器


允许用户在文件中预览和修改颜色。

color-decorators

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "colorProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/documentColortextDocument/colorPresentation请求。

直接实现
  1. class GoColorProvider implements vscode.DocumentColorProvider {
  2. public provideDocumentColors(
  3. document: vscode.TextDocument, token: vscode.CancellationToken):
  4. Thenable<vscode.ColorInformation[]> {
  5. ...
  6. }
  7. public provideColorPresentations(
  8. color: Color, context: { document: TextDocument, range: Range }, token: vscode.CancellationToken):
  9. Thenable<vscode.ColorPresentation[]> {
  10. ...
  11. }
  12. }
  13. export function activate(ctx: vscode.ExtensionContext): void {
  14. ...
  15. ctx.subscriptions.push(
  16. vscode.languages.registerColorProvider(
  17. GO_MODE, new GoColorProvider()));
  18. ...
  19. }

基础实现

返回文档中的全部颜色引用。在颜色面板岁支持色彩格式(如rgb(…),hsl(…))

进阶实现

格式化代码


提供整个文档的代码格式化支持。

format-document

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "documentFormattingProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/formatting请求。

直接实现
  1. class GoDocumentFormatter implements vscode.DocumentFormattingEditProvider {
  2. public formatDocument(document: vscode.TextDocument):
  3. Thenable<vscode.TextEdit[]> {
  4. ...
  5. }
  6. }
  7. export function activate(ctx: vscode.ExtensionContext): void {
  8. ...
  9. ctx.subscriptions.push(
  10. vscode.languages.registerDocumentFormattingEditProvider(
  11. GO_MODE, new GoDocumentFormatter()));
  12. ...
  13. }

基础实现

不提供格式化支持

进阶实现

你应该尽量减少代码格式化的影响。稍有不慎,诊断功能就可能失效。

格式化选中区域


为用户选中区域提供代码格式化支持。

format-document-range

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "documentRangeFormattingProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/rangeFormatting请求。

直接实现
  1. class GoDocumentRangeFormatter implements vscode.DocumentRangeFormattingEditProvider{
  2. public provideDocumentRangeFormattingEdits(
  3. document: vscode.TextDocument, range: vscode.Range,
  4. options: vscode.FormattingOptions, token: vscode.CancellationToken):
  5. Thenable<vscode.TextEdit[]>;
  6. ...
  7. }
  8. }
  9. export function activate(ctx: vscode.ExtensionContext): void {
  10. ...
  11. ctx.subscriptions.push(
  12. vscode.languages.registerDocumentRangeFormattingEditProvider(
  13. GO_MODE, new GoDocumentRangeFormatter()));
  14. ...
  15. }

基础实现

不提供格式化支持

进阶实现

你应该尽量减少代码格式化的影响。稍有不慎,诊断功能就可能失效。

随用户输入格式化代码


支持用户输入时动态调整文本格式。

!> 注意:用户设置中的editor.formatOnType控制着本功能。

format-on-type

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。服务器还得告诉客户端哪些字符需要被格式化,moreTriggerCharacters是可选的。

  1. {
  2. ...
  3. "capabilities" : {
  4. "documentOnTypeFormattingProvider" : {
  5. "firstTriggerCharacter": "}",
  6. "moreTriggerCharacter": [";", ","]
  7. }
  8. ...
  9. }
  10. }

另外,你的语言服务器还要能够响应textDocument/onTypeFormatting请求。

直接实现
  1. class GoOnTypingFormatter implements vscode.OnTypeFormattingEditProvider{
  2. public provideOnTypeFormattingEdits(
  3. document: vscode.TextDocument, position: vscode.Position,
  4. ch: string, options: vscode.FormattingOptions, token: vscode.CancellationToken):
  5. Thenable<vscode.TextEdit[]>;
  6. ...
  7. }
  8. }
  9. export function activate(ctx: vscode.ExtensionContext): void {
  10. ...
  11. ctx.subscriptions.push(
  12. vscode.languages.registerOnTypeFormattingEditProvider(
  13. GO_MODE, new GoOnTypingFormatter()));
  14. ...
  15. }

基础实现

不提供格式化支持

进阶实现

你应该尽量减少代码格式化的影响。稍有不慎,诊断功能就可能失效。

重命名符号


允许用户重命名符号,并更新对应符号的全部引用。

rename

语言服务器协议

为了响应请求initialize方法,语言服务器需要声明它能提供这项功能。

  1. {
  2. ...
  3. "capabilities" : {
  4. "renameProvider" : "true"
  5. ...
  6. }
  7. }

另外,你的语言服务器还要能够响应textDocument/rename请求。

直接实现
  1. class GoRenameProvider implements vscode.RenameProvider {
  2. public provideRenameEdits(
  3. document: vscode.TextDocument, position: vscode.Position,
  4. newName: string, token: vscode.CancellationToken):
  5. Thenable<vscode.WorkspaceEdit> {
  6. ...
  7. }
  8. }
  9. export function activate(ctx: vscode.ExtensionContext): void {
  10. ...
  11. ctx.subscriptions.push(
  12. vscode.languages.registerRenameProvider(
  13. GO_MODE, new GoRenameProvider()));
  14. ...
  15. }

基础实现

不提供本功能支持。

进阶实现

返回工作区中全部需要生效的编辑区,比如当一个符号在项目的各个地方都被引用时。