likes
comments
collection
share

Vue3写一个面向对象插件系列5(工具篇1)

作者站长头像
站长
· 阅读数 9

前言

在前面几期的博文中,我们讨论了如何使用面向对象的方式来编写Vue代码,以提高代码的可维护性和可扩展性。然而,随着我们探索新的语法和特性,发现在VSCode中并未找到相应的插件支持,这严重影响了我们的开发体验。因此,为了让我们的开发环境紧跟最新的语法,从本期开始,我们将陆续介绍如何自制VSCode语言插件,以适配新的语法。

VSCode语言插件的制作是一个复杂的过程,但在本系列教程中,我们将尽量简明扼要地介绍每个必要的流程和代码片段。通过自制插件,我们可以为VSCode增添针对Vue的智能提示、语法高亮、代码片段以及其他便利功能,极大地提升我们的开发效率和舒适度。

为什么需要自制VSCode语言插件?

VSCode作为一款轻量级且功能强大的代码编辑器,受到了广大开发者的喜爱。其拥有丰富的扩展生态系统,几乎涵盖了所有主流编程语言和框架。然而,随着面向对象插件的不断发展,新的语法和特性不断涌现,这些新功能可能并未得到官方插件的支持。

在Vue生态系统中,由于其灵活性和易扩展性,社区经常会提出新的语法糖、指令、装饰器等特性。这些新特性对于Vue开发者来说非常有吸引力,但由于VSCode未提供相应的官方支持,我们的开发体验可能会受到限制,比如缺乏智能提示、错误提示等功能。

为了解决这一问题,自制VSCode语言插件成为了一种解决方案。通过自己开发插件,我们可以根据需要添加新的语法支持,从而让VSCode完美适配我们使用的Vue版本,提供更加便捷的开发体验。

自制VSCode语言插件的复杂性

自制VSCode语言插件是一项挑战性的任务,需要对插件开发流程有一定了解,并且熟悉VSCode插件开发的相关API和机制。不过,本系列教程将尽量降低难度,以简单易懂的方式呈现插件开发的关键步骤和技巧。

我们将主要关注以下几个方面(分几期讲):

  1. 语法高亮:通过定义语法规则,使得Vue代码在编辑器中能够得到良好的颜色标识,增加代码的可读性。
  2. 智能提示:为Vue的特定语法和指令添加智能提示功能,让开发者在编写代码时能够得到即时的建议和补全。
  3. 错误检查:针对新语法进行错误检查,及时发现并提示代码中的问题,避免在编译过程中产生错误。
  4. 代码片段:提供常用代码片段,简化重复性的代码编写过程。

虽然插件开发过程相对复杂,但一旦掌握了相关技能,将为我们带来巨大的便利和满足感。让我们一起开启自制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会创建一个新的插件,其结构如下

Vue3写一个面向对象插件系列5(工具篇1)

由于我们并没有创建新的语言,只是在现有的语言基础上支持新的语法,所以需要删除生成的package.json中的languages配置。

Vue3写一个面向对象插件系列5(工具篇1)

  • 运行插件

在编辑器内按F5, 这会在新的开发窗口中编译&运行拓展

Vue3写一个面向对象插件系列5(工具篇1) 可以看出,目前template是没有任何语法高亮以及智能提示的,现在vscode只会template属性值当作字符串处理,我们需要为其定义不同的语法高亮规则

语法高亮

语法高亮在代码编辑器中决定源代码的颜色和样式,它的主要职责是为关键字(例如JavaScript中的iffor)、字符串、注释、变量名等语法元素提供着色。在VSCode中,语法高亮是通过使用TextMate语法进行定义的。如果你想深入了解TextMate语法的工作原理和用法,我强烈建议阅读这篇文章:TextMate语法指南

TextMate语法是一种强大的语法定义格式,它允许我们明确定义不同语法元素的匹配规则和样式信息。在开发自定义的语法高亮插件时,理解TextMate语法的原理对于正确实现各种代码元素的高亮非常重要。

通过TextMate语法,我们可以灵活地指定如何匹配特定的语法结构,例如,我们可以定义一个用于匹配JavaScript中的变量名的正则表达式,然后为匹配到的变量名指定特定的颜色和样式。类似地,我们可以定义其他语法元素的规则,以实现精确的代码高亮效果。

总的来说,语法高亮是代码编辑器中一个非常重要的功能,它不仅使代码更加美观,还提供了更好的可读性和编码体验。通过深入理解TextMate语法,并根据具体语言的特性来定义相应的规则,我们可以开发出功能强大、高度定制化的语法高亮插件,为开发者提供更优秀的编辑环境。

  1. 配置grammars

    提炼一下我们的需求, 以需求为导向来一步步实现我们的所需的功能:

    1. 在ts文件中@Component中的template值需要支持html语法,而且在html的基础支持vue的模板语法以及我们自定义的语法
    2. 在ts文件中@Component中的style值需要支持css语法 基于以上的需求,我们在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"
            }
          }
        ]
      }
    }
    
    
    1. "injectTo": ["source.ts"]:表示将这个语法规则注入到 source.ts 这个作用范围中。这意味着当在 TypeScript 文件中遇到符合"./syntaxes/inline-template.json规则时,会应用 inline-template.vg 这个语法规则来处理。
    2. "embeddedLanguages": { ... }:用于定义嵌套语言。在这里,它定义了 text.htmlsource.csssource.js 三种嵌套语言。嵌套语言表示在当前语言中可以包含其他语言的代码块,例如在 TypeScript 中嵌套 HTML、CSS。
    3. "scopeName": "inline-template.vg":表示这个语法规则的作用范围名称。作用范围名称用于标识文本的语法类型,它会被用于对文本进行语法高亮。
  2. 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"
          }
        ]
      }
    }
  }
  1. "injectionSelector": "L:meta.decorator.ts -comment -text.html":表示在哪些作用范围中注入这个语法规则。L:meta.decorator.ts 表示注入到 TypeScript 中的装饰器作用范围中,-comment -text.html 表示排除注释和 HTML 作用范围。这样配置后,inline-template.vg 的语法规则会应用于 TypeScript 文件的装饰器中,但不会影响注释和 HTML 部分,L:代表注入的语法添加在现有语法规则的左边。也就是说我们注入的语法规则会在任何现有语法规则之前生效

  2. "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>`注意第一个`前还有一个空格

  3. 插件运行效果

    我们可以看到现在template值不是字符串样式,具备了html的语法高亮了,离目标又进了一步

    Vue3写一个面向对象插件系列5(工具篇1)

    目前我们还只是对template值这个大的范围定义了规则,还有插值语法属性绑定语法事件绑定语法等没实现, 本文中以插值语法为例,其他语法的实现就不一一详细描述了。

  4. 实现插值语法的高亮效果

    • 改造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代码高亮插件开发中值得关注的一点,也是提高插件质量和稳定性的重要步骤。

    • 插件运行效果如下:

      Vue3写一个面向对象插件系列5(工具篇1) 我们可以看到现在变量高亮正常显示了,不过目前还有很多问题,插值语法内不仅仅只有变量,还有字符串,数字等。

      Vue3写一个面向对象插件系列5(工具篇1)

    • 支持字符串(其他的类似): 在规则中加入如下规则即可

      "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]?|.|$)"
      }
      

      Vue3写一个面向对象插件系列5(工具篇1)

总结

本文详细介绍了如何编写语法高亮插件,为VSCode中的Vue 面向对象插件代码提供良好的颜色标识,以提升代码的可读性。尽管我们在此只给出了一个简单的示例,但是实际开发一个完整的语言插件是相当复杂的过程。

在插件开发的过程中,我们面临着诸多挑战,包括对语法规则的定义、智能提示的实现、错误检查的处理以及代码片段的提供。这些都需要深入理解VSCode插件开发的相关API和机制,以及对目标语言的深刻理解。

本文虽然无法详尽地展示每个细节,但我们希望通过抛砖引玉,给各位道友提供了关键的思路和基本的开发过程。自制VSCode语言插件是一项具有挑战性但又极具成就感的任务。随着我们逐步学习和实践,我们相信能够越来越熟练地开发出功能强大的插件,为自己和其他开发者带来更加优秀的开发体验。

工具篇的道路任重而道远,本文只是一个起点,未来还有许多知识和技巧等待我们探索和应用。在接下来的系列文章中,我们将持续探讨更多有关VSCode语言插件的内容,包括智能提示、错误检查和代码片段等方面的开发,敬请期待!

让我们携手共进,共同开发出更加优秀的VSCode语言插件,为开发者的工作带来更多便利与创造力。未完待续,敬请期待下一篇的精彩内容!

转载自:https://juejin.cn/post/7264176460755566652
评论
请登录