3.1. Vim 指令碼基礎
在 .vimrc 檔案中,和在第二章提到的外掛和語法檔案中,使用的語言就是 Vim 指令碼語言。這種指令碼語言語法有點像 BASIC,表示式有點像 C,還是比較容易理解的。本章中並不打算對其作很系統的介紹(要完整了解的話,請參見“:help usr_41.txt”),而只是介紹一些基本知識,特別是,瞭解定製 .vimrc 所需要的基本知識。
Vim 指令碼相當於可直接在命令模式下執行的命令,只是不需要輸入前面的冒號(如果用了冒號也不會出錯)。因此,像設定選項、建立鍵盤對映這樣的命令是直接可用 的。當然,作為一種指令碼語言,除了普通鍵盤上會輸入的命令外,我們還需要一些更復雜的功能,特別是:變數,表示式,條件和迴圈語句,函式。
3.1.1. 變數
Vim 中使用如下的語法對變數進行賦值(建立變數):
1 |
let 變數名 = 數值 |
變數型別有兩種,整數和字串,在第一次賦值之前都不能使用。變數名除了可使用常規的字母、下劃線和數字外,還可以使用幾種特殊的字首:
●“b:”——只對當前緩衝區(buffer)有效的變數;
●“w:”——只對當前編輯視窗(window)有效的變數。
●“g:”——全域性變數(在函式中訪問全域性變數必須使用該字首,不加字首的話則認為是函式內的區域性變數);
●“s:”——變數名只在當前指令碼中有效;
●“a:”——函式的引數;
●“v:”——Vim 內部預定義的特殊變數(參見“:help vim-variable”)。
下面三個字首用來訪問特殊的數值,由於行為和變數較為相似(可以讀取和修改),也放在這兒一起講:
●“$”——訪問環境變數;
●“&”——訪問 Vim 選項;
●“@”——訪問暫存器。
當變數不再使用時,可以使用“unlet 變數名”刪除變數。
3.1.2. 表示式
和 C 非常類似,可以使用變數和常量,可以使用括號,可以呼叫函式(“函式名(…)”),支援加法(“+”)、減法(“-”)、乘法(“*”)、除法 (“/”)和取模(“%”),支援邏輯操作(“&&”、“||”和“!”),支援三元條件表示式(“a ? b : c”)。字串操作方面當然比 C 要強,可以使用“.”進行字串拼接;可使用“==”、“<=”等進行字串大小比較,可使用“=~”和“!~”進行正規表示式匹配,而且可以在比 較操作符後面新增“#”或“?”來強制進行大小寫敏感或不敏感的比較(預設受 Vim 選項 ignorecase 影響)。顯示一個表示式的結果,可以使用“:echo 表示式”顯示到狀態列上,或者在插入模式下使用“Ctrl-R=表示式”插入到緩衝區的文字中。
和其它很多在 Unix 下成長起來的語言一樣,Vim 的字串常量有雙引號和單引號兩種方式。使用單引號的話,單引號間的任何字元都是字串的一部分,其中不能再包含單引號。使用雙引號的話,則可以使用 “\”產生換碼序列(具體可參考“:help expr-quote”),如“\n”代表換行符,“\””代表雙引號,“\\”代表反斜槓本身,等等。
需要注意的話,雙引號除了可以表示字串常量外,還可以表示註釋。行首的“””,以及表示式中出現的成單的“””,都表示“””後面的部分全部是註釋。
3.1.3. 條件和迴圈語句
條件語句形式如下:
1 2 3 |
if 表示式 語句 endif |
或
1 2 3 4 5 |
if 表示式 語句 else 語句 endif |
或
1 2 3 4 5 |
if 表示式 語句 elseif 表示式 語句 endif |
迴圈語句形式如下:
1 2 3 |
while 表示式 語句 endwhile |
條件和迴圈語句都可以巢狀。這些比較簡單,就不多加說明了。
3.1.4. 函式
在表示式中使用函式時,就跟 C 裡面的方式類似,直接使用函式名加括號,括號裡寫上引數(可選)。在不需要返回值的情況下呼叫函式時,稍稍有些不同,要使用“call”命令,後面跟函式名和括號(括號裡面寫上可能有的引數)。
定義函式使用下面的語法:
1 2 3 |
function 函式名稱(引數列表) 語句 endfunction |
如果已有同名函式存在,Vim 會報錯,除非在“function”後面加上一個“!”。
如果引數中不包含“…”,那麼引數的數量是固定的,函式的呼叫者必須提供跟定義同樣多的引數(在函式定義中使用引數名之前加上“a:”進行訪 問)。如果引數中包含“…”,那麼引數的數量不固定,除了可以使用引數名稱訪問傳遞過來的引數外,還可以使用“a:0”知道額外傳遞的引數數量,使用 “a:1”、“a:2”等訪問這些額外傳遞的引數。
要在函式的中間返回,或者要返回數值的話,可以使用“return”語句。
Vim 內部定義了一百多個函式,詳細列表請參見“:help function-list”。
3.2. 我的 .vimrc
作為一個 Vim 指令碼的一個具體示例,我將講解一下最實用的情況,我的 .vimrc 檔案。檔案 .vimrc.html (請下載到本地開啟) 是我的 .vimrc 檔案通過以下步驟生成的 HTML 檔案:
1. 在 Vim 中開啟 .vimrc 檔案;
2. 執行命令“:colorscheme koehler”(預設配色可能在瀏覽器中效果不佳)
3. 執行命令“:%!nl -w4 -s’ ‘”(1.11 節)
4. 執行命令“:TOhtml”(1.13 節)
5. 執行命令“:w”
可以把瀏覽器中的文字內容貼上到 Vim 中,然後使用下面這個替換命令“:%s/^ \+[0-9]\+ //”刪除前面的行號,來恢復出最初的 .vimrc 檔案。
下面逐行進行講解,幷包含理解其內容所需的資料的連結。建議大家直接閱讀 .vimrc 檔案的內容,並在有疑問時查閱下面的解釋。
第 1 行:註釋(3.1.2 節末段),其中包含一個模式行(1.4 節和 1.5 節)。
第 2 行:首先判斷系統是否具有“自動命令”(autocmd)的支援,有的話才執行第3到第六行的內容(1.1 節、“:help has”和“:help feature-list”)。
第 3 行:純註釋(後面我將跳過註釋行不再說明)。
第 4 行:清除所有的自動命令(“:help autocmd-remove”),以方便除錯,可以使用“source ~/.vimrc”檢視一些修改後的效果(“:help source”)。
第 6 行:對於字尾為“.asm”的檔案,認為其是微軟的 Macro Assembler 格式(“:help masm-syntax”)。
第 7 行:與第 2 行的 if 語句配對。
第 8-10 行:當使用了圖形介面時(“:help feature-list”),確保所有的檔案型別會在選單“語法”(“Syntax”)下出現,而不是出現一個選單項“Show filetypes in menu”。預設行為可以讓 Vim 啟動得更快一點點。
第 11-13 行:當使用了圖形介面,並且環境變數 LANG 中不含“.”(即沒有規定編碼)時,把 Vim 的內部編碼設為 UTF-8。
第 14 行:不需要保持和 vi 非常相容(“:help ‘compatible’”)。
第 15 行:執行 Vim 預設提供的 .vimrc 檔案的示例,包含了開啟語法加亮顯示等最常用的功能。
第 16 行:開啟自動縮排(1.4 節)。
第 17 行:預設不產生備份檔案(“:help ‘backup’”)。
第 18 行:在輸入括號時游標會短暫地跳到與之相匹配的括號處,不影響輸入(“:help ‘showmatch’”)。
第 19 行:正確地處理中文字元的折行和拼接(1.12 節)。
第 20 行:可自動識別的檔案型別為帶 BOM 字元的 Unicode 檔案、UTF-8 編碼的檔案和 GBK 編碼的檔案。
第 21 行:設定狀態行,使其能額外顯示檔案的編碼資訊,如圖 2 中的“gbk”和“big5”(“:help ‘statusline’”)。
圖 2
第 22-24 行:如果該 Vim 支援滑鼠,則啟用滑鼠支援(1.3 節)。
第 25-29 行:判斷 Vim 是否包含多位元組語言支援(multi_byte 特性),並且版本號(“:help v:version”)大於 6.1(包含 ambiwidth 選項)。
第 26-28 行:如果 Vim 的語言(“:help v:lang”;受環境變數 LANG 影響)是中文(zh)、日文(ja)或韓文(ko)的話,將模糊寬度的 Unicode 字元的寬度(ambiwidth選項,1.2 節)設為雙寬度(double)。
第 31-36 行:改變上、下方向鍵行為方式:通常情況下這些鍵的作用範圍是邏輯行,所以如果行很長的話游標的移動可能會不太方便;這些鍵盤對映把這些鍵的作用範圍改成 螢幕行(“help gk”),還為習慣使用“j”、“k”的人增加了對映“Ctrl-j”和“Ctrl-k”作用於螢幕行。前面四個對映使用的命令是“noremap”,作 用於正常模式、可視模式和命令執行時;後面兩個對映使用的命令是“inoremap”,僅作用於插入模式,其中使用“Ctrl-O”臨時執行一個普通模式 的命令(“:help i_CTRL-O”)。
第 38-41 行:在 Vim 中的插入模式中可以使用“Ctrl-R =”計算整數表示式的數值,但 Vim 本身沒有計算浮點表示式的能力。這四個對映提供了浮點表示式的計算能力:使用“\ma”(假設 Leader 字元為預設的“\”,參見“:help <Leader>”)可將計算的結果放到下一行上(待計算的表示式為當前行或在可視模式選中的內容),使用“\mr”則用計算的結果替換待計 算的表示式(同樣為當前行或在可視模式選中的內容)。這些對映假設有一個命令“calcu”可用來計算一個表示式的內容。該命令可用下面的 shell 指令碼簡單實現:
1 2 3 4 |
#! /bin/sh echo "$*" | sed -e $'s/\r$//' -e 's/sin *(/s(/g' -e 's/cos *(/c(/g' -e 's/atan * (/a(/g' -e 's/log *(/l(/g' -e 's/exp *(/e(/g' | bc -l |
該指令碼把表示式轉換成 bc [1] 能接受的形式(把“sin(x)”轉換成“s(x)”,等等),並通過標準輸出送到 bc 的標準輸入。
該對映較為複雜,此處不詳加解釋了——其中心思想都是選取待計算的表示式,放到無名暫存器中,然後使用“Ctrl-R””貼上到命令列上,使用 calcu 進行計算,再把結果貼上回正在編輯的緩衝區中;最後一個最複雜,因為為了替換原先的表示式,還需要記住原先被選中的內容的起始和結束位置,你可能希望看一 下“:help gv”、“:help v_o”、“:help m”、“:help ”,並複習節 1.11。可以注意一下,在對映中使用了“<silent>”(“:help map-<silent>”),這會防止命令列上回顯執行的內容。
第 43-44 行:允許使用者使用 F2 來取消搜尋/替換的加亮顯示。此處一個對映用於正常模式(nmap),一個用於插入模式(imap)。上面已經提過一次,“Ctrl-O”可以在插入模式中執行一個正常模式的命令。
第 46-47 行:這兩個對映用於 taglist 外掛,使用 F9 直接開啟(或關閉)taglist 的視窗。
第 49-50 行:方便快速修訂視窗(1.10 節)的使用,可使用 F11(和 F12)檢視下一個(上一個)錯誤(或 grep 項等)。
第 52-65 行:一些適用於文字模式執行的 Vim 的設定;詳見下面的具體說明。
第 54-56 行:將變數 Tlist_Inc_Winwidth 的值設為 0,防止 taglist 外掛改變終端視窗的大小(有些情況下會引起系統不穩定)。使用“has('eval')”是讓該語句僅在功能較為完整、至少支援表示式的 Vim 版本中執行。
第 58-64 行:在系統支援 wildmenu 特性(“:help 'wildmenu'”)啟用文字模式的選單。
第 59 行:開啟 wildmenu 選項,啟動具有選單項提示的命令列自動完成。
第 60 行:確保字元序列“<C-Z>”被理解為 Ctrl-Z 而不是分開的五個字元(“:help 'cpoptions'”)。
第 61 行:設定使用 Ctrl-Z 啟用自動完成提示。
第 62-63 行:把正常模式和插入模式下的 F10 對映成執行選單項,並自動提示選單內容。注意預設選單仍不會自動載入,我使用該特性的主要目的是在文字模式的 Vim 中使用 CVS 選單。圖 16 是按 F10 鍵後再按 Tab 鍵的結果。
圖 16
第 66-161 行:使用自動命令(autocmd)特性的設定。使用“has”來防止該部分內容在不支援自動命令的 Vim 版本中執行。
第 67-129 行:定義了若干個下面的自動命令會用到的函式,具體在下面的自動命令中講。請注意在每個“function”之後都用了一個“!”(“:help E122”):這也是為了方便除錯,讓“source ~/.vimrc”能正確執行而不會報告函式已定義的錯誤。
第 131-133 行:只要沒有將環境變數 VIM_HATE_SPACE_ERRORS 的值設為零,則把變數 c_space_errors 的值設為 1——效果是在 C/C++ 程式碼中“不正確”的空白字元(行尾的空白字元和緊接在製表符之前的空格字元)將會被高亮顯示。圖 17所示的程式碼中,第 3 行的行尾多了兩個空格,第 5 行的第一個製表符之前多了個空格。Vim 提示#935 裡有一些額外的說明。同時請參看對第 160 行的說明。
圖 17
第 135 行:使用的英文拼寫變體為加拿大風格,即:使用拼寫“abridgement”(而不是“abridgment”)、“colour”(而不是 “color”)、“realize”(而不是“realise”)、“theatre”(而不是“theater”)等,比較符合中國人一般的英語教科 書中的拼寫方式,也比較適合於寫“國際”英語。
第 138 行:使用鍵盤對映“\a”來檢視游標下字元的屬性,主要用於除錯 Vim 的語法檔案。圖 18顯示了游標下的字元所屬的語法“組”為 vimOption,使用配色方案中的 PreProc(預處理符號)項,前景色為紫色(RGB:#a020f0)。有興趣可檢視 Vim 指令碼#383 的具體內容。
圖 18
第 140 行:在函式找不到時(“:help FuncUndefined”),自動在執行環境(Linux 下一般為 ~/.vim)的 autoload 目錄下讀入與函式名同名的 .vim 檔案。這是指令碼#383 的建議安裝方式(SyntaxAttr.vim 檔案放在 autoload 目錄下,僅在執行時載入)。
第 142 行:設定適用於 C/C++ 檔案的選項(1.4 節)。
第 143 行:把補丁檔案的縮排和製表符寬度設定設成和 C/C++ 檔案相同(1.4 節)。
第 144 行:取消 Vim 對 HTML 標記自動產生的縮排,但開啟自動縮排選項(1.4 節)。
第 145 行:對於變更日誌型別的檔案,設定行寬為 76 個字元(1.12 節)。
第 147 行:當檔案字尾為“.gb”時,認為這是一個 GBK 編碼的檔案,在讀入檔案之前(“:help BufReadPre”)呼叫函式 SetFileEncodings 把原先的 fileencodings 選項的內容儲存在本緩衝區的一個變數中(3.1.1 節),然後把 fileencodings 設成 gbk,即只嘗試對檔案內容作為 GBK 字元序列來解釋。
第 148 行:類似於上面把“.big5”字尾的檔案當作 Big5 編碼的檔案,在讀入檔案之前把 fileencodings 設成 big5,只嘗試對檔案內容作為 Big5 字元序列來解釋。
第 149 行:類似於上面把“.nfo”字尾的檔案當作 CP437 編碼(即英文 DOS 的 OEM 字符集編碼)的檔案。效果可參看圖 19。
圖 19
第 150 行:在讀入 .gb、.big5 或 .nfo 檔案之後(“:help BufReadPost”),呼叫函式 RestoreFileEncodings 恢復儲存起來的 fileencodings 原數值。
第 151 行:對於 .txt 字尾的檔案,在顯示檔案時(“:help BufWinEnter”,確保在模式行被執行之後)呼叫函式 CheckFileEncoding 檢查檔案是否已修改並且 fileencoding 設有數值。條件滿足的話說明該檔案在模式行中修改了 fileencoding,因而使用該編碼(“:help ++enc”)重新強制(“!”)讀入該檔案以保證檔案被正確解碼。Vim 提示#911 裡有一些額外的說明。
第 153 行:在遇到 HTML 檔案時,如果 Vim 判斷出的編碼型別和 HTML 程式碼中使用“<meta http-equiv="Content-Type" content="text/html; charset=編碼">”規定的編碼不一致,將使用網頁中規定的編碼重新讀入該檔案。函式 ConvertHtmlEncoding 會把一些網頁中使用的編碼名稱轉換成 Vim 能夠正確處理的編碼名稱;函式 DetectHtmlEncoding 在判斷檔案型別確實是 HTML 之後,會記下當前的游標位置,並搜尋上面這樣的 HTML 程式碼行,找出字符集編碼後,在編碼不等於當前檔案編碼(fileencoding)時且當前檔案編碼為空或等於系統判斷出的檔案編碼時,使用該編碼強制重 新讀入檔案,忽略任何錯誤(“silent!”)。該自動命令寫成是可巢狀執行的(“:help autocmd-nested”),目的是保證語法高亮顯示有效,且上次開啟檔案的游標位置能夠正確保持。Vim 提示#1074 裡有一些額外的說明。
第 155-156 行:確保把 /usr/include/c++ 和 /usr/include/g++-3 目錄下的所有檔案都當成 C++ 型別的檔案,不管 Vim 原先認定這些檔案型別是什麼(“:help BufEnter”)。C++ 的很多標準標頭檔案(如“algorithm”)沒有檔案字尾,預設情況下不會被 Vim 當作 C++ 檔案。
第 158 行:第 142 行把 C/C++ 檔案的製表符寬度設成了 4(個人設定),但系統的原始碼一般使用 GNU 編碼規範,製表符寬度為 8。該行設定所有 /usr 目錄下的檔案都使用 GNU 編碼規範(1.4 節)。
第 160 行:在寫檔案之前(“:help BufWritePre”),呼叫函式 RemoveTrailingSpace:只要沒有將環境變數 VIM_HATE_SPACE_ERRORS 的值設為零,則對於檔案型別為 C、C++、Vim 指令碼型別的檔案,自動悄悄清除所有的行尾空白字元;“normal m”記憶當前的游標位置,“normal ”恢復記憶下來的游標位置。
至此為此,我已經介紹了 Vim 的基本知識、很多實用技巧和一些最常用的 Vim 外掛,並通過定製 .vimrc 檔案介紹了指令碼的基本知識。如果有需要進一步深入學習 Vim 或是想提什麼關於 Vim 的特定問題的話,不妨參加從 Vim 的網站上參加 Vim 的郵件討論列表,應該會獲益良多。而作者也希望本文至此也已經完成了引導讀者學習、瞭解 Vim 的高階特性的任務。
參考資料