Visual Studio Code 插件开发中的语言功能

本文作者为 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 追踪、任务关联等。

如下图示例:

270c1899475584373cf5ca96bf4dca16.gif

VS Code 的语言功能(Language Feature)

语言功能大致分为两类:

声明式语言功能
  • 语法高亮

  • 代码片段补全

  • 括号匹配

  • 括号自动闭合

  • 括号自动包围

  • 注释切换

  • 自动缩进

  • 折叠(通过标记)

程序化语言功能
  • 自动补全

  • 错误检查

  • 跳转到定义位置

本文的例子 CodeLens 功能属于程序化语言功能

程序化语言功能通常由语言服务器提供支持,语言服务器是一个通过分析项目代码以提供动态功能的程序。

有两种方法可以调用程序化语言功能:

1. 通过 vscode.languages.* API 调用。

2. 通过启动一个语言服务器,这个服务器需要支持语言服务器协议。

下面我们使用第一种方式实现 CodeLens 功能。

1. 使用语言服务 API 实现 CodeLens 功能

官方给了一个简单的例子 codelens-sample[1]

官方例子演示:fbd75514df1de60b9aa4c69c818e5866.gif

启动 codelens-sample 项目

首先,从地址将示例插件下载到本地,使用命令npm install安装依赖。然后在调试视图中运行插件,就会在新打开的 VS code 窗口中运行插件了。

b971914e2a36c0461b6666a21a8f2bdd.gif

可以看到 codelens-sample 示例插件为每一行代码都添加了 CodeLens 功能。

codelens-sample 项目的主要文件有两个:

  • extension.ts 文件的任务是注册 Provider ,以及注册相关命令(registerCommand)。

  • CodelensProvider.ts 则是定义了一个 codelensProviderextension.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

  • registerCommand 注册命令

这里先来看下 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<vscode.CodeLens[]> {
  ......
 }

 public resolveCodeLens(codeLens: vscode.CodeLens, token: vscode.CancellationToken) {
  ......
 }
}

CodeLensProvider 需要提供两个方法

  • provideCodeLenses

  • resolveCodeLens

provideCodeLenses

展开看下代码:

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

    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 要求返回一个数组、 undefinednull。数组项要是 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 中展示的命令提示,如图:6e93ee42f36ceee51c1a5019f8058412.png

  • 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 并显示出来。

064943bde9ed34c54038421e9a469941.gif

在命令面板中控制 codelens 是否显示


ecbdc739cf6dfd2297456eaeedf87a4e.gif


在 Visual Studio Code 中最常用的就是命令面板,那么如何在命令面板注册命令呢?

需要两个步骤:

第一步:在代码中注册命令,并添加响应函数。

第二步:在 package.json 中绑定命令标识。这样才能在命令面板中看到命令。

例子中是有做这个功能的。

可以注意到 extension.ts 文件中还注册了另外两个命令:

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);
});

这里绑定了命令触发时的响应函数。两个命令都是在被触发时去改变 enableCodeLens 的值。在 CodelensProvider.ts 文件中 codelens 会被显示的前提条件就是 enableCodeLens 的值为 true 。这个值是是从哪里取的呢?会在后标题“在设置中控制 codelens 的显示”中说到。

接下来看 package.json 文件的配置:

{
  "contributes": {
    "commands": [
      {
        "title": "Enable CodeLens",
        "command": "codelens-sample.enableCodeLens",
        "category": "CodeLens Sample"
      },
      {
        "title": "Disable Codelens",
        "command": "codelens-sample.disableCodeLens",
        "category": "CodeLens Sample"
      }
    ]
  }
}

字段含义如前边 commands 一样。

完成这两步,就可以在命令面板中控制 codelens 是否显示了。

在设置中控制 codelens 的显示

cc09be20f5fcc3c75b2f53ceddc8cac3.gif

前文提到过 enableCodeLens,这个值就是从设置配置里面获取的。

设置配置也是在 package.json 配置的:

"contributes": {
  "configuration": {
    "properties": {
      "codelens-sample.enableCodeLens": {
        "type": "boolean",
        "default": false
      }
    }
  }
}

配置的的 enableCodeLens 属性会以 Enable Code Lens 的格式在配置面板中显示。

codelens-sample 则是一个命名空间。codelens-sample.enableCodeLens 的值可以通过:

workspace.getConfiguration("codelens-sample").get("enableCodeLens")

获取到,同时可以进行更改值等操作。

以上就是如何在 VS Code 添加 CodeLens 功能的简单例子,希望可以对你有帮助。

感谢阅读~

参考资料

[1]

官方例子(codelens-sample): https://github.com/microsoft/vscode-extension-samples/tree/main/codelens-sample

- END -

如果您关注前端+AI 相关领域可以扫码进群交流

 138672761c1c1d99eb91fdfa2c48b7c9.jpeg

添加小编微信进群😊

关于奇舞团

奇舞团是 360 集团最大的大前端团队,非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

9b6c1e5b26c4516caaa8c0c8577c10b0.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值