前言
在前面几期的博文中,我们讨论了如何使用面向对象的方式来编写Vue代码,以提高代码的可维护性和可扩展性。然而,随着我们探索新的语法和特性,发现在VSCode中并未找到相应的插件支持,这严重影响了我们的开发体验。因此,为了让我们的开发环境紧跟最新的语法,从本期开始,我们将陆续介绍如何自制VSCode语言插件,以适配新的语法。
VSCode语言插件的制作是一个复杂的过程,但在本系列教程中,我们将尽量简明扼要地介绍每个必要的流程和代码片段。通过自制插件,我们可以为VSCode增添针对Vue的智能提示、语法高亮、代码片段以及其他便利功能,极大地提升我们的开发效率和舒适度。
为什么需要自制VSCode语言插件?
VSCode作为一款轻量级且功能强大的代码编辑器,受到了广大开发者的喜爱。其拥有丰富的扩展生态系统,几乎涵盖了所有主流编程语言和框架。然而,随着面向对象插件的不断发展,新的语法和特性不断涌现,这些新功能可能并未得到官方插件的支持。
在Vue生态系统中,由于其灵活性和易扩展性,社区经常会提出新的语法糖、指令、装饰器等特性。这些新特性对于Vue开发者来说非常有吸引力,但由于VSCode未提供相应的官方支持,我们的开发体验可能会受到限制,比如缺乏智能提示、错误提示等功能。
为了解决这一问题,自制VSCode语言插件成为了一种解决方案。通过自己开发插件,我们可以根据需要添加新的语法支持,从而让VSCode完美适配我们使用的Vue版本,提供更加便捷的开发体验。
自制VSCode语言插件的复杂性
自制VSCode语言插件是一项挑战性的任务,需要对插件开发流程有一定了解,并且熟悉VSCode插件开发的相关API和机制。不过,本系列教程将尽量降低难度,以简单易懂的方式呈现插件开发的关键步骤和技巧。
我们将主要关注以下几个方面(分几期讲):
- 语法高亮:通过定义语法规则,使得Vue代码在编辑器中能够得到良好的颜色标识,增加代码的可读性。
- 智能提示:为Vue的特定语法和指令添加智能提示功能,让开发者在编写代码时能够得到即时的建议和补全。
- 错误检查:针对新语法进行错误检查,及时发现并提示代码中的问题,避免在编译过程中产生错误。
- 代码片段:提供常用代码片段,简化重复性的代码编写过程。
虽然插件开发过程相对复杂,但一旦掌握了相关技能,将为我们带来巨大的便利和满足感。让我们一起开启自制VSCode语言插件的奇妙之旅吧!
创建项目
-
安装yo generator-code
npm install -g yo generator-code
这个脚手架会生成一个可以立马开发的项目。
-
使用yo来生成项目骨架
yo code
# 根据引导 一步一步创建所需要的项目骨架
# What type of extension do you want to create? 这一步选择New Language Support
# URL or file to import, or none for new: 迁移现成的textMate语法 直接回车
# What's the name of your extension? () 输入拓展名称 这我输入vs-template-lang
# What's the identifier of your extension template-lang
# What's the description of your extension? () 输入插件描述
# Language id: () 这里先随便输入 项目中用不到
# Language name: () 这里先随便输入 项目中用不到
# File extensions: 拓展名 先随便写 后续也用不到
# Scope names: () source.后缀名
# Initialize a git repository? (Y/n) y
回答了一大堆问题之后,YO会创建一个新的插件,其结构如下
由于我们并没有创建新的语言,只是在现有的语言基础上支持新的语法,所以需要删除生成的
package.json
中的languages
配置。
-
运行插件
在编辑器内按F5, 这会在新的开发窗口中编译&运行拓展
可以看出,目前template是没有任何语法高亮以及智能提示的,现在vscode只会template属性值当作字符串处理,我们需要为其定义不同的语法高亮规则
语法高亮
语法高亮在代码编辑器中决定源代码的颜色和样式,它的主要职责是为关键字(例如JavaScript中的if
、for
)、字符串、注释、变量名等语法元素提供着色。在VSCode中,语法高亮是通过使用TextMate语法进行定义的。如果你想深入了解TextMate语法的工作原理和用法,我强烈建议阅读这篇文章:TextMate语法指南。
TextMate语法是一种强大的语法定义格式,它允许我们明确定义不同语法元素的匹配规则和样式信息。在开发自定义的语法高亮插件时,理解TextMate语法的原理对于正确实现各种代码元素的高亮非常重要。
通过TextMate语法,我们可以灵活地指定如何匹配特定的语法结构,例如,我们可以定义一个用于匹配JavaScript中的变量名的正则表达式,然后为匹配到的变量名指定特定的颜色和样式。类似地,我们可以定义其他语法元素的规则,以实现精确的代码高亮效果。
总的来说,语法高亮是代码编辑器中一个非常重要的功能,它不仅使代码更加美观,还提供了更好的可读性和编码体验。通过深入理解TextMate语法,并根据具体语言的特性来定义相应的规则,我们可以开发出功能强大、高度定制化的语法高亮插件,为开发者提供更优秀的编辑环境。
-
配置grammars
提炼一下我们的需求, 以需求为导向来一步步实现我们的所需的功能:
- 在ts文件中
@Component
中的template
值需要支持html
语法,而且在html的基础支持vue的模板语法以及我们自定义的语法 - 在ts文件中
@Component
中的style
值需要支持cs
s语法
基于以上的需求,我们在package.json
中将我们定义的TextMate
规则作用到ts中,package.json
定义如下:
{ "name": "template-lang", "displayName": "vs-template-lang", "description": "vuGualr template", "version": "0.0.1", "engines": { "vscode": "^1.81.0" }, "categories": [ "Programming Languages" ], "contributes": { "grammars": [ { "path": "./syntaxes/inline-template.json", "scopeName": "inline-template.vg", "injectTo": [ "source.ts" ], "embeddedLanguages": { "text.html": "html", "source.css": "css" } } ] } }
"injectTo": ["source.ts"]
:表示将这个语法规则注入到source.ts
这个作用范围中。这意味着当在 TypeScript 文件中遇到符合"./syntaxes/inline-template.json
规则时,会应用inline-template.vg
这个语法规则来处理。"embeddedLanguages": { ... }
:用于定义嵌套语言。在这里,它定义了text.html
、source.css
和source.js
三种嵌套语言。嵌套语言表示在当前语言中可以包含其他语言的代码块,例如在 TypeScript 中嵌套 HTML、CSS。"scopeName": "inline-template.vg"
:表示这个语法规则的作用范围名称。作用范围名称用于标识文本的语法类型,它会被用于对文本进行语法高亮。
- 在ts文件中
-
inline-template.json
{
// 表示这个语法规则的作用范围名称。作用范围名称用于标识文本的语法类型
"scopeName": "inline-template.vg",
"injectionSelector": "L:meta.decorator.ts -comment -text.html",
// 表示一系列的匹配规则
"patterns": [
{
"include": "#inlineTemplate"
}
],
"repository": {
"inlineTemplate": {
// 在@Component中template:开始匹配
"begin": "(template)\\s*(:)",
"beginCaptures": {
"1": {
"name": "meta.object-literal.key.ts"
},
"2": {
"name": "meta.object-literal.key.ts punctuation.separator.key-value.ts"
}
},
// 用于匹配 `,` 或 `}`,但不会包含在匹配结果中
"end": "(?=,|})",
"patterns": [
{
// 引用vgTemplate规则
"include": "#vgTemplate"
}
]
},
"vgTemplate": {
// 以`或者'或者"开始
"begin": "\\G\\s*([`|'|\"])",
"beginCaptures": {
"1": {
"name": "string"
}
},
// `\1` 是一个反向引用,表示匹配到的内容必须与前面捕获组中的内容相同,
// 这里指的是 `([`|'|"])` 中匹配到的单引号、双引号或反引号
"end": "\\1",
"endCaptures": {
"0": {
"name": "string"
}
},
// 表示匹配到的内容应用的作用范围名称
"contentName": "text.html",
"patterns": [
{
// 引用了名为 `text.html.derivative` 的规则。
// 这个规则定义了匹配一般的 HTML 标签内容的语法规则
"include": "text.html.derivative"
}
]
}
}
}
-
"injectionSelector": "L:meta.decorator.ts -comment -text.html"
:表示在哪些作用范围中注入这个语法规则。L:meta.decorator.ts
表示注入到 TypeScript 中的装饰器作用范围中,-comment -text.html
表示排除注释和 HTML 作用范围。这样配置后,inline-template.vg
的语法规则会应用于 TypeScript 文件的装饰器中,但不会影响注释和 HTML 部分,L:
代表注入的语法添加在现有语法规则的左边。也就是说我们注入的语法规则会在任何现有语法规则之前生效 -
"inlineTemplate"
匹配范围是template:开始到,或者} 但不含,或者},举例说明一下:@Component({ styleUrls: ['./demo.less'], template: `<div></div>`}) export default class AngularDemo{}
从这个例子来看,结束标志是}但不包含}也就是说真正匹配的范围是
`<div></div>`
注意第一个`前还有一个空格@Component({ template: `<div></div>`, styleUrls: ['./demo.less'], }) export default class AngularDemo{}
从这个例子来看,结束标志是,但不包含,也就是说真正匹配的范围是
`<div></div>`
注意第一个`前还有一个空格 -
插件运行效果
我们可以看到现在template值不是字符串样式,具备了html的语法高亮了,离目标又进了一步
目前我们还只是对template值这个大的范围定义了规则,还有插值语法属性绑定语法事件绑定语法等没实现, 本文中以插值语法为例,其他语法的实现就不一一详细描述了。
-
实现插值语法的高亮效果
- 改造
package.json
由于我们的template语法不仅在行内模板生效,也在单独的html的文件中生效,所以单独提取template.vg
规则
"contributes": { "grammars": [ { "path": "./syntaxes/inline-template.json", "scopeName": "inline-template.vg", "injectTo": [ "source.ts" ], "embeddedLanguages": { "text.html": "html", "source.css": "css" } }, { "path": "./syntaxes/template.json", "scopeName": "template.vg", "injectTo": [ "text.html.derivative", "source.ts" ], "embeddedLanguages": { "text.html": "html", "source.css": "css" } } ] }
- 编写template规则
{ "scopeName": "template.vg", // 在HTML 作用范围该规则生效 "injectionSelector": "L:text.html -comment", "patterns": [ { // 插值语法匹配规则 "include": "#interpolation" } ], "repository": { "interpolation": { "begin": "{{", "beginCaptures": { "0": { "name": "punctuation.definition.block.ts" } }, "end": "}}", "endCaptures": { "0": { "name": "punctuation.definition.block.ts" } }, "patterns": [ { "include": "#expression" } ] }, "expression": { "name": "meta.expression.ng", "patterns": [ { "include": "#identifiers" } ] }, "identifiers": { "patterns": [ { // 变量名匹配规则 "name": "variable.other.readwrite.ts", "match": "[_$[:alpha:]][_$[:alnum:]]*" } ] } } }
injectionSelector
(注入选择器)来指定在HTML作用范围内生效的规则,但这个选择器不会在注释中中生效。
解释下为什么在inline-template.vg
中不需要引用template.vg
规则。
原因在于,inline-template.vg
规则中已经声明了text.html
范围。由于inline-template.vg
是在template.vg
中嵌套的,它会继承template.vg
的作用范围,因此我们无需在inline-template.vg
中额外引用template.vg
的规则。
通过这种方式,我们可以在不产生冲突的情况下,有效地定义不同范围内的样式,使得代码高亮在Vue模板中得到正确的应用,这样的设计使得我们能够更加灵活地管理规则,并确保在不同层级的嵌套中,样式能够正确地展示。这是Vue代码高亮插件开发中值得关注的一点,也是提高插件质量和稳定性的重要步骤。-
插件运行效果如下:
我们可以看到现在变量高亮正常显示了,不过目前还有很多问题,插值语法内不仅仅只有变量,还有字符串,数字等。 -
支持字符串(其他的类似):
在规则中加入如下规则即可"qstringDouble": { "name": "string.quoted.double.ts", "begin": """, "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.ts" } }, "end": "(")|((?:[^\\\n])$)", "endCaptures": { "1": { "name": "punctuation.definition.string.end.ts" }, "2": { "name": "invalid.illegal.newline.ts" } }, "patterns": [ { "include": "#stringCharacterEscape" } ] }, "qstringSingle": { "name": "string.quoted.single.ts", "begin": "'", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.ts" } }, "end": "(\')|((?:[^\\\n])$)", "endCaptures": { "1": { "name": "punctuation.definition.string.end.ts" }, "2": { "name": "invalid.illegal.newline.ts" } }, "patterns": [ { "include": "#stringCharacterEscape" } ] }, "stringCharacterEscape": { "name": "constant.character.escape.ts", "match": "\\(x\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)" }
- 改造
总结
本文详细介绍了如何编写语法高亮插件,为VSCode中的Vue 面向对象插件代码提供良好的颜色标识,以提升代码的可读性。尽管我们在此只给出了一个简单的示例,但是实际开发一个完整的语言插件是相当复杂的过程。
在插件开发的过程中,我们面临着诸多挑战,包括对语法规则的定义、智能提示的实现、错误检查的处理以及代码片段的提供。这些都需要深入理解VSCode插件开发的相关API和机制,以及对目标语言的深刻理解。
本文虽然无法详尽地展示每个细节,但我们希望通过抛砖引玉,给各位道友提供了关键的思路和基本的开发过程。自制VSCode语言插件是一项具有挑战性但又极具成就感的任务。随着我们逐步学习和实践,我们相信能够越来越熟练地开发出功能强大的插件,为自己和其他开发者带来更加优秀的开发体验。
工具篇的道路任重而道远,本文只是一个起点,未来还有许多知识和技巧等待我们探索和应用。在接下来的系列文章中,我们将持续探讨更多有关VSCode语言插件的内容,包括智能提示、错误检查和代码片段等方面的开发,敬请期待!
让我们携手共进,共同开发出更加优秀的VSCode语言插件,为开发者的工作带来更多便利与创造力。未完待续,敬请期待下一篇的精彩内容!