打造你的第一个 VSCode 插件

1,206 阅读10分钟

前言

VSCode 非常强大,原因在于开发者可以使用插件来丰富和扩展它的功能

目前插件市场的插件非常多,但是需求复杂多变的,总有一些场景是现有插件无法满足,因此需要开发者手动实现需要的功能。

下面就开始介绍如何开发一个VSCode插件。

准备工作

在开始之前,确保您的开发环境已经安装有以下工具:

  1. Node.js: 插件开发需要使用到 Node.js。
  2. npm (Node Package Manager): 用于安装 VSCode 扩展生成器及其他依赖项。
  3. Visual Studio Code: 显然,您需要 VSCode 来开发插件。

创建插件

  1. 安装 Yeoman 和 VS Code Extension Generator

    Yeoman 帮助开发者快速启动新项目,而 VS Code Extension Generator 是一个 Yeoman 插件,专为 VSCode 插件设计。

    npm install -g yo generator-code
    
  2. 生成插件项目:

    在终端上运行以下命令:

    yo code
    

选择生成插件(JS 或 TS),跟随提示填写插件名称、描述、作者等信息:

image.png

打开生成的项目,其中最重要的两个文件:

  1. 入口文件 extension.js
  2. 配置文件 package.json

image.png

VSCode插件是功能的集合,而这些功能又对应着一个个命令,每个命令又对应着某些操作

那么,如何注册一个命令呢?

注册命令

命令需要在入口文件先注册,绑定对应的回调函数。

下面是初始化项目后脚手架生成的 extension.js, 其中 demo 是项目名:

image.png

所有命令的注册都需要放在 activate 方法内。

总的来说,注册命令分为两步:

1. 使用vscode.commands.registerCommand 注册命令。

这个方法接受两个参数:

  • 第一个参数是一个字符串,表示命令的唯一标识符。在这个例子中是 'demo.helloWorld'。你可以通过这个标识符在其他地方(比如编辑器的命令面板、快捷键绑定或菜单中)引用这个命令。

  • 第二个参数是一个函数,这个函数定义了当命令被执行时应该进行的操作。在此例中,函数体内调用了 vscode.window.showInformationMessage 方法,显示一个信息框给用户,内容是 'Hello World from demo!'。这意味着每次用户触发这个命令时,VSCode 都会弹出一个包含 “Hello World from demo!” 的信息对话框。

2.将命令添加到context

context.subscriptions.push(disposable);

此行代码将前面创建的 disposable 添加到 VSCode 扩展的上下文订阅中,确保当扩展被停用或卸载时,与这些资源相关联的清理工作能够被执行。

注册完命令,需要在 package.json 中声明命令。

image.png

contributes.commands是一个命令数组,每个元素包括commandtitle两个属性。 (有没有其他属性笔者也不清楚,反正一直都只用这两个)

  • command:入口文件中注册的命令名 (入口文件是extension.js,因为 main 是这么配的)
  • title:搜索命令时展示的名称(不理解的话,可以把它当做前端Select组件的Label, 而command就是value)

调试

按下F5就会弹出一个新VSCode窗口。

新窗口标题会注明扩展开发宿主,表示当前窗口已经加载了调试插件

image.png

接下来就是输入注册的命令:

  • macOS:使用 Cmd+Shift+P 打开命令面板
  • Windows 和 Linux:使用 Ctrl+Shift+P 打开命令面板

(如果记不住快捷键,直接点击上面那个搜索栏,输入> )

image.png

好了,现在就能输命令了。

image.png

结果发现没找到这个命令。

这种情况一般是插件package.json限制的vscode版本高于当前vscode版本

将插件的 engines.vscode 版本降一下:

image.png

注意:修改版本后需要将 devDependencies 中的 @types/vscode版本也一起改,不然打包会失败。

改完需要重新启动调试。(每次修改插件都需要重启调试)

先停止原来的调试

image.png

然后重新启动调试(F5), 打开命令面板输入命令

image.png

然后点击命令,就会执行命令的回调函数(弹窗提示)

image.png

最简单的插件就实现了!

—————--------------------------------我是分隔线---------------------------------------—————

下面会介绍一些插件的配置,如果不需要可以跳过,直接看最后的打包和发布。

插件激活

插件并不是安装启用之后就会激活。

像下面这个插件就处于安装启用但尚未激活状态:

image.png

插件具体什么时候激活是在 package.json 中的 activationEvents 声明。

每个插件都应该声明具体的加载时机。

以下是几种常用的声明方式:

  • *: VSCode 启动时,插件开始激活
    { 
        "activationEvents": ['*'] 
    }

image.png

  • onStartupFinished: VSCode 启动一段时间后才会激活插件。类似于 * 类激活事件,但它不会减慢 VSCode 启动,该事件在所有 * 类插件激活完成后触发:
    { 
        "activationEvents": [ "onStartupFinished" ] 
    }

image.png

  • onCommand:Hello World: 当执行命令 Hello World 时激活插件:

  • onLanguage:languageId: 编辑区包含特定语言文件时激活插件。

"activationEvents": [
  "onLanguage:json",
  "onLanguage:markdown",
  "onLanguage:typescript"
]

image.png

只要编辑区(上图红色区域)打开 json、markdown 或者 ts文件时,就会激活该插件。

这几个激活事件可以覆盖大部分情况,还有其他一些激活事件,没有一一列举,感兴趣的可以阅读一下官方文档

内置命令

在开发 VSCode 插件时,通常需要利用 VSCode 提供的 API 执行各种操作,比如打开、修改及保存文档内容、关闭文档以及刷新文件夹等。

使用vscode.commands.executeCommand API,你可以调用命令以集成 VSCode 的内置功能到你的插件中

例如,下面的代码用来关闭 VSCode 当前打开的文档:

  vscode.commands.executeCommand('workbench.action.closeActiveEditor');

关闭所有打开的文档:

  vscode.commands.executeCommand('workbench.action.closeAllEditors');

效果就和这个一样

image.png

刷新当前工作区文件夹:

   vscode.commands.executeCommand('workbench.files.action.refreshFilesExplorer');

更多命令详见:Built-in Commands

有些功能可以通过查看 VSCode 官方文档找到,但很多是没有写在文档上的。

笔者发现了一个方便查找命令的方式,就是直接看 VSCode 的键盘快捷方式:

image.png

然后可以在这里搜索命令。

右键点击需要的命令,选择复制命令ID

image.png

右键菜单配置

什么是右键菜单?

就是把注册的命令添加到点击右键后出现的选项中,类似这样:

image.png

右键菜单有很多配置,这里只列举比较常见的。

1. 编辑器右键菜单

编辑器就是文件的内容区域。

image.png

如果想要命令出现在文件内容区域右键时的菜单上,那就需要在package.json中配置contributes.menus

{ 
    "contributes": { 
        "menus": { 
            "editor/context": [ 
                { "command": "helloWorld", 
                  "group": "navigation", 
                  "when": "true" 
                } 
             ] 
         } 
     } 
 }
  • command 定义菜单被点击后要执行的命令
  • group 定义菜单分组
  • when 控制菜单出现时机

when语句语法有很多,这里列举几个常用的:

  • resourceLangId == javascript:当编辑的文件是js文件时;
  • resourceFilename == test.js:当当前打开文件名是test.js时;
  • isLinux、isMac、isWindows:判断当前操作系统;
  • editorFocus:编辑器具有焦点时;
  • editorHasSelection:编辑器中有文本被选中时;
  • view == someViewId:当当前视图ID等于someViewId时;
  • ……
    多个条件可以通过与或非进行组合,例如:editorFocus && isWindows && resourceLangId == javascript。

这么讲可能过于抽象,下面介绍笔者的插件配置:

  1. 将注册完的命令在 contributes.commands 中配置
  2. contributes.menus.editor/context 配置编辑器右键菜单

image.png

当安装启动插件后,在编辑器右键就能看到这3个命令。

2. 资源管理器面板菜单

下面所示就是资源管理器

image.png

如果想把命令加到这里的右键菜单上,需要配置contributes.menus.explorer/context

下面介绍笔者的插件配置:

  1. 将注册完的命令在 contributes.commands 中配置
  2. contributes.menus.explorer/context 配置资源管理器右键菜单

image.png

当安装启动插件后,在资源管理器区域右键就能看到这2个命令。

快捷键配置

快捷键是提升用户体验的一种有效方式。

  1. contributes.commands 声明已经注册的命令
  2. contributes.keybindings 配置快捷键 (可以为不同操作系统指定不同的快捷键)

image.png

在搜索命令时,能看到快捷键:

image.png

注意:在配置快捷键时,确保不与 VSCode 的默认快捷键或其他插件的快捷键冲突。

contributes 还有 viewsviewsContainers 等配置,这里不一一讲述。

接下来,就以一个开发中经常遇到的问题为例,实现一个简单的插件。

插件案例

在实际开发中,经常有一些和后端约定好的枚举值,然后页面上有一个筛选项,展示每个枚举值对应的名称。

例如:

枚举Statement有5个枚举值:

export enum Statement {
  SaPe = 1,
  LineHaul = 2,
  PreInvoice = 3,
  Config = 4,
  GlobalRule = 5,
}

需要把枚举转成label和value的形式。

export const StatementOptions: Array<TagOptions<Statement>> = [
  { label: 'Sa Pe', value: Statement.SaPe },
  { label: 'Line Haul', value: Statement.LineHaul },
  { label: 'Pre Invoice', value: Statement.PreInvoice },
  { label: 'Config', value: Statement.Config },
  { label: 'Global Rule', value: Statement.GlobalRule },
];

如何实现呢?

首先,将功能拆分成4个小功能点,来一一实现:

  1. 获取选中内容(也就是整个枚举)
  2. 解析选中的枚举字符串
  3. 生成目标代码
  4. 插入到当前文件

1.获取选中内容

 const editor = vscode.window.activeTextEditor;
  if (!editor) {
      return;
  }
 
 const selection = editor.selection;
 const selectedText = editor.document.getText(selection);

selectedText就是用户在文件上选中的字符串。

2.解析选中的枚举字符串

这里采取正则匹配的方式。

function parseEnumText(text: string) {
  const enumNameMatch = text.match(/\s*enum (\w+)/);
  if (!enumNameMatch) {
    return null;
  }
  const enumName = enumNameMatch[1];
  const options = [];

  const lines = text.split("\n");
  for (const line of lines) {
    const match = line.trim().match(/(\w+)\s*=\s*(-?\w+),?/);

    if (match) {
      options.push({
        label: addSpaceBeforeCapital(match[1]),
        value: match[1],
      });
    }
  }

  return { enumName, options };
}

对于每个枚举值,按照一定规范生成它的label。

function addSpaceBeforeCapital(str: string) {
  // 使用正则表达式匹配所有的大写字母,并在其前面添加空格
  // [A-Z] 匹配任意大写字母
  // (?<!^) 是一个负向后查找,确保匹配的大写字母不是字符串的开始
  // g 代表全局匹配
  return str.replace(/(?<!^)([A-Z])/g, " $1");
}

3. 生成目标代码

目标代码的生成就是单纯的字符串拼接。

function generateOptionsCode(
  enumName: string,
  options: Array<{
    label: string;
    value: number | string;
  }>
) {
  const items = options.map(
    (opt) =>
      `  { label: '${opt.label}', value: ${enumName}.${opt.value}},`
  );
  return `export const ${enumName}Options: Array<TagOptions<${enumName}>> = [\n${items.join(
    "\n"
  )}\n];`;
}

4. 将生成的字符串插入到文档

 editor.edit((editBuilder) => {
    editBuilder.insert(selection.end, "\n\n" + generatedCode);
  });

整体代码以及配置如下:

image.png

效果:

d4akm-pnswl.gif

—————--------------------------------我是分隔线---------------------------------------—————

打包

写完代码调试确认后,需要将你的插件打包成.vsix文件,这是 VSCode 插件的打包格式。

  1. 确保你的插件代码没有错误,所有功能正常。

  2. 安装 VSCode 的包管理工具 vsce

    npm install -g vsce

vsce 是 VSCode Extension CLI 的缩写,用于管理 VSCode 插件的各种操作,包括打包和发布。

  1. 打包插件

在项目的根目录中打开命令行,运行以下命令:

 vsce package

这个命令将会自动收集所有必需的文件和依赖,然后创建一个 .vsix 文件。这个文件就是你的插件的打包版本,你可以将它上传至 Marketplace 或自行分发。

下面对demo进行打包。

失败了,提示需要修改 README.md 文件

image.png

改完 README.md 重新打包:

image.png

成功了,在当前项目根目录生成了一个 demo-0.0.1.vsix 文件,其中 demo是项目名,0.0.1package.json中的 version

注意:请确保每次打包version都不一样,否则无法发布到Marketplace。

如果不想发布到 Marketplace,可以在本地手动安装。

右键点击生成的.vsix文件,选择安装扩展VSIX,然后重启VSCode就可以使用插件了。

image.png

发布

为了让我们的插件能在VSCode插件市场中被搜到,需要发布到 Marketplace。

  1. 打开 marketplace.visualstudio.com/vscode
  2. 登录
  3. 点击右上角 Publish extension

image.png 4. Create publisher

  1. New extension

image.png

  1. 选择 Visual Studio Code, 上传打包生成的 .vsix
  2. 如果上传成功,一般得等几分钟后才能在vscode的插件市场搜索到