Monaco 语言与高亮:注册语言、Monarch 语法与 Theia 映射

这一篇把 Monaco 里的“语言体系”和语法高亮机制梳理一下,再看 Theia 是怎么在这套机制之上做语言注册和适配。

在 Monaco 里,语言相关功能集中在 monaco.languages 命名空间中。
最基础的是先注册语言 ID

1monaco.languages.register({
2  id: 'myLang',
3  extensions: ['.mlg'],
4  aliases: ['MyLang'],
5});

这一步只是告诉 Monaco:“有一个叫 myLang 的语言,后续高亮/补全等都可以和它关联”,还没有真正的语法规则。

接下来可以为它配置高亮规则、自动缩进、代码补全等提供者。

Monaco 自带一个轻量的词法高亮引擎 Monarch,可以用类似状态机 + 正则的方式描述高亮规则:

 1monaco.languages.setMonarchTokensProvider('myLang', {
 2  defaultToken: 'invalid',
 3
 4  tokenizer: {
 5    root: [
 6      [/[a-z_$][\w$]*/, 'identifier'],
 7      [/\d+/, 'number'],
 8      [/\/\/.*/, 'comment'],
 9      [/".*?"/, 'string'],
10      [/[{}()[\]]/, '@brackets'],
11      [/[;,.]/, 'delimiter'],
12    ],
13  },
14});

核心是 tokenizer 字段,它定义了不同状态下的匹配规则:
每一行 [pattern, token] 表示“遇到某个正则,就打上某种 token 类型”,
Monaco 再根据 token 类型映射到具体样式(由主题负责)。

在更复杂的语言里,可以:

  • 使用状态切换(如 @string@comment 等)处理多行结构;
  • 为关键字、操作符等定义单独的 token 类型。

高亮最终呈现效果由主题决定。
主题会为不同 token 类型指定颜色和样式,例如:

 1monaco.editor.defineTheme('my-dark', {
 2  base: 'vs-dark',
 3  inherit: true,
 4  rules: [
 5    { token: 'identifier', foreground: 'dcdcaa' },
 6    { token: 'number', foreground: 'b5cea8' },
 7    { token: 'comment', foreground: '6a9955', fontStyle: 'italic' },
 8    { token: 'string', foreground: 'ce9178' },
 9  ],
10  colors: {
11    'editor.background': '#1e1e1e',
12  },
13});
14
15monaco.editor.setTheme('my-dark');

Monarch 负责“把源代码切成带 token 类型的流”,
主题负责“把 token 类型映射成具体颜色和样式”,
两者配合就形成了整套语法高亮。

Theia 自己提供了对 Monaco 语言 API 的一层封装和注册流程,大致包括:

  • 语言注册服务:对 monaco.languages.register 做封装,并配合 Theia 的语言 ID/文件关联机制;
  • 高亮规则注册:在前端模块中为各语言调用 setMonarchTokensProvider 或加载已有语言支持;
  • 主题系统:在 Theia 主题与 Monaco 主题之间做颜色规则映射。

一个典型的语言接入过程会包括:

  1. 定义语言 ID、扩展名、别名等元信息;
  2. 在前端模块中调用 Monaco 语言 API 注册语言和高亮规则;
  3. 如有需要,再注册补全、格式化等语言特性提供者;
  4. 通过 Theia 的语言注册/配置,把文件扩展名和语言 ID 关联起来。

这样,当编辑器打开特定扩展名的文件时,就会自动选择对应语言,并应用高亮和其它语言特性。

Monarch 主要负责词法高亮,而更高级的语言功能(补全、跳转定义、重命名等)通常由 LSP 或自定义语言服务提供。
Monaco 通过 monaco.languages.registerCompletionItemProvider 等 API 暴露挂载点:

 1monaco.languages.registerCompletionItemProvider('myLang', {
 2  provideCompletionItems(model, position, context, token) {
 3    return {
 4      suggestions: [
 5        {
 6          label: 'print',
 7          kind: monaco.languages.CompletionItemKind.Function,
 8          insertText: 'print($0);',
 9          insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
10        },
11      ],
12    };
13  },
14});

在 Theia 中,这些提供者往往不是直接手写,而是通过 LSP 客户端和语言服务器对接,
由语言服务器负责分析代码,前端只是把结果通过 Monaco 的语言 API 呈现出来。

语言和高亮这一块,可以简单记成三层:

  • 语言元信息和 ID:monaco.languages.register
  • 词法高亮规则:Monarch tokens provider + 主题;
  • 更高级的语言特性:LSP 或自定义语言服务,通过 Monaco 的语言 API 挂接。

Theia 利用这套机制,把自身的语言注册、主题系统和 LSP 集成起来,
让 Monaco 成为一个既能自定义高亮,又能承载语言服务器能力的编辑器内核。**