在写代码的时候,不应该仅仅追求完成某件功能,而是需去寻找一个尽量优雅的方法解决。我不单是这么想,而且也的的确确这么做的。在处理
cucumber
的语法高亮模块时,我思考了很久。最后选择了
rename
办法,即是通过
rename
和重载原命令来得到命令参数,实现代码注入。举个例子:
1.package require Tk
2.text .t
3.pack .t
4.rename .t t
5.proc .t {args} {
6. # injected code
7. uplevel #1 t $args
8.}
对于复杂的对象,处于简化使用或者其他的目的,往往会隐藏一些底层的方法,而仅仅提供高级的抽象功能。比如
text
组件的剪切功能,将删除、赋值和标记设定等命令以一定的方式顺序组合在一起,对程序员提供一个指令来完成这一系列的事件。以上的办法为在执行这些底层命令前提供了捕获控制的机会。在高亮的解决方案中,通过这种机制,介入所有的
text
组件指令,筛选出
insert
、
delete
、
mark set
和
see insert
这四个指令,完成当发生包括输入,黏贴、删除,剪切、撤销和重做等事件时的文本高亮渲染。
在这个基本思想的基础上,
cucumber1.1.1
版本实现了关键词、变量、注解和引号语句的高亮,但如在实现顺序上有错疏忽,虽然可能不会对破坏的渲染结果,但是会极大影响大文本渲染的效率。为什么单指大文本,因为对于文本输入一般包括键盘输入和大段文本的载入如打开文件或者黏贴。与电脑的处理相比无论打字速度多快,其中的间歇都是足够渲染处理打入的单个符号的上下文环境;而大文本的载入首先是一次操作完成的,与下一个操作之间只有一次间隔,第二需要渲染的内容很多,这么一来渲染效率的瓶颈边出现在大文本处理上了。所以,我所说的效率可以理解为特指大文本渲染效率。在第二个版本里我是使用了以下的一系列优化代码。在这里我首先通过一下函数完成捕获
1.proc _tool_core_fake {cucumber fake} {
2. set ::$fake [regsub
{(.*)\..+$} $cucumber {\1.linumColumn}]
3. rename $cucumber $fake
4. eval "proc $cucumber
{args} {
5. after idle
\[_tool_core_sortMap $fake \$args\]
6. uplevel 1 $fake
\$args
7. }"
8.}
_tool_core_fake
第
2
行是设置了一个与当前
text
文本相对应的行号显示组件变量,这里就不再多说了。真正的高亮渲染注入发生在第
5
行,通过以下的函数返回需要执行的指令集。
1.proc
_tool_core_sortMap {fake args} {
2. eval "set args $args"
3. switch -glob -- $args {
4. insert* {
5. set from [$fake index [lindex $args
1]]
6. set to "$from+[string length
[lindex $args 2]]c"
7. return "
8. _tool_core_hiContent $fake $from
$to;
9. _tool_incrLinum $fake;
10.
_tool_core_hiLine $fake
11. "
12. }
13. delete* {
14. set from [$fake
index [lindex $args 1]]
15. return "
16. _tool_core_hiContent $fake $from $from;
17. _tool_decrLinum
$fake
18.
_tool_core_hiLine $fake
19. "
20. }
21. {mark set insert*} {
return "_tool_core_hiLine $fake" }
22. {see insert} {
return "_tool_core_hiLine $fake" }
23. }
24.}
_tool_core_sortMap
第二行的作用是消除字符串外的大括号,将如“
{insert end nihao}
”变化为“
insert end nihao
”。之后使用
glob
匹配的
switch
来筛选命令参数。在
1.1.1
版本的筛选函数中,首先我没有加入用于筛选高亮当前行显示的
insert
标记判断,而是在单独的模块中通过绑定事件来完成,这显然是累赘的;第二,最初使用
none
函数来处理非匹配的指令,这个自以为保证代码一致性的手法在事后证明是一个画蛇添足的举动,因为即使是一个空函数,也存在执行消耗,而大量累加执行的结果就是拖慢渲染的速度,这在
1.1.1
中已经被删除了;另外,在处理
insert
和
delete
时,存在值得商榷的渲染级别。在目前代码中可以看见,事实上渲染发生在以上代码的第
8
行和第
16
行里的
_tool_core_hiContent
函数里。顺便提一下,在该函数以下的两个命令,跟行号和当前行指示有关,这里不做详解。
1.proc _tool_core_hiContent {fake from to} {
2. set temp $from
3. set temp
[_tool_core_hiWord $fake $temp]
4. while {[$fake compare
$temp <= $to]} {
5. set temp
[_tool_core_hiWord $fake $temp]
6. }
7. _tool_core_hiQuoteContext
$fake
8.
_tool_core_hiCommentContext $fake $from $to
9.}
_tool_core_hiContent
在
_tool_core_hiConetnt
的文本渲染函数中,包含了三个同层次的渲染,分别是处于
index
位于
from
和
to
之间的单词循环渲染和各行注解渲染,以及一次全文引号渲染。这么安排的道理在于,一般情况系关键词和变量都是以单词的形式出现的,可以统一在
_tool_core_hiWord
中处理;注解可能占据一行或者后半行,与其在每次单词渲染时判断是否存在注解起始符,不如在处理完所有的单词之后统一检索当前文本段,前者是单词数量级的运算,而后者是行数量级的运算,显然后者更为高效;引号数量的单双数变化,会造成全文渲染范围的颠倒,但考虑到引号数量与单词相比可能相差两三个数量级,因此这种全文渲染变化是很快速的,尤其是我更引入了延迟渲染的机制,消耗更是可以忽略不计。以下是这三个函数。
1.proc
_tool_core_hiWord {fake id} {
2. if [_tool_ifInWord $fake $id] {
3. set head [_tool_indexWordHead $fake $id]
4. set end
[_tool_indexWordEnd $fake $id]
5. foreach hi {
6. _tool_core_hiTextContext
7. _tool_core_hiVariableContext
8. } { $hi $fake $head $end}
9. return "$end +1c"
10. }
11. return "$id +1c"
12.}
_tool_core_hiWord
1
.
proc
_tool_core_hiCommentContext {fake from to} {
2. set f [_tool_linum $fake $from]
3. set t [_tool_linum $fake $to]
4. while {$f <= $t} {
5. $fake tag remove Com "$f.0"
"$f.0 lineend"
6. if {[$fake search -regexp
$::hi_comment_regexp "$f.0" "$f.0 lineend"] != {}} {
7. $fake tag add Com [$fake search
-regexp $::hi_comment_symbol "$f.0" "$f.0 lineend"]
"$f.0 lineend"
8. }
9. incr f
10. }
11.}
_tool_core_hiCommentContext
1.proc
_tool_core_hiQuoteContext {fake} {
2. after cancel [list _hiQuoteContext $fake]
3. after
1000 [list _hiQuoteContext $fake]
4.}
5.proc
_hiQuoteContext {fake} {
6. if {[$fake get 1.0] == "\""}
{ set rid [list 1.0] } else { set rid {} }
7. set qid [$fake search -nolinestop -overlap
-all -regexp {[^\\]\"} 1.0 end]
8. foreach id $qid { lappend rid [$fake index
[$fake index "$id + 1c"]]
}
9.
10. if ![set e [llength
$rid]] {return}
11. set now [lindex $rid 0]
12. $fake tag remove Quo 1.0
$now
13
.
14. set i 1
15. while {$i < $e} {
16. set old $now
17. set now [lindex $rid
$i]
18. if [expr $i % 2] {
19. $fake tag add
Quo $old "$now +1c"
20. } else {
21. $fake tag remove
Quo "$old +1c" $now
22. }
23. incr i
24. }
25.
26. if [expr $e % 2] {
27. $fake tag add Quo
"$now +1c" end
28. } else {
29. $fake tag remove Quo
"$now +1c" end
30. }
31.}
_tool_core_hiQuoteContext