本文作者为 360 奇舞团前端开发工程师
本文将以
CodeLens
功能为例,通过
vscode.languages.*
API 调用来讲解如何使用 Visual Studio Code 插件开发中的
语言功能
(Language Feature)。
Visual Studio Code 的
语言功能
可以提供智能编辑能力。VS Code 本身并不提供内置的语言支持,而是提供了一组 API 来实现丰富的语言功能。例如,VS Code 包含一个 HTML 扩展,使 VS Code 能够为 HTML 文件显示语法高亮。
CodeLens
则是 Visual Studio Code 中一项非常实用的功能,它可以为开发者
提供关于代码的上下文信息
和
快速操作
的能力。
CodeLens 在
编辑器中的特定代码行上方显示可操作的、与上下文相关的信息
。这些信息可以是代码引用次数、单元测试结果、代码更改历史、Bug 追踪、任务关联等。
如下图示例:
VS Code 的语言功能(Language Feature)
语言功能大致分为两类:
声明式语言功能
程序化语言功能
本文的例子
CodeLens
功能属于
程序化语言功能
。
程序化语言功能
通常由语言服务器提供支持,
语言服务器
是一个通过分析项目代码以提供动态功能的程序。
有两种方法可以调用程序化语言功能:
1. 通过
vscode.languages.*
API 调用。
2. 通过启动一个语言服务器,这个服务器需要支持语言服务器协议。
下面我们使用第一种方式实现 CodeLens 功能。
1. 使用语言服务 API 实现 CodeLens 功能
官方给了一个简单的例子
codelens-sample
[1]
官方例子演示:
启动 codelens-sample 项目
首先,从地址将示例插件下载到本地,使用命令
npm install
安装依赖。然后在调试视图中运行插件,就会在新打开的 VS code 窗口中运行插件了。
可以看到 codelens-sample 示例插件为每一行代码都添加了
CodeLens
功能。
codelens-sample 项目的主要文件有两个:
extension.ts
文件的任务是注册
Provider
,以及注册相关命令(
registerCommand
)。
CodelensProvider.ts
则是定义了一个
codelensProvider
供
extension.ts
注册时使用。
extension.ts
文件
内容如下:
// The module 'vscode' contains the VS Code extensibility API // Import the module and reference it with the alias vscode in your code below import { ExtensionContext, languages, commands, Disposable, workspace, window } from 'vscode' ;import { CodelensProvider } from './CodelensProvider' ;// this method is called when your extension is activated // your extension is activated the very first time the command is executed let disposables: Disposable[] = [];export function activate (context: ExtensionContext ) { const codelensProvider = new CodelensProvider(); languages.registerCodeLensProvider("*" , codelensProvider); commands.registerCommand("codelens-sample.enableCodeLens" , () => { workspace.getConfiguration("codelens-sample" ).update("enableCodeLens" , true , true ); }); commands.registerCommand("codelens-sample.disableCodeLens" , () => { workspace.getConfiguration("codelens-sample" ).update("enableCodeLens" , false , true ); }); commands.registerCommand("codelens-sample.codelensAction" , (args: any ) => { window .showInformationMessage(`CodeLens action clicked with args=${args} ` ); }); }// this method is called when your extension is deactivated export function deactivate ( ) { if (disposables) { disposables.forEach(item => item.dispose()); } disposables = []; }
文件主要做了两件事情:
registerCodeLensProvider
注册 Provider
这里先来看下
registerCodeLensProvider
,下个标题再说
registerCommand
。
registerCodeLensProvider
languages.registerCodeLensProvider("*" , codelensProvider)
注册时调用了
API
languages.registerCodeLens
。
languages.registerCodeLensProvider
需要两个参数。
第一个参数是语言 ID
,决定了我们注册的这个 Provider 会被应用于哪些类型的文件。
*
表示所有文件都会被应用这个 Provider。
如果换成
typescript
:
languages.registerCodeLensProvider("typescript" , codelensProvider);
我们就只能在
.ts
后缀的文件中看到
codelens
。
如果换成
["typescript", "javascript"]
:
languages.registerCodeLensProvider(["typescript" , 'javascript' ], codelensProvider);
则只能在
.ts
和
.js
后缀的文件中看到
codelens
。以此类推。
第二个参数要求是 CodeLensProvider 类型
。这个 Provider 的作用就是在代码中以一横排的方式显示命令。
CodelensProvider.ts
文件
看下如何定义
CodelensProvider
。
import * as vscode from 'vscode' ;/** * CodelensProvider */ export class CodelensProvider implements vscode.CodeLensProvider { private codeLenses: vscode.CodeLens[] = []; ...... constructor () { this .regex = /(.+)/g ; vscode.workspace.onDidChangeConfiguration((_ ) => { ...... }); } public provideCodeLenses(document : vscode.TextDocument, token: vscode.CancellationToken): vscode.CodeLens[] | Thenable { ...... } public resolveCodeLens(codeLens: vscode.CodeLens, token: vscode.CancellationToken) { ...... } }
CodeLensProvider
需要提供两个方法
provideCodeLenses
展开看下代码:
public provideCodeLenses(document : vscode.TextDocument, token: vscode.CancellationToken): vscode.CodeLens[] | Thenable { if (vscode.workspace.getConfiguration("codelens-sample" ).get("enableCodeLens" , true )) { this .codeLenses = []; const regex = new RegExp (this .regex); const text = document .getText(); let matches; while ((matches = regex.exec(text)) !== null ) { const line = document .lineAt(document .positionAt(matches.index).line); const indexOf = line.text.indexOf(matches[0 ]); const position = new vscode.Position(line.lineNumber, indexOf); const range = document .getWordRangeAtPosition(position, new RegExp (this .regex)); if (range) { this .codeLenses.push(new vscode.CodeLens(range)); } } return this .codeLenses; } return []; }
document
参数是指当前文档。通过
document.getText()
方法可以获取到文本内容。
const text = document .getText();
然后将文档内容与定义的正则表达式进行匹配。
this .regex = /(.+)/g ; ......const regex = new RegExp (this .regex); matches = regex.exec(text)
codelens-sample 示例之所以会在每一行都显示 codelens 就是因为在这里匹配到了每一行。
provideCodeLenses
要求返回一个数组、
undefined
或
null
。数组项要是
CodeLens
实例。
this .codeLenses.push(new vscode.CodeLens(range))
上行代码中的
range
是指代码范围,这个范围不能超过一行。
最终每一个匹配到的代码上方都会出现 CodeLens 命令。
resolveCodeLens
命令行的内容则是在
resolveCodeLens
方法中添加的。
public resolveCodeLens(codeLens: vscode.CodeLens, token: vscode.CancellationToken) { if (vscode.workspace.getConfiguration("codelens-sample" ).get("enableCodeLens" , true )) { codeLens.command = { title: "Codelens provided by sample extension" , tooltip: "Tooltip provided by sample extension" , command: "codelens-sample.codelensAction" , arguments : ["Argument 1" , false ] }; return codeLens; } return null ; }
resolveCodeLens
的参数
codeLens
就是
provideCodeLenses
匹配到并返回的
codeLens
实例 。在
codeLens.command
中配置 UI 显示的命令文字。
command
各属性的含义如下:
command.title
:在 UI 中显示的命令名
command.tooltip
:在 UI 中展示的命令提示,如图:
command.command
:实际命令处理程序的标识符(由
commands.registerCommand
进行注册)
command.arguments
:命令处理程序的参数
registerCommand
现在回到上文
extension.ts
文件中提到的
registerCommand
命令注册。
command.command
中用到的程序标识符
codelens-sample.codelensAction
是在
extension.ts
文件中定义的:
commands.registerCommand("codelens-sample.codelensAction" , (args: any , aa ) => { window .showInformationMessage(`CodeLens action clicked with args=${args} ` ); });
args
参数就是通过
command.arguments
传过来的。
例子中点击命令会在右下角弹出一个提示,提示信息可以拿到参数值
Argument 1
并显示出来。
在命令面板中控制 codelens 是否显示
在 Visual Studio Code 中最常用的就是命令面板,那么如何在命令面板注册命令呢?
需要两个步骤:
第一步:在代码中注册命令,并添加响应函数。
第二步:在
package.json
中绑定命令标识。这样才能在命令面板中看到命令。
例子中是有做这个功能的。
可以注意到
extension.ts
文件中还注册了另外两个命令: