src/ 目录
功能与 activate()
每个功能导出 activate(): void | (() => void):注册 action、工具窗口、状态栏项、编辑器扩展和服务,并返回一个撤销全部注册的 disposer。src/features/index.ts 按激活顺序列出所有功能,并把每个 activate 包在 try 里调用,一个功能抛错不会影响其他功能。src/workbench/activate.ts 先于它运行,提供功能依赖的外壳服务(工具窗口、通知)。
一个完整的功能可以小到这样:
src/features/logcat/index.ts
lazyComponent),避免拖慢启动;pnpm perf 对初始包体积有预算。
功能隔离
功能之间禁止互相 import。 它们只通过三种方式通信:- Action:
executeAction("project.dontOpen")按稳定 id 调用另一个功能的行为,而无需 import 它。 - Contribution:功能向
src/workbench/contributions.ts(或src/editor/api.ts)里的扩展点注册内容;渲染该扩展点的一方并不知道是谁贡献的。 - 服务:跨功能服务只提供一次,通过访问函数获取(见下文)。
platform/、ui/、workbench/ 或 editor/api,而不是放进某个功能再让别的功能去 import。
Action
用户能触发的一切都是带稳定点分 id 的 action(src/platform/actions.ts)。菜单、快捷键、工具栏按钮、右键菜单和 Search Everywhere 的 Actions 标签页都解析到同一个注册表,所以功能只需注册一次:
用
executeAction(id, arg?) 执行 action。registerAction 返回 disposer。
Keymap 与键盘分发
默认快捷键写在src/platform/keymaps/ 里,而不是写在 action 上。keymap 是 action id 到绑定的映射:
src/platform/keymaps/intellij-macos.ts
intellij-macos(macOS 默认)、intellij-windows(Windows 与 Linux 默认)、vscode-macos、vscode-windows、eclipse-macos、eclipse-windows。新增带默认快捷键的 action 时,两个 IntelliJ 预设都要加,其他预设有对应按键的也一并加上。
绑定语法:修饰键 Meta Ctrl Alt Shift 用 + 连接到按键(Meta+Shift+O);空格分隔组合键的多个击键(Meta+K Meta+C);连按两次修饰键写作 Shift Shift;鼠标按键为 Button1…Button5。按键按物理位置匹配(KeyboardEvent.code)。
installKeyboardDispatcher 在捕获阶段监听 keydown,因此 IDE 快捷键优先于 CodeMirror 的默认按键;它把击键与当前 keymap 及用户覆盖项匹配,然后运行第一个启用的 action(满足 when 和 keyWhen)。需要自己处理某个按键的组件注册一个 key claim(registerKeyClaim),比如 Vim 模式处理快捷键冲突时。
上下文键与 when 子句
组件把事实发布为上下文键(src/platform/context.ts);action 和快捷键通过基于这些键的 when 表达式决定是否启用。
||、&&、!、括号,以及 key、key == value、key != value。空子句恒为真。子句编译一次后缓存。插件的菜单项使用同样的子句。
扩展点(Contribution)
src/workbench/contributions.ts 定义了工作台的扩展点,每个 register* 函数都返回 disposer。
编辑器通过
src/editor/api.ts 扩展:registerEditorContribution({ id, extensions }) 为每个文档添加 CodeMirror 扩展。editor/core 持有视图;LSP、VCS 装订线、断点、行内调试值都是 contribution,从不 import 编辑器内部实现。
插件的 UI 贡献最终也落到这些函数上:后端先校验,src/features/plugins/projection.ts 再把每一项映射到现有的 register* 调用。见插件宿主。
服务
服务是只有一个实现的跨功能 API,由所属方提供:
不带
maybe 的访问函数在没有提供方时会抛错,所以要在使用时调用,不要在模块加载时调用。
与后端通信
功能只通过@/platform/ipc 的 commands 调用后端,用 onIdeEvent 监听事件;见 IPC。不少功能还为普通浏览器模式(pnpm dev)保留了内存版后端,单元测试和 E2E 测试都用它,由 isTauri() 决定使用哪一个。