Monaco:前世今生与在 Theia 中的角色

继 Lumino 和 InversifyJS 之后,这一块就轮到编辑器内核 Monaco。
这篇先不急着进 API,而是想搞清楚三件事:Monaco 到底是什么,它和 VS Code/其他编辑器有什么关系,以及它在 Theia 里具体扮演什么角色。

Monaco 是 VS Code 用的那套编辑器内核,被抽出来做成了一个可以单独在浏览器里使用的库。

  • 它提供的是代码编辑相关的所有“底层能力”
    • 文本模型(ITextModel)、视图(Editor)、多光标、箭头键行为等;
    • 语法高亮、代码折叠、代码格式化、代码补全、跳转定义等语言特性接口;
    • 差异视图(diff editor)、只读视图、只显示某些行的装饰器等。
  • 不关心
    • 应用的菜单、布局(这是 Lumino/Theia 的事情);
    • 工程结构、终端、Git 集成(这些是 IDE 壳做的事)。

VS Code 本身是一个“大壳 + 多种服务”的集合:

  • Monaco 负责“编辑器中间那块写代码的区域”;
  • 其他部分(侧边栏、面板、命令面板、调试视图等)是用别的技术栈/基础设施堆出来的。

Theia 做的事情,有点类似于:
用 Lumino + InversifyJS 搭一个“VS Code 风格的壳”,然后把 Monaco 这块编辑器内核嵌进去。

在 Web 编辑器内核里,常见选项还有 CodeMirror、Ace 等。
Monaco 相比它们,有几个 Theia 这种 IDE 场景特别在意的优势:

  • 语言服务友好

    • Monaco 的 monaco.languages 模块为各种语言特性(补全、诊断、重命名、跳转定义等)提供了统一接口;
    • 和 LSP(Language Server Protocol)非常契合,VS Code 本身就是 LSP 的头号用户。
  • 大文件与复杂编辑场景经验丰富

    • VS Code 已经在各种“打开几万行文件”“多光标编辑”等场景里被折腾过很多轮;
    • Monaco 继承了这些经验,对滚动性能、高亮延迟加载、tokenization 做了不少优化。
  • 和 VS Code 生态的自然对接

    • Theia 希望尽量靠近 VS Code 的用户体验和扩展生态(包括 VS Code 插件兼容);
    • 用 Monaco 能在编辑体验上减少差异,比如快捷键、选择行为、折叠/小地图等。

简单说:
如果目标是做“类 VS Code 的 Web IDE”,选 Monaco 几乎是顺理成章。

可以把现在你看过的三块东西做个简单对照:

  • InversifyJS:负责依赖注入 + 扩展点

    • 谁是服务,谁依赖谁,扩展怎么挂进系统,生命周期怎么管。
  • Lumino:负责桌面感窗口/布局

    • DockPanel / SplitPanel / TabBar 等,把各种视图拼成一个 IDE 壳。
  • Monaco:负责代码编辑区本身

    • 文本模型、光标行为、语言特性、装饰器、差异视图等。

在 Theia 的前端结构里,大概是这样的层次:

  1. 最外层是 ApplicationShell(Lumino DockPanel 驱动的布局)。
  2. Shell 的某个区域里挂着 Editor Widget(Theia 自己的 MonacoEditor/TextEditor 抽象)。
  3. 每个 Editor Widget 里面再嵌着一个真正的 Monaco Editor 实例(monaco.editor.IStandaloneCodeEditor 等)。

也就是说,Monaco 从来不直接决定“窗口在哪里、怎么分屏”,它只负责“一块矩形区域里的编辑体验”。

这部分在专门讲 MonacoInit 的文章里会展开,这里先给一个大致轮廓:

  • 在前端入口(你已经看到的 index.js)里,有一行:

    1const { MonacoInit } = require('@theia/monaco/lib/browser/monaco-init');
    2// ...
    3MonacoInit.init(container);
    
  • MonacoInit.init(container) 这一步主要做的事情包括:

    • 配置 Monaco 环境(比如 worker 加载方式、语言注册等);
    • 把与 Monaco 相关的服务/工厂注册到 DI 容器中(通过 InversifyJS 绑定);
    • 为后续 MonacoEditor 的创建提供基础设施。
  • 之后,在 Theia 的编辑器模块里,会有类似:

    • 把某个 MonacoEditor 绑定到容器;
    • 暴露 EditorManager / TextEditor 抽象,供其它扩展使用。

最终效果是:
Theia 里的其它代码几乎不用直接碰 Monaco 的底层 API(除非你愿意),只需要通过 Theia 抽象(如 TextEditorEditorManager)来操作编辑器。