gyn

Win32下的Perl,无用的select,停滞的Tk,结束吧....

关于缩进与高亮

其实这是两个完全不一样的东西,应该分开来讲,这里和在一起是因为只是打算做一个介绍,谈点个人的看法和一些关键点,并不需要摊开来讲,因此如若单独切开来就显得太少了,索性和在一起吧。

缩进基本上就是说在某一块或者上下文环境中的程序代码前添加若干空白,以使得程序结构清晰。就 TclTk 来说,可能需要的一般有三种情况:在花括号中、在方括号中和反斜杠后。对于花括号的判断,我考察了一些编辑器的做法,最偷懒的可能就是查找前一行的缩进位置了,这几乎费不了什么资源和时间,但害处也是显而易见的,倘若前一行缩进发生错误,则之后的都会跟着出错。

一种正确的做法是查询行所在的上下文环境的起始行的缩进,比如 if 或者 while 所处行的缩进。期间我试验了多种方法,最后还是选择了向后的正则查询,这是基于对 TclTk 正则匹配速度的信任所做出的决定。一开始的时候,考虑到大文本的处理性能,我打算为每一行维护信息队列,包括了正反花括号的数量与位置,这样当需要查找当前行所在上下文环境时,只需要对比该位置之前的正反括号数量即可。打个比方:

{{} {}

       {}

你好

}

}

其中各行正反花括号的数量信息如下:

1:  ’{‘ => 3, ‘}’ => 2

2:  ‘{’ => 1, ‘}’ => 1

3:  ‘{’ => 0, ‘}’ => 0

4:  ‘{’ => 0, ‘}’ => 1

5:  ‘{’ => 0, ‘}’ => 0

以上的这种情况中,我们是如何得到你好的缩进位置的呢?首先,“你好“处在第三行,它的上下文环境应考虑第 3 行之前的信息,这里是第 1 2 行;令 ’{’ 值为 1 ’}’ 值为 -1 ,将第二行相加可得值为 0 ,则继续向上查询;将第一行信息相加得到 1 为正值,即说明第三行所在上下文的起始行即为第一行。

这种方法在即使是巨大的上下文环境时也可以将性能消耗控制在线性范围内,但是维护这么一个信息组也不是一件容易的事情。对于行插入或者删除的情况,都需要对该信息组进行操作,可能需要在 list 中移动上万个单元。幸好 lreplace 的性能及时在百万级的数据下,也表现得不错,应该是够用了。

至于为什么用到之后选择反向正则匹配,是因为我在 text 中拷贝了《 wxPython in action 》的某一章的全部内容并进行了匹配试验,发现速度竟然比使用信息组的性能还要好,而一般的单个程序文件我想不会有这么多字吧。

至于方括号也大致如此,事实上我是用了一个统一的函数来进行处理的。

proc myLeft {id_1 id_2 l r} {

    set id_1 [.f.content search -backward -nolinestop -regexp "\\$l" $id_1 1.0]

    set id_2 [.f.content search -backward -nolinestop -regexp "\\$r" $id_2 1.0]

    if {$id_1 eq {}}  {return {}}

    if {$id_2 eq {}}  {return $id_1}

    if {[.f.content compare $id_1 < $id_2]} {return [myLeft $id_1 $id_2 $l $r]}

    return $id_1

}

以上的函数用来得到第一个正向括号,是 "{" 还是“ [ ”则可以通过复制 l r 参数来决定。基本上这是一个递归,每次比较得到的相向括号的位置前后。

最后的反斜杠的处理,这是最简单的,只需要查询上一行的位置即可,然后缩进 4 个空格。

这个差不多花费了我一周的时间来解决,最后还是因为一件不相关的事情而得到了启发。一般地,我们会考虑将作色绑定在可能使文本内容发生改变的事件上,但是这并不是一件简单的事情,应该说存在着若干个截然不同的情况。比如说,删除动作之后,我们应该考虑当前插入点的前后文本的变化;而在拷贝事件时,需要考虑被插入文本的前后端以及本身的内容,但如果之前有范围选择存在,则还需要首先考虑选择区域应被删除。总之这是一件很复杂的事情,很难保证代码的清晰易懂,也很有可能随之带来 bug ,因此这个思路被暂时搁置了。

在确定 <modified> 事件是发生在文本发生改变之后,我试图寻找一个简洁的办法来得到事件前后的文本差异,但是似乎只能寄希望于极耗时间的全文比对,这种着色引擎只适用于大文本的载入情况,而对于实时渲染显然非常低效。

考虑再三,我还是选择了绑定各个事件的办法,但编写调试的过程相当痛苦,充满了复杂的逻辑和奇怪的问题。这个时间里,群里有人大约是在讨论关于 rename 的必要性,我记忆里隐约记得是有这么一个看似毫无价值的命令的存在。随手 google 了一下权作放松,可恰恰是这次搜索使得高亮的进展柳暗花明。

tcltk wiki 上,有一篇关于在 text 中通过 rename 的办法得到实际操作指令的的例子。具体的讲,比如一个 copy ,可能会被分解为选择,删除和插入等几个步骤,通过 rename 我们可以在具体的指令操作发生之前捕捉到它。对于高亮而言,事实上需要关心的只是文本的变化,也就是说我只要能够捕捉到插入和删除这两个基本事件就可以了。

这个技巧很诡异:

text .t

pack .t

rename .t fake

proc .t {args} {

    puts "catch: $args"

uplevel #1 fake $args

}

rename ”应该是会注销掉“ .t ”的命令,但由于 tk 本身的命名规则,“ .t ”依然会被使用到,因此我们必须将其重新定义为一个接收参数的过程,在该过程中使用“ fake ”来真正执行指令,显然在此之前我们已经捕获了它。需要注意的是,别在真正执行指令之后插入别的代码,因为该过程在最后会对全局带来一些必要的副作用,不能将其覆盖。因此,诸如着色的操作代码必须写在真正指令之前。

这算是一个矛盾的事情,着色代码放在了引发着色事件的前面,这岂不是在当前操作未来的内容。幸好还有“ after idle ”,用来缓冲代码的命令。纵观整个程序,充满了“ after idle ”,实在是非同小可的东西。

posted on 2009-05-30 23:13 gyn_tadao 阅读(445) 评论(2)  编辑 收藏 引用 所属分类: TclTk

评论

# re: 关于缩进与高亮 2009-06-01 14:12 游子

呵呵,看不懂。
是写什么编辑器吧。  回复  更多评论   

# re: 关于缩进与高亮 2009-06-02 18:52 gyn_tadao

是的,基本上是写好了~  回复  更多评论   

只有注册用户登录后才能发表评论。
<2009年9月>
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

导航

统计

常用链接

留言簿(15)

随笔分类(126)

随笔档案(108)

相册

搜索

最新评论

阅读排行榜

评论排行榜