概述
编程语言特性(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 Code
hover了一段JavaScript代码 VS Code
将hover告知LC
LC
从LS
处查询hover的结果,让后将其返回给VS Code
VS Code
展示hover的结果
上述过程比较繁琐,但有两个比较大的长处:
LS
是语言无关的,可以用任何开发语言LS
是可以在其它编辑器里被复用
以后会有专门文章介绍更多关于LS
的信息。本文将会重点介绍VS Code
提供的各种编程语言特性
特性列表
下面这个列表列出了VS Code API
和对应的LSP
,本文后面内容皆是根据此列表而来
特性详情
代码诊断
我们利用代码诊断指出当前代码存在的问题
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);
});
})
}
基础应用
在每次保存的时候做最低限度的代码诊断,如果做的好一些的话,可以仅对本次保存更改的内容做诊断,仅针对打开的文件
进阶应用
给文件夹下的每一个文件无论是否打开,都做代码诊断
代码补全建议
代码补全主要是为用户提供与上下文相关的建议
LSP方式
LS
在onInitialize
方法中申明支持代码补全以及是否支持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功能会展示当前鼠标下代码的信息,通常是描述或数据类型
LSP方式
需要LS
在onInitialize
方法中申明支持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()));
// ...
}
基础应用
展示类型信息,如果可能的话增加一些备注
进阶应用
用与代码着色相同的样式着色方法签名
展示函数、方法的帮助信息
当用户输入方法或函数时展示其信息
LSP方式
需要LS
在onInitialize
方法中申明支持帮助功能:
{
...
"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方法提供参数的文档
展示定义
允许用户在变量、函数、方法使用的地方查看其定义
LSP方式
需要LS
在onInitialize
方法中申明支持跳转到定义位置的功能:
{
...
"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()));
...
}
基础应用
支持展示多个定义
找到所有的引用
支持用户查看某个变量、函数、方法、符号的所有使用位置
LSP方式
需要LS
在onInitialize
方法中申明支持符号引用定位的功能:
{
...
"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和范围)
符号高亮
方便用户查看整个文档中同一个字符的使用情况
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()));
...
}
基础应用
返回符号的引用位置
显示当前文档的全部定义
支持用户快速浏览一个打开的文档里的全部符号定义
LSP方式
需要LS
在onInitialize
方法中申明支持符号文档定位的功能:
{
...
"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、方法等
展示文件夹里的所有字符定义
支持用户快速浏览工作区内的所有符号定义
LSP方式
需要LS
在onInitialize
方法中申明支持全局符号定位的功能:
{
...
"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、方法等
提供可能的错误或报警修复信息
在错误或警告旁边向用户提供可能的纠正措施,通过一个灯泡来提示用户,当用户点击了灯泡时显示一系列可用的修复措施
LSP方式
需要LS
在onInitialize
方法中申明支持编码指导的功能:
{
...
"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
为用户提供可操作的上下文信息,显示在源代码中
LSP方式
需要LS
在onInitialize
方法中申明支持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
的回应
显示取色器
支持用户预览和修改文档中的颜色
LSP方式
需要LS
在onInitialize
方法中申明提供颜色信息的功能:
{
...
"capabilities" : {
"colorProvider" : "true"
...
}
}
此外,LS
需要对textDocument/documentColor
和textDocument/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)提供颜色表示
文档代码格式化
提供用户格式化整个文档的能力
LSP方式
需要LS
在onInitialize
方法中申明支持文档格式化功能:
{
...
"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()));
...
}
基础应用
不提供格式化支持
进阶功能
返回尽可能小的导致源代码格式化的文本编辑。以确保如诊断结果的调整是正确的,而不是丢失
格式化选中的内容
LSP方式
需要LS
在onInitialize
方法中申明支持格式化选中内容的功能:
{
...
"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
是否启用此功能
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()));
...
}
基础应用
不提供格式化支持
进阶功能
返回尽可能小的导致源代码格式化的文本编辑。以确保如诊断结果的调整是正确的,而不是丢失
重命名
允许用户重命名一个符号并且自动将所有引用都更新
LSP方式
需要LS
在onInitialize
方法中申明支持重命名功能:
{
...
"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()));
...
}
基础应用
不提供重命名
进阶应用
返回需要执行的所有工作空间编辑列表,例如,跨文件的字符引用编辑