我如何制作一个不难理解的 VSCode 语法荧光笔?

How would I make a VSCode syntax highlighter that is not extremely hard to understand?

我正在尝试为我自己的标记语言制作自定义语法荧光笔。所有的例子都很复杂,缺少步骤并且非常非常难以理解。

是否有完整的文档说明如何制作语法高亮器?

(对于 VSCode,顺便说一句)


例如,这个视频 https://www.youtube.com/watch?v=5msZv-nKebI 中间有一个非常大的跳跃,并没有真正解释太多。

我当前使用 Yeoman 生成器制作的代码是:

{
    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
    "name": "BetterMarkupLanguage",
    "patterns": [
        {
            "include": "#keywords"
        },
        {
            "include": "#strings"
        }
    ],
    "repository": {
        "keywords": {
            "patterns": [{
                "name": "entity.other.bml",
                "match": "\b({|}|\\|//)\b"
            }]
        },
        "strings": {
            "name": "string.quoted.double.bml",
            "begin": "`",
            "end": "`"
        }
    },
    "scopeName": "source.bml"
}

剧情简介

我不确定你在什么层次上解决了这个问题,但基本上有两种 syntax-highlighting:

  • 只需识别规范可识别标记(字符串、数字、可能的运算符、保留字、注释)的小块并突出显示,或者
  • 做前者,添加上下文感知。

tmLanguage引擎基本上有两个工作:

  • 分配范围。
  • 维护上下文堆栈。

示例

假设您使用以下模式定义整数:

"integers": {
    "patterns": [{
        "name": "constant.numeric.integer.bml",
        "match": "[+-]\d+"
    }]
},

当引擎匹配这样的整数时,它将匹配到正则表达式的末尾,从 "name" 分配范围,然后在相同的上下文中继续匹配 中的内容。

将其与您的“字符串”定义进行比较:

"strings": {
    "name": "string.quoted.double.bml", // should be string.quoted.backtick.bml
    "begin": "`",
    "end": "`"
},

那些 "begin""end" 标记表示 tmLanguage 堆栈中的更改。您已推入一个新上下文 字符串内部。

现在,在此上下文中没有配置任何匹配项,但您可以通过添加带有一些 "match""include""patterns" 键来实现。 "include"s 是其他匹配集,例如您在别处定义的“整数”。您可以将它添加到“字符串”模式以匹配字符串中的整数。匹配整数可能很愚蠢,但想想转义的反引号:你想限定它们的范围并留在“字符串”中的相同上下文中。您不希望那些过早地弹出。

运算顺序

您最终会注意到遇到的第一个模式是匹配的。还记得整数集吗?当你有 45.125 时会发生什么?它将决定将 45125 匹配为整数 并完全忽略 .。如果你有一个“浮点数”模式,你想在 before 你的原始整数模式中包含它。下面的这两个“数字”定义是等价的,但其中一个可以让你 re-use 独立的浮点数和整数(如果这对你的语言有用的话):

  • "numbers": {
        "patterns": [
            {"include": "#floats"},
            {"include": "#integers"}
        ]
    },
    "integers": {
        "patterns": [{
            "name": "constant.numeric.integer.bml",
            "match": "[+-]\d+"
        }]
    },
    "floats": {
        "patterns": [{
            "name": "constant.numeric.float.bml",
            "match": "[+-]\d+\.\d*"
        }]
    },
    
  • "numbers": {
        "patterns": [{
                "name": "constant.numeric.float.bml",
                "match": "[+-]\d+\.\d*"
            }, {
                "name": "constant.numeric.integer.bml",
                "match": "[+-]\d+"
        }]
    },
    

做对了

“数字”/“整数”/“浮点数”是微不足道的,但是 well-designed 语法定义将定义实用程序组,"include" 等同于 re-usability:

  • 一个普通的编程语言会有这样的东西

    • 可以直接执行的所有事物的“语句”组。这可能会或可能不会 (language-dependent) 包括...
    • 可以放在作业 right-hand-side 中的一组“表达式”,其中肯定包括...
    • 字符串、数字、字符等的“原子”组也可能是有效的语句,但这也取决于您的语言。
    • "function-definitions" 可能不会出现在“表达式”中(除非它们是 lambda),但可能 会出现在“语句”中。函数定义可能会推入允许您 return 等的上下文。
  • 像你这样的标记语言可能有

    • 一个“内联”组,用于跟踪一个块 中可以拥有的所有标记。
    • 一个“块”组来保存列表、引用、段落,headers。
    • ...

虽然您可以学到更多(捕获组、注入、作用域约定等),但希望这是一个实用的入门概述。

结论

当你写语法高亮的时候,你自己想一想:匹配这个标记是否让我处于一个可以再次匹配类似东西的地方?或者它是否将我置于不同的地方,应该匹配不同的东西(更多 更少)?如果是后者,那我returns原来的那套火柴呢?