深入了解Ace Editor的mode

Ace Editor是一款非常有名的基于Web的源代码编辑器,具有丰富的可扩展性,而且性能也很棒,处理上百万行代码不在话下。据我所知,维基百科、可汗学院、Overleaf、CodeCombat等知名网站都在使用这款编辑器。不过,它也有一个大问题就是文档写得非常简陋。

Ace Editor用来支持语法高亮的资源文件称作language mode(语言模式),Ace提供了扩展自定义语言mode的方法1,但根据这个文档去看很多现有的mode都会一头雾水,因为有很多用法在文档里一个字都没有写。不过在本文中,我会把我阅读Ace代码了解到的东西都分享给大家,这样大家就不用再读一遍代码了。

最基本的mode用法

还是从文档里已经介绍的基本用法讲起。每个mode都会把它包含的所有规则放在this.$rules之中。这些规则每个用一个数组表示。虽然格式相同,但是有一个地位特殊,那就是start这条规则,它是整个规则集的入口。

每条规则的数组就类似于PEG中的有序选择,排在前面的规则优先,而且前面匹配成功就不会检查后面的规则。token一般是一个数组,在经过regex匹配过后所有的捕获会按照顺序对应上token里面的每一项。next表示下一个状态(规则),如果不指定next的话,则会返回start规则。

我们可以简单地利用基本用法来识别一下MediaWiki中的标题

官方文档未提到的用法

几乎大部分我们接下来要介绍的用法都是在text_highlight_rules.js这个mode中实现的。这个mode应该算是一种meta mode,并非针对一种语言。而是几乎所有语言都理所当然地要继承这个mode。更准确地,是通过this.normalizeRules这个函数来实现的。

push与pop

可以发现很多现有的mode都使用了这个特性。那么它是做什么的呢?从名字我们就可以大概猜出来,应该是实现了一个stack。那么什么情况下会用到这个特性呢?通常在用来保证语法的开始与结束能够配对的时候需要用。就比如在MediaWiki中模板定义文法中的模板参数的fallback链,就需要用到。

为了能让内部参数456的结尾}}}不会被误认为结束外部参数123,我们这里就要在每次开启参数的时候push进去一个state,每次关闭参数的时候pop一下,这样就不会造成内部关闭外部的情形了。实际写出来大概就是这样:

在Ace中push是用一个数组来表示的,比较反直观,而且push实际上也隐含next的意味。上面定义的规则的意思是说,我这个openArg啊,它要push到stack里,同时它的下一条状态就是push里面这个数组(因为在Ace里面,就是用数组来表示rule)。

那好啦,到了下一个状态的时候,它就会按照顺序,先匹配}}}这条规则,一旦匹配上,那就会调用pop,也就意味着要跳出内部这个argument了。那么为什么还要加一个start呢?这个的作用是说,我argument内部是要按照start规则来匹配的,放到MediaWiki里意思就是说,我模板参数的fallback可以是任何wikitext,当然也能包含另一个模板参数啦。

其他特性

结语

在弄清Ace Editor的文档未写清的特性之后,Ace Editor就变为了一个容易扩展语言的一个编辑器,并且性能还不错。在扩展语言mode的方式方面,其他JS代码编辑器(如CodeMirror)也大同小异。

这篇文章我想要达到的作用就是,把这些重要的参数介绍给大家,让大家至少能够看懂已经有的这些mode是在做什么。因为我发现在我弄懂,写这篇文章之前,我真的看不懂它那些push、pop是什么意思,到底为啥rule是写成数组形式等等。当然这也反映出写好文档的重要性。