Vim,一个开放源代码的文本编辑器

Vim,http://linux.21ds.net/2002/03/13/0268dc26fd9c725c23dae68d797935f3/

作者:Bram Moolenaar
翻译:slimzhao

开放源码的自由软件-VIM的主创者在本文中讲述了 vim的开发内幕和发展方向. 同时提出慈善软件的概念并解释了Bram为何将这一概念用之于vim. 本文也深入程序内部的函数和数据结构, 作者讲述了vim这一复杂的程序是如何工作的, 并且讨论了在vim最新版6.0中的新功能.

介绍
不太可能你还从来没有听 说过vim吧. 它作为标准的Vi编辑器存在于Linux的几乎每一种发行版里. 在很多系统里如FreeBSD和Sun Solaris 8它作为一个便于安装的独立软件包, 在其它系统(MS-Windows, Macintosh, OS/2等等)上的源代码和已编译好的可执行程序在因特网上很多地方都可以下载

Vim是一个类似于Vi的文本编辑器, 不过在Vi的基础上增加了很多新的特性, Vim普遍被推崇为类Vi编辑器中最好的一个, 事实上真正的劲敌来自Emacs的不同变体. 1999 年Emacs被选为Linuxworld文本编辑分类的优胜者, Vim屈居第二, 但在2000年2月 Vim赢得了Slashdot Beanie的最佳开放源代码文本编辑器大奖, 又将Emacs推至二线, 总的来看, Vim和Emacs同样都是非常优秀的文本编辑器.

Vim(和Vi)一个最大的优势在于, 它最常用的命令都是简单的字符, 这比起使用复杂的控制组合键要快得多, 而且也解放了手指的大量工作, 学习使用这些命令的时间很快就能从由此带给你的高效率中得到回报. 另外, 与Vi不同, Vim也支持在插入模式下使用上下箭头键, 这使初学者可以很容易上手.

与其它的类Vi编辑器相比, Vim拥有众多的特性: 对180多种语言的语法高亮功能, 对C语言的自动缩进, 以及一个功能强大的内置脚本语言. 对每个人来说, Vim总有让他们喜欢的东西.

Vim的开发仍然在继续进行, 写作本文时, Vim6.0版的工作已经于9月份(2001年 )完成了, 在这之后, vim的主要目标是更稳定, 更实用, 而不是再去增加更多的新功能了, 完善已有功能和修改bug的工作已经够人忙的了. 比起Vim, 原始版的Vi自1985年并没有多大的变化.

技术分享

上图是GTK版Vim的一个快照, 图片上显示正在编辑的C源代码, 代码的不同部分以相应的语法高亮显示, 被查找的字符模式”msg_didany”以黄色的背景高亮显示. 图上还显示了一个可视化的选择(灰色背景), 绿色的闪烁光标突出显示这一模式. 在底行”– VISUAL –”表明当前所处的工作模式, 右边是当前光标在文件中的位置信息.

Vim历史
很久以前我 自己有一台Amiga计算机. 因为我过去一直使用Vi, 所以我想找一个 Amiga版的类似Vi的编辑器. 我找到一些叫”克隆”的版本. 但没一个中意的. 所以我就挑了其中最好的一个来改进它. 最初的目标是达到所有Vi的功能. 后来我逐渐增加了一些Vi所没有的额外功能, 如多级撤消.

当它看起来已经运行得不错时, 我发布了一个Vim(意思是Vi IMitation[译注] Vi的仿造品)放在由Fred Fish搞的一个公共磁盘上. 接下来开始有人发给了我一些补丁, 还有一些人努力把Vim移植到其它平台上, 如MS-DOS和Unix. 我又增加了更多的一些功能让它看起来更好用. 此时, Vim可以名正言顺地叫”Vi IMProved”( [译注]增加版Vi)了. 后来整个源代码被重新设计并且扩展了很多, 几乎再也找不到原始的”克隆”版源代码的痕迹了.

起初 我在Vim所作的工作完全是为了方便自己. 后来我意识到Vim同样可以方便他人, 于是我就把它公之于众. 从此我就不断地致力于让它成为一个更好的工具以服务于它的大量用户. 创造一个对他人有用的东西是很有趣的. 同时, 还有很多优秀的合作小组和用户鼓舞着我的工作.

下面是Vim的发展简历:

————————————————————————-
1991 Nov 2 - Vim 1.14: 第一版 (在Fred Fish的第591号磁盘上).
1992 - Vim 1.22: 移植到Unix上. Vim开始挑战Vi
1994 Aug 12 - Vim 3.0: 支持多窗口多缓冲区
1996 May 29 - Vim 4.0: 支持GUI(图形用户界面) (主要是Robert Webb的努力).
1998 Feb 19 - Vim 5.0: 颜色支持和语法高亮
2001 Sep 26 - Vim 6.0: 折行, 插件, 垂直窗口分隔
————————————————————————-

Vim中的IMproved(增强)到底意味着什么?
1.22版的Vim比之Vi已经多了很多功能. 我决定把”Vi IMitation”改为”Vi IMproved”. 随着时间的推移两者之间的差距越来越大, 现在, 几乎没有理由去使用Vi而不是Vim. 我建议所有的Vi用户转而使用Vim. 下面是其大量优点的部分摘录:

- 对文本行的长度没有限制, 允许ASCII码为0的字符; 可以编辑任意类型的文件
, 包括二进制文件.
- 多级的撤消和重做功能: 当你的转换大小写的键盘灯不慎灯亮起来时, 无须担
心误操作会毁了你的文件.
- 多窗口和多缓冲区: 同时可以编辑多个文件, 并且可以在它们之间复制内容.
- 语法高亮: 让你快速把握整个文档的结构, 迅速发现错误所在.
- 命令行历史记录和命令补全: 更正打字失误, 快速重复历史命令, 快速键入
长文件名.
- 可以删除和添加矩形的文本区域: 用于编辑表格
- 错误信息解析: 在Vim中运行编译器并且能根据编译产生的错误信息迅速定位
到源代码中发生错误的地方([译注]对程序员尤其有用)
- 带有超链接([译注]不同于WEB浏览器中的链接, 但功能类似)的在线帮助: 让
你快速找到关于任何命令的详细帮助, 阅读相关帮助主题.
- 一个功能强大的脚本语言: 可以增加你自己的扩展功能.

Vim的开发
几年的辛苦工作之后Vim6.0版终于完成了, 核心的工作由我亲自操刀, 另外有很多人也加入了工作, 有时他们新加一项功能然后发给我一个补丁, 以便我把他们的新功能加进去, 但是多数时间这些发来的补丁还要做一些整合的工作, 很少有人能去把握Vim的各个部分是如何协同工作的. 因为整个代码实在是太复杂了. 比如说, 有人发过来一个跨行搜索字符模式的补丁. 这表明这一功能可以如何实现以及源代码中哪个地方需要做哪些改动. 不幸的是补丁的作者使用指针操作每个文本行, 他并没有意识到这些指针可能是非法的. 我必需总览整个改过的新代码来改正它. 虽然融合这些补丁很麻烦, 但这确实能帮我把握正确的方向.

用户们在Vim邮件列表上的问题往往能指明哪些是最经常碰到的问 题. 有时也有人直接发补丁给我, 或者要求增加一些新功能. 我认为这种与用户的互动和合作开发是vim的主要优势. 用户与开发者能直接地开诚布公地交流. 这也正是开源软件比之商业软件更好的原因.

发行
Vim可以自由发行, 不过, 也有少许的限制. 下面一段文字是与Vim一起发行的:

————————————————————————-
摘要
Vim是福利软件. 你可以自由使用和复制, 但是我们希望你能关注一下乌干达的
孤儿. 参见下面的|iccf|节.
如果你将Vim在CD-ROM中发行, 我很高兴能收到一份. 只是想知道世界上又有人
在这样发行Vim(也可以向我的朋友们展示:-)).
正文
对Vim不加修改的复制和发行都没有限制, Vim也可以部分发发行, 但是这段文字
必需包括在内. 你也可以在发行版中包括你自己从一个未被修改的源代码中编译
的可执行文件, 或者你自己的使用例子, 脚本.
如果你要发行一个修改过的Vim版本, 我们(维护者)希望收到一份你的包括源代码
的修订版. 或者让维护者可以通过ftp来下载; 如果所作的改动很小,(比如只是
更改了Makefile) 仅仅把不同之处EMAIL给当前维护者也可以, 但无论如何如果维
护者要求的话, 你必需提供源代码给他.
Vim的维护者保留将这些改进包括到Vim正式版中的权利. 不过仍有商量余地, 但
不允许在你不准备把你的源代码提供给维护人员的情况下发行你的修订版Vim.
当前的维护者是Bram Moolenaar([email protected]), 如有变动, 会在适当的地方公开声明(主要是
如www.vim.org 网站上或comp.editors这样的新闻组). 如果实在没法联系维护者,
当然你也就不再有义务把你的修订版源代码发给他.
不允许从Vim源代码发行版或部分发行版中删除本文, 本文的声明也适用于Vim
的前期版本以代替当时的版本声明.
————————————————————————-

我更希望给用户在使用源代码方面更大的自由, 之所以加上如上的限制是为了避免Elvis(vi的一个克隆版)过去遭遇过的事情: 有人弄到了Elvis源代码, 加了一些诸如Windows的GUI特性的东西, 然后开始拿去卖钱. 修改之后的Elvis不公开源码而且整个软件的核心代码仍然是最初的Elvis, 这实在太不公平了. 不光是因为有人拿他人的劳动去赚钱, 更重要的是他不公开源码, 从而拒绝别人对该软件进行进一步的改进. 正因为这个我才加了如上限制以保证别人对源代码做的修改必需要让我知道. 虽然这些限制仍留有余地, 比如某公司做了一个Vim的修订版然后跟我协商能否不把他们所做出的修改公之于众, 但毕竟让我保留了对自己创造的软件的决定权.

为何不用GNU的GPL(Gnu公共许可证)?
GNU的公共许可证有更严 格的限制, 虽然它声明保护软件自由, 但实际上限制了你所能做出的改变. 你可以做出修改, 但如果你要发行修订版的话你必需将你的所有改动公之于众, 这样人们就失去了自己持有他所修改过的源代码的权利, 我想这实际上限制了你的自由. 另一方面, 如果允许任何人都可以任意修改然后作为自己的私藏, 甚至他并未作出修改就从Vim的某部分受益, 也并不公平, 所以我决定加上这一条件: 所有对源代码的修改必需让我知道. 我可据此决定这些改动中哪些确实是对人们普遍有益的, 从而把它添加到Vim中. 而哪些只拥有少量的用户群, 所以允许某个公司从中赚点钱, 毕竟, 如果一个软件的源代码是公开的话, 很难再去要用户为此付钱.

同时我也并不认为所有的软件都应该是免费的或是都应该是开源项目, 我所知道的所有为开源软件工作的人也都为商业软件工作谋生, 或者是全职工作为其工作, 或者是正准备找, 如果没有商业软件,那这些软件业的人又能如何谋生呢? 我认为自由软件, 开源软件和商业软件将会共存, 很多商业软件不可能公开源代码, 因为这样公司就会失去竞争优势. 软件源代码的创造代价昂贵, 所以商业软件公司可不想别人能坐享其成. 由于软件的专利和版权保护力实际上是很微弱的, 不予公开在很多情况下仍然是最好的保护措施. 一个不幸的后果就是, 你无法从中学习这些商业软件是如何实现的, 也无法自己去修改你花钱买来的软件里面的BUG, 解决之道可以是公开一个软件的大部分源代码, 但保留其中核心的部分.

开源软件的前景
什么软件会是开源项目而什么软件又不会是开源项目? 这一问题无从回答, 对于某一个应用领域, 是否有人愿意为此花费大量心力去创造并维护一个软件, 很大程度上依赖于一个个人的动机因素: 又没钱得, 又要花费自己的大量劳动. 这一点无法控制, 而且后果莫测. 不久以前人们还认为只有小的软件项目才可能是开放源代码的, 而大型的软件项目由于项目周期和资金方面的问题必需由一个商业软件公司来承担, 但Linux的发展打破了这想法, 而且Linux并不是个别的例外, 还有不少其它的大型的开源软件项目也都成功了, 如KDE.

现实中我发现多数人只会对自己要使用的软件感兴趣. 这正是Vim当时的情况: 我每天都用它. 这几乎成了一种制约, 因为越来越多的人开始学写程序. 这确实限制了致力于某个专用程序的人数, 理论上可以按如下公式去计算有多少人可能为开源软件工作:

人数 = 感兴趣的人数 x 能力 x 动机

其中:

人数 为某个开源项目工作的人数
感兴趣的人数 有兴趣使用该程序的人数
能力 这其中有能力写程序的人所占的百分比
动机 某个人有志于从事这个项目的比率

值得注意的是, 有兴趣使用某个程序的人数也依赖于已有程序的可用性, 如果没有足够好的可替代程序或者太贵了, 人数就会升高, 如果已经有一个又便宜又好的程序. 人数就会下降.

不是说随便一个人都能写软件, 而且写不同的程序所需要的技巧差别也很大. 如果目标程序是软件工程方面的, 可能就有很多人投身其中, 这其中不乏确实有足够的编程能力的人, 如果程序是处理稀有鸟类的数据, 可能感兴趣的人数就很少.

整个公式中动机是一个最难以把握的因素, 在有能力写程序的人中又有多少人愿意去写? 这一问题听起来也蛮有趣, 看看是否这个因素也是可以估算出来的.

对这一公式的修正要引入另一因素:有多少人愿意为了别人来写程序. 尤其是为伤残人士. 不过, 我想人数很少.

假设人数最终还是被算出来了, 剩下的一个大问题就是: 事实上真的会有人这么做吗. 或者情况好一点: 就算会, 什么时间? 只要有足够的时间, 我相信每个程序都需要有人去做. 应该有一个公式也可以计算出某个程序今年是否会被写出来. 这个就留给读者们去考虑吧…

总的来说, 这个公式中有太多的不确定因素, 可以说难以预测将来会有多少开源软件.

福利软件
由于Vim是一个开源软件并且免费发布, 所以用户不需要为使用它而付钱. 即使这样, 还是有一些经常使用Vim的人表示他们想通过某种方式褒奖一下我为此作出的工作. 我自己其实不需要额外的钱财, 并且我也不喜欢人们因为使用一个自由软件而付钱给我. 当时我开始考虑”福利软件”的概念. 主要意思是使用Vim的人要向福利事业捐赠. 这样使用Vim还是免费的, 但是如果你认为值的话, 可以为一个更好的原因付钱.

我怎么会选择了福利? 是这样的, 我曾经作为一个志愿者为一个项目在乌干达南部工作过一年. 这一地区被爱滋病严重侵害. 估计有10%到30%的成人感染有HIV. 很多身为父母的人都死了, 留下他们的孩子孤苦伶仃. 这个项目通过几种方式帮助这些嗷嗷待哺的孩子. 为他们找一个新家, 保证他们可以上学, 或给予他们医疗方面的关心, 等等.

从乌干达回来之后, 我仍然心里想着那里. 我决定至少我可以通过给他们一些钱来继续支持这个项目. 与Vim关联起来是一件顺理成章的事. 所以现在我要求Vim的用户们考虑为乌干达的孤儿们捐款. 你可以在财政上收养一个孩子, 这会使他得到长期稳定的帮助, 对孩子来说这是最好的. 由于我们只是跟志愿者们一直工作并且钱是直接送到该项目去的, 所以绝大多数钱确实能真正用于乌干达.

你可以在http://iccf-holland.org找到更多的相关信息.

Nabasagi Morine是荷兰ICCF赞助的孩子之一.

福利软件真的行得通吗?
通过福利软件, 我确实收到不少给乌干达孤儿的捐赠品. 情况不定, 有时一个月也没有一次, 有时一次就有好几笔捐赠. 一些数目很少, 也有的数目很大. 我说不准具体的数目, 因为不是所有的捐赠都是经我之手, 也搞不清楚到底是不是因为Vim. 大概在1997年共收到2000美元, 1998年有4000美元. 相当多的一部分来自德国.

不光是钱的问题, 福利软件也让人知道了其他人的需要. 如果我不这样做, 就不会有那么多人知道乌干达的这个项目, 对很多人来说很难想象生命中除了挣钱和关心自己还有其它很多有价值的事情. 我也接触到一些人, 他们无力捐赠, 但是被这一概念深深触动. 在某些方面福利软件确实能改变人.

福利软件也适用于其它项目吗?
检查以下几点以确定它是否适用于你的项目:

你自己不需要钱. 如果你把钱用在比邻居更大的汽车上, 它不适合你.
你正在做一个共享软件, 但所获不多, 因为人们不愿把钱给你.
你目前提供的软件完全免费, 但你认为它值得人们为它付出点什么.
何时你会决定做福利软件, 你会使用什么理由? 最好的一个是通过这个你可以亲自接触一些人. 用户会更好地理解你的动机, 另外, 找一个你可以信任的需要钱和人们注意的组织. 我不主张支持大的组织, 尤其是他们本来就有路子联系到赞助者的组织.

福利软件的变体
我不强制Vim的用户向乌干达的项目捐赠. 也没有数目上的限定, 最大的自由留给用户自己, 他也可以看完乌干达爱滋病的事然后就忘了它.

另一种做法, 用户被告知他们必需要捐赠, 并且还要指定一个最小限额. 声明必需放在一个不可能被用户忽略的地方. 但愿这一做法会带来更多的捐赠, 但是注意不要把用户给惹火了. 你必需设定一个最小限额, 否则这种强制将毫无意义. 不过确定这个最小限额到底是多少又是一个难题.

另一个更具强制性的做法是限制一些或全部的功能, 直到收到用户的捐赠. 这不是一个好办法, 因为这不合乎仁道; 这样穷人就不能用你的程序了!

Vim内幕
Vim已变为一个很大的项目. 这里是一个关于程序主要部分和源代码分布的概览. 我把每个文件(针对5.6版)的大小也统计进去, 以便你知道程序的各个部分到底需要多少的代码量

程序启动代码, 主要的命令循环 main.c 43538
普通命令模式的命令处理 normal.c 140320
执行命令行命令 ops.c 109742
在屏幕上显示文本 screen.c 165136
多窗口处理 window.c 52168
多缓冲区处理 buffer.c 63743
命令预修饰符,键映射和缩写 getchar.c 76964
终端输入输出 term.c 109869
文件读写 fileio.c 122685
交换文件管理 memfile.c 31516
缓冲行管理 memline.c 114193
撤消与重做 undo.c 29014
插入模式 edit.c 142302
保留文件中的标记 mark.c 25582
命令行编辑 ex_getln.c 89509
执行Ex命令 ex_docmd.c 176724
组合Ex命令 ex_cmds.c 100549
快速改错命令 quickfix.c 31760
正则表达式匹配 regexp.c 73771
搜索命令 search.c 93721
标记命令 tag.c 69337
选项设置 option.c 148884
vim脚本命令,表达式求值 eval.c 122346
与具体系统相关的代码 os_*.c up to 94640
通用的GUI代码 gui.c 73468
Motif的GUI代码 gui_motif.c 26963
Athena的GUI代码 gui_athena.c 25220
一般的Motif和Athena GUI 代码 gui_x11.c 62913
GTK的GUI代码 gui_gtk*.c 158289
MS-Windows的GUI代码 gui_w32.c 141809
Macintosh的GUI代码 gui_mac.c 82600
菜单处理 menu.c 36164
Perl接口 if_perl.c 21246
Python接口 if_python.c 51814
Sniff接口 if_sniff.c 25035

每个文件一般都很大, 因为我喜欢把功能上相关的程序段放在一起, 也可以使用多个的小文件, 对于一个版本控制系统这样做尤其适用, 但对我来说目前这样就蛮好, 似乎没理由把它改成其它样子. 所有的文件都放在同一个目录下, 这样做最大的好处就是一个简单的grep命令都可以方便地找出某个标识符来.

上面的列表中有几处值得注意. “撤消”功能的代码量相对较少, 主要是因为它使用了一种`well thought-out’机制, 这种机制简洁有力. 另一方面, 你也可以看到GUI相关的文件一般都很大, 为GUI编码确实费劲.

Michael Godfrey教授就Vim的体系结构和开发做了一些研究, 你可以在 http://plg.uwaterloo.ca/~migod/找到相关论述.([译注]我没找到, 如果你找到了, 欢迎给我一个准确的URL)

下面是一些你可能感兴趣的特殊问题.

可移植性
Vim被设计成可以工作于不同的操作系统上, 做到这一点并不轻松. 仅仅是支持多数主流的Unix版本工作量已经相当大. 增加对MS-DOS和MS-Windows的支持又带来了些另外的问题, 如在文件名中使用反斜杠. 象Amiga和Macintosh这样的机器都有自己特有的一套操作系统, 对它们需要另搞一套办法.

一个首要的选择是在Vim的整个代码中使用著名的也是”老”的K&R C. 这使得代码可以在几乎任何一种系统上编译. 使用ANSI C外加一个预处理器也可收到同样效果. 比如, 象下面这样的函数原型:

void ml_open_file __ARGS((BUF *buf));

对非ANSI标准的编译器”__ARGS(())”部分被处理为”()”. 这些原型都是由 cproto程序生成. 使用工具自动生成不光是省去了手工键入这些原型, 同时也避免了错误的可能. 使用__ARGS结构带来一些额外的工作, 不过它可以使Vim可以在一些古旧的系统上编译, 而现代的编译器又可以检查原型, 以避免很多的麻烦.

为了让Vim得以在不 同的系统上顺利编译, 对每种系统都有一个系统相关的文件, 如”os_unix.c”或”os_amiga.c”. 这些文件包含了很多系统相关的代码. 但是, 这并不解决所有问题, 在一般文件里还有大量的”#ifdef”这样的预编译器控制选项来解决与不同编译器, 不同操作系统, 不同体系结构相关的问题. 有时我会清除这些东西以避免代码的混乱. 这就要在每个系统相关文件中另建一个如”mch_xxx()”这样的函数 , 从每个一般源文件里调用它. 因为我不可能拥有所有这些不同的系统, 所以还得靠合作的开发者们去测试这些改动.

对Unix来说”autoconf”命令可 以用于检验一个系统中各个不同的东西. 它会找到每个被”include”的头文件在哪里, 库文件在哪里等等. 它使用一个叫 “configure.in”的描述文件来列出哪些东西需要”探测”. autoconf命令处理这个文件并生成一个叫”configure”的SHELL脚本, autoconf会照顾到位让这个脚本可以在各个Unix里都能吃得开. 虽然使用autoconf并不容易, 我还是向大家推荐它. 如果你要使自己的程序在各个不同的Unix上都能四通八达, autoconf正是管这事的.

对用户来说标准的做法是运行” configure”命令, 它会生成一个名为”Makefile” 的文件. 对Vim来说, 情况有所不同, 因为在此之前, 已经有了一个”Makefile”, 第一次运行”make”时, 它会自动为你调用”configure”, 使用默认的参数, 这导致生成一个叫”config.mk”的文件, 该文件被包含在Makefile中, 这种略有不同的方式的一个主要好处是Makefile文件可以包含configure参数的一些例子, 你可以注释掉这些参数. 另外你也可以通过运行”configure –help”找到所有这些可用参数, 检查这一命令的输出, 找到你要小心对付的参数, 对Vim来说, 你可以手工编辑Makefile文件, 读它的注释说明, 或者删除或者插入一些”#”字符, 这很好做. 传统的运行configure也可以, 所以这一额外的办法并没有带来负作用.

Storing text
文本存储
一个文本编辑器的主要工作就是读取一个文件, 修改, 然后写这个文件. 文本要以何种形式保存在编辑器内部要考虑以下几个因素:

- 读写文件要快.
- 对文件大小, 字符集, 文本行的长度没有限制.
- 处理文件的代码不能太多.
- 允许撤消误操作.
- 系统崩溃时, 必需能恢复未被保存的改动.

没有省事的办法能轻而易举地满足所有这些要求. 首先, 不可能把所有文本都放在内存里, 一些系统根本没多少内存, 对另一些系统而言使用大量内存是非常低效的, 这正是我使用一个交换文件的原因. 文件存取的操作很慢, 所以必需有一些文本是通过一种缓存机制放在内存里的. 由于使用固定大小的几K字节的数据块时文件I/O操作很快, 所以最好不要把单个的文本行写回到交换文件中. 考虑几种替代方案后, 我决定使用一种结构来存放一定数量的文本行在数据块中. 这些数据块通过一种类似BSD系统上”db”函数的方式操纵, 但是根据Vim使用文本的方式进行了一番剪裁.

可以在文件” memfile.c”中发现处理存放文本行的数据块的代码. 它通过维护一个缓冲区来减少磁盘I/O的负担. 工作原理跟所有的缓存系统一样: 数据块使用太多内存时就写向交换文件, 或者是保存用户所作出的修改. 这也使得系统意外宕机时可以从交换文件中恢复未保存的内容.

将文本行打包成数据块的代码在”memline.c”文件中. 因为数据块大小是固定的, 所以能放入的文本行的行数也各不相同. 这需要不少代码来处理, 如何能够插入, 删除和修改文本行. 复杂性主要来自这样一种情况: 插入一个文本行时, 容纳它的数据块放不下, 这导致这个数据块必需被一分为二. 此外还有一种特殊情况: 一个文本行长到一整个数据块连它一行都容它不下. 这就需要创建一个非常规的足够大的数据块来存放这一行的内容. 这就做就可以对文本行的长度不加限制, 同时又不影响处理短的文本行的效率.

存放在交换文件中的数据块是没有特定顺序的, 如果要排序的话, 那么往中间插入一个数据块就要求它后面的所有数据块都要往后推移, 这太慢了. 为了通过行号找到一个文本行, 需要使用数据块索引. 数据块索引包含了一个数据块中所含文本行行号的列表, 如果文件太大, 这个列表可能在一个单个的数据块里放不下. 也需要分成多个数据块, 问题来了, 这就需要另有一个索引数据块来”索引”这些因数据增长而必需分而置之的数据块.([译注]这有点象C语言中指针的指针). 这组成了一个索引块的平衡树, 文本块是叶子节点. 实践证明这种结构稳定而高效.

语法高亮
分析一个文件的结构本身已 经够复杂了. 另外还要考虑这种解析可能要以一个文件中的任意位置为开始, 两个问题一起来, 必死无疑. 幸运的是Vim的设计把整个任务分为两部分: 核心的语法引擎, 用C语言实现, 通过字符模式指定不同语言自己的语法项. 这使Vim无需重新编译即可支持一种新的语法.

基本思路是用一个正则表达式来指定一种语言的一个语法项. Vim试图在每行中匹配这个模式. 如果匹配到了, 就会相应的高亮颜色突出显示匹配到的字符串. 对于都在一行中的语法项这很容易实现. 但对跨行的语法项来说情况就复杂多了, 在Vim中你需要指定一个字符模式来规定一个语法项如何开始又怎么样才算结束. 比如, 对于C语言中的注释, 发乎”/*”而止于”*/”. 但Vim碰到一个半截的注释行时会怎么样呢? 此行中没有”/*”, 这样它就无以确定它是否是一个注释了. 这需要Vim回头做一些搜索以确定当前行是否是一个注释的一部分. 这叫同步.

另一种结构是嵌套. 当你想以另一种不同于注释的颜色定义显示包含于注释中的”TODO”时就碰到了这种情况. 所以在注释语法项中又有一种”TODO”语法项的子项. 还有一种, 如果在一个字符串中发现了”/*”字符串, 显然它不标志着一个注释的开头, 这就让字符串语法项要多个心眼, 防着其中的”/*”或”*/”混淆视听. 所有这些情况归之为”可包含的语法项”, 对每个语法项用户可以指定它可以再包含哪些子语法项. 这一处理适用于大多数语言的结构.

处理语法项的代码维护一个嵌套语法项的栈. 对于文件中的任何位置, Vim会去查找可以包含在当前语法项中的子语法项, 所以这个语法项的栈也代表着语法高亮引擎的当前状态. 为了加速语法高亮的处理, 这个状态被保存下来并在需要显示同一行内容时被重用. 文本行的内容发行改变时保存的语法状态也因之失效. 一行的改变也可能引起它后续行的语法状态随之改变, 比如: 在一行中插入一个”/*”来注释掉部分代码时, 直到找到匹配的”*/”, 所有后来的文本都被作为注释处理. 程序知道位于”*/”之后的语法状态与此前的旧的状态一样, 所以此后的所有语法状态仍然有效, 对语法状态的重新洗牌就止于”*/”.

数据结构
Vim使用不同的数据结构来存储不同的内容. 折衷考虑通用的数据结构和针对具体问题量体裁衣的特殊数据结构. 比如, 一个字符串可以被存储在栈中一个固定大小的字符数组中, 也可以放在一个动态分配的固定大小的内存区, 或动态分配的大小不定的内存区, 具体用哪一种取决于要存储的字符串的类型.

对于一个已知其长度的上限的字符串, 使用栈中一个字符数组就无需进行 malloc()和free()的调用. 这通常用来处理文件名, 但Vim为避免对用户的限制. 其它的字符串可以是任意长度, 所以对于其它的字符串长度未定的情况栈并不适用.
分配固定大小的内存区可用之于不经常发生变动的内存需求. 比如, 用于 选项值, 这不会浪费什么内存, 而malloc()/free()带来的开销也不成问题.
对 于经常改变的字符串, 调用malloc()/free()会花费很多时间. 对于那 些字符串和列表的尺寸要经常增加的情况尤其如此, 在Vim中一种叫”结构化 的增长数组”的动态内存分配结构专门对付这种情况. 内存分配的步长很大, 所以可以无需进行重新分配即可容纳更大的数据.
在Vim中创建这些数据结构真要了我的命. 缺点是这太花时间, 也增加了复杂性, 维护起来也更加困难. 优点是带给Vim更高的效率. 对Emacs来说, 用户最大的抱怨就是说它太慢了, 太耗资源了. 我的印象是Emacs使用了常规的数据结构并且寄希望于速度更快的硬件. 在我看来我的选择是正确的. 一个文本编辑器是要被很多人每天都用的, 值得让作者花些时间和精力来让它更加高效.

特性列表
1998年11月在Vim用户中展开了一项调查以投票决定Vim要作出哪些改变. 调查结果最好地说明了用户最想要的东西是什么, 下面是排行榜上的前6名:

1. 折行功能(只显示一段文本中被选出来的部分文本行)
2. 垂直分隔窗口
3. 增加对很多语言的可配置的自动缩进
4. 修改大大小小的BUGS, 让它更健壮.
5. 增加与PERL兼容的搜索模式
6. 跨行搜索模式字符

前3个和第6个已经在Vim6.0中实现了, 对BUG的修改跟往常一样, 从来就没停过! 对模式字符的搜索已经被扩展为包容了PERL的所有相关特性, 但是并不是直接与Perl兼容的写法, 因为这会带来Vim的向后不兼容性.

折行
最先加到Vim6.0中的特性是折行. 这是一种隐藏部分文本行的机制, 得以让整个文本的结构凸现出来. 看起来就象折起来的纸. 这个改动很大. 也影响了其它的代码 - 还不知道用户会对不同的文件如何使用这些功能. 我花了很多时间研究如何让使用折行的用户界面更加友好, 不同的用户有不同的需求. 我设置了如下几种折行模式:

手工 手工选择哪些行要折叠起来.
根据缩进 文本行的缩进作为哪些行要折叠的依据, 比如, 所有缩进量超
过8个空格的行要折叠.
根据表达式 就象根据缩进一样, 但是依据是一个表达式.
语法 语法高亮的项决定折叠区域
标记 用标记来指示文本中从哪里到哪里要折叠起来.

很多折行方法都不要求改变文件. 这对于那些只需要查看文件内容的人来说是必要的, 或者是那些与别人共享文件的人. 但这些方法不允许任意指定一个折叠应该如何开始如何结束. 所以增加了一种”标记”方法. 这种方法允许你在希望开始折叠的地方插入一个标记, 比如, 如果你在函数定义之前有一些注释行, 就可以把注释标记放在这些注释行的上面, 以使它们与函数属于同一个折叠, 也可以指定一些额外的文字来说明折叠的内容, 比如”local declarations” 或者是函数名附带一个简短的说明. 这种折行定义的缺点是文件本身因这些插入的标记而被修改.

技术分享

这幅图展现了使用了折行功能的C源代码. ‘foldlevel’选项被设置为对每个函数显示一行. 这些折行也被定义了标记, 象”{{{1″, 附带着对被折叠内容的简述. 函数”lineFolded()”的折叠被打开以查看该函数.

多行搜索模式字符
如前所述, 我增加了跨行搜索模式字符的功能, 这项新功能在文档中的描述很简短, 因为很容易说明白, 就是一个新的模式可以匹配到一个行尾. 但这项功能对整个源代码的影响很大, 主要是问题是一个缓冲区中最少的底限是保留一行内容在内存中, 开始另一行时, 前一行所占的空间可能已被释放以腾出供其它行使用. 这限制了内存的使用. 但调用模式匹配的代码总是假设它的指针都是可用的. 但现在它要取出缓冲中的其它行以检查一个模式匹配是否符合, 所以它的假设并非总是成立的, 解决的办法是使用行号和列的位置来代替使用指针.

我把跨行模式匹配的补丁加入代码中只花了一天, 但由此引发的改动却花了足足一星期. 很容易低估它的影响. 真正的结果只有在实现它时和对新功能进行测试时才会明朗起来. 如果这种事发生在商业软件的开发中你就会错过最后交货期, 预算也变得紧张, 总之麻烦大了, 所幸, 对于自由软件的开发这并不是顶顶重要的, 你不过要多花一些时间, 软件发布推迟一些, 所以通常不值得因此引起计划延迟. 这是自由软件的巨大优势.

UTF-8
另一个加到6.0版中的是国际化支持. Vim已经支持双字节的文件. 这叫做多字节支持, 不过实际上对很多双字节它并不能正常工作, 因为大家都在转向Unicode, 那才是我加入的东西. 历史上Vim在内部使用字节, 所以自然而然地使用UTF-8编码(Unicode字符集编码作为一个8位字节的序列). 另一个办法是使用宽字符, 但这需要改变所有的文本处理代码, 因为大多数字符都还是7位的ASCII 码, 所以这样可能会带来效率问题.

增加对UTF -8的支持时需要确定几件事:是Vim处理的文本都以UTF-8编码, 还是说使用一个选项在ASCII和UTF-8甚至双字节之间切换? 把所有东西都按UTF-8处理有一个好处, 就是代码很简单, 因为它只需要处理一种类型的编码. 但是键入字符, 显示文本和与其它应用程序之间的复制粘贴却要使用另一种编码, 这需要在UTF-8和其它规范之间互相转换, 这可能会引起overhead并且使文本发生改变. 后者是绝对不可接受并要竭力避免的. 如果你用Vim读入一个文件并且在没有作任何修改的情况下写回文件, 这应该让你得到完全一样的一个文件才对.

所以主要问题在于文件读入Vim时是要把它转换为UTF-8编码还是保持其原来的编码方式:

在内部以UTF-8保存所有文本.
读入文件时, 把它转为UTF-8, 写回文件时转换为文件原来的模式, 键入的文字也需要转换, 如果输入法不支持UTF-8格式, 或是屏幕显示使用另一种格式, 就也需要转换.

保持文件原来的编码方式.
键入的文字和送到屏幕的字符只在他们使用另一种格式时才需要转换. 所以处理文本的函数都必需知道所有可能的格式. 在一个窗口中复制文本到另一个使用不同编码格式的窗口中时也需要转换.

两种方法都各有利弊. 一番思索之后, 我决定使用两者的混合. 内部格式可以选择, 或是ASCII, UTF-8, 或者是双字节编码甚至是其它的什么编码方式. 一般来说这应该与环境相符合(当前的locale设置). 可能的话, 文件格式和内部表现格式之间要发生转换. 当转换不可能时(不可用或有非法字符)就跳过去. 这可能导致错误地显示文本, 但至少文本写回去时不致于发生错误

结论
Vim已经成长为一个大的开源项目. 它不光是一个有用的流行软件, 同时也是一个开源软件开发的成功典范. 我希望Vim能启发和帮助其它的开源软件作者.

如果你想使用Vim, 可以在很多地方找到它: 如果你装有Linux, FreeBSD 或Solaris 8的发行版, 找一找一个你可以安装的Vim软件包, 安装很容易, 对 Linux来说Vi命令会启动一个Vim的简装版, 如果你想使用更多的特性就要自己去安装Vim软件包.

关于如何下载Vim, 参看下面的URL: http://vim.sf.net/download.php.

关于Vim的大量信息可以从http://www.vim.org获得. 很幸运 Sven Guckes在维护这个站点, 使我可以把精力放在Vim的开发上. Vim的技巧提示和插件可以在http://vim.sf.net上找到. 这些内容都由Vim的用户们创造, 上载.

如果你有什么问题, BUG报告, 或是想帮助Vim的开发, 可以加入Vim的邮件列表. 可参考http;//www.vim.org/mail.html

Bram Moolenaar

——————————————————————————–

Bram Moolenaar在Delft大学研究电子工程, 在1985年毕业时却在搞Unix双处理器结构, 现在他主要作软件方面的工作, 不过他还记得怎么用电烙铁, 他生于荷兰. 一个盛开郁金香的地方, 现在他生活在荷兰东部的Venlo, 他为Oce工作多年, 设计公司的第一个数字图象复印机. 他目前从事自由职业, 花了很多时间在免费的开源软件项目Vim(http://www.vim.org/)上, 他是Vim的主要作者. 做这项工作要处理大量的邮件, 与用户和其它合作开发者联系, 修改BUG和增加新的功能. Bram建立了荷兰的ICCF基金会. 以支持乌干达的儿童救治中心(Kibaale Children’s Centre), 这个项目通过教育上, 医疗上帮助爱滋病受害者, 他的主页在http://www.moolenaar.net/, email地址是: %[email protected]

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。