Table of Contents
Vim進階索引[2]::摺疊
摺疊對我來說原本不是什麼必不可少的功能。但現在卻越來越感覺到這實在是個體貼/方便的功能,我經常要儲存一些資料但我不喜歡零散的一堆文字檔案所以同樣的某些特定型別的資料我將它們放在同一個檔案中,透過摺疊用我可以在同一個檔案中管理管理多個文件而不至於混亂。與Vim的其他功能相比摺疊是很容易掌握的(至少manual、indent和marker規則是這樣的),如果你還沒用上這項功能的話趕快往下看吧……
這一篇我們將要講的是摺疊。什麼是摺疊呢?複製一段文字到一新建檔案中,在Vim中輸入`:set foldmethod=manual‘。輸入`:3,8fo‘,看第三行到第八行是不是摺疊起來了呢?這就是Vim的摺疊功能,很容易理解吧。先記住相關的幫助命令:
:help folding :help foldmethod :help fold-methods
在Vim中輸入`:help folding‘,可以看到Vim中摺疊的相關文件。往下翻頁你可以看到這裡面的內容非常多,尤其是以z開頭的那一部分指令。好訊息是要用好摺疊並不要求全部掌握這些命令。當然至少要掌握這幾個命令:zM zR zo zc zf。摺疊的用法的精髓在該頁的第一部分`fold methods'。它決定Vim進行摺疊所依據的規則。本文的目的是使使用者對建立摺疊有一個瞭解。並學會按自己想法來制定摺疊規則。
1 摺疊有什麼用途?
如果你用過字處理軟體Word的話那你可能會知道Word有一個大綱檢視將“標題一”“標題二”作為大綱顯示出來而相應的正文則呈現摺疊狀態。雙擊一個標題會展開對應的正文。透過這個檢視使用者可以對文件的結構有一個全域性的瞭解,更可以有效地組織文字。摺疊作用與大綱檢視相近,同樣讓你可以有效的管理文件結構;方便地在文件的不同部分移動;讓文件更清爽。此外摺疊可以隱藏資訊將不需要修改或不想看內容摺疊起來。另外結合modeline、filetype或autocmd我們可以在更多的場合使用摺疊。
Note:請注意我們可以摺疊任意行,所以摺疊不等同於顯示大綱。另外與Word大綱依靠手工套用樣式生成不同,摺疊可以用手工生成也可以是使用者指定的規則生成。透過指定合適的規則我們使摺疊成了一種腦力勞動。
摺疊讓自己的文件看上去更整潔。同時可以方便地選取感興趣的內容或遮蔽(如果不能刪除的話)不想要的內容。如果你的文件有10行的話你可以不想這做,但如果是成百上千行呢?
使用摺疊通常要求使用者對文件進行某種方式的格式化,這使使用者的文件既使在其他文字編輯器上也有較好的可讀性。
當然,沒有人規定摺疊該怎麼用,所以發揮你的創意吧。
2 摺疊的生成
首先要了解foldmethod設定項,它指定了摺疊產生的方式。詳細的文件請參考這兩個命令:
:help 'foldmethod' :help fold-methods
其中的manual、indent、expr、syntax、diff、marker規則分別表示根據手工設定、縮排、表示式、語法、diff、標記的方式生成摺疊。在瞭解Vim如何生成摺疊之前我們要先了解一個概念:摺疊層級
2.1 摺疊層級
摺疊層級是用數字表示的摺疊標誌,Vim根據摺疊層級來決定是否摺疊及怎樣摺疊某一行或某幾行。當我們下摺疊命令時(zM)Vim對每一行計算摺疊層級,然後將摺疊層級大於或等於`1‘(預設情況下)的行摺疊起來。如果低摺疊層級的幾行中有高摺疊層級的行時就形成摺疊的巢狀。
那這個摺疊層級是如何計算出來的呢?摺疊層級的計算方式取決於我們設定的`foldmethod‘。下面是不同摺疊規則對應的計算方式:
- `manual‘
- 手工規則下,摺疊層級由摺疊區域的巢狀關係計算。當我們手工指定一個摺疊的區域後,Vim對這個區域的開始行和結束行做記號,多個區域的開始行和結束行形成了巢狀關係。如果一個摺疊區域不包含在其他區域之中,則其摺疊層級為1;當這個區域直接包含於另一個區域時則其為摺疊層級為另一個區域的層級加1;依些類推。
- `indent‘
- 行的縮排寬度除以`shiftwidth‘,並向下取整得到每一行的摺疊層級。同一摺疊層級及更高摺疊層級的連續行形成摺疊。而其中的更高摺疊層級的行——如果有的話,形成巢狀的摺疊。
- `marker‘
- 當使用標記規則摺疊時,層級的計算跟手工規則相似。除了它是根據檔案中的標記來劃分一個摺疊區域而不是手工指定。然後根據這些區域間的巢狀關係計算摺疊層級。具體使用的標記透過`foldmarker‘設定。預設是使用'{{{,}}}'。
- `syntax‘
- 跟`marker‘差不多,只是所用的標記是在語法檔案中定義的,而不是透過`foldmarker‘設定。
- `diff‘
- 除了差異行及其前後三行1外,其餘行摺疊(層級為1)。
- `expr‘
- 由使用者指定摺疊層級的計算方式。方法是對`foldexpr‘進行設定。具體用法稍後說明。
2.2 手工規則(manual)
首先設定設定foldmethod為manual或者marker(沒錯marker也行)。然後高亮選擇可摺疊的行,輸入指令`zf‘。就這麼簡單你已經摺疊了文字。還可以在命令列用:fold用法跟其他Vim指令一樣,如`:1,.fo‘表示將第一行到當前行都摺疊起來。需要注意的是如果使用marker摺疊規則的話新建摺疊時Vim會為指定範圍的開始和結束行新增標記(marker)。
注意:如果只摺疊一行的話,你可能不會立即看到效果。
2.3 縮排規則(indent)
首先,設定規則`:set foldmethod=indent‘。然後在編輯過程中依需要進行縮排2。
縮排挺容易理解的,將下面的例子複製到檔案中儲存關閉再用Vim開啟看看:
開始 第一行 摺疊層級為1,無巢狀 第二行 第三行 摺疊層級為2,第1層巢狀 第四行 第五行 摺疊層級為3,第2層巢狀 第六行 第七行 摺疊層級為2,第1層巢狀 第八行 摺疊層級為1,無巢狀 第九行 摺疊層級為3,無巢狀 第五行 結束 vim: shiftwidth=4:foldmethod=indent
注意:正如前面提到的摺疊的層級並不僅僅取決於你按了幾個空格或製表符還與`shiftwidth‘有關。改變上面例子中的`shiftwidth‘看有什麼不一樣。
2.4 標記規則(marker)
見手工規則。
3 expr規則
expr要比前面的幾種方式複雜點所以我們在這裡單獨討論。使用expr時,我們需要對一設定項進行設定——`foldexpr‘。它是用來儲存我們設定的表示式,也就是我們指定的計算摺疊層級的公式。我們將設定項設為特定的表示式 -> 然後Vim透過`foldexpr‘這個項得到我們所指定的表示式 -> Vim逐行處理,並對每一行使用這個表示式計算出一個數值。這個數值就是該行的摺疊層級 -> 根據層級進行摺疊。舉個例子:假設我們在Vim中使用瞭如下命令:
:set foldmethod=expr :set foldexpr=1
所有的行都會被摺疊(如果沒有的話再輸入指令`zM‘)。Vim在每一行用foldexpr指定的表示式計算結果。這個例子中我們的表示式是1,所以每行得到的摺疊層級都是1,於是所有行摺疊成一行。同理如果你將它賦於2的話則所有行的摺疊層級為2,所有行被摺疊成一行。當我們的表示式最後返回一個數值時這個數值就是摺疊層級。為了讓這表示式更靈活,更能滿足我們的需要我們需要補足一些Vim指令碼知識3,這裡是三個在摺疊的表示式比較常用幾點:
- `v:lnum‘
- 內建變數,表示是“當前行的行號”。:help v:var檢視更多內建變數。
- `getline()‘
- 函式用以返回指定行的內容。
- `?:‘
- 三元條件語句。見:help expr1
另外如果需要構造複雜的表示式,我們可以在自定義的函式中定義。但這又是另一篇教程了。
看一下現在我們能構造出怎麼樣的表示式:
:set foldexpr=v:lnum
指定每行的行號為摺疊層級,這樣所有行會摺疊成一行。試試看用指令`zo‘逐層開啟摺疊,開啟20層之後就沒有摺疊了——而不是我們想的“巢狀數=行數”。這是因為Vim對摺疊的巢狀數是有限制的,預設最深可以20層。但我們可以用foldnestmax這個項來進行自定義設定,如`set foldnestmax=10‘。
注意:Vim最多支援20層的巢狀,所以設定超過20的值會被當成20。
將第8至第20行摺疊。也就是讓expr在第8至20行時返回數值1(也可以是2、3、4……),其他時候為0。我們知道Vim是逐行處理的,所以如果我們知道Vim正在處理的行的行號我們就可以進行比較了。當然你已經知道了,我們需要的就是`v:lnum‘:
v:lnum>=8&&v:lnum<=20?1:0 v:lnum>=8 && v:lnum<=20 ? 1:0
下面是用expr規則模擬indent規則的例子。基本上沒有任何實用價值,但至少能讓我們又expr的強大有個認識:
" source this.vim set foldmethod=expr set foldexpr=Myindent(v:lnum) " 用expr模擬indent規則 func! Myindent(lnum) " 用indent()函式得到當前縮排 let s:idt=indent(a:lnum) " 用&運算子得到一個設定項shiftwidth的值 let s:sw=&shiftwidth " 如果有縮排寬度超過閾值(shiftwidth) if s:idt>=s:sw " 則計算摺疊層級 return (s:idt-s:idt%s:sw)/s:sw else return 0 endf
除了可以向foldexpr返回數值外還可以返回一些特殊的值,分別是=, a, s, (詳細說明見`:help fold-expr‘,這裡不現重複)。Vim的文件不推薦使用=, a, s。
這裡再給兩個簡單的例子:
" 如果一行以@samp{#}開始,摺疊。 :set foldexpr=getline(v:lnum)=~/^#/?1:0
" 以每5行為一組摺疊 set v:lnum%5-1?1:'>1'
4 例項演示
摺疊在寫程式時是比較常用的比如python、C語言之類可以將規則設定為`indent‘進行摺疊。但我說過了摺疊可以有很多不同的用法。下面簡單的舉幾個例子演示一下摺疊還能怎麼用。由於這裡面有些內容需要用到Vim指令碼的知識,我不會在這裡詳細解說,有不明白的地方可以發郵件給我。
4.1 唐詩
這裡我借唐詩的例子演示一下expr規則和`foldtext‘設定項的用法。下面是一個檔案的內容,這是一個有80首詩的檔案每首詩之間有一空行。我的閱讀的習慣是看一下目錄然後挑自己感興趣的部分。所以我決定用摺疊來模擬目錄的效果。
《感遇其一》 作者:張九齡 蘭葉春葳蕤,桂華秋皎潔。 欣欣此生意,自爾為佳節。 誰知林棲者,聞風坐相悅。 草木有本心,何求美人折? 《感遇其二》 作者:張九齡 江南有丹桔,經冬猶綠林。 豈伊地氣暖,自有歲寒心。 可以薦佳客,奈何阻重深。 運命唯所遇,迴圈不可尋。 徒言樹桃李,此木豈無陰。 《下終南山過斛斯山人宿置酒》 作者:李白 暮從碧山下,山月隨人歸。 卻顧所來徑,蒼蒼橫翠微。 相攜及田家,童稚開荊扉。 綠竹入幽徑,青蘿拂行衣。 歡言得所憩,美酒聊共揮。 長歌吟松風,曲盡河星稀。 我醉君復樂,陶然共忘機。
首先,確定使用的摺疊規則。大概的看一下前面的幾種規則,看來我們只能用`expr‘規則了。因為我們要將空行以外的所有部分摺疊起來。所以我們可以構造這樣的表示式:
" 用getline(v:lnum)得到當前行 " 用正規表示式@samp{.}判斷當前行是否含有文字。 set foldexpr=getline(v:lnum)=~'.'?1:0
將上面的唐詩複製到一檔案中,用Vim開啟,設定摺疊。是不是看到所有的詩摺疊了。不過還有一個問題,在摺疊文字(`foldtext‘)中沒有註明詩的作者這樣的話這個目錄似乎有點美中不足。不過我們可以定義摺疊文字(:help 'foldtext')的:
" 想顯示詩人? set foldtext=foldtext().v:folddashes.getline(v:foldstart+1)
透過設定`foldcolumn‘,我們還可以使用滑鼠來開啟或摺疊一首詩。最後我們可以在該檔案的最後加入一模式行這樣我們每次開啟都有摺疊的效果了:
vim: ro: fdm=expr: fde=getline(v:lnum)=~'.'?1:0: foldtext=foldtext().v:folddashes.getline(v:foldstart+1): foldcolumn=2
上面的摺疊表示式中不同的摺疊(詩)之間有一空行,如果希望顯示效果更緊湊一點的話試一下這個表示式:
set foldexpr=getline(v:lnum)=~'S'&&getline(v:lnum-1)!~'S'?'>1':'='
4.2 筆記
在閱讀文字檔案或者多人共同創作說明文件時,經常需要作一些筆記。這些筆記可以是讀書心得、體會,也可以是創作說明或者作為以後擴充內容的佔位符。如果有很多的筆記的話會影響原文件的可讀性,透過摺疊筆記我們可以在保留這些筆記的前提下,保證文件的可讀性。
- 使用模式行vim: se fdm=marker
- 選擇合適的地方寫下自已的想法或註釋。
- 使用`zf‘或`:fo‘進行摺疊。
這樣自己做筆記的地方總是高亮顯示。
同時可以使用
:folddoclosed .w! >>筆記.txt
來匯出筆記。
注意::folddoclosed4只對當前關閉的摺疊有效,所以如果要匯出所有摺疊可以先使用指令`zR‘。如果要刪除所有的marker,`:g/{{{/norm zD‘
4.3 郵件
我習慣在本地儲存一份郵件的副本但有時候在翻以前的郵件時很不方便。因為郵件頭通常很長尤其是郵件列表(maillist)中的郵件,有時要看正文得先按好幾次CTRL-F。當然在學了摺疊後我們當然有更好的辦法。
我們以下面這封精簡過郵件頭並作了適當的修改(防垃圾郵件:()的郵件為例:
Received: from sino.cmm (unknown [202.108.xx.230]) Received: (qmail 25748 invoked by uid 99); 30 Dec 2004 04:42:47 -0000 Message-ID: <200412300xxx47.25xxx.qmail@sino.cmm> From: chxxxxTo: hqxxe@123.cmm Subject: RE: XX報告 MIME-Version: 1.0 Date: Thu, 30 Dec 2004 12:42:47 +0800 X-Priority: 3 hqxxe: 你的報告已經收到,謝謝! 陳 ----- Original Message ----- > ……………… > blah blah >> blah blah >> blah blah
在'.vimrc'(windows中是'_vimrc')中加入,下面的內容:
" 根據郵件的字尾名進行相關的設定。如果開啟的檔案字尾名是'.eml',則當成郵件處理。 autocmd! BufReadPre *.eml se fdm=expr fde=v:lnum==1?1:getline(v:lnum)=~'^$'?0:'=' fdt=Mailfdt(v:foldstart,v:foldend) ft=mail | syn on " 定義函式,用來返回摺疊的標題。 " 以摺疊的第一和最後一行的行號為引數 func! Mailfdt(fst,fen) let fst=a:fst " 儲存郵件的標題和發信人 let hfrom='' let hsub='' let tline='' while a:fen!=fst let tline=getline(fst) " 判斷當前行是否是我們感興趣的行 " 如果是則儲存 if tline=~'^From: ' let hfrom=tline elseif tline=~'^Subject: ' let hsub=tline endif let fst=fst+1 endwhile " 返回相關資訊 if strlen(hfrom) || strlen(hsub) return hsub . "ttt" . hfrom else return getline(a:fst) endif endfunc
在加入上面的內容後,我們現在用Vim開啟郵件(實際是以.eml作字尾名的檔案)看看,是不是清爽多了!
5 使用提示
使用manual一般是臨時性的摺疊。如果每次編輯特定檔案都需要做同樣摺疊時時建議結合modeline使用其他摺疊規則。如果不得不使用manual方式時,你可以用:mksession儲存包括摺疊在內的一切當前編輯設定或者用:mkview儲存當前視窗。具體說明見文件。
indent和marker的摺疊規則可以用於程式檔案或格式文字。一般可以配合modeline使用。
syntax通常用於程式或特定格式的文字。並且由於是在語法檔案中定義的所以一般與autocommand一起使用。:help filetype :help command
diff用於比較檔案內容後對照修改時使用。可用:mkview或:mksession儲存設定。
expr在上面幾種方式無法滿足需要時我們要使用expr方式。expr可以在模式行中儲存設定,也可以儲存在Vim的指令碼中。相關命令:mkvimrc :mksession
6 小結
這一篇中我們簡單的介紹了Vim中進行摺疊的原理,及幾種相關的摺疊規則及其使用方法。expr規則實際是一種自定義規則,在學習了Vim指令碼後我們還能構造出更復雜的規則。但就現在而言使用者掌握了manual、indent和marker就行了(雖然在前面我們舉了許多expr的例子,但在實際運用中expr用得比較少,因為通常使用者不會絞盡腦汁只為了將文字摺疊起來——包括我自己。當然你想要成為進階使用者這是一定要掌握的!)syntax和diff方式會另外講解,敬請期待。
下一篇我們將開始討論與編輯相關的一些內容。下次見。
Appendix A
這是比較不常用但又可能比較有用的內容。使用:help檢視相關資訊。
v:foldstart | 內建變數 | 只讀變數記錄只前所在摺疊的起始行號 |
v:foldend | 內建變數 | 結束行號,其餘同上 |
foldlevel() | 函式 | 返回指定行的摺疊層級 |
'foldlevel' | 設定項 | 只有高於這個值的摺疊層級才會進行摺疊 |
'foldnestmax' | 設定項 | 指定最深的巢狀數 |
'foldignore' | 設定項 | 在indent規則中以這個值開始的行的將根據前後行的值來設定摺疊層級 |
:folddoclose | 命令 | 對當前閉合的行執行命令 |
:folddoopen | 命令 | 對未摺疊的行或定義了摺疊但未閉合的行執行命令 |