19.译文 | Sublime Text 语法定义

原文: http://docs.sublimetext.info/en/latest/extensibility/syntaxdefs.html#analyzing-patterns

19.译文 | Sublime Text 语法定义_第1张图片
英文列表

语法定义(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 *)。

规则按顺序应用,一次一行,并按以下顺序应用:

  1. 在一行中的第一个位置匹配
  2. 数组中第一个出现

每个规则使用匹配的文本区域,因此将从下一个规则的匹配尝试中排除(除了少数例外)。实际上,这意味着在创建新的语法定义时,您应该注意从更具体的规则转向更一般的规则。否则,一个贪婪的正则表达式可能会吞下你想要不同样式的部分。

来自单独文件的语法定义可以组合,也可以递归应用。

你的第一个语法定义

举个例子,让我们为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.text.。对于编程语言,请使用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
就像使用简单的捕获一样,这将为整个匹配设置范围名称,包括beginend标记。实际上,这将为此规则中定义的beginCapturesendCapturepatterns创建嵌套的范围。可选。

contentName
name不同,这仅将范围名称应用于所包含的文本。可选。

begin
此范围的开始标记的正则表达式。

end
此范围的结束标记的正则表达式。

beginCaptures
捕获begin标记。他们的工作就像捕捉简单的匹配。可选。

endCaptures
beginCaptures相同,但用于结束标记。可选。

pattern
与begin-end里的内容匹配的模式数组;不匹配由beginend产生的文本消耗。可选。

我们将使用此规则在代码段中为嵌套的复杂字段设置样式:

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: .

这是我们将在本教程中看到的最复杂的模式。beginend键是自明的:它们定义一个包含在$ {}之间的区域。我们需要将begin模式包装为引号,否则尾部将告诉解析器期望另一个字典键。 beginCaptures进一步将开始标记划分为较小的范围。

最有趣的部分, patterns。递归和排序的重要性,终于在这里出现了。

我们已经看到,字段可以嵌套。为了解决这个问题,我们需要递归地设计嵌套字段。这就是include规则在提供$ self值时的作用:它递归地将我们的整个语法定义应用于由begin-end规则捕获的文本。此部分不包括正则表达式为beginend单独使用的文本。

记住,匹配的文本被消耗;因此,它从下一次匹配尝试中排除,并且不能再次匹配。

要完成复杂字段,我们将占位符作为字符串。因为我们已经匹配了复杂字段内的所有可能的标记,所以我们可以安全地告诉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: 语法定义参考

你可能感兴趣的:(19.译文 | Sublime Text 语法定义)