原文: http://docs.sublimetext.info/en/latest/extensibility/syntaxdefs.html#analyzing-patterns
语法定义(Syntax Definitions)
语法定义使Sublime Text注意到编程和标记语言。最明显的是,它们利用颜色形成语法高亮。语法定义定义了将缓存区文本划分为命名区域的范围。而Sublime Text的多数编辑功能广泛使用这种细粒的上下文环境信息。
基本上,语法定义包含查找文本的正则表达式,和或多或少任意的点号隔开的字符串,称为范围(* scopes )或范围名称( scope names )。对于每次给定正则表达式的出现,Sublime Text为匹配的文本提供相对应的范围名称( scope names *)。
备注
从Sublime Text Build 3084开始,一个新的语法定义格式添加了,其中包含.sublime
语法扩展。
强烈鼓励使用本文档中描述的传统格式,除非考虑与旧版本的兼容性。
更多文档可在如下网址获得:
http://www.sublimetext.com/docs/3/syntax.html
前提
为了遵循本教程,需要安装 PackageDev,一个旨在简化为Sublime Text创建新语法定义的包。请按照readme的“入门(Getting Started)”中的安装说明进行操作。
文件格式化
Sublime Text使用 属性列表(Plist)文件来存储语法定义。而由于编辑XML文件是个复杂的事情,我们将使用 YAML来代替以及之后转换为Plist格式。这也是PackageDev 包的所在。
备注
如果你在本教程中遇到意外的错误,这可能是PackageDev 或者YAML 的错,不要立即认为问题出现在Sublime Text上。
如果您喜欢使用XML工作,请手动编辑Plist文件,但始终牢记其在转义序列及许多XML标签等方面的不同需求。
范围(scopes)
范围是Sublime Text中的一个关键概念。基本上,它们在缓冲区中被命名为文本区域,但自己不做任何事情,Sublime Text会在需要上下文信息时快速查看它们。
例如,当您触发代码段时,Sublime Text将检查绑定到代码段的范围,并查看文件中插入符的位置。如果插入符的当前位置与代码段的范围选择器匹配,Sublime Text则将其触发。否则,什么也不会发生。
范围可以嵌套以允许高度的粒度。您可以使用CSS选择器向下展开层次结构。例如,由于有范围选择器,您可以在Python源代码中的单个引用字符串中激活键绑定,但不能在任何其他语言的单引号字符串中激活。
Sublime Text继承了Textmate(Mac的文本编辑器)范围的思想。 Textmate的在线手册包含有关范围选择器的更多信息,这些信息对Sublime Text用户也很有用。特别是可使用自己期望的颜色对语言进行风格化。
scopes跟scopes selectors的比较
范围和范围选择器之间存在细微差异:范围是在语法定义中定义的名称,而范围选择器用于类似片段和键绑定的项目,以定位范围。当创建新的语法定义时,您关心范围;当您想要将某个片段限制到某个范围时,可以使用范围选择器。
语法定义如何工作
在它们的核心,语法定义是与范围名称配对的正则表达式的数组。 Sublime Text将尝试将这些模式与缓冲区的文本进行匹配,并将相应的范围名称附加到所有事件。这些正则表达式和作用域名称的对称为规则(* rules *)。
规则按顺序应用,一次一行,并按以下顺序应用:
- 在一行中的第一个位置匹配
- 数组中第一个出现
每个规则使用匹配的文本区域,因此将从下一个规则的匹配尝试中排除(除了少数例外)。实际上,这意味着在创建新的语法定义时,您应该注意从更具体的规则转向更一般的规则。否则,一个贪婪的正则表达式可能会吞下你想要不同样式的部分。
来自单独文件的语法定义可以组合,也可以递归应用。
你的第一个语法定义
举个例子,让我们为Sublime Text片段创建一个语法定义。我们将为实际的代码段内容设置样式,而不是整个.sublime-snippet
文件。
备注
由于语法定义主要用于启用语法高亮,因此我们将使用短语风格来* 将源代码文件分解为范围 *。但请记住,颜色与语法定义不同,除了语法高亮之外,范围还有更多的用途。
以下是我们要在片段中设置样式的元素:
- 变量(
$ PARAM1
,$ USER_NAME
...) - 简单字段(
$ 0
,$ 1
...) - 带有占位符的复杂字段(
$ {1:Hello}
) - 嵌套字段(
$ {1:Hello $ {2:World}!}
) - 转义序列(
\\ $
,\\ <
...) - 非法序列(
$
,<
...)
下面是我们不想要命名的元素,因为它们对于这个例子太复杂了:
- 变量替换(
$ {1 / Hello / Hi / g}
)
备注
在继续之前,请确保您已如上所述安装了PackageDev包。
创建新的语法定义
要创建新的语法定义,请按照下列步骤操作:
- 转到** Tools | Packages | Package Development | New Syntax Definition **
- 将新文件作为
.YAML-tmLanguage
文件保存在Packages / User
文件夹中。
你现在应该看到这样的文件:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Syntax Name
scopeName: source.syntax_name
fileTypes: []
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
-
...
让我们来看看关键元素。
name
所定义语法的描述性名称(Sublime Text将在语法定义下拉列表中显示)。使用简短的描述性名称,通常使用正在为其创建语法定义的编程语言的名称。
scopeName
这是此语法定义的最大范围。它采用表单source.<lang_name>
或text.<lang_name>
。对于编程语言,请使用source
;对于标记和其他,使用text
。
fileTypes
这是一个文件扩展名列表(没有前导点)。打开这些类型的文件时,Sublime Text将自动为其激活此语法定义。
uuid
这是此语法定义的唯一标识符。每个新的语法定义都有自己的uuid。虽然Sublime Text本身忽略它,但不要修改它。
patterns
这是你的模式的容器。
在我们的例子中,请使用以下信息填充模板:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
-
...
备注
YAML不是一个非常严格的格式,但你不知道它的约定是一件很头痛的事。它支持单双引号,只要该内容不创建另一个YAML字面值,也可以省略它们。如果转换为Plist失败,请查看输出面板以获取有关错误的更多信息。我们将在之后解释如何将YAML中的语法定义转换为Plist。这也将覆盖模板中的第一个已注释的行。
---
和...
是可选的。
分析模式
patterns
数组可以包含几种类型的元素。我们将在以下部分查看其中的一些。如果您想了解更多关于模式的信息,请参阅Textmate的在线手册。
匹配
如下:
match: (?i:m)y \s+[Rr]egex
name: string.format
comment: This comment is optional.
match
Sublime Text将使用正则表达式来查找匹配项。
name
范围的名称,应用于任何match
的出现。
comment
可选。为了提供信息。
语法定义中的正则表达式语法
Sublime Text在语法定义中使用Oniguruma的正则表达式语法。一些现有的语法定义使用不是perl-style一部分的正则表达式引擎支持的特性,因此需要Oniguruma。
回到我们的例子。如下:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
-
...
也就是说,确保patterns
数组为空。
现在我们可以开始添加Sublime片段的规则。从简单字段开始,可以与正则表达式匹配如下:
\$[0-9]+
# or...
\$\d+
我们可以构建这样的模式:
name: keyword.other.ssraw
match: \$\d+
comment: Tab stops like $1, $2...
我们可以把它添加到我们的语法定义:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
- comment: Tab stops like $1, $2...
name: keyword.other.ssraw
match: \$\d+
...
备注
您应该使用两个空格缩进。这是YAML的推荐缩进,并与如上所示的列表排成一行。
我们现在可以将我们的文件转换为.tmLanguage
。出于兼容性原因,语法定义使用Textmate的.tmLanguage
扩展。如上所述,它们只是Plist XML文件。
按照以下步骤执行转换:
- 确保在** Tools | Build System**中选择
Automatic
,或选择Convert to ...
- 按F7
- 将在与
.YAML-tmLanguage
文件相同的文件夹中为您生成.tmLanguage
文件 - Sublime Text将重新载入对语法定义的更改
PackageDev为什么知道你想要将转换的文件:它在第一条注释行中指定。
您现在已经创建了第一个语法定义。接下来,打开一个新文件并用扩展名.ssraw
保存。缓冲区的语法名称应该自动切换到“Sublime Snippet(Raw)”,如果你键入$ 1
或任何其他简单的代码段字段,你应该得到语法高亮。
让我们继续为环境变量创建另一个规则。
comment: Variables like $PARAM1, $TM_SELECTION...
name: keyword.other.ssraw
match: \$[A-Za-z][A-Za-z0-9_]+
重复上述步骤更新.tmLanguage
文件。
微调匹配
你可能已经注意到,例如,$ PARAM1
中的整个文本的样式是相同的方式。根据你的需要或你的个人喜好,你可能想要$
脱颖而出。这就是captures
出现的地方。使用捕获,你可以将模式分解成组件,以单独定位它们。
让我们使用captures
重写之前的模式来:
comment: Variables like $PARAM1, $TM_SELECTION...
name: keyword.other.ssraw
match: \$([A-Za-z][A-Za-z0-9_]+)
captures:
'1': {name: constant.numeric.ssraw}
捕获引入了规则的复杂性,但是它们非常简单。注意数字从左到右指向括号组。当然,你可以有你想要的尽可能多的捕获组。
备注
由于PackageDev,在新行上写1
,然后按Tab将自动完成'1':{name:}
。
可以说,你希望另一个范围在视觉上与这个一致。继续改变它。
备注
与通常的正则表达式和取代一样,捕获组'0'
适用于整个匹配。
开始 - 结束规则
到目前为止,我们一直使用一个简单的规则。虽然我们已经看到了如何将模式分解为更小的组件,但有时候需要定位源代码的更大部分,该源代码由开始和结束标记明确分隔。
用引号或其他定界结构括起来的文字字符串最好由起始规则处理。这是这些规则之一的骨架:
name:
begin:
end:
好吧,至少在他们最简单的版本。让我们来看看包含所有可用的选项:
name:
contentName:
begin:
beginCaptures:
'0': {name: }
# ...
end:
endCaptures:
'0': {name: }
# ...
patterns:
- name:
match:
# ...
一些元素可能看起来很熟悉,但它们的组合可能是令人生畏的。让我们单独检查它们。
name
就像使用简单的捕获一样,这将为整个匹配设置范围名称,包括begin
和end
标记。实际上,这将为此规则中定义的beginCaptures
,endCapture
和patterns
创建嵌套的范围。可选。
contentName
与name
不同,这仅将范围名称应用于所包含的文本。可选。
begin
此范围的开始标记的正则表达式。
end
此范围的结束标记的正则表达式。
beginCaptures
捕获begin
标记。他们的工作就像捕捉简单的匹配。可选。
endCaptures
与beginCaptures
相同,但用于结束标记。可选。
pattern
仅与begin-end里的内容匹配的模式数组;不匹配由begin
或end
产生的文本消耗。可选。
我们将使用此规则在代码段中为嵌套的复杂字段设置样式:
name: variable.complex.ssraw
contentName: string.other.ssraw
begin: '(\$)(\{)([0-9]+):'
beginCaptures:
'1': {name: keyword.other.ssraw}
'3': {name: constant.numeric.ssraw}
end: \}
patterns:
- include: $self
- name: support.other.ssraw
match: .
这是我们将在本教程中看到的最复杂的模式。begin
和end
键是自明的:它们定义一个包含在$ {<NUMBER>:
和}
之间的区域。我们需要将begin模式包装为引号,否则尾部:
将告诉解析器期望另一个字典键。 beginCaptures
进一步将开始标记划分为较小的范围。
最有趣的部分, patterns
。递归和排序的重要性,终于在这里出现了。
我们已经看到,字段可以嵌套。为了解决这个问题,我们需要递归地设计嵌套字段。这就是include
规则在提供$ self
值时的作用:它递归地将我们的整个语法定义应用于由begin-end规则捕获的文本。此部分不包括正则表达式为begin
和end
单独使用的文本。
记住,匹配的文本被消耗;因此,它从下一次匹配尝试中排除,并且不能再次匹配。
要完成复杂字段,我们将占位符作为字符串。因为我们已经匹配了复杂字段内的所有可能的标记,所以我们可以安全地告诉Sublime Text给任何剩余的文本(.
)一个字符串范围。注意,如果我们使模式贪婪(.+
),这不工作,因为这包括可能的嵌套引用。
备注
我们可以使用contentName:string.other.ssraw
,代替最后一个模式,这样我们就介绍了排序的重要性以及如何使用匹配。
Final Touches
最后,让我们风格转义序列和非法序列,然后我们可以结束。
- comment: Sequences like \$, \> and \<
name: constant.character.escape.ssraw
match: \\[$<>]
- comment: Unescaped and unmatched magic characters
name: invalid.illegal.ssraw
match: '[$<>]'
这里唯一难的是不要忘记[]
在YAML中包含数组,因此必须用引号括起来。除此之外,如果你熟悉正则表达式,规则是相当简单的。
但是,您必须注意将第二条规则放在任何匹配$
字符的其他规则之后,否则它将被使用并导致每个后面的表达式不匹配。
此外,即使在添加这两个附加规则后,请注意,我们从上面的递归开始结束规则继续按预期工作。
这里是最后的语法定义:
# [PackageDev] target_format: plist, ext: tmLanguage
---
name: Sublime Snippet (Raw)
scopeName: source.ssraw
fileTypes: [ssraw]
uuid: 0da65be4-5aac-4b6f-8071-1aadb970b8d9
patterns:
- comment: Tab stops like $1, $2...
name: keyword.other.ssraw
match: \$(\d+)
captures:
'1': {name: constant.numeric.ssraw}
- comment: Variables like $PARAM1, $TM_SELECTION...
name: keyword.other.ssraw
match: \$([A-Za-z][A-Za-z0-9_]+)
captures:
'1': {name: constant.numeric.ssraw}
- name: variable.complex.ssraw
begin: '(\$)(\{)([0-9]+):'
beginCaptures:
'1': {name: keyword.other.ssraw}
'3': {name: constant.numeric.ssraw}
end: \}
patterns:
- include: $self
- name: support.other.ssraw
match: .
- comment: Sequences like \$, \> and \<
name: constant.character.escape.ssraw
match: \\[$<>]
- comment: Unescaped and unmatched magic characters
name: invalid.illegal.ssraw
match: '[$<>]'
...
有更多可用的结构和代码重用技术使用“存储库”,但上述解释应该可以让您开始创建语法定义。
备注
如果以前使用JSON作为语法定义,您仍然可以这样做,因为PackageDev向后兼容。
如果你想考虑切换到YAML(从JSON或直接从Plist),它提供一个命令称为PackageDev: Convert to YAML and Rearrange Syntax Definition
,将自动以愉快的方式格式化生成的YAML。
更多:
Syntax Definitions: 语法定义参考