Vincent Chan 的巴士站 🚉

Syntax Highlighting之后的视图更新

前一篇blog讲述了如何给编辑器做Syntax Highlighting,可是做完之后要怎么样给视图更新才会做到更高效呢

毕竟我们采用这种Syntax Highlighting的方法, 就是要保证高效,如果不能好好利用,那么我们的这种方法就没有意义了。

我们知道,当一行改变了之后,这行下面的状态可能全部都会改变,所以这一行包括下面所有行都要重新进行Tokenize,毫无疑问,如果文件很大,如果用UI线程进行Tokenize,可能会造成卡顿。不过就算是一个几千行的文件,每输入一次,进行全文Tokenize,全文扫一遍,也是瞬间完成的事情,我们大可不必为此操心,但是谁知道会不会有其它情况,比如突然发生了GC,或者输入者手速太快了。当然,这层我们是不知道的,但是我想到了更好的办法。

一行发生了改变,那么这一行要马上更新,这时肯定的,也几乎不会消耗什么时间,就算一行有好几百个字,也几乎可以忽略不计,所以我的编辑器在输入这一行的时候,会马上进行Tokenize,马上进行上色。但是这一行下面的行却不要求马上进行更新,因为如果正在输入这一行,只是输入了一些正常的字符,而没有触发什么加粗什么的,那么下面的行其实无需更新,就算触发了,我们也不要求马上进行更新,对吧。很明显下面行数的更新就要异步进行。

RenderLine

我们看这张图,就是一行的渲染状态图了

Null -> 没有进行渲染

Plain Text -> 渲染了,没有上色,是纯色文字

Colored -> 已经完成了上色

我们渲染有两种模式,分别是立即(Imd)和延迟(Lazy)更新,这样我们可以有下图的表格

Null Plain Text Colored
Imd 马上在UI层进行渲染,跳到Colored状态 马上进行上色,跳到Colored状态 立即进行重新渲染,更新
Lazy 先渲染成纯色的字符,不进行上色,跳到PlainText状态 调用后台线程进行上色,上色完成后调用Callback,然后更新UI,然后跳到Colored状态 调用后台线程进行重新上色和渲染,完成后调用Callback更新UI层

所以如果是更新正在编辑的行的话,就调用Imd模式进行渲染和更新,如果是正在编辑下面的行的话,就调用Lazy渲染和更新。

当然,由于我是用NodeJS进行实现,所以就没有办法调用后台线程(Thread),只能通过IPC调用后台进程(Process),不过原理是一样的。

但是关于后台更新有一个问题,就是后台Tokenize完成以后,用户已经又更新了,比如说我第7行完成了Tokenize,但是这时可能用户又重新输入了第3行,这时第3行之后的行数都会在后台进行Tokenize操作,可是这时原来已经Tokenize的第7行的结果已经没有意义了,所以不能让它更新UI层。这时我们可能需要维护一个优先队列(Priority Queue),来得到目前等待Tokenize的最小的行号。

仍然用回刚才的例子,当我们第7行已经Tokenize完成的时候,准备更新UI层,这时第3行触发了输入,第三行本身立刻完成了渲染,这时第3行之后的行数(4..)全部加入Priority Queue,这时第7行的callback调用Priority Queue的队列头,得到当前最小的是4,也就是第4行正在后台进行Tokenize,所以第7行Tokenize的结果不会更新到视图。当第4行的Tokenize完成之后,调用更新视图的Callback,这时检查到当前Priority Queue 的队列头正是4,然后更新视图,把4在队列里面Pop出来。