声明与所在行数不兼容_译:TextMate 语言语法声明

4112111e996c648fcc2fe741a4441b16.png
原文地址: https:// macromates.com/manual/e n/language_grammars
为了了解 VSCode 高亮赶时间粗制滥翻的。

语言语法声明用于为文档元素的命名,如关键字、注释、字符串等等。它的目的主要用于语法高亮,使文本编辑器对于光标所在位置的上下文更“智能”。例如你可能想在键入的时候根据不同上下文出现不同的行为,或者说想要当你在某个位置键入的时候禁止拼写检查(如 HTML 标签)。

语言语法声明仅用于解析文档并将命名后的内容设为该文档的子集。这个时候作用域选择器就可以将其用于样式、首选项以及决定键触和触发器的行为。

有关此概念的更全面介绍,请参阅作用域介绍的博客文章。

语法示例

你可以自行创建一个新的语言语法,只需打开 Bundle 编辑器(Window → Show Bundle Editor)然后选择左下角 add 按钮的“New Language”。

这里给出一个初始声明,我将在后续解释。

01  {  scopeName = 'source.untitled';
02     fileTypes = ( );
03     foldingStartMarker = '{s*$';
04     foldingStopMarker = '^s*}';
05     patterns = (
06        {  name = 'keyword.control.untitled';
07           match = 'b(if|while|for|return)b';
08        },
09        {  name = 'string.quoted.double.untitled';
10           begin = '"';
11           end = '"';
12           patterns = ( 
13              {  name = 'constant.character.escape.untitled';
14                 match = '.';
15              }
16           );
17        },
18     );
19  }

格式就是一个属性列表,根级元素是 5 个键值对:

  • scopeName(第 1 行):这是声明的唯一标识,以点号(.)进行分隔。通常情况下,该名字应该有两部分,第一部分为 text 或者 source,而第二部分为该语言的名字或者文档类型。如果你定义一个已经存在的类型,你可能会想要派生出一个名字来。如 Markdown 就可以是 text.html.markdown,Ruby on Rails(rhtml 文件)就可以是 text.html.rails。从 text.html 派生的好处就是所有在 text.html 作用域下生效的特性一样会在 text.html.«something» 生效(但是优先级会低于当前派生出来的作用域)。
  • fileTypes(第 2 行):这是一个文件类型后缀的数组。当 TextMate 打开一个文件的时候,不知道是什么类型的文件时会参考的数组。但是如果用户从状态栏的语言弹出窗中选择相应的语言,TextMate 也会记住该选择。
  • foldingStartMarker / foldingStopMarker(第 3-4 行):这里面是正则表达式,将与文件内容进行匹配。如果文件中的某一行匹配上了(不是两者都匹配上),那么这行就会被标上折叠标记(详见“折叠”章节)。
  • patterns(第 5-18 行):这是用于解析文档的真实规则数组。在上面的样例中有两个规则(第 6-8 行和第 9-17 行)。规则将在下个章节中展开解释。

实际上在根级元素还有两种键,只不过在上述样例中没有给出展示:

  • firstLineMatch:一个用于匹配文档第一行的正则表达式(首次载入时进行匹配)。如果匹配到了,那么该声明语法就会被用于该文档(除非用户自己覆盖了规则)。例如:^#!/.*brubyb
  • repository:一个可以包含从其它地方声明出来的语法的键值对字典。键名就是规则的名字,值就是真实规则。之后会在介绍 include 规则的时候展开介绍。

语言规则

语言规则负责匹配文档内容。通常情况下,规则先会指定一个名字,该名字将会给到匹配到该规则的文档内容。

规则可以通过两种形式进行匹配。它可以是一个正则表达式,也可以是两个。就像上面的那个样例中第一个规则(第 6-8 行),只要匹配到这个正则表达式的内容就可以获得它对应的名字。例如第一个规则对应的名字是 keyword.control.untitled,对应的匹配项为 ifwhileforreturn 这几个关键字。我们接下去就可以使用 keyword.control 这个作用域选择器用相应的主题来高亮这些关键字。

另一种匹配类型就是上面样例的第二个规则了(第 9-17 行)。这里有两个正则表达式,分别在 beginend 里。该规则的名字将被赋予那些匹配到 beginend 之间的内容。如果只匹配到了 begin,那么 end 会被定为文档结尾。

在第二种匹配类型中,在 beginend 之间的内容还可以继续匹配子规则。在我们的样例中,我们将匹配开始 " 与匹配结束的 " 给括成字符串,并在字符串中将转义符标记为 constant.character.escape.untitled(第 13-15 行)。

需要注意的是,正则表达式一次只匹配文档的一行。也就是说它无法匹配多行内容。这是由技术原因导致的:解析器可以在任意行重新启动,并且只需要重新解析受编辑影响的最小行数。在大所述情况下,我们都可以使用 begin / end 模式来克服此限制。

规则字段

下面的这一系列键名都可以用在规则中。

  • name:规则名。它将用于样式、作用域级别的设置和行为,这意味着它通常应该从一个标准的名称中派生出来(详见命名约定)。
  • match:一个用于匹配的正则表达式,例如 'b(true|false)b
  • begin / end:这两个字段允许匹配多行,并与 match 字段互斥。begin 用于匹配开始,end 用于匹配结束。在 begin 中捕获的内容可以在 end 中被引用。这常用语 here-docs,例如:
{   name = 'string.unquoted.here-doc';
    begin = '<<(w+)';  // match here-doc token
    end = '^1$';       // match end of here-doc
}

一个 begin / end 规则可以用 pattern 嵌套。例如:

{  begin = '<%'; end = '%>'; patterns = (
      { match = 'b(def|end)b'; … },
      …
   );
};


上面的规则将会在 <% … %> 代码块之间匹配 defend 关键字(实际上嵌入式的语法还可以使用后续讲到的 include 字段)。

  • contentName:该字段与 name 类似,但是只用于将名字赋给 begin / end之间的内容。例如如果我们要将 #if 0#endif 之间的内容标记为注释,就需要:
{  begin = '#if 0(s.*)?$'; end = '#endif';
   contentName = 'comment.block.preprocessor';
};
  • captures / beginCaptures / endCaptures:这些字段允许你为 matchbegin 或者 end 正则表达式捕获到的内容附上属性。在 begin / end 模式下使用 captures 相当于是 beginCapturesendCaptures 同值的一种缩写。
    这些字段的值是一个键值对字典,其中键名为正则中捕获到的序号,值为需要附上的属性。目前只有 name 属性可用。例如:
{  match = '(@selector()(.*?)())';
   captures = {
      1 = { name = 'storage.type.objc'; };
      3 = { name = 'storage.type.objc'; };
   };
};

这个例子中,我们将会匹配形如 @selector(windowWillClose:) 字样,storage.type.objc 只会赋给 @selector()

  • include:该字段允许你从别的语言引用规则,可以递归引用。
    • 从别的语言引用,使用该语言的作用域名:
{  begin = '<?(php|=)?'; end = '?>'; patterns = (
      { include = "source.php"; }
   );
}
    • 引用自身,使用 $self
{  begin = '('; end = ')'; patterns = (
      { include = "$self"; }
   );
}
    • 引用当前语法库,在名称前加上井号(#):
patterns = (
   {  begin = '"'; end = '"'; patterns = (
         { include = "#escaped-char"; },
         { include = "#variable"; }
      );
   },
   …
); // end of patterns
repository = {
   escaped-char = { match = '.'; };
   variable =     { match = '$[a-zA-Z0-9_]+'; };
};

这该可以用于匹配递归结构,如嵌套字符集:

patterns = (
   {  name = 'string.unquoted.qq.perl';
      begin = 'qq('; end = ')'; patterns = (
         { include = '#qq_string_content'; },
      );
   },
   …
); // end of patterns
repository = {
   qq_string_content = {
      begin = '('; end = ')'; patterns = (
         { include = '#qq_string_content'; },
      );
   };
};

它可以用于匹配这样的内容:qq( this (is (the) entire) string).

命名约定

在 TextMate 中你用语法声明规则可以将任何的名字赋给文档的任意部分,然后在作用域选择器中使用这些名字。

然而,有些惯例可以使得一个主题可以适配更多的语言,而不需要针对每种语言都出不同的规则。例如你可以不希望在插入字符串和注释的时候自动匹配 ,这与语言无关,而在任何场景都是比较有必要的。

在介绍命名约定之前,我需要说一下几个要点:

  1. 一个最小化的主题只会定义 11 个根组中的 10 个样式(其中 meta 是不会有视觉样式的),所以你需要“展开”你的命名,而不是将所有内容都放到 keyword 下面。你需要问自己“我需要将这两种元素的样式做到不一样吗?”如果是的,它们就应该被放到不同的根组中。
  2. 即使你需要“展开”你的命名,当你找到相应组(例如 storage)的时候,你也应该先复用该组下已经存在的名字(例如 storage 下的 modifier 或者 type),而不是马上写一个新的名字。你还该为你所选的子类型添加尽可能多的信息。例如,如果你在 storage modifier 匹配到了 static,那么应该将其命名为 storage.modifier.static.«language»,而不是仅仅叫它 storage.modifier。为其加上更多的附加信息就可以将其余其它的 storage.modifier 区别开来。
  3. 在名字的最后加上语言名。这看起来是多余的,因为你通常可以使用一个作用域选择器:source.«language» storage.modifier,但是对于嵌套声明语言来说这就不是一直会生效的了。

下面是 11 个根组,并附加上一些解释。这里的排版为分层列表,但是就是作用域名是通过每个级别的名称用点号(.)拼接起来的。例如 double-slash 全称就是 comment.line.double-slash

  • comment:注释。
    • line:行注释,我们将注释分化,以便可以从范围中提取注释开始字符的类型。
      • double-slash// comment
      • double-dash-- comment
      • number-sign# comment
      • percentage% comment
      • 字符:其它行类型
    • block:多行注释,如 /* … */<!-- … -->
      • documentation:嵌入式文档
  • constant:常量。
    • numeric:匹配数字,如 421.3f0x4AB1U
    • character:匹配字符,如 &lt;e031
      • escape:转义序列,如 e 应该为转义序列。
    • language:由语言层面定义的特殊常量,如 truefalsenilYESNO 等。
    • other:其它常量,如 CSS 中的颜色。
  • entity:实体,指文档中比较大坨的部分,如章节、类、函数或者标记。我们并不讲所有的实体都归于 entity.*,有些会归于 meta.*。但我们确实会使用 entity.* 作为一些大坨实体的占位符,例如如果一个实体是章节,那么我们会用 entity.name.section 作为章节标题。
    • name:用于命名一个大实体。
      • function:函数实体。
      • type:类型定义或者类。
      • tag:标记。
      • section:章节。
    • other:其它实体。
      • inherited-class:超级、基类。
      • attribute-name:属性(通常在 tag 中出现)。
  • invalid:非法内容。
    • illegal:非法,如 HTML 中的 & 等(非实体 / tag 中的部分)。
    • deprecated:过时内容,如使用了不推荐使用的 API 函数。
  • keyword:关键字(当它无法被归并到别的组时使用)。
    • control:通常是流程控制关键字,如 continuewhilereturn 等。
    • operator:文本(如 or)或者字符类型的操作符。
    • other:其它关键字。
  • markup:适用于标记语言,通常适用于较大的文本子集。
    • underline:下划线文本。
      • link:用于超链接,为了方便起见,它是从 markup.underline 派生的,这的话即使没有专门针对 markup.underline.link 的主题规则,也能继承下划线样式。
    • bold:加粗文本。
    • heading:章节头。(可选)为接下去的元素提供标题级别,如 HTML 中的 markup.heading.2.html<h2>…</h2>
    • italic:斜体文本。
    • list:列表项。
      • numbered:有序列表项。
      • unnumbered:无序列表项。
    • quote:引用文本。
    • raw:逐字文本,如代码项。通常对于 markup.raw 是禁用拼写检查的。
    • other:其它标记结构。
  • meta:meta 作用域通常用于标记文档的较大部分。如声明函数的整行将是 meta.function,子集将是 storage.typeentity.name.functionvariable.parameter 等,并且只有后者才会被样式化。
  • storage:与存储相关的内容。
    • type:类型,如 classfunctionintvar 等。
    • modifier:存储相关的修饰符,如 staticfinalabstract 等。
  • string:字符串。
    • quoted:引号字符串。
      • single:单引号字符串,如 'foo'
      • double:双引号字符串,如 "foo"
      • triple:三引号字符串,如 """Python"""
      • other:其它字符串,如 $'shell'%s{...}
    • unquoted:如 here-docs 和 here-strings。
    • interpolated:需要被计算的字符串,如 反引号date反引号$(pwd)
    • regexp:正则表达式。
    • other:其它字符串类型。
  • support:框架或者库提供的内容。
    • function:框架或者库提供的函数,如 Objective-C 中的 NSLog 就应该是 support.function
    • class:框架或者库提供的类。
    • type:框架或者库提供的类型,有可能只在 C 系语言中使用,如 typedef 等。大多数其它语言都会被认为是类。
    • constant:框架或者库提供的常量。
    • variable:框架或者库提供的变量,如 AppKit 中的 NSApp
    • other:其它内容。
  • variable:变量。
    • parameter:参数。
    • language:语言保留变量,如 thissuperself 等。
    • other:其它变量,如 $some_variables
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值