Markdown 编辑器 -2- 语法
官方语法定义
定义语法
我也没兴趣自创语法,但默认的Markdown语法是经典语法,一些后期扩展的语法,比如删除线、上标下标、脚注等语法均未涵盖。
为了能让语法高亮得更彻底一点,必须要对当前markdown语法解析进行扩展。
因为Markdown有其特殊性,在这里就无需考虑智能提示(其它语言可能会有关键词提示之类的),所以我们只需要提供三个东西。
- 语法名
- 解析器
- 语法配置
语法名我们暂定为markdownEx
,接下来解析器的构建就在原版markdown语法的基础上进行就可以了。
构建解析器
创建一个新的markdownEx.js
的文件,定义一个对象language
:
1 | let language = { |
tokenizer
中就是解析步骤,主要用的是正则表达式。我是正则表达式的苦手,但在已经有一堆定义的基础上,进行语法的扩展也不过就是换关键词而已。
请看下题:
已知Markdown中加粗的语法是**Bold**
,且正则表达式为\*\*([^\\*]|@escapes|\*(?!\*))+\*\*
,现求Mark标记语法==Mark==
的正则表达式。
那答案就呼之欲出了:\=\=([^\\=]|@escapes|\=(?!\=))+\=\=
。
你不需要知道整个表达式的构成原理,只需要把关键的\\替换为\=即可*
所以语法的解析并不复杂,依样画葫芦罢了,如果你对Markdown究竟有哪些扩展语法感兴趣,可以查看markdown-it的说明,这个Markdown解析库应该是目前适用性最广的解析库了。
替换token名称
在查看解析代码的时候,你可能会注意到正则表达式后面一般都跟着一个字符串,有些标注了这是token,有些没标,有些还用一个数组包起来多个token ~(由于缺少文档资料,在这里我还研究了好一会儿)~ 。
这里的token,简而言之就是你解析出来的这段特殊文本的名字,后期可以根据这个名字进行语法着色,让你的文档变得花里胡哨。
但默认的名字很难让人将markdown中的语法与token名联系起来,后面进行着色规则的配置时还要时不时回来看看,这是很累的一件事,所以在定义语法规则的时候,不妨就给它把名字重新定一下。
:::warning
这里可以重新命名是因为我只打算做markdown这一种语法,可以专门为之构建配色方案。如果你要适配多语言,切勿更改,保留原本的命名,这会让你的配色方案简单很多(因为其它的语法解析都会暴露相同的名称,不必逐个适配)
:::
比如对于引用语法,我们可以重命名如下:
1 | // quote |
后期写着色规则的时候我们就可以进行匹配:
1 | { |
但token有一种情况比较特殊,以标题语法为例,它长这样(重命名之后):
1 | // headers (with #) |
一个数组内有多个string,这些string都是token吗?是的。
那为什么会有这么多token?为了分组。
一个完整的标题语法可以这样写:
1 | ### Head ### |
直观来看,它可以分成3组,前面的#
,中间的文本与收尾的#
,而解析器这边则分成了四组,加了行首的空格。
这就是后面4个token的由来了。
在正则表达式中,这种分组可以用小括号来表示,在解析时,会解析该表达式的所有一级
小括号,这意味着三件事:
- 表达式中的嵌套括号是不被计算在内的,只算最外层的那一级。
- 给出的token数量如果不为一,那么要求token的数量与表达式分组的数量必须对应上
- 不能有分组之外的单个匹配字符存在(而像
^
,$
这样表示一种匹配模式的字符不受此规则限制)
特殊语法解释
@语法
在解析器中,我们能看到一些比较特殊的表达式,比如
1 | [/^\s*\|/, '@rematch', '@table_header'] |
加了@
符号的,表示一种引用。
@rematch
,表示重新匹配。比如在解析自定义容器的时候,我们知道语法像这样:
1 | ::: tip |
我们可以为三个冒号这种语法设置token,但是在解析容器内部的文本时,我们就不能一以概之的使用一种token来定义了,谁知道使用者在里面写了什么东西呢?这个时候我们就可以用@rematch
来表示继续进行语法解析。
1 | //... |
:::tip
关于@rematch, @pop以及其它的语法,在官方文档Monarch中有着详细的解释。
:::
构建基础配置
语法配置其实很简单,甚至可以说是通用。Markdown要配置的就是一些快捷操作:
1 | let config = { |
这里的notIn,在monaco的源码中也语焉不详,我也不知道可选值有哪些,只能将就着先用着。
相比起默认的配置,我对中文中的全角符号进行了一些适配,比如书名号,引号之类的。
这一块没啥好说的,各个模块的说明可以参见文档:LanguageConfiguration.
使用语法
先将我们之前定义的解析器和配置模块导出:
1 | export { language, config }; |
到这一步就还挺简单的,因为我们只需要定义一个解析器和一个语法配置器,所以可以这样写:
1 | import { language, config } from "../lib/markdownEx.js"; |