VS CODE支持很多的插件,今天我们就是利用code的一个插件实现对新的语言(伪代码)进行编辑。
code不像vs studio,他不是ide,没有运行的环境,而是一款轻量级的跨平台的编辑器。所以环境我们要自己进行搭建,新的语言要利用一些插件实现。

原作者的小建议:这篇文章的最佳阅读姿势是在电脑上打开,并跟着文章一步步做下去。

创建项目

编写插件的第一步,就是创建我们的目录结构。这里我们使用一个叫做yo的脚手架工具。yo是一个富有高度扩展性的通用脚手架,可以通过插件来实现不同目录结构和初始选项。VS Code官方提供了名为generator-code的插件,来进行插件目录的创建。首先我们需要安装yo以及插件generator-code

1
npm i -g yo generator-code

在安装完成以后,使用下面的命令来创建目录结构。

1
yo code

在运行yo code以后,它会问你下面这些问题。建议大家和我的输入保持相同,以免遇到意外。这里我们给伪代码的取名为zhuanzhuan,并且告诉VS Code,当碰到一个文件的扩展名为.zhuanzhuan或者.zz时,就要运行我们这个插件。

img

输入完所有的选项以后,我们的插件目录就创建完成了。结构是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
├── CHANGELOG.md

├── README.md

├── language-configuration.json

├── package.json

├── syntaxes

├── # 在tmLanguage.json中自定义语法

│ └── zhuanzhuan.tmLanguage.json

└── vsc-extension-quickstart.md

了解Scope

想要实现语法高亮,就需要将一串代码字符串,拆分成无数的小碎片,然后分别为它们指定color等样式。这些拆分后的小碎片,被称作token。这里的tokenjwt中的token不同,并没有安全、令牌等方面的意思,而是更偏向"符号"的含义。我们来看一个简单例子???来理解一下这段话。

1
2
3
4
5
6
7
8
9
function sum(a: number, b: number): number {



return a + b;



}

在这段TS代码中,我们定义了一个用于求和的函数。这时候我们按下VS Code快捷键,shift+cmd+p,然后输入inspect editor tokens and scopes,就可以看到每个token对应的类型。比如sum这个token的类型就是functionab的类型是parameter

img

另外从截图的底部中,我们还可以看到,每个token还具有一个叫textmate scope的属性。通俗地说,scope指这个token所处的位置。

比如下面的代码片段里,有两个a变量。第一个是一个变量声明,而第二个是函数的参数之一。虽然它们都可以被笼统地称为变量,但是因为所处的scope不同(也就是处于不同环境),所以在VS Code中会被显示成不同的颜色。(由于微信文章中的代码高亮较弱,看不出区别。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const a = 1;







function sum(a: number, b: number): number {



return a + b;



}

支持注释

至此前置知识已经介绍完了,现在开始真正修改脚手架创建的代码。我们的第一个目标是,让zhuanzhuan语言支持注释。

首先打开根目录下的language-configuration.json文件,找到comments字段,将lineComment从默认的//修改为注释:。完成以后按下F5启动Debug程序,VS Code会打开一个新的窗口,且我们的插件会在其中生效。在新窗口中,我们随意打开一个空文件夹,然后新建名为fakeCode.zz,并输入以下内容进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
注释: 当我们用中文伪代码来描述执行过程的时候,

注释: 不管什么内容都被显示成灰色的字符串了。

注释: 我们的目标就是让它们变得五彩斑斓。

注释: 不要试图重构这个方法,不然你会虚度一天的光阴。

如果 ([某个条件]) {

做一些条件成立时的事情

} 否则 {

当条件不成立的时候...

}

遍历 商品

打印 《商品id》

结束

函数 [函数名] {

函数的内容

}

这时,我们按下注释转换的快捷键cmd+/,就会惊讶地发现,VS Code会为你自动转换注释内容,在这之间转换:注释:具体内容具体内容

这样,我们的Hello World项目就完成了,开始做稍微复杂一些的事情。打开/syntexes/zhuanzhuan.tmLanguage.json文件,将这个文件的所有内容替换成下面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
{

"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",

"name": "zhuanzhuan",

"scopeName": "source.zz",

"patterns": [

{

"include": "#comments"

}

],

"repository": {

"comments": {

"name": "punctuation.comment",

"begin": "注释:",

"end": "\\n",

"beginCaptures": {

"0": {

"name": "punctuation.comment.open"

}

},

"endCaptures": {

"0": {

"name": "punctuation.comment.close"

}

}

}

}

}

第一眼看的时候都会懵的,我们慢慢理解一下这到底是什么意思。

  • patterns & repository

    repository是规则的仓库,它规定了该条规则如何识别其适用的对象。而patterns则是规定了规则仓库中,哪些规则是需要生效的。所以如果我们想要加一条新的规则,需要在repository中加规则的内容,并在patterns中将这条规则include,不然即使在repository添加了规则也不会生效。

  • comments

    这是我们加入的自定义规则,comments是规则的名字。

  • begin & end

    决定这条规则的适用对象。这里我们将注释:开头,回车符结尾的这部分字符作为适用对象。

  • beginCapture & endCapture & name

    这三个属性,代表着我们赋予适用对象的scope名称。比这样一条注释"注释:不要试图重构这个方法,不然你会虚度一天的光阴"。,对应到我们这条规则,就是注释:这部分被赋予了punctuation.comment.open scope,不要试图重构这个方法,不然你会虚度一天的光阴。scope为punctuation.comment,最后的回车符scope为punctuation.comment.open

然后切换到刚才使用F5打开的Debug窗口,按下cmd+shift+p,运行reload window,让我们的修改生效,就可以看到scope名称的变化:

img

支持关键字

关键字同样是编辑这个文件/syntexes/zhuanzhuan.tmLanguage.json,在repository中新加入keywords规则(记得在顶部的patterns中include它):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{

"keywords": {

"patterns": [

{

"match": "\\b(如果|遍历|结束|打印|函数)\\b",

"name": "keyword.control.zhuanzhuan",

}

]

}

}

解释一下这里的意思:

  • match 这是一个正则,如果碰上如果|遍历|结束|打印|函数其中之一,就将它标记为关键字。
  • name 这些关键字对应的scope是什么。

效果如图:

img

从图中我们可以看出,正则中匹配的字符(如果、遍历、函数等)已经被一一高亮了。不过你的VS Code中不一定是蓝色,这取决于你当前使用的主题。

支持字符串

接着我们让zhuanzhuan语言支持字符串功能,同样是修改json文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{

"repository": {

"strings": {

"name": "string.quoted.book.zhuanzhuan",

"begin": "《",

"end": "》",

"beginCaptures": {

"0": {

"name": "string.quoted.book.open"

}

},

"endCaptures": {

"0": {

"name": "string.quoted.book.close"

}

}

}

}

}

就像JS中使用单双引号和模板字符串作为字符串的标志,为了体现zhuanzhuan语言的不同之处,我们使用书名号,而不是单双引号,来标志一个字符串。例如《xxxx》,它被分成了 xxxx 三个部分,这三个部分有各自的scope,对应关系如下:

  • string.quoted.book.open
  • xxxx string.quoted.book.zhuanzhuan
  • string.quoted.book.close

为了看到修改后的效果,需要在调试窗口中,cmd+shift+p并运行reload window。重载后的效果是这样的:

img

这时候第13行发生了变化,从原来的黑色,变成了绿色。

深入理解scope

看到效果以后,再回过头看那份json文件,它到底表达了什么意思?

首先我们规定了顶层的scope名字叫source.zz。也就是说,当我们新建了.zz结尾的文件,开始写代码,这时所有的代码都处在顶层scope

patterns属性规定了在顶层scope中,有哪些方式可以开辟一个子scopepatterns数组inclucde(即引入)了名为stringskeywords的规则,这些规则被放在了repository(也就是仓库,一个规则的仓库)。

1
2
3
4
5
6
7
8
9
10
11
12
13
{

"strings": {

"name": "string.quoted.book.zhuanzhuan",

"begin": "《",

"end": "》"

}

}

repository中,以strings规则为例,当VS Code解析引擎遇到以“《”开头,“》”结尾的token时,中间的内容会被认为是字符串。也就是说,我们让书名号具备了和JS中的单双引号相同的功能。字符串的scope变成了我们规定的string.quoted.book.zhuanzhuan。我们可以通过inspect editor tokens and scopes命令来验证这一点。

img

图片中,xxxx所属的scope有两个,一个是constant.character.escape.zhuanzhuan,另一个就是根scope。一个token往往拥有多个scope,就像字符串,同时处于根scope和书名号创建的一个scope

在上文的json文件中,还有一个叫keywords的属性,当有字符串满足match字段中的正则表达式时,会被认为是一个关键字。

事实上,当我们把上文的json规则进行更多的扩展和嵌套,就会越来越接近现流行的其他语言,存在无数的嵌套。一个token会属于无数的scope

那么问题来了,这些scope的作用是什么?我们花了很多的力气去定义json格式,来让不同位置的token拥有不同的scope。这样我们就拥有了一个类似于CSS选择器的东西,我们可以为不同scope指定不同的样式,从而让我们自创的语言高亮起来。

使用Scope

接下来我们要使用上文中定义的几个scope。因为目前为止,我们只是重新定义了zhuanzhuan语言中一部分情景下的scope名称,我们可以利用这些自定义的scope,做出更细致的高亮配置。

使用scope的方式就是创建一个theme类型的插件(没错我们要写第二个插件了)。这次我们需要cd到用户文件夹下的.vscode/extensions,这样我们的主题就可以免安装,可以直接出现在VS Code主题列表中。

img

使用VS Code打开项目,然后编辑theme/zhuanzhuan-lang-theme-color-theme.json文件,文件的结构是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{

"name": "zhuanzhuan-lang-theme",

"type": "light",

"colors": {

"editor.background": "#f5f5f5",

"editor.foreground": "#333333"

},

"tokenColors": [

{

"name": "Comments",

"scope": [

"comment",

"punctuation.definition.comment"

],

"settings": {

"fontStyle": "italic",

"foreground": "#AAAAAA"

}

}

]

}

其中,tokenColors字段是我们需要关心的地方,它针对了不同的scope,指定不同的样式。name是这条规则的名字,可以随意命名,保证唯一性即可。scope类似于CSS选择器,是规则应用的对象。settings则是具体的样式。

然后我们在tokenColors中,加上我们自定义的样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{

"tokenColors": [

// 省略其他原有的规则,仅列出新增的规则。

{

"name": "quotedBookOpen",

// 将scope为string.quoted.book.open的token,

// 也就是《,颜色设置成#33ec0e(原谅色)。

"scope": "string.quoted.book.open",

"settings": {

"foreground": "#33ec0e"

}

}

// 省略了大段雷同的配置。

// 只要scope和我们的zhuanzhuan语言定义中的scope相同,

// 就可以高亮对应的token。

]

}

然后在zhuanzhuan-lang插件的调试窗口,打开主题选择列表,选择zhuanzhuan-lang-theme主题,就可以看到上面的三条规则对《商品id》这部分生效了。

img

文字及其对应的scope和颜色如下:

    • scope: string.quoted.book.open
    • 颜色:#33ec0e
    • scope: string.quoted.book.close
    • 颜色:#33ec0e
  • xxxx
    • scope: string.quoted.book.open
    • 颜色:#eb8837

其他的scope可以自行挑选喜欢的色值一一定义,这里就不再重复罗列。

成果

经过上面一系列的努力,然后再添加亿点点细节,最终的效果就是下图。

img

总结

通过阅读文章,我们总共创建了两个VS Code插件。一个是语言支持插件,通过简单的配置,使zhuanzhuan语言支持了中文关键字、书名号字符串以及中括号表示的变量。第二个是主题插件,为zhuanzhuan语言中自定义的scope提供了高亮规则。scope名称,是连接两个插件的枢纽。

不过zhuanzhuan语言离一门完善的语言还需要海量的工作,我们需要定义更多的scope规则,规则之间往往还存在复杂的嵌套关系。这篇文章只是讲了冰山露出海面的那一角。如果想深入学习这方面的知识,仍需参考VS Code官方的文档,以及学习编译原理相关知识。

另外附上文章中两个插件最终的代码:

https://github.com/inkyMountain/zhuanzhuan-lang

https://github.com/inkyMountain/zhuanzhuan-lang-theme

参考文档:https://code.visualstudio.com/api#vscode

文章来源:链接

因为我对这方面也不是太懂,市面上也已经有很多的轮子了,本着不造烂轮子误人子弟的原则,整理了这篇文章。

水平有限,若有错漏,敬请指正。