VS Code 插件:命令、菜单与快捷键——从 contributes 到代码注册
在 VS Code 插件里,最常见的扩展能力就是「加一个命令,挂到菜单或快捷键上」。
这一篇不走「按 API 文档从上到下扫一遍」的路线,而是想把整条链路讲清楚:一个命令从package.json里的contributes声明,到真正出现在命令面板、右键菜单和快捷键里,中间到底发生了什么。
命令是什么?和菜单、快捷键是什么关系?
可以先用一句话定个调:
- 命令(command)是核心动作,菜单和快捷键只是触发命令的入口。
在 VS Code 里:
- 命令有一个全局唯一的 id(如
"extension.sayHello"),代表一个可以被触发的动作; - 命令可以被多种入口触发:
- 命令面板(
Ctrl+Shift+P); - 菜单/右键菜单;
- 快捷键;
- 代码内部直接调用
vscode.commands.executeCommand。
- 命令面板(
所以在设计插件时,思路一般是:
- 先想清楚你要暴露哪些「动作」,为它们定义命令;
- 再根据需要,把这些命令挂到合适的位置上(菜单、快捷键等)。
在 package.json 里声明命令:contributes.commands
第一步,是在 package.json 里告诉 VS Code:「我有这些命令」。
典型结构是这样的:
1"contributes": {
2 "commands": [
3 {
4 "command": "extension.sayHello",
5 "title": "Say Hello",
6 "category": "Demo"
7 }
8 ]
9}
要点:
command:命令 id,后续会在代码和菜单/快捷键里引用它;title:显示在命令面板等位置的文案;category:可选,用于在命令面板中分组显示(如Demo: Say Hello)。
这一步只是「登记了命令的存在」,并没有告诉 VS Code 触发时要干什么。
在代码里注册命令实现:vscode.commands.registerCommand
第二步,在扩展的 activate 函数中,用 vscode.commands.registerCommand 注册命令的具体实现。
示意代码:
1import * as vscode from "vscode";
2
3export function activate(context: vscode.ExtensionContext) {
4 const disposable = vscode.commands.registerCommand(
5 "extension.sayHello",
6 () => {
7 vscode.window.showInformationMessage("Hello from extension!");
8 }
9 );
10
11 context.subscriptions.push(disposable);
12}
要点:
registerCommand(id, handler):id必须和package.json里声明的命令 id 一致;handler是执行逻辑,可以接收参数(例如来自executeCommand或菜单的上下文)。
- 返回值是一个 disposable,需要加到
context.subscriptions里,确保扩展卸载时正确清理。
结合上一节,可以把命令的“注册过程”理解成:
package.json里声明:我有一个叫extension.sayHello的命令;activate里注册:当这个命令被触发时,请执行这段 handler。
菜单:命令如何出现在右键菜单 / 标题栏等位置?
有了命令之后,下一步就是决定它在哪些菜单里出现。
这依然是通过 package.json 的 contributes.menus 来完成。
示意结构(只展示一部分):
1"contributes": {
2 "menus": {
3 "editor/context": [
4 {
5 "command": "extension.sayHello",
6 "group": "navigation",
7 "when": "editorTextFocus"
8 }
9 ]
10 }
11}
几个关键点:
editor/context:- 表示编辑器里的右键菜单(上下文菜单);
- 还有其他位置,如
explorer/context(资源管理器右键)、editor/title(编辑器标题栏)等。
command:- 填之前声明的命令 id;
group:- 决定菜单项在该菜单中的相对分组位置,类似「分组/排序键」;
when:- 一段条件表达式,决定菜单项何时显示;
- 如
editorTextFocus表示只有编辑器有焦点时才显示。
这样,当用户在编辑器里右键时,就会在对应分组里看到「Say Hello」菜单项,点击后触发 extension.sayHello 命令。
快捷键:为命令绑定按键组合
快捷键同样通过 contributes.keybindings 来声明。
示意片段:
1"contributes": {
2 "keybindings": [
3 {
4 "command": "extension.sayHello",
5 "key": "ctrl+alt+h",
6 "when": "editorTextFocus"
7 }
8 ]
9}
字段含义:
command:依然是命令 id;key:按键组合,支持平台差异(如cmd/ctrl);when:条件表达式,和菜单里的when用的是同一套上下文系统。
理解上可以记成:
- 命令是动作;
- 快捷键就是「把某个按键组合映射到这个命令」的规则;
when决定这条映射规则什么时候生效。
一条完整链路复盘:从 contributes 到最终触发
用一个具体例子把上面几块串起来:
- 在
package.json里:contributes.commands:声明extension.sayHello命令;contributes.menus.editor/context:把该命令挂到编辑器右键菜单;contributes.keybindings:为该命令绑定一个快捷键。
- 在扩展入口代码中:
- 在
activate(context)里,用vscode.commands.registerCommand("extension.sayHello", handler)注册实现; - 把返回的 disposable 放进
context.subscriptions。
- 在
- 运行时:
- 当用户打开某种文档/视图,满足
activationEvents时,VS Code 激活扩展并调用activate; - 菜单和快捷键系统根据
contributes中的声明,将命令挂到对应位置; - 用户通过命令面板、菜单、快捷键等任一入口触发命令,最终执行 handler。
- 当用户打开某种文档/视图,满足
理解了这条链路之后,再看更复杂的情况(比如带参数命令、条件菜单、不同平台快捷键)就会简单很多。
常见坑与实践建议
在实际做命令/菜单/快捷键扩展时,常见的一些问题包括:
- 命令 id 不一致
package.json里写的是一个 id,代码里注册时不小心写成了另一个,导致命令在 UI 里可见但点了没反应。
- 滥用全局激活事件
- 为了图省事把所有命令都挂在
*或onStartupFinished下,扩展一上来就被激活,拖慢启动; - 更推荐用
onCommand:xxx或语言/文件类型等更精确的激活条件。
- 为了图省事把所有命令都挂在
when条件写得太宽或太窄- 太宽:菜单/快捷键在不合适的上下文也出现或生效;
- 太窄:用户预期看到的菜单/快捷键没有出现。
- 建议多用 VS Code 的「开发者工具」面板和内置文档来确认可用的上下文 key。
答题或写文时,可以把实践建议归纳成一句:
- 先把命令划清边界,再用菜单和快捷键有节制地暴露入口,同时用合适的激活事件和 when 条件控制好「何时可见 / 可用」。