语义高亮(Semantic Highlighting)是 VS Code 中对语法高亮(Syntax Highlighting)的增强,它允许语言服务器根据对项目符号的更深层次理解来提供额外的标记信息。与基于正则表达式的 TextMate 语法不同,语义标记基于语言服务器对代码上下文的分析。
### 1. 语言扩展如何提供语义标记
要实现语义高亮,语言扩展需要注册一个语义标记提供者(Semantic Token Provider)。开发者可以通过实现 `vscode.DocumentSemanticTokensProvider` 接口来提供语义标记。该接口要求实现 `provideDocumentSemanticTokens` 方法,该方法接收一个 `vscode.TextDocument` 对象,并返回一个包含语义标记的 `vscode.ProviderResult<vscode.SemanticTokens>`。
在实现中,首先需要定义一个 `vscode.SemanticTokensLegend` 实例,用于声明扩展将使用的自定义 Token 类型和 Token 修饰符。然后,在 `provideDocumentSemanticTokens` 方法中,使用 `vscode.SemanticTokensBuilder` 来构建标记,并使用 `tokensBuilder.push()` 方法添加标记的范围(Range)、类型(Type)和修饰符(Modifiers)。最后,通过 `vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend)` 将提供者注册到 VS Code 中,其中 `selector` 定义了该提供者适用的文档范围。
**语义标记的分类:**
每个返回的标记都包含一个分类,该分类由**Token 类型**和**Token 修饰符**组成。VS Code 提供了标准化的类型和修饰符集(如 `class`, `variable`, `declaration`, `readonly` 等),但扩展也可以自由定义新的类型和修饰符。
### 2. 支持自定义 Token 类型和修饰符的声明
当需要支持非标准类型或修饰符时,可以在扩展的 `package.json` 文件中使用 `contributes` 字段来声明它们:
* **自定义 Token 类型:** 使用 `semanticTokenTypes` 贡献点。可以定义新类型,并使用 `superType` 属性使其继承自现有标准类型(例如,声明一个名为 `templateType` 的新类型,并将其 `superType` 设置为 `type`)。
* **自定义 Token 修饰符:** 使用 `semanticTokenModifiers` 贡献点。可以定义新的修饰符(例如,声明一个名为 `native` 的修饰符)。
**自定义映射到 TextMate 作用域:**
仅仅定义了自定义类型和修饰符是不够的,还需要告诉 VS Code 它们应该如何映射到 TextMate 作用域,以便主题能够正确着色。这通过 `semanticTokenScopes` 贡献点实现。
1. **扩展定义自定义映射:** 如果扩展定义了自定义类型或修饰符,它可以在 `package.json` 的 `semanticTokenScopes` 中声明,指定这些新类型/修饰符对应的 TextMate 作用域。
2. **语言提供者定义语言特定映射:** 如果是提供 TextMate 语法的扩展,它也可以通过 `semanticTokenScopes` 贡献点,并指定 `language` 属性,来描述特定语言下的语义标记到 TextMate 作用域的映射。
**注意:** 声明的自定义映射规则不会自动从父类型继承,子类型需要重新定义映射,最好是映射到更具体的范围。
### 3. 主题映射和启用语义高亮
**主题着色:**
主题(Color Theme)通过 `semanticTokenColors` 属性来定义基于语义标记的着色规则。该属性的格式是一个对象,键是**选择器(Selector)**,值是描述样式的字符串或对象。选择器的格式遵循 `(*|tokenType)(.tokenModifier)*(:tokenLanguage)?` 的模式。
例如,`"variable.readonly:java": "#ff0011"` 表示在 Java 语言中,所有被标记为 `variable` 且带有 `readonly` 修饰符的元素,其前景颜色应为 `#ff0011`。
**语义标记的启用:**
语义高亮的全局开关由 `editor.semanticHighlighting.enabled` 设置控制。语言扩展可以在其 `package.json` 中使用 `configurationDefaults` 来为特定语言启用或禁用语义高亮。
**回退机制:**
如果主题没有为某个语义标记定义特定的着色规则,VS Code 会使用**语义标记作用域映射(Semantic Token Scope Map)**作为回退机制。这个机制将语义标记选择器映射到预定义的或扩展自定义的 TextMate 作用域,然后主题会使用 `tokenColors` 中的 TextMate 规则来着色。