Monaco 命令与操作:Editor Actions、快捷键与 Theia 命令系统
这一篇聚焦在 Monaco 自身的命令与操作能力上,以及这些能力如何与 Theia 的命令/快捷键系统对接。
Editor Action:在编辑器上挂操作
Monaco 允许在编辑器实例上注册命令式的“操作”,常见用于:
- 增加右键菜单项;
- 为编辑器特定状态添加行为(例如格式化当前选区、注释/取消注释等)。
注册一个简单的 action 示例:
1const editor = monaco.editor.create(domNode, { /* ... */ });
2
3editor.addAction({
4 id: 'my-indent-action',
5 label: 'Indent Two Spaces',
6 keybindings: [
7 monaco.KeyMod.CtrlCmd | monaco.KeyCode.US_CLOSE_SQUARE_BRACKET, // Ctrl/Cmd + ]
8 ],
9 contextMenuGroupId: 'navigation',
10 contextMenuOrder: 1.5,
11 run(ed) {
12 const model = ed.getModel();
13 if (!model) {
14 return;
15 }
16 // 这里只是示意:实际应按行缩进
17 const selection = ed.getSelection();
18 if (!selection) {
19 return;
20 }
21 const text = model.getValueInRange(selection);
22 model.pushEditOperations(
23 [selection],
24 [{ range: selection, text: ' ' + text }],
25 () => null,
26 );
27 },
28});
关键字段:
id:操作的唯一标识;label:显示在菜单/命令面板中的名称;keybindings:与此操作关联的快捷键(使用KeyMod与KeyCode组合);contextMenuGroupId/contextMenuOrder:控制在编辑器右键菜单中的分组与顺序;run:执行逻辑,参数是当前编辑器实例。
快捷键与 Keybinding 配置
Monaco 内部有自己的快捷键系统,keybindings 使用形如 CtrlCmd | Shift | F10 这一类组合。
常见写法:
1const keybinding = monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S; // Ctrl/Cmd + S
在仅使用 Monaco 的场景中,可以完全依赖它的 keybinding 机制来定义编辑器内的快捷键。
在 Theia 中,则需要和它自己的命令/快捷键系统协同工作。
Theia 中的命令系统与 Monaco Action 的关系
Theia 有一套独立的命令系统(CommandRegistry / CommandContribution),
常见模式是:
- 在
CommandContribution中注册命令 ID 与执行逻辑; - 在
KeybindingContribution中为命令绑定快捷键; - 编辑器相关的命令通常由某个 EditorService 或 EditorManager 内部转发到具体的 Monaco 编辑器。
典型示意:
1export const MyCommands = {
2 indentTwoSpaces: {
3 id: 'my.indentTwoSpaces',
4 label: 'Indent Two Spaces',
5 },
6} as const;
7
8@injectable()
9export class MyCommandContribution implements CommandContribution {
10 constructor(
11 @inject(EditorManager) protected readonly editorManager: EditorManager,
12 ) {}
13
14 registerCommands(registry: CommandRegistry): void {
15 registry.registerCommand(MyCommands.indentTwoSpaces, {
16 execute: () => {
17 const widget = this.editorManager.currentEditor;
18 if (!widget) {
19 return;
20 }
21 const editor = widget.editor; // TextEditor 抽象
22 // 通过 TextEditor 抽象或向下转型到 MonacoEditor 调用 Monaco API
23 },
24 });
25 }
26}
在这种模式下:
- 编辑器行为的“来源”是 Theia 命令系统;
- 编辑器内部的具体执行则是通过
MonacoEditor调用 Monaco 的编辑操作或 action。
两套命令系统的协同思路
可以归纳为两种常见协同方式:
以 Theia 命令为主,Monaco Action 为辅
- 大部分编辑器相关操作都注册成 Theia 命令;
- 在实现上,通过
MonacoEditor的 API 或editor.getAction(id)来执行 Monaco 内建操作; - 好处是所有行为都能通过 Theia 的命令/快捷键体系统一管理。
在 Monaco 内注册特定的编辑器内部行为
- 将某些仅在编辑器上下文有意义的操作注册为 Monaco action(例如某个特定语言的临时帮助);
- 通过编辑器右键菜单或特定快捷键触发,不一定映射到全局命令系统。
在像 Theia 这样的 IDE 场景中,更推荐第一个路径:
命令的“源头”统一在 Theia 层,Monaco 作为执行载体,这样对扩展、快捷键自定义、命令面板都更友好。
小结
Monaco 为编辑器本身提供了一套完整的动作与快捷键机制(addAction、run、keybindings),
Theia 则在其之上建立了统一的命令系统和快捷键层,将编辑器操作纳入整个 IDE 的行为编排中。
理解这两层的分工,有助于在扩展中合理地选择:
哪些操作应该是全局命令(从 Theia 命令系统切入),哪些只是编辑器内部的小功能(可以直接用 Monaco action 实现)。**