Skip to content

VSCode 插件开发教程

VSCode 插件开发全攻略(一)概览 - 我是小茗同学 - 博客园 (cnblogs.com)

因为 vscode 本身是基于 electron 开发的,所以总体来说开发插件就是在写 node 代码,额外再加一些编辑器 api,插件发布的过程和 npm 包的发布很类似。

vscode 官方提供的脚手架还帮忙加上了调试配置,调试非常方便。

Visual Studio Code 拓展开发 API

拓展开发 API 文档:扩展 API |Visual Studio Code 扩展 API

指南:扩展指南 |Visual Studio Code 扩展 API

示例代码:microsoft/vscode-extension-samples: Sample code illustrating the VS Code extension API. (github.com)

用户体验 UX 指南:用户体验指南 |Visual Studio Code 扩展 API

如果要查找已发布的扩展,请前往 VS Code 扩展市场

环境准备

这个很简单,我就直接拷贝过来了。

  • nodejs: 建议使用 LTS 版本
  • npm: 建议最新版本
  • yeoman : npm install -g yo
  • generator-code : npm install -g generator-code

另外小TIPS,我们平时直接安装的插件所在目录是~/.vscode/extensions,有兴趣的可以看看这些插件是怎么实现的。

脚手架

安装的 yo 可以直接生成一个 Hello World 版本的插件目录。执行

bash
yo code

即会提示一些问题,按照个人喜好填写即可,最后会生成样板代码:

bash
.
├── CHANGELOG.md                 插件变更记录
├── README.md
├── extension.js                 插件入口main文件
├── jsconfig.json                编辑器关于js的配置
├── package.json                 全局配置
├── test                         测试代码文件夹
   ├── extension.test.js
   └── index.js
├── vsc-extension-quickstart.md  新手介绍
└── yarn.lock

其中的quickstart.md是新手引导,里面包含了对文件的作用解析、如何运行插件、测试插等等,推荐去看一看,我们在下面也会介绍一些。除此之外在 package.json 里也包含了很多非常重要的信息:

json
{
	"name": "hello-world", // 插件名称
	"displayName": "hello-world", // 插件显示名称
	"description": "hello world", // 插件描述
	"version": "0.0.1",
	"publisher": "my", // 插件发布者
	"engines": {
		"vscode": "^1.35.0" // 运行插件需要vscode最低版本
	},
	"categories": ["Other"],
	"activationEvents": ["onCommand:extension.helloWorld"], // 如何激活插件:在命令面板(Command+Shift+P吊起)输入helloWorld. 注意command名需要在contributes.commands中有配置
	"main": "./extension.js", // 插件入口
	"contributes": {
		"commands": [
			// 此数组表示插件支持的所有命令
			{
				"command": "extension.helloWorld", // 命令对应的Command,需要和代码里保持一致
				"title": "Hello World" // 命令的显示名称
			}
		]
	},
	"scripts": {
		// 正常的npm script
		"postinstall": "node ./node_modules/vscode/bin/install",
		"test": "node ./node_modules/vscode/bin/test"
	},
	"devDependencies": {
		// 依赖包
		"typescript": "^3.3.1",
		"vscode": "^1.1.28",
		"eslint": "^5.13.0",
		"@types/node": "^10.12.21",
		"@types/mocha": "^2.2.42"
	}
}

启动、调试插件

启动运行

脚手架生成的其实就是一个 node 应用,直接按 F5 即可运行。对配置感兴趣的也可以查看根目录下的.vscode/launch.json

跑起来以后默认会新开一个 vscode 窗口,然后会发现什么都没有发生,这是由插件的启动方式决定的,配置于package.json里的activationEvents项。常用的有:

  • onLanguage 在打开特定语言类型的文件后激活
  • onCommand 在执行特定命令后激活

由于我们的插件是配置的onCommand启动,并且指定的命令名是Hello World,所以我们在新开的vscode窗口中按下快捷键Command+Shift+P后再找到Hello World,选中并执行即可。

img

最后顺利的话,编辑器右下角会弹出 Hello World!。

img

如果细心的话,还会在源窗口的控制台的调试控制台 tab 中看到如下输出:

bash
Congratulations, your extension "hello-world" is now active!

这个就是由插件的真正代码部分输出的了。我们接下来看看 extension.js 的内容:

js
// vscode编辑器api入口
const vscode = require("vscode");

/**
 * 此生命周期方法在插件激活时执行
 * @param {vscode.ExtensionContext} context
 */
function activate(context) {
	// console的各种方法都是输出在`调试控制台`tab下
	console.log('Congratulations, your extension "hello-world" is now active!');

	// registerCommand用于注册命令并提供具体逻辑,命令名需要和package.json里写的一致。
	// 回调函数在命令被触发时执行。
	let disposable = vscode.commands.registerCommand(
		"extension.helloWorld",
		function () {
			// 在编辑器右下角展示一个message box
			vscode.window.showInformationMessage("Hello World!");
		}
	);

	// 将registerCommand的返回值放入subscriptions可以自动执行内存回收逻辑。
	context.subscriptions.push(disposable);
}
exports.activate = activate;

// 当插件被设置为无效时执行此生命周期钩子
function deactivate() {}

module.exports = {
	activate,
	deactivate,
};

以上就是此插件的完整逻辑了,配置注释是很简单的。可以看到主要就是两个生命周期函数,另外搭配一些编辑器 api 就完成了。

调试

脚手架已经贴心的帮我们加了调试配置,我们只用添加断点即可:

img

Command 配置

上面提到了生成一个 command 只需要 2 步,先是利用 vscode.commands.registerCommand 注册一个,然后再到 package.json 里的 contributes.commands 中配置即可。围绕 command 还可以做一些其他事情,最常见的就是配置右键菜单和快捷键。

右键菜单

表示右键的菜单里出现指定 command,配置方法:

json
"contributes":{
  "menus": {
    "editor/context": [
      {
        "when": "editorHasSelection && resourceFilename =~ /.js|.vue|.ts/", // 出现时机,当编辑器中有选中文本同时文件名后缀是js/vue/ts
        "command": "extension.starling_textSearch", // 需要在`contributes.commands`存在此命令
        "group": "6_Starling" // 命令所在的组,右键菜单可以分组,组与组之间存在分隔线
      },
    ]
  }
}

img

快捷键

有了快捷键后,就不用每次在命令面板里查找并运行命令了,同样是在 package.json 中配置:

json
"contributes": {
  "keybindings": [
    {
      "command": "extension.starling_textSearch",
      "key": "ctrl+f11", // 在Windows上的快捷键
      "mac": "cmd+f11", // 在mac上的快捷键
      "when": "editorTextFocus" // 出现时机, 当编辑器焦点在某个文本中
    }
  ],
}

发布

主要参考的是官方文档

首先需要安装 vsce 工具:

bash
npm install -g vsce

本地打包将插件打包成.vsix 文件。

bash
vsce package

会在项目根目录生成 hello-world-0.0.1.vsix,然后在编辑器的插件面板选择从 VSIX 安装即可:

img

发布到插件市场

  • 需要获取一个 token,参考官方文档
  • 利用 token 创建一个 publisher,这是在插件市场的用户
bash
vsce create-publisher (publisher name)
  • 本地登录此用户
bash
vsce login (publisher name)
  • 发布插件
bash
vsce publish

顺利的话在控制台会提示发布成功,然后过几分钟就可以在插件市场搜到自己的插件啦!?

版本升级

当插件内容发生变更时,重新发布时最好更新版本号,vsce 可以遵循语义化版本指定升级大(major)/小(minor)/补丁(patch)版本,也可以直接指定版本号。例如只升级小版本:

bash
vsce publish minor

如果插件代码在 gitlab 上,因为仓库在内网,需要事先将 README 里的图片替换为公网 cdn 上的路径。

snippets

snippets 是代码片段,可以理解为代码快捷键,在输入很少量触发代码后即可联想出一大坨关联代码,非常方便。对于 js、ts、vue 都可以在插件市场找到非常多的 snippets 插件。

开发 snippets 只用两步:

  • 编写 snippets 映射文件,它是一个 json,例如 javascript.json:
json
{
	"this$t": {
		"prefix": "tt'", // 触发代码
		"body": [
			// 联想出来的关联代码
			"this.\\$t('${1:key}')" // ${1: key} 是占位符,联想出来后会自动聚焦在这里
		],
		"description": "this.$t" // snippets描述,当有多个匹配的代码片段时,可以用来识别
	}
}
  • 在 package.json 中配置
json
"contributes": {
  "snippets": [
    {
      "language": "javascript", // 代码片段起作用的语言类型
      "path": "./src/snippets/javascript.json" // 对应的映射文件
    }
  ]
}

最后就可以在编辑器看到效果了:

img

  • 更多细节参考 snippets-syntax

插件默认配置

很多插件是需要一些额外配置才能工作的,设置默认配置同样在 package.json 里:

json
"contributes": {
  "configuration": { // 默认配置
    "type": "object",
    "title": "",
    "required": [
      "sid"
    ],
    "properties": {
      "includes": {
        "type": "Array",
        "default": [
          "json"
        ],
        "description": "文件类型过滤器"
      }
    }
  },
}

默认配置是 json schema 格式,在覆盖默认配置时如果校验出错会有提示。

插件中使用 getConfiguration 来读取配置:

js
function getConfig() {
	const config = vscode.workspace.getConfiguration();
	const includes: string[] | undefined = config.get("includes"); // 获取指定配置项

	return {
		includes: includes || [],
	};
}

监听配置项修改

在用户安装了插件后,可能会修改配置,如何实时监听配置项的修改呢?vscode 提供了 onDidChangeConfiguration 事件监听。

js
vscode.workspace.onDidChangeConfiguration(function (event) {
	const configList = ["includes"];
	// affectsConfiguration: 判断是否变更了指定配置项
	const affected = configList.some((item) => event.affectsConfiguration(item));
	if (affected) {
		// do some thing ...
	}
});

常见编辑器 api

所有 vscode 相关 api 都可以在官网文档查找,vscode 内部也集成了.d.ts 文件,编辑器内直接跳转定义即可。这里只列举一些常见的 api.

  • messgae

用于展示提示性消息,出现在编辑器右下角,而不是顶部或右上角。

和 console 类似,提供了普通消息、警告消息、错误消息。

js
vscode.window.showInformationMessage("普通消息");
vscode.window.showWarningMessage("警告消息");
vscode.window.showErrorMessage("错误消息");

消息也支持交互按钮,当选中按钮时返回的是按钮本身:

js
vscode.window
	.showErrorMessage(
		`与starling的远程交互依赖vscode-starling.sid配置项`,
		"打开配置项"
	)
	.then((selection) => {
		if (selection === "打开配置项") {
			vscode.commands.executeCommand("workbench.action.openSettings");
		}
	});

img

input box

在编辑器顶部展示一个 input 输入框,使用 vscode.window.showInputBox,会返回一个 Promise:

js
const text: string | undefined = await vscode.window.showInputBox({
  '最后一步,输入文案'
})

img

quick pick

用于从一组选项中选择一个,类似于 select 组件。使用 vscode.window.showQuickPick,同样返回一个 Promise,resolve 时得到被选中的选项或 undefined:

js
const lang: string | undefined = await vscode.window.showQuickPick(
	["en", "zh", "ja"],
	{
		placeHolder: "第一步:选择语言",
	}
);

img

每个选项也可以是对象类型:

js
const option: Object | undefined = await vscode.window.showQuickPick(
	[
		{ id: 1, name: "a" },
		{ id: 2, name: "b" },
		{ id: 3, name: "c" },
	],
	{
		placeHolder: "select an option",
	}
);

output channel

在利用 Control + ~打开控制台后,会出现 4 个 tab,从左到右依次是问题、输出、调试控制台、终端。output channel 就是用于控制输出 tab 的内容,可以往其中追加文本、追加行、清空,可以将其看成一个简单的文件。output channel 适用于一次展示大量信息.

使用 vscode.window.createOutputChannel 创建 output channel 实例,然后就可以操作各种 api 了。

js
const opc = vscode.window.createOutputChannel("textSearch"); // 可以有多个OutputChannel共存,使用参数名区分

opc.clear(); // 清空
opc.appendLine("水电费"); // 追加一行
opc.show(); // 打开控制台并切换到OutputChannel tab

一个例子:

img

file selector

有些时候需要操作本地文件系统,例如选择某个文件、将文件保存到指定位置等。

  • 保存文件到指定位置使用 showSaveDialog,它会打开文件选择器弹窗,选择了保存路径后点击确定会返回选中的路径,如果点击取消会返回 undefined。
js
// 让用户手动选择文件的的存储路径
const uri = await vscode.window.showSaveDialog({
	filters: {
		zip: ["zip"], // 文件类型过滤
	},
});
if (!uri) {
	return false;
}

writeFile(uri.fsPath); // 写入文件
  • 文件选择 showOpenDialog 同样会打开文件选择器弹窗,不过这次是用于选择文件,如果有选择文件会返回选中的文件路径,反之返回 undefined。
js
// showOpenDialog返回的是文件路径数组
const uris = await window.showOpenDialog({
	canSelectFolders: false, // 是否可以选择文件夹
	canSelectMany: false, // 是否可以选择多个文件
	filters: {
		json: ["json"], // 文件类型过滤
	},
});

if (!uris || !uris.length) {
	return;
}

handleFiles(uris);

img

hover

有时候需要在 hover 到文本上时展示一些提示信息,例如 eslint 插件在 hover 到不合规的代码上时会展示具体违反了哪些规则:

img

处理 hover 需要注册一个 hover 处理器,vscode 会在 hover 到文本上时自动调用处理器,同时传递 hover 相关的信息。例如一个展示光标所在的单词 hover 处理器:

js
/**
 * document: 打开的文本
 * position:hover的位置
 * token: 用于取消hover处理器作用
 */
async function hover(
	document: vscode.TextDocument,
	position: vscode.Position,
	token: vscode.CancellationToken
) {
	const line = document.lineAt(position).text; // 光标所在的行
	// getWordRangeAtPosition获取光标所在单词的行列号范围;getText获取指定范围的文本
	const positionWord = document.getText(
		document.getWordRangeAtPosition(position)
	);

	console.log("光标所在位置的单词是:", positionWord);
}

// registerHoverProvider的第一个参数数组表明此处理器的作用范围
const hoverDisposable = vscode.languages.registerHoverProvider(
	["javascript", "vue"],
	{
		provideHover: hover,
	}
);

context.subscriptions.push(hoverDisposable);

selection

与 hover 类似,有时候需要处理选中的文本,获取它是通过 vscode.TextEditor 实例上的属性,有两个相关属性

  • selections:所有被选中的文本信息
  • selection:第一个被选中的文本信息, 等同于 selections[0]

获取 TextEditor 的一个方法是通过注册 textEditorCommand,会在回调函数里提供 TextEditor 实例,例如展示选中文本:

js
let command = vscode.commands.registerTextEditorCommand(
	"extension.selection",
	function (textEditor, edit) {
		const text = textEditor.document.getText(textEditor.selection);
		console.log("选中的文本是:", text);
	}
);

context.subscriptions.push(command);

FileSystemWatcher

用于监听文件是否发生了变化,可以监听到新建、更新、删除这 3 种事件,也可以选择忽略其中某个类型事件。创建 watcher 是利用 vscode.workspace.createFileSystemWatcher:

typescript
function createFileSystemWatcher(
	globPattern: GlobPattern,
	ignoreCreateEvents?: boolean,
	ignoreChangeEvents?: boolean,
	ignoreDeleteEvents?: boolean
): FileSystemWatcher;

例如监听所有 js 文件的变动:

js
const watcher = vscode.workspace.createFileSystemWatcher('*.js', false, false, false);
watcher.onDidChange(e => { // 文件发生更新
  console.log('js changed,' e.fsPath);
});
watcher.onDidCreate(e => { // 新建了js文件
  console.log('js created,' e.fsPath);
});
watcher.onDidDelete(e => { // 删除了js文件
  console.log('js deleted,' e.fsPath);
});

使用 Rust 开发 VS Code 扩展

Visual Studio Code 现在支持通过 WebAssembly 执行引擎扩展运行 WASM 二进制文件。这使得开发者能够将 Rust 编写的程序编译为 WebAssembly,并在 VS Code 中直接运行。

完成这一过程的关键技术是组件模型,该模型通过 WIT 文件标准化 WebAssembly 组件与宿主环境的交互。

原文同时提供了 Rust 计算器示例,展示了如何在 VS Code 扩展中集成用 Rust 编写的库,并生成 Rust 和 TypeScript 绑定

原文链接 https://code.visualstudio.com/blogs/2024/05/08/wasm

egui 在 web 中使用多线程

作者在将桌面应用移植到 Web 过程中,通过使用 egui 进行 GUI 编写,并通过 WebWorkers 替代线程来进行计算密集型任务,以提升用户体验和性能。

通过引入gloo-worker crate,并调整相关 Rust 代码和 index.html 文件,实现了 WebWorker 的创建和通信

原文链接 https://voelklmichael.github.io/Blog/2024/05/12/egui-wasm-threads.html

bevy_ios_iap: Bevy 中提供 IOS 原生 StoreKit2 API

bevy_ios_iap 提供从 Bevy Apps 内部访问 iOS 原生 StoreKit2 Swift API。它使用 Swift-Bridge 自动生成胶水代码并传输数据类型。

图片

原文链接 https://github.com/rustunit/bevy_ios_iap

Rust 构建决策树

决策树可以处理分类或回归任务,适用于类别或连续型特征。作者选择了polars crate 来加载和处理数据,以简化数据集特征的访问和迭代过滤。使用 Gini 不纯度指标评估最有效的分割点,并通过数据分割来计算这个指标。最终,通过选择数量最多的类别来进行类别预测。

原文链接:https://noiseonthenet.space/noise/2024/05/sowing-a-decision-tree

Released under the MIT License.