语义高亮指南
语义高亮是对语法高亮指南中所述语法高亮的补充。Visual Studio Code 使用 TextMate 语法作为主要的词法分析引擎。TextMate 语法将单个文件作为输入,并根据用正则表达式表达的词法规则对其进行拆分。
语义词法分析允许语言服务器根据其对如何在项目上下文中解析符号的了解,提供额外的词法单元信息。主题可以选择使用语义词法单元来改进和完善来自语法的语法高亮显示。编辑器在语法高亮显示的基础上应用来自语义词法单元的高亮显示。
以下是语义高亮可以增加的内容示例
没有语义高亮

带有语义高亮

注意基于语言服务符号理解的颜色差异
- 第 10 行:
languageModes被着色为参数 - 第 11 行:
Range和Position被着色为类,document被着色为参数。 - 第 13 行:
getFoldingRanges被着色为函数。
语义词法单元提供程序
为了实现语义高亮,语言扩展可以通过文档语言和/或文件名注册一个 semantic token provider(语义词法单元提供程序)。当需要语义词法单元时,编辑器将向提供程序发出请求。
const tokenTypes = ['class', 'interface', 'enum', 'function', 'variable'];
const tokenModifiers = ['declaration', 'documentation'];
const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers);
const provider: vscode.DocumentSemanticTokensProvider = {
provideDocumentSemanticTokens(
document: vscode.TextDocument
): vscode.ProviderResult<vscode.SemanticTokens> {
// analyze the document and return semantic tokens
const tokensBuilder = new vscode.SemanticTokensBuilder(legend);
// on line 1, characters 1-5 are a class declaration
tokensBuilder.push(
new vscode.Range(new vscode.Position(1, 1), new vscode.Position(1, 5)),
'class',
['declaration']
);
return tokensBuilder.build();
}
};
const selector = { language: 'java', scheme: 'file' }; // register for all Java documents from the local file system
vscode.languages.registerDocumentSemanticTokensProvider(selector, provider, legend);
为了适应语言服务器的功能,语义词法单元提供程序 API 有两种类型:
-
DocumentSemanticTokensProvider- 始终将整个文档作为输入。provideDocumentSemanticTokens- 提供文档的所有词法单元。provideDocumentSemanticTokensEdits- 以对前一个响应的增量形式提供文档的所有词法单元。
-
DocumentRangeSemanticTokensProvider- 仅对某个范围生效。provideDocumentRangeSemanticTokens- 提供文档某个范围的所有词法单元。
提供程序返回的每个词法单元都带有分类信息,该分类由词法单元类型、任意数量的词法单元修饰符和词法单元语言组成。
如上例所示,提供程序在 SemanticTokensLegend 中命名了它将要使用的类型和修饰符。这允许 provide API 返回作为图例索引的词法单元类型和修饰符。
语义词法单元分类
语义词法单元提供程序的输出由词法单元组成。每个词法单元都有一个范围和一个词法单元分类,用于描述该词法单元代表哪种语法元素。如果词法单元是嵌入语言的一部分,则分类还可以选择性地命名一种语言。
为了描述语法元素的类型,使用了语义词法单元类型和修饰符。此信息类似于语法高亮指南中描述的 TextMate 作用域,但我们希望构建一个专门且更整洁的分类系统。
VS Code 提供了一套标准的语义词法单元类型和修饰符,供所有语义词法单元提供程序使用。不过,语义词法单元提供程序也可以自由定义新的类型和修饰符,并创建标准类型的子类型。
标准词法单元类型和修饰符
标准类型和修饰符涵盖了许多语言常用的概念。虽然每种语言对某些类型和修饰符可能使用不同的术语,但通过遵循标准分类,主题作者将能够定义跨语言生效的主题规则。
以下是 VS Code 预定义的标准语义词法单元类型和语义词法单元修饰符
标准词法单元类型
| ID | 描述 |
|---|---|
namespace |
用于声明或引用命名空间、模块或包的标识符。 |
class |
用于声明或引用类类型的标识符。 |
enum |
用于声明或引用枚举类型的标识符。 |
interface |
用于声明或引用接口类型的标识符。 |
struct |
用于声明或引用结构体类型的标识符。 |
typeParameter |
用于声明或引用类型参数的标识符。 |
type |
用于声明或引用上述未涵盖的类型的标识符。 |
parameter |
用于声明或引用函数或方法参数的标识符。 |
variable |
用于声明或引用局部或全局变量的标识符。 |
property |
用于声明或引用成员属性、成员字段或成员变量的标识符。 |
enumMember |
用于声明或引用枚举属性、常量或成员的标识符。 |
decorator |
用于声明或引用装饰器和注解的标识符。 |
event |
用于声明事件属性的标识符。 |
function |
用于声明函数的标识符。 |
method |
用于声明成员函数或方法的标识符。 |
macro |
用于声明宏的标识符。 |
label |
用于声明标签的标识符。 |
comment |
用于表示注释的词法单元。 |
string |
用于表示字符串字面量的词法单元。 |
keyword |
用于表示语言关键字的词法单元。 |
number |
用于表示数字字面量的词法单元。 |
regexp |
用于表示正则表达式字面量的词法单元。 |
operator |
用于表示操作符的词法单元。 |
标准词法单元修饰符
| ID | 描述 |
|---|---|
declaration |
用于符号的声明。 |
definition |
用于符号的定义,例如在头文件中。 |
readonly |
用于只读变量和成员字段(常量)。 |
static |
用于类成员(静态成员)。 |
deprecated |
用于不应再使用的符号。 |
abstract |
用于抽象类型和抽象成员函数。 |
async |
用于标记为异步的函数。 |
modification |
用于对变量进行赋值的变量引用。 |
documentation |
用于文档中出现的符号。 |
defaultLibrary |
用于属于标准库的符号。 |
除了标准类型和修饰符外,VS Code 还定义了类型和修饰符到相似的 TextMate 作用域的映射。这将在语义词法单元作用域映射一节中介绍。
自定义词法单元类型和修饰符
如有必要,扩展可以通过其扩展的 package.json 中的 semanticTokenTypes 和 semanticTokenModifiers 贡献点来声明新类型和修饰符,或创建现有类型的子类型
{
"contributes": {
"semanticTokenTypes": [
{
"id": "templateType",
"superType": "type",
"description": "A template type."
}
],
"semanticTokenModifiers": [
{
"id": "native",
"description": "Annotates a symbol that is implemented natively"
}
]
}
}
在上面的示例中,扩展声明了一个新类型 templateType 和一个新修饰符 native。通过将 type 命名为超类型,针对 type 的主题样式规则也将应用于 templateType
{
"name": "Red Theme",
"semanticTokenColors": {
"type": "#ff0011"
}
}
上面显示的 semanticTokenColors 值 "#ff0011" 既适用于 type,也适用于它的所有子类型,包括 templateType。
除了自定义词法单元类型外,扩展还可以定义如何将这些类型映射到 TextMate 作用域。这在自定义映射一节中有说明。请注意,自定义映射规则不会自动从超类型继承。相反,子类型需要重新定义映射,最好映射到更具体的作用域。
启用语义高亮
是否计算并高亮显示语义词法单元由设置 editor.semanticHighlighting.enabled 决定。它的值可以是 true、false 和 configuredByTheme。
true和false用于为所有主题开启或关闭语义高亮。configuredByTheme是默认值,它允许每个主题控制是否启用语义高亮。VS Code 自带的所有主题(例如默认的“Dark+”)默认都启用了语义高亮。
依赖语义词法单元的语言扩展可以在其 package.json 中覆盖其语言的默认设置
{
"configurationDefaults": {
"[languageId]": {
"editor.semanticHighlighting.enabled": true
}
}
}
主题化
主题设计是关于为词法单元分配颜色和样式。主题规则在颜色主题文件(JSON 格式)中指定。用户还可以在用户设置中自定义主题规则。
颜色主题中的语义着色
为了支持基于语义词法单元的高亮显示,颜色主题文件格式中新增了两个属性。
属性 semanticHighlighting 定义了该主题是否准备好使用语义词法单元进行高亮显示。它的默认值为 false,但我们鼓励所有主题启用它。当设置 editor.semanticHighlighting.enabled 设置为 configuredByTheme 时,将使用此属性。
属性 semanticTokenColors 允许主题定义新的着色规则,以匹配由语义词法单元提供程序发出的语义词法单元类型和修饰符。
{
"name": "Red Theme",
"tokenColors": [
{
"scope": "comment",
"settings": {
"foreground": "#dd0000",
"fontStyle": "italic"
}
}
],
"semanticHighlighting": true,
"semanticTokenColors": {
"variable.readonly:java": "#ff0011"
}
}
variable.readonly:java 称为选择器,其形式为 (*|tokenType)(.tokenModifier)*(:tokenLanguage)?。
该值描述了规则匹配时的样式。它既可以是一个表示前景色字符串,也可以是一个对象,形式为 { foreground: string, bold: boolean, italic: boolean, underline: boolean } 或 { foreground: string, fontStyle: string }(就像 tokenColors 中用于 TextMate 主题规则的那样)。
前景色必须遵循颜色格式中所述的颜色格式。不支持透明度。
以下是选择器和样式的其他示例
"*.declaration": { "bold": true } // 所有声明都加粗"class:java": { "foreground": "#0f0", "italic": true } // java 中的类
如果没有匹配的规则,或者主题没有 semanticTokenColors 部分(但启用了 semanticHighlighting),VS Code 将使用语义词法单元作用域映射来为给定的语义词法单元评估一个 TextMate 作用域。该作用域将与 tokenColors 中主题的 TextMate 主题规则进行匹配。
语义词法单元作用域映射
为了使语义高亮对于未定义任何特定语义规则的主题也能工作,并作为自定义词法单元类型和修饰符的回退,VS Code 维护了一个从语义词法单元选择器到 TextMate 作用域的映射。
如果主题启用了语义高亮,但未包含针对给定语义词法单元的规则,则将改用这些 TextMate 作用域来查找 TextMate 主题规则。
预定义的 TextMate 作用域映射
下表列出了当前预定义的映射。
| 语义词法单元选择器 | 回退 TextMate 作用域 |
|---|---|
namespace |
entity.name.namespace |
type |
entity.name.type |
type.defaultLibrary |
support.type |
struct |
storage.type.struct |
class |
entity.name.type.class |
class.defaultLibrary |
support.class |
interface |
entity.name.type.interface |
enum |
entity.name.type.enum |
function |
entity.name.function |
function.defaultLibrary |
support.function |
method |
entity.name.function.member |
macro |
entity.name.function.preprocessor |
variable |
variable.other.readwrite , entity.name.variable |
variable.readonly |
variable.other.constant |
variable.readonly.defaultLibrary |
support.constant |
parameter |
variable.parameter |
property |
variable.other.property |
property.readonly |
variable.other.constant.property |
enumMember |
variable.other.enummember |
event |
variable.other.event |
自定义 TextMate 作用域映射
扩展可以通过其 package.json 中的 semanticTokenScopes 贡献点来扩展此映射。
扩展这样做主要有两个用例
-
定义自定义词法单元类型和词法单元修饰符的扩展提供 TextMate 作用域作为回退,以防主题未对添加的语义词法单元类型或修饰符定义主题规则
{ "contributes": { "semanticTokenScopes": [ { "scopes": { "templateType": ["entity.name.type.template"] } } ] } } -
TextMate 语法提供程序可以描述特定于语言的作用域。这有助于包含特定于语言的主题规则的主题。
{ "contributes": { "semanticTokenScopes": [ { "language": "typescript", "scopes": { "property.readonly": ["variable.other.constant.property.ts"] } } ] } }
亲自试一试
我们有一个语义词法单元示例,演示了如何创建语义词法单元提供程序。
作用域检查器工具允许你探索源文件中存在哪些语义词法单元以及它们匹配了哪些主题规则。要查看语义词法单元,请在 TypeScript 文件上使用内置主题(例如 Dark+)。