想让VSCode识别自己的编程语言?立马安排

前言

大家好!最近我在做自己的毕业设计,简单来说需要自制一个编程语言,当然具体情况还要更复杂。现在语言本身已经有一定完成度了,但是在我测试的时候,看白底黑字的Notepad总是感觉差那么点意思。VSCode那多彩的高亮,自动补全括号真是用了就忘不掉,那有没有办法让我们自己的语言也能被VSCode高亮呢?

当然有!本篇博客我就来教大家如何使用Yeoman generator来制作VSCode代码高亮插件。

一、安装Yeoman generator

Yeoman是一个通用型脚手架工具,简单来说就是帮你搭建一个项目的基本结构。在Yeoman所能完成的各种项目中,就包括VSCode插件,这也是我们所需要的:

npm install -g yo generator-code

这里我们使用nodejs安装Yeoman以及对应的Generator,下面开始搭建项目。

二、插件项目搭建

在全局安装好Yeoman之后,我们打开一个项目文件夹,然后执行:

yo code

这时你会看到如下界面,我们选择New Language Support

yo_code

回车后会开始让我们回答一系列问题,分别为:

  1. 引入文件/URL,或者留空以搭建新项目。我们直接回车留空

  2. 该扩展的名字是什么。给我们自己的扩展起一个名字,比如: “MyLang Syntax Highlighting”

  3. 该扩展的标识符是什么。这个是当你安装插件后,在VSCode的扩展一栏中的显示名。一般会根据你上面的起名给一个默认的名字,我这里就是用默认名了

  4. 请描述你的扩展。写点东西让别人直到是干什么的啦

  5. 请输入你的语言的id。注意,这里的id唯一标记了你的语言,格式要求为单词(无空格)、小写。比如:“mylang”

  6. 请输入你的语言的名字。当你在VSCode中新建了一个文件,它会提示“选择语言以开始”,这里的起名就是显示在这里的。没有格式要求,随意起啦

  7. 请输入启用扩展的文件后缀。顾名思义,C++扩展为.cpp启用,Python扩展为.py启用。这里你需要指定哪些文件后缀启用我们自定义的扩展,比如:".ml"

  8. 请输入语法根作用域名。这个一般就是source.{7中定义的后缀},比如:“source.ml”

  9. 是否需要初始化一个git仓库。如果只是为了自己使用,选no就好了

现在,你会看到自动生成了一个以3中自定义标识符为名的文件夹,我们所需要的项目文件就全在里面了!

三、项目结构

大体看一下文件目录:

D:.
│  .vscodeignore
│  CHANGELOG.md
│  language-configuration.json
│  package.json
│  README.md
│  vsc-extension-quickstart.md
│
├─.vscode
│      launch.json
│
└─syntaxes
        mylang.tmLanguage.json
  1. package.json,保存了我们在第二步中给出的答案,你也可以在此处进行修改。

  2. 几个Markdown文件,为你提供了标准的通知模板,如果你有留意过VSCode下载插件界面的内容,会发现与该模板大差不差。

  3. language-configuration.json,该文件中的内容,相当于全局定义,它包括以下几部分:注释方式、允许的括号、自动补全的括号、选中文字后输入会自动括起来的符号。

  4. /syntexes/mylang.tmLanguage.json,该文件是本项目的重点,我们需要在此处定义编程语言的文法,下面我们将展开讲述这一部分。

四、Syntaxes

TextMate官方文档指路

tm是TextMate的缩写,原本是一个Mac系统上的一个编辑器,支持一系列用户自定义功能。微软为VSCode添加了一个vscode-textmate的解释器,使得VSCode也可以使用TextMate文法定义的扩展组件。

我们打开mylang.tmLanguage.json,看看里面的内容:

{
	"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
	"name": "MyLang",
	"patterns": [
		{
			"include": "#keywords"
		}
	],
	"repository": {
		"keywords": {
			"patterns": [
				{
					"name": "keyword.control.mylang",
					"match": "\\b(if|while|for|return)\\b"
				}
			]
		}
	},
	"scopeName": "source.ml"
}

4.1 $schema

模式。这个模式所指向的文件,将定义接下来tmLanguage文件中键值对的意义,接受的值类型,以及预定义的高亮主题。

注意该文件最后的枚举值,我们自定义的匹配规则应当总是继承自这些已有的枚举值:

// 限于篇幅,只列出了部分枚举值
{
	"type": "string",
	"enum": [
		// 注释
        "comment",
        "comment.block",
        "comment.line",
        "comment.line.double-dash",
        "comment.line.double-slash",
		// 常量
        "constant",
        "constant.character",
        "constant.character.escape",
        "constant.language",
        "constant.numeric",
        "constant.other",
        "constant.regexp",
        "constant.rgb-value",
        "constant.sha.git-rebase",
		// 实例
        "entity",
        "entity.name",
        "entity.name.class",
        "entity.name.function",
        "entity.name.method",
        "entity.name.type",
		// 无效
        "invalid",
        "invalid.deprecated",
        "invalid.illegal",
		// 关键字
        "keyword",
        "keyword.control",
        "keyword.control.less",
        "keyword.operator",
        "keyword.operator.new",
		// 标记(加粗、斜体等)
        "markup",
        "markup.bold",
        "markup.italic",
        "markup.list",
        "markup.quote",
        "markup.underline",
		// 存储(外部文件相关,如import)
        "storage",
        "storage.modifier",
        "storage.modifier.import.java",
        "storage.modifier.package.java",
        "storage.type",
		// 字符串
        "string",
        "string.quoted",
		"string.quoted.single",
        "string.quoted.double",
        "string.quoted.triple",
        "string.regexp",
        "string.xml",
        // 内置支持(用于内部函数、内部类)
        "support",
        "support.class",
        "support.constant",
        "support.function",
        "support.other",
        "support.property-value",
        "support.type",
        "support.variable",
		// 变量
        "variable",
        "variable.language",
        "variable.name",
        "variable.parameter"
    ]
}

具体的使用示例我将在之后演示。

4.2 patterns

规则。规则是一个列表,每个列表项是一个键值对(字典),这些规则将用来解析我们的代码。一般来讲,我们只需要在patterns中定义,解析该语言需要哪些规则,而规则的具体内容,将在之后的repository中完成。例如:

"patterns": [
	{
		"include": "#comments"
	},
	{
		"include": "#keywords"
	},
	{
		"include": "#statements"
	}
	{
		"include": "#strings"
	}
]

这里我们定义了几个规则,分别是:注释规则、关键字规则、语句规则、字符串规则。接下来我们需要实现这些规则的定义

4.3 repository

仓库中需要我们来实现patterns里面定义的规则,仓库本身是一个字典,它的键值对为:规则:[规则内容],规则内容包含很多可选项,我们举个例子说明:

"keywords": {
    "patterns": [
        {
            "name": "keyword.control.mylang",
            "match": "\\b(if|else|while|for|return|and|or|break|continue)\\b"
        }
        {
            "name": "constant.language.boolean.mylang",
            "match": "\\b(true|false)\\b"
        }
    ]
},
"strings": {
    "name": "string.quoted.double.mylang",
    "begin": "\"",
    "end": "\""
},

在上面这两个键值中,我们实现了规则中定义的“关键字”和“字符串”,让我们来看看都使用了什么规则内容:

  1. patterns。如果一个规则中包含着很多个子规则,你可以用一个列表对它们进行定义。或者,你也可以使用类似4.2中patterns的语法,include一个子规则,而后再后面继续定义。如果你的语言语法比较复杂,那么建议使用后一种方法,对语法进行细分,更详细的划分方式可以参考VSCode如何为Python撰写的tmLanguage文件

  2. name,你需要为每一个规则提供一个名字。注意,这里的名字需要继承自schema中提供的枚举值。就像上例中的strings规则,它的名字是string.quoted.double.mylang,就是继承自string.quoted.double,双引号括起的字符串。这个名字将决定最后使用主题中定义的哪个颜色为其上色,关于主题,请看VSCode的官方文档

  3. match,匹配规则。TextMate中的所有匹配规则都以正则表达式的形式定义,又因为我们是在json中以字符串形式传递这些正则表达式,所以不可避免地要进行转义字符。match语句用于定义一条完整的正则表达式,例如上面的:"\\b(true|false)\\b",用于捕获单词true/false,然后设定为boolean高亮。

  4. begin & end。除了使用match定义一条正则语句外,你还可以定义起始匹配标志与终止匹配标志,来实现区间捕获。可以看到上例中对于字符串的处理,就是以双引号起始,双引号终结的一段。

  5. captures。上面的例子很简单,我们一条正则表达式就对应一个语法,但有时候语法可以拆分成很多部分,而且有些部分是可选的,那么就可以使用captures来精确细分:

    {
        "match": "\\b(func)\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(([A-Za-z0-9,\\s]*)\\)",
        "captures": {
            "1": {
                "name": "storage.type.function.cploxplox"
            },
            "2": {
                "name": "entity.name.function.cploxplox"
            },
            "3": {
                "patterns": [
                    {
                        "match": "\\b[A-Za-z_][A-Za-z0-9_]*\\b",
                        "name": "variable.parameter.cploxplox"
                    }
                ]
            }
        }
    },
    

    上面给的这个例子,是我自己的语言cploxplox中对于函数的定义语法,bnf形式表述为:

    func (identifier)? "(" (identifier (, identifier)*)? ")" block

    这里面可以分出几部分:

    1. func,一个标志着接下来开始定义函数的字符

    2. identifier,如果有就是一个有名函数,否则为匿名函数

    3. 参数列表,可以为空

    4. 函数体

    前三部分,分别对应正则表达式中三个()分割开的捕获分组,它们三个不是用同样的颜色高亮,因此我们使用captures,分别对1、2、3分组做了更进一步的高亮定义。

  6. beginCaptures & endCaptures。captures是针对match的,那么同理针对begin和end也有相应的captures,使用的方法和上面captures的例子类似,不再赘述。

以上便是repository中比较常用的几个规则内容了,当然还有更多,比如为了方便别人理解,你可以添加comment字段;想要为begin和end之间的部分添加高亮,可以使用contentName来完成;引用其他语言的高亮规则,可以使用include一个文件/URL的方式实现。这些就需要你详细的阅读官方文档了。

下面我放出本次教程中我书写的完整tmLanguage文件内容,供大家参考:

{
	"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
	"name": "MyLang",
	"patterns": [
		{
			"include": "#comment"
		},
		{
			"include": "#statements"
		},
		{
			"include": "#keywords"
		},
		{
			"include": "#strings"
		}
	],
	"repository": {
		"comment": {
			"begin": "//",
			"end": "\\n",
			"name": "comment.line.double-slash"
		},
		"keywords": {
			"patterns": [
				{
					"name": "keyword.control.mylanguage",
					"match": "\\b(if|else|while|for|return|and|or|break|continue)\\b"
				},
				{
					"name": "constant.language.boolean.mylanguage",
					"match": "\\b(true|false)\\b"
				}
			]
		},
		"statements": {
			"match": "\\b(func)\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(([A-Za-z0-9,\\s]*)\\)",
			"captures": {
				"1": {
					"name": "storage.type.function.mylang"
				},
				"2": {
					"name": "entity.name.function.mylang"
				},
				"3": {
					"patterns": [
						{
							"match": "\\b[A-Za-z_][A-Za-z0-9_]*\\b",
							"name": "variable.parameter.mylang"
						}
					]
				}
			}
		},
		"strings": {
			"name": "string.quoted.double.mylang",
			"begin": "\"",
			"end": "\"",
			"patterns": [
				{
					"name": "constant.character.escape.mylang",
					"match": "\\\\."
				}
			]
		}
	},
	"scopeName": "source.ml"
}

五、安装插件

好,现在我们已经完成了插件的实现部分,要怎么才能让VSCode识别到呢?非常简单,把整个插件文件夹,拷贝到%USERPROFILE%/.vscode/extensions(Linux是~/.vscode/extensions),重启VSCode就可以啦!

最终效果

结语

不知道大家有没有自制编程语言的兴趣呢?如果有的话,可以来我的仓库中,帮我一同开发cploxplox哦!在仓库的scripts中我提供了扩展cploxplox的指南,有兴趣的小伙伴可以在该项目中出一份力!

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值