语法高亮指南
语法高亮确定在Visual Studio Code编辑器中显示的源代码的颜色和样式。它负责为像这样的关键字着色如果或对于在JavaScript中与字符串和注释及变量名不同。
语法高亮有两部分组成:
在深入细节之前,一个良好的开端是使用作用域检查器工具来探索源文件中有哪些令牌以及它们匹配哪些主题规则。要同时查看语义和语法令牌,请在TypeScript文件上使用内置主题(例如,Dark+)。
标记化
文本的标记化是将文本分割成段,并对每个段进行标记类型的分类。
VS Code 的标记化引擎由 TextMate 语法提供支持。TextMate 语法是一个结构化的正则表达式集合,以 plist (XML) 或 JSON 文件形式编写。VS Code 扩展可以通过 语法贡献点。
TextMate 词法分析引擎与渲染器运行在同一个进程,用户输入时,词法会相应更新。词法用于语法高亮显示,同时也用于将源代码分类为注释、字符串、正则表达式等区域。
从1.43版本开始,VS Code还允许扩展通过语义令牌提供程序来提供令牌化。语义提供程序通常由对源文件有更深入理解的语言服务器实现,可以在项目上下文中解析符号。例如,常量变量名称可以在整个项目中使用常量突出显示显示,而不仅仅是在其声明处。
基于语义标记的突出显示被认为是基于 TextMate 的语法突出显示的补充。语义突出显示会在语法突出显示之上进行。由于语言服务器加载和分析项目可能需要一些时间,因此语义标记突出显示可能会出现短暂的延迟。
本文重点介绍基于TextMate的标记化。语义标记化和主题化在语义高亮指南中解释。
TextMate 语法
VS Code 使用 TextMate 语法 作为语法标记化引擎。它们最初是为 TextMate 编辑器发明的,由于开源社区创建和维护的大量语言包,许多其他编辑器和 IDE 也采用了它们。
TextMate 语法依赖于 Oniguruma 正则表达式,通常以 plist 或 JSON 的形式编写。你可以在 这里 找到关于 TextMate 语法的良好介绍,你还可以查看现有的 TextMate 语法以了解它们的工作原理。
TextMate 令牌和作用域
标记是属于同一程序元素的一个或多个字符。示例标记包括运算符,例如输入:+和输入:*变量名称,例如我的变量,或字符串,例如"我的字符串"输入:.
每个标记都与一个作用域相关联,该作用域定义了标记的上下文。作用域是用点分隔的标识符列表,这些标识符指定了当前标记的上下文。输入:+在JavaScript中进行操作,例如,具有作用域关键字.操作符.算术.js输入:.
主题将作用域映射到颜色,并将样式映射到提供语法突出显示。TextMate 提供了常见作用域列表,许多主题都针对这些作用域。为了使您的语法尽可能广泛地得到支持,请尽量基于现有的作用域而不是定义新的作用域。
作用域嵌套,使得每个标记还与一个父作用域列表相关联。下面的示例使用作用域检查器来显示作用域层次结构输入:+操作符在一个简单的JavaScript函数中。最具体的范围列在顶部,更一般的父范围列在下面:

父作用域信息也用于主题化。当一个主题针对某个作用域时,除非该主题为各个具体作用域提供了更具体的着色规则,否则所有具有该父作用域的标记都会被着色。
配置括号匹配范围
某些语言包含一些令牌,尽管它们在视觉上类似于括号,但不应参与括号匹配。
有俩个属性用于配置括号匹配行为:
平衡括号作用域定义哪些作用域参与括号匹配。默认情况下,所有作用域都包括在内。不匹配的括号范围定义了应从括号匹配中排除的范围。
{
"不匹配的括号作用域": ["meta.scope.case-pattern.shell"]
}
贡献基本语法
VS Code 支持 json TextMate 语法。这些语法是通过以下方式贡献的语法 贡献点.
每个语法贡献都指定了:语法适用的语言的标识符,语法令牌的顶级作用域名称,以及语法文件的相对路径。下面的示例显示了一个虚构语法的语法贡献abc语言:
{
"contributes": {
"languages": [
{
"id": "abc",
"extensions": [".abc"]
}
],
"grammars": [
{
"language": "abc",
"scopeName": "source.abc",
"path": "./syntaxes/abc.tmGrammar.json"
}
]
}
}
语法文件本身由一个顶级规则组成。这通常被拆分为一个模式列出程序的顶级元素的部分和一个仓库定义每个元素。语法中的其他规则可以引用元素。仓库使用{ "包含": "#id" }输入:.
示例abc语法标记字母一个,输入:b,和输入:c作为关键字,并将括号的嵌套作为表达式。
{
"scopeName": "source.abc",
"patterns": [{ "include": "#expression" }],
"repository": {
"expression": {
"patterns": [{ "include": "#letter" }, { "include": "#paren-expression" }]
},
"letter": {
"match": "a|b|c",
"name": "keyword.letter"
},
"paren-expression": {
"begin": "\\(",
"end": "\\)",
"beginCaptures": {
"0": { "name": "punctuation.paren.open" }
},
"endCaptures": {
"0": { "name": "punctuation.paren.close" }
},
"name": "expression.group",
"patterns": [{ "include": "#expression" }]
}
}
}
语法引擎将尝试依次应用表达式对文档中的所有文本应用规则。对于一个简单的程序,例如:
a
(
b
)
x
(
(
c
xyz
)
)
(
a
示例语法生成以下作用域(从最具体到最不具体的范围按从左到右的顺序列出):
a keyword.letter, source.abc
( punctuation.paren.open, expression.group, source.abc
b keyword.letter, expression.group, source.abc
) punctuation.paren.close, expression.group, source.abc
x source.abc
( punctuation.paren.open, expression.group, source.abc
( punctuation.paren.open, expression.group, expression.group, source.abc
c keyword.letter, expression.group, expression.group, source.abc
xyz expression.group, expression.group, source.abc
) punctuation.paren.close, expression.group, expression.group, source.abc
) punctuation.paren.close, expression.group, source.abc
( 标点符号.括号.左, 表达式.分组, 来源.abc
a 关键字.字母, 表达式.分组, 来源.abc
请注意,未被规则匹配的文本,例如字符串xyz, 包含在当前范围内。文件末尾的最后一个括号是的一部分表达式.组即使结束规则未匹配,因为文档结束在之前发现的结束规则是。
嵌入式语言
如果你的语法包括嵌入在主语言中的嵌入语言,例如 HTML 中的 CSS 样式块,你可以使用嵌入语言贡献点来告诉 VS Code 将嵌入语言视为与父语言不同。这确保了在嵌入语言中括号匹配、注释和其他基本语言功能按预期工作。
该嵌入语言贡献点将嵌入式语言中的范围映射到顶级语言范围。在下面的示例中,任何在meta.embedded.block.javascript范围将被视为JavaScript内容:
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/abc.tmLanguage.json",
"scopeName": "source.abc",
"embeddedLanguages": {
"meta.embedded.block.javascript": "javascript"
}
}
]
}
}
现在,如果你尝试对一组标记的代码进行注释或触发片段meta.embedded.block.javascript, 他们将得到正确的输入://JavaScript 注释和正确的 JavaScript 代码片段。
开发新的语法扩展
要快速创建一个新的语法扩展,请使用VS Code 的 Yeoman 模板来运行你的代码并选择新语言选项:

Yeoman将引导您回答一些基本问题,以构建新的扩展。创建新语法时需要关注的重要问题有:
语言编号- 一个唯一标识你语言的标识符。语言名称- 一个易读的人类语言名称。作用域名称- 根据你的语法,设置 TextMate 的 scope name。

生成器假定您希望定义一种新的语言和该语言的新语法。如果您正在为现有语言创建语法,请仅填写目标语言的信息,并确保删除语言生成的贡献点package.json输入:.
在回答所有问题后,Yeoman 将根据以下结构创建一个新的扩展:

记住,如果你正在为VS Code已知的语言贡献语法,请确保删除语言生成的贡献点package.json输入:.
将现有的 TextMate 语法转换
你的代码也可以帮助将现有的 TextMate 语法转换为 VS Code 扩展。同样,先运行你的代码选择语言扩展当被要求提供一个现有的语法文件时,给出完整路径到任何一个.tmLanguage或.jsonTextMate 语法文件:

使用YAML编写语法
当语法变得越来越复杂时,理解并维护它可能会变得困难,特别是以JSON格式。如果你发现自己在编写复杂的正则表达式或需要添加注释来解释语法的某些方面,请考虑使用YAML来定义你的语法。
Yaml 语法具有与基于 json 的语法完全相同的结构,但允许您使用 yaml 更简洁的语法,并且具有多行字符串和注释等特性。

VS Code 只能加载 json 语法,因此基于 yaml 的语法必须转换为 json。js-yaml 包 和命令行工具使这变得容易。
# 在你的扩展中将js-yaml作为开发依赖安装
$ npm install js-yaml --save-dev
# 使用命令行工具将yaml语法转换为json
$ npx js-yaml syntaxes/abc.tmLanguage.yaml > syntaxes/abc.tmLanguage.json
注入语法
注入语法允许你扩展一个现有的语法。注入语法是一种特殊的 TextMate 语法,它被注入到现有语法中的特定作用域内。注入语法的示例应用:
- 突出显示关键词例如
待办事项在评论中。 - 为现有的语法添加更具体的范围信息。
- 为Markdown代码块添加对新语言的高亮显示。
创建基本注入语法
注入语法通过 贡献package.json就像普通的语法。然而,与其指定一个语言,注射语法使用注入到指定目标语言范围列表以注入语法。
在这个例子中,我们将创建一个简单的注入语法,以突出显示待办事项作为JavaScript注释中的关键字。为了在JavaScript文件中应用我们的注入语法,我们使用源.js目标语言范围在注入到输入:
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/injection.json",
"scopeName": "todo-comment.injection",
"injectTo": ["source.js"]
}
]
}
}
该语法本身是标准的 TextMate 语法,除了顶级部分。注入选择器条目。注入选择器是一个作用域选择器,用于指定注入的语法应应用于哪些作用域。对于我们的示例,我们希望突出显示单词待办事项总计输入:// 注释。使用 作用域检查器,我们发现JavaScript的双斜线注释的作用域是 注释.行.双斜线因此,我们的注射选择器是L:注释.行.双斜线输入:
{
"scopeName": "todo-comment.injection",
"injectionSelector": "L:comment.line.double-slash",
"patterns": [
{
"include": "#todo-keyword"
}
],
"repository": {
"todo-keyword": {
"match": "TODO",
"name": "keyword.todo"
}
}
}
该L:在注入选择器中表示注入将添加到现有语法规则的左侧。基本上这意味着我们的注入语法规则将优先于任何现有的语法规则应用。
嵌入式语言
注入语法也可以向其父语法贡献嵌入语言。就像普通语法一样,注入语法可以使用嵌入语言将嵌入语言的作用域映射到顶级语言的作用域。
一个扩展,用于在JavaScript字符串中突出显示SQL查询,例如,可以使用嵌入语言确保字符串中所有标记的令牌meta.embedded.inline.sql被视为用于基本语言功能(如括号匹配和片段选择)的SQL。
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/injection.json",
"scopeName": "sql-string.injection",
"injectTo": ["source.js"],
"embeddedLanguages": {
"meta.embedded.inline.sql": "sql"
}
}
]
}
}
令牌类型和嵌入式语言
对于嵌入的注射语言,有一个额外的复杂情况:默认情况下,VS Code 将字符串内的所有标记视为字符串内容,并将所有带有注释的标记视为注释内容。由于括号匹配和自动关闭对等功能在字符串和注释内被禁用,如果嵌入的语言出现在字符串或注释内,这些功能在嵌入的语言中也会被禁用。
要覆盖此行为,您可以使用一个meta.embedded.*范围重置 VS Code 标记令牌为字符串或注释内容。始终将嵌入式语言包裹在meta.embedded.*范围以确保 VS Code 正确处理嵌入式语言。
如果你不能添加一个meta.embedded.*范围到你的语法,你也可以使用令牌类型在语法的贡献点中将特定范围映射到内容模式。令牌类型下面的部分确保了任何内容在my.sql.template.string范围被视为源代码:
{
"contributes": {
"grammars": [
{
"path": "./syntaxes/injection.json",
"scopeName": "sql-string.injection",
"injectTo": ["source.js"],
"embeddedLanguages": {
"my.sql.template.string": "sql"
},
"tokenTypes": {
"my.sql.template.string": "other"
}
}
]
}
}
主题化
主题化是将颜色和样式分配给标记。主题化规则在颜色主题中指定,但用户可以在用户设置中自定义主题化规则。
TextMate 主题规则定义在代币颜色与常规 TextMate 主题具有相同的语法。每条规则定义一个 TextMate 作用域选择器和一个相应的颜色和样式。
在评估标记的颜色和样式时,当前标记的作用域会与规则的选择器匹配,以找到每个样式属性(前景色、加粗、斜体、下划线)的最具体规则。
该颜色主题指南描述了如何创建一个颜色主题。语义标记的主题在语义突出显示指南中解释。
范围检查员
VS Code 的内置作用域检查工具有助于调试语法和语义标记。它显示文件中当前位置的标记和语义标记的作用域,以及该标记适用的主题规则的元数据。
在命令面板中通过触发范围检查器开发者:检查编辑器令牌和范围 命令或 创建一个快捷键 为此:
{
"键": "cmd+alt+shift+i",
"命令": "editor.action.inspectTMScopes"
}

范围检查器显示以下信息:
- 当前的代币。
- 关于该代币的元数据和其计算出的外观信息。如果您正在处理嵌入式语言,这里的重要条目
语言和代币类型输入:. - 当当前语言有可用的语义标记提供程序并且当前主题支持语义突出显示时,将显示语义标记部分。它显示当前的语义标记类型和修饰符以及匹配该语义标记类型和修饰符的主题规则。
- TextMate 部分显示了当前 TextMate 标记的范围列表,最具体的范围在顶部。它还显示与范围匹配的最具体的主题规则。这仅显示负责当前标记样式的主题规则,不显示被覆盖的规则。如果存在语义标记,只有当主题规则与匹配语义标记的规则不同时才显示主题规则。