Monaco 核心概念:Model / Editor / View 与 Theia 抽象

这一篇集中把 Monaco 里最基础的那几块概念理一理:文本模型(Model)、编辑器实例(Editor)、视图(View),
再对照一下 Theia 里的 TextEditorEditorManager 和编辑器 Widget,方便之后看源码时不迷路。

在 Monaco 里,ITextModel 是“文本内容 + 标记”的核心载体

  • 负责存储文本文字本身;
  • 管理行信息、换行、EOL 风格;
  • 承载语法高亮 token、装饰器范围、折叠区域等元数据。

创建一个最简单的模型示例:

1const model = monaco.editor.createModel(
2  'function hello() {\n  console.log("hello");\n}\n',
3  'javascript',
4  monaco.Uri.parse('file:///example.js'),
5);

特点:

  • 一个 ITextModel 不依赖具体的编辑器实例,可以在没有任何可见编辑器的情况下存在;
  • 同一个模型可以被多个编辑器共享(例如“分屏查看同一个文件”)。

在 Theia 里,对应的概念是文本文档模型,通常由文档服务/编辑器服务管理,
编辑器 Widget 更多是“挂在这个模型上提供视图和交互”的角色。

Monaco 的编辑器实例最常用的是 IStandaloneCodeEditor

1const editor = monaco.editor.create(domNode, {
2  model,
3  language: 'javascript',
4  readOnly: false,
5  automaticLayout: true,
6});

这个实例负责:

  • 在指定 DOM 节点里渲染视图;
  • 处理键盘、鼠标等输入;
  • 提供一系列操作 API,例如:
    • getModel() / setModel()
    • getPosition() / setPosition()
    • executeEdits() / getSelections()
    • 滚动、聚焦、布局等。

可以简单地把它看成“一个挂在特定 DOM 节点上的编辑器视图 + 控制器”,
而真正的数据和结构性信息仍然在底层的 ITextModel 里。

在 Theia 中,MonacoEditor 正是对这个编辑器实例的一层封装,
并通过 TextEditor 接口暴露出更稳定的抽象供其它扩展使用。

Monaco 允许多个编辑器实例共享同一个模型,常见场景是“分屏查看同一个文件”:

1const model = monaco.editor.createModel('...', 'typescript', monaco.Uri.parse('file:///a.ts'));
2
3const editor1 = monaco.editor.create(domNode1, { model });
4const editor2 = monaco.editor.create(domNode2, { model });

效果:

  • editor1editor2 显示同一份文本;
  • 在其中一个编辑器里修改内容,另一个会同步更新(因为底层模型是同一个);
  • 两个编辑器可以有各自的滚动位置、光标位置、装饰器(部分装饰器可以绑定在视图侧)。

Theia 里的分屏行为背后就是利用了这一点:
只要有合适的抽象和 Widget 组合,就可以在不同布局区域贴上多个共享同一模型的 Monaco 视图。

从 MVC/MVVM 的角度看,可以粗略这样对应:

  • Model(ITextModel):负责数据和元信息;
  • View + Controller(Editor 实例):负责渲染和交互;
  • 语言服务 / 装饰器:可以类比为各种“服务层”,对模型和视图提供额外能力。

这套划分在 Theia 里的映射大致是:

  • 文本模型 → 文档模型服务(背后还是基于 Monaco 的 ITextModel);
  • Monaco 编辑器实例 → MonacoEditor 实现类;
  • 编辑器 Widget → 把 MonacoEditor 包到 Lumino Widget 里,参与布局。

理解这一点之后,再看 Theia 的接口设计时会更自然:
很多 API 是围绕“文档/模型”和“编辑器/视图”两条线分别展开的。

后面在聊语言服务、装饰器、命令操作时,这几个基本点会经常被用到:

  • 文本内容和高亮、折叠等结构性信息存放在 ITextModel 里;
  • 编辑器实例负责把模型展示出来,并处理编辑操作;
  • Theia 在这个基础上加了一层自己的抽象(TextEditor、Widget、Manager),
    既不直接暴露 Monaco 的所有细节,又保留了足够的能力去做 IDE 级别的功能。

记住这张图,对之后阅读 Monaco 与 Theia 相关源码会很有帮助。**