在前端项目中,VS Code内置的插件会自动识别package.json里的scripts字段,将对应的命令行脚本显示在侧边栏中,方便我们点击运行。
受此启发,我开发了一款简单的VS Code的插件,以便在Rust项目中实现类似的功能。
项目地址:taiyuuki/vscode-cargo-scripts
插件地址:Cargo Scripts - Visual Studio Marketplace
Cargo Scripts
插件名称就Cargo Scripts,下载插件后,在Cargo.toml中添加[package.metadata.scripts]或[workspace.metadata.scripts],然后在侧边栏中就会显示一栏CARGO SCRIPTS,每条命令后都有一个运行图标,点击即可运行对应的命令。
插件的一些开发细节
对VS Code插件开发感兴趣的可以继续读下去,我以前也写过一篇文章:
我写了个提示颜色代码的VS Code插件——中国传统色 记录一下我自学开发VS Code插件的过程 - 掘金 (juejin.cn)
这是我曾经开发的另一款插件:Chinese Colors,它可以通过代码提示,自动补全预设的颜色代码。
项目创建
这次我使用pnpm、tsup构建项目,初始的项目结构如下:
vscode-cargo-scripts 项目文件夹
├─src 源码目录
│ └─index.ts 入口文件
├─eslint.config.mjs eslint配置文件
├─LICENSE.md 许可文件
├─package.json 包管理文件
├─README.md readme
├─tsconfig.json TS配置文件
└─tsup.config.ts tsup配置文件
需要安装这些依赖:
- @types/node - Node API类型提示
- @types/vscode - VS Code插件API的类型提示
- @vscode/test-electron - 插件调试依赖
- tsup - 打包、构建
这里要注意@types/vscode的版本,必须与在package.json中配置VS Code的版本一致,也就是下面engines -> vscode 这一项,它是插件需要兼容的VS Code版本。
package.json配置
{
"name": "vscode-cargo-scripts",
"displayName": "Cargo Scripts",
"publisher": "taiyuuki",
"version": "0.3.1",
"description": "Run scripts from Cargo.toml",
"main": "./dist/index.js",
"engines": {
"vscode": "^1.74.0"
},
"activationEvents": [
"workspaceContains:**/Cargo.toml",
"onLanguage:rust"
],
"contributes": {},
"scripts": {
"lint": "eslint --fix",
"dev": "tsup --watch",
"build": "tsup"
},
"keywords": [
"rust",
"cargo",
"scripts"
],
"author": "taiyuuki <taiyuuki@qq.com>",
"license": "MIT",
"files": [
"dist"
],
"devDependencies": {
"@taiyuuki/eslint-config": "^1.4.14",
"@types/node": "^18.11.18",
"@types/vscode": "^1.74.0",
"@vscode/test-electron": "^2.2.2",
"eslint": "^9.6.0",
"tsup": "^8.1.0"
}
}
各个字段的作用:
| 名称 | 必要 | 类型 | 说明 |
|---|---|---|---|
name | 是 | string | 插件名称,必须为小写且不能有空格。 |
version | 是 | string | 插件版本 |
publisher | 是 | string | 发布者 |
engines | 是 | object | 一个至少包含vscode键值对的对象,该键表示的是本插件可兼容的VS Code的版本,其值不能为*。比如 ^0.10.5 表示插件兼容VS Code的最低版本是0.10.5。 |
license | 否 | string | 授权。如果有授权文档LICENSE.md,可以把license值设为"SEE LICENSE IN LICENSE.md"。 |
displayName | 否 | string | 插件市场中显示的名字。 |
description | 否 | string | 描述,说明本插件是什么以及做什么。 |
categories | 否 | string[] | 插件类型:[Languages, Snippets, Linters, Themes, Debuggers, Other] |
keywords | 否 | array | 一组 关键字 或者 标记,方便在插件市场中查找。 |
galleryBanner | 否 | object | 插件市场中横幅的样式。 |
preview | 否 | boolean | 在市场中把本插件标记为预览版本。 |
main | 否 | string | 插件的入口文件。 |
contributes | 否 | object | 一个描述插件 贡献点 的对象。 |
activationEvents | 否 | array | 插件的激活事件。 |
dependencies | 否 | object | 生产环境Node.js依赖项。 |
devDependencies | 否 | object | 开发环境Node.js依赖项。 |
extensionDependencies | 否 | array | 一组本插件所需的其他插件的ID值。格式 ${publisher}.${name}。比如:vscode.csharp。 |
scripts | 否 | object | 和 npm的 scripts一样,但还有一些额外VS Code特定字段。 |
icon | 否 | string | 一个128x128像素图标的路径。 |
打包设置
tsup.config.ts
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
target: 'esnext',
splitting: false,
sourcemap: true,
clean: true,
external: [
'vscode', // 打包时排除vscode内置库
],
noExternal: [], // 如果第三方库需要打包进插件的,最好配置在这一项
format: ['cjs'],
})
打包命令:pnpm build或npx tsup
Hello World
一个简单的插件示例
src/index.ts
import * as vscode from 'vscode'
// activate方法会在插件被激活时调用
export function activate(context: vscode.ExtensionContext) {
// 注册命令,第一个参数是命令名称,第二参数是命令的回调函数
const disposable = vscode.commands.registerCommand('vscode-demo.helloWorld', () => {
// 弹出消息提示
vscode.window.showInformationMessage('Hello World from vscode-demo!')
})
// 添加到插件上下文
context.subscriptions.push(disposable)
}
// deactivate方法会在插件失活时调用
export function deactivate(context: vscode.ExtensionContext) {
context.subscriptions.forEach(d => d.dispose())
}
侧边栏的树状菜单
要在侧边栏创建一个树状菜单,首先要在package.json中配置:
"activationEvents": [
"workspaceContains:**/Cargo.toml",
"onLanguage:rust"
],
"contributes": {
"commands": [
{
"command": "cargoScripts.run",
"title": "Run",
"icon": "$(debug-start)"
},
{
"command": "cargoScripts.refresh",
"title": "Refresh",
"icon": "$(search-refresh)"
}
],
"views": {
"explorer": [
{
"id": "cargoScripts",
"name": "Cargo Scripts",
"when": "showCargoScript"
}
]
},
"menus": {
"view/title": [
{
"command": "cargoScripts.refresh",
"group": "navigation"
}
],
"view/item/context": [
{
"command": "cargoScripts.run",
"group": "inline",
"when": "viewItem == script_item",
"icon": "$(run)"
}
]
},
"icons": {
"custom-icon": {
"description": "Custom Icon",
"default": {
"fontPath": "res/iconfont.woff2",
"fontCharacter": "\\E694"
}
}
}
}
activationEvents是插件的激活条件,当项目中有Cargo.toml这个文件或者处于Rust语言中激活本插件。
然后是contributes——
commands用于注册命令以及对应的图标
"commands": [
{
"command": "cargoScripts.run",
"title": "Run",
"icon": "$(debug-start)"
},
{
"command": "cargoScripts.refresh",
"title": "Refresh",
"icon": "$(search-refresh)"
}
]
command是命令的id,命令对应的操作需要在代码中进行注册。 在上面hello world的例子中,vscode.commands.registerCommand注册了一个id为vscode-demo.helloWorld的命令。title是命令的名称,当鼠标悬停时会显示该名称。icon是该命令对应的图标,格式为$(name),name就是图标名。
图标可以是VS Code内置的图标,我们可以在官方文档中查询内置图标对应的名称:Product Icon Reference | Visual Studio Code Extension API
除此之外还可以使用自定义的字体图标集。
icons
要想使用自定义的图标,需要在icons中注册图标字体。
"icons": {
"custom-icon": { // 图标名
"description": "Custom Icon", // 描述
"default": {
"fontPath": "fonts/iconfont.woff2", // 字体文件目录
"fontCharacter": "\\E694" // 图标在字体中对应的字符编码
}
}
}
这样我们就可以使用$(custom-icon)将命令设置为该自定义的图标了。
views和menu
views和menu设置树状菜单栏,这个地方很难用文字说明,详情如下图所示:
至于菜单的内容如何设置,因为过程比较复杂,这里我推荐参考官方提供的示例:vscode-extension-samples/tree-view-sample,以及文档:Tree View API | Visual Studio Code Extension API
这里我只讲解一下when这个字段,它可以根据条件控制菜单栏/图标/按钮的显示或隐藏。
第一种情况是,我们可以给上下文注册一个布尔类型的变量,通过变量的值控制显示状态。
export function activate(context: vscode.ExtensionContext) {
vscode.commands.executeCommand('setContext', 'showCargoScript', true) // 显示
}
export function deactivate(context: vscode.ExtensionContext) {
vscode.commands.executeCommand('setContext', 'showCargoScript', false) // 隐藏
}
"when": "showCargoScript"即可控制该菜单的显示状态。
第二种情况是,在树节点构造器中给它一个contextValue属性,例如我给其赋值为script_item,然后在when中使用viewItem == script_item来判断是否显示该图标。
export class ScriptTreeItem extends vscode.TreeItem {
constructor(label: string, cmd: string, cwd: string) {
super(label, vscode.TreeItemCollapsibleState.None)
// 设置命令对应的id和参数
this.command = {
title: 'Open',
command: 'cargoScripts.open',
arguments: [label, cmd, cwd],
}
// 控制显示状态
this.contextValue = 'script_item'
}
iconPath = new vscode.ThemeIcon('wrench')
}