開放源碼的自由軟件-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]