Usr_41
Nvim :help
頁面,由 產生,來源是 原始碼,使用 tree-sitter-vimdoc 解析器。
VIM 使用者手冊 - 作者:Bram Moolenaar
撰寫 Vim 腳本
Vim 腳本語言用於啟動 vimrc 檔案、語法檔案和許多其他用途。本章說明 Vim 腳本中可以使用的項目。項目很多,因此本章內容很長。
您第一次接觸 Vim 腳本是透過 vimrc 檔案。Vim 會在啟動時讀取並執行其中的指令。您可以將選項設定為您偏好的值。而且您可以在其中使用任何冒號指令 (以「:」開頭的指令;這些指令有時稱為 Ex 指令或命令列指令)。語法檔案也是 Vim 腳本。為特定檔案類型設定選項的檔案也是。複雜的巨集可以由單獨的 Vim 腳本檔案定義。您可以自行思考其他用途。
讓我們從一個簡單的範例開始
:let i = 1
:while i < 5
: echo "count is" i
: let i += 1
:endwhile
注意:這裡實際上不需要「:」字元。您只有在輸入指令時才需要使用它們。在 Vim 腳本檔案中,它們可以省略。無論如何,我們將在這裡使用它們,以清楚表明這些是冒號指令,並使它們與 Normal 模式指令區分開來。注意:您可以嘗試範例,方法是從此處的文字中複製程式碼行,然後使用 :@" 執行它們
範例程式碼的輸出是
count is 1
count is 2
count is 3
count is 4
在第一行,":let" 指令將值指定給變數。其通用形式為
:let {variable} = {expression}
在這個例子中,變數名稱是「i」,而運算式是一個簡單的值,數字 1。":while" 指令啟動迴圈。其通用形式為
:while {condition}
: {statements}
:endwhile
只要條件為 true,就會執行到相符的 ":endwhile" 為止的語句。這裡使用的條件是運算式「i < 5」。當變數 i 小於 5 時,此條件為 true。
注意:如果您碰巧編寫了一個持續執行的 while 迴圈,您可以按下
CTRL-C
(在 MS-Windows 上為
CTRL-Break
) 來中斷它。
":echo" 指令會印出其引數。在這個例子中,字串是「count is」和變數 i 的值。由於 i 是 1,這將會印出
然後是 ":let i += 1" 指令。這與 ":let i = i + 1" 的作用相同。它會將 1 加到變數 i,並將新值指定給同一個變數。
提供這個範例是為了說明這些指令,但是如果您真的想要建立這樣的迴圈,則可以使用更簡潔的方式編寫
:for i in range(1, 4)
: echo "count is" i
:endfor
我們稍後才會說明
:for 和
range() 的工作方式。如果您等不及,請按一下連結。
四種數字類型
數字可以是十進位、十六進位、八進位或二進位。
十六進位數字以「0x」或「0X」開頭。例如,「0x1f」是十進位 31。
31.
八進位數字以「0o」、「0O」或零和另一個數字開頭。「0o17」是十進位 15。
二進位數字以「0b」或「0B」開頭。例如,「0b101」是十進位 5。
十進位數字只是數字。注意:不要在十進位數字前加上零,它會被解釋為八進位數字!
":echo" 指令始終印出十進位數字。範例
:echo 0x7f 0o36
數字可以用負號來表示負數。這也適用於十六進位、八進位和二進位數字。負號也用於減法。將這與先前的範例進行比較
:echo 0x7f -0o36
運算式中的空格會被忽略。但是,建議使用空格來分隔項目,以使運算式更容易閱讀。例如,為了避免與上面的負數混淆,請在負號和後面的數字之間加上空格
:echo 0x7f - 0o36
變數名稱由 ASCII 字母、數字和底線組成。它不能以數字開頭。有效的變數名稱為
counter _aap3 very_long_variable_name_with_underscores FuncLength LENGTH
無效的名稱為「foo.bar」和「6var」。這些變數是全域的。若要查看目前已定義變數的列表,請使用此指令
:let
您可以在任何地方使用全域變數。這也表示,當變數「count」在一個腳本檔案中使用時,它也可能在另一個檔案中使用。這至少會導致混淆,最壞的情況會導致實際問題。為了避免這種情況,您可以使用以「s:」為開頭的腳本檔案的區域變數。例如,一個腳本包含以下程式碼
:let s:count = 1
:while s:count < 5
: source other.vim
: let s:count += 1
:endwhile
由於「s:count」是此腳本的區域變數,您可以確定執行「other.vim」腳本不會變更此變數。如果「other.vim」也使用「s:count」變數,則它會是不同的複本,是該腳本的區域變數。有關腳本區域變數的詳細資訊,請參閱此處:
script-variable。
b:name 緩衝區的區域變數 w:name 視窗的區域變數 g:name 全域變數 (也在函數中) v:name Vim 預先定義的變數
刪除變數
變數會佔用記憶體,並顯示在 ":let" 指令的輸出中。若要刪除變數,請使用 ":unlet" 指令。範例
:unlet s:count
這會刪除腳本區域變數「s:count」,以釋放其使用的記憶體。如果您不確定變數是否存在,而且不希望在變數不存在時出現錯誤訊息,請附加「!」
:unlet! s:count
當腳本處理到結尾時,其中宣告的區域變數將不會被刪除。在腳本中定義的函數可以使用它們。範例
:if !exists("s:call_count") : let s:call_count = 0 :endif :let s:call_count = s:call_count + 1 :echo "called" s:call_count "times"
"exists()" 函數會檢查是否已定義變數。其引數是您要檢查之變數的名稱。不是變數本身!如果您執行此動作
:if !exists(s:call_count)
則 s:call_count 的值將會用作 exists() 檢查的變數名稱。這不是您想要的。「!」驚嘆號會反轉值。當值為 true 時,它會變成 false。當值為 false 時,它會變成 true。您可以將其讀取為「not」。因此,「if !exists()」可以讀取為「if not exists()」。Vim 認為 true 的任何值都不是零。零為 false。
注意:Vim 在尋找數字時會自動將字串轉換為數字。當使用不以數字開頭的字串時,產生的數字為零。因此,請注意這一點
:if "true"
「true」將會被解釋為零,因此為 false!
字串變數和常數
到目前為止,只有數字被用作變數值。字串也可以使用。數字和字串是 Vim 支援的變數基本類型。類型是動態的,每次使用 ":let" 將值指定給變數時都會設定。有關類型的詳細資訊,請參閱
41.8。若要將字串值指定給變數,您需要使用字串常數。這些常數有兩種型別。第一種是雙引號中的字串
:let name = "peter"
:echo name
如果您想要在字串中包含雙引號,請在它前面加上反斜線
:let name = "\"peter\""
:echo name
為了避免需要反斜線,您可以使用單引號中的字串
:let name = '"peter"'
:echo name
在單引號字串中,所有字元都按原樣顯示。只有單引號本身是特殊的:您需要使用兩個單引號才能取得一個單引號。反斜線會被逐字擷取,因此您無法使用它來變更其後字元的含義。在雙引號字串中,可以使用特殊字元。以下是一些有用的字元
\t <Tab>
\n <NL>
,換行 \r <CR>
、<Enter>
\e <Esc>
\b <BS>
,倒退鍵 \" " \\ \, 反斜線 \<Esc> <Esc>
\<C-W> CTRL-W
最後兩個只是範例。「\<name>」形式可以用於包含特殊按鍵「name」。有關字串中特殊項目的完整列表,請參閱
expr-quote。
Vim 具有豐富但簡單的方式來處理運算式。您可以在這裡閱讀定義:
expression-syntax。在這裡,我們將顯示最常見的項目。上面提到的數字、字串和變數本身就是運算式。因此,在預期運算式的任何地方,您都可以使用數字、字串或變數。運算式中的其他基本項目包括
$NAME 環境變數 &name 選項 @r 暫存器
範例
:echo "The value of 'tabstop' is" &ts
:echo "Your home directory is" $HOME
:if @a > 5
&name 形式可用於儲存選項值、將其設定為新值、執行某些操作並還原舊值。範例
:let save_ic = &ic
:set noic
:/The Start/,$delete
:let &ic = save_ic
這可確保「The Start」模式在
'ignorecase' 選項關閉的情況下使用。不過,它仍會保留使用者設定的值。(另一種方法是將「\C」加入到模式中,請參閱
/\C。)
數學
如果我們組合這些基本項目,就會變得更有趣。讓我們從數字的數學運算開始
a + b 加法 a - b 減法 a * b 乘法 a / b 除法 a % b 模數
會使用一般的運算子優先順序。範例
:echo 10 + 5 * 2
分組使用括號。這裡沒有任何意外。範例
:echo (10 + 5) * 2
字串可以用「..」串連起來 (請參閱
expr6)。範例
:echo "foo" .. "bar"
當 ":echo" 指令取得多個引數時,它會用空格分隔它們。在範例中,引數是單一運算式,因此不會插入空格。
從 C 語言借用的是條件運算式
a ? b : c
如果「a」評估為 true,則會使用「b」,否則會使用「c」。範例
:let i = 4
:echo i > 5 ? "i is big" : "i is small"
建構的三個部分會先進行評估,因此您可以將其視為
(a) ? (b) : (c)
只有在符合條件時,":if" 指令才會執行下列語句,直到符合的 ":endif" 為止。其通用形式為
:if {condition}
{statements}
:endif
只有當運算式 {condition}
評估為 true (非零) 時,才會執行 {statements}
。這些語句仍然必須是有效的指令。如果它們包含垃圾,Vim 將無法找到 ":endif"。您也可以使用 ":else"。其通用形式為
:if {condition}
{statements}
:else {statements}
:endif
只有在未執行第一個 {statements}
時,才會執行第二個 {statements}
。最後,還有 ":elseif"
:if {條件}
{陳述式}
:elseif {條件}
{陳述式}
:endif
這和使用 ":else" 然後 "if" 的效果相同,但不需要額外的 ":endif"。在您的 vimrc 檔案中,一個有用的例子是檢查
'term' 選項,並根據其值執行某些操作。
:if &term == "xterm"
: " Do stuff for xterm
:elseif &term == "vt100"
: " Do stuff for a vt100 terminal
:else
: " Do something for other terminals
:endif
邏輯運算
我們已經在範例中使用了一些。以下是最常用的:
a == b 等於 a != b 不等於 a > b 大於 a >= b 大於等於 a < b 小於 a <= b 小於等於
如果條件成立,結果為 1,否則為 0。一個範例:
:if v:version >= 700
: echo "congratulations"
:else
: echo "you are using an old version, upgrade!"
:endif
這裡 "v:version" 是 Vim 定義的變數,其值為 Vim 版本。600 代表版本 6.0。版本 6.1 的值為 601。這對於編寫可與多個 Vim 版本一起使用的腳本非常有用。
v:version
邏輯運算子適用於數字和字串。比較兩個字串時,會使用數學差異。這會比較位元組值,對於某些語言可能不正確。將字串與數字比較時,字串會先轉換為數字。這有點棘手,因為當字串看起來不像數字時,會使用數字零。範例:
:if 0 == "one"
: echo "yes"
:endif
這將會輸出 "yes",因為 "one" 看起來不像數字,因此會被轉換為數字零。
對於字串,還有另外兩個項目:
a =~ b 符合 a !~ b 不符合
左邊的項目 "a" 會被當作字串使用。右邊的項目 "b" 會被當作模式使用,就像搜尋時所用的模式一樣。範例:
:if str =~ " "
: echo "str contains a space"
:endif
:if str !~ '\.$'
: echo "str does not end in a full stop"
:endif
請注意模式使用了單引號字串。這很有用,因為在雙引號字串中,反斜線需要加倍,而模式通常包含許多反斜線。
當比較字串時,會使用
'ignorecase' 選項。當您不想要這樣做時,請附加 "#" 來比對大小寫,並附加 "?" 來忽略大小寫。因此,"==?" 會比較兩個字串是否相等,同時忽略大小寫。而 "!~#" 會檢查模式是否不符合,同時也檢查字母的大小寫。如需完整表格,請參閱
expr-==。
更多迴圈
之前已經提過 ":while" 命令。在 ":while" 和 ":endwhile" 之間可以使用另外兩個陳述式:
:continue 跳回 while 迴圈的開頭;迴圈繼續執行。 :break 跳到 ":endwhile";迴圈終止。
範例:
:while counter < 40
: call do_something()
: if skip_flag
: continue
: endif
: if finished_flag
: break
: endif
: sleep 50m
:endwhile
":sleep" 命令會讓 Vim 小睡一下。"50m" 指定 50 毫秒。另一個範例是 ":sleep 4",這會睡眠 4 秒。
使用 ":for" 命令可以進行更多迴圈,請參閱下方的
41.8。
到目前為止,腳本中的命令都是由 Vim 直接執行的。":execute" 命令允許執行運算式的結果。這是一種非常強大的方式來建立命令並執行它們。一個範例是跳到一個標籤,該標籤包含在變數中:
:execute "tag " .. tag_name
".." 用於將字串 "tag " 與變數 "tag_name" 的值串連起來。假設 "tag_name" 的值為 "get_cmd",那麼將會執行的命令是:
:tag get_cmd
":execute" 命令只能執行冒號命令。":normal" 命令執行普通模式命令。然而,它的參數不是運算式,而是字面命令字元。範例:
:normal gg=G
這會跳到第一行並使用 "=" 運算子格式化所有行。為了讓 ":normal" 與運算式一起使用,請將 ":execute" 與它結合使用。範例:
:execute "normal " .. normal_commands
變數 "normal_commands" 必須包含普通模式命令。請確保 ":normal" 的參數是完整的命令。否則,Vim 將會遇到參數結尾並中止命令。例如,如果您開始插入模式,您也必須離開插入模式。這可行:
:execute "normal Inew text \<Esc>"
這會在目前行插入 "new text "。請注意特殊鍵 "\<Esc>" 的使用。這避免了在腳本中輸入真正的
<Esc>
字元。
如果您不想執行字串,而是評估它以取得其運算式值,可以使用 eval() 函數:
:let optname = "path"
:let optval = eval('&' .. optname)
"&" 字元會附加到 "path",因此 eval() 的參數為 "&path"。結果將會是
'path' 選項的值。可以使用以下方式完成相同的事情:
:exe 'let optval = &' .. optname
使用 ":call" 命令呼叫函數。參數會放在括號之間,並以逗號分隔。範例:
:call search("Date: ", "W")
這會呼叫 search() 函數,參數為 "Date: " 和 "W"。search() 函數會使用其第一個參數作為搜尋模式,並使用第二個參數作為標誌。"W" 標誌表示搜尋不會環繞到檔案結尾。
可以在運算式中呼叫函數。範例:
:let line = getline(".")
:let repl = substitute(line, '\a', "*", "g")
:call setline(".", repl)
getline() 函數會從目前的緩衝區取得一行。它的參數是行號的規格。在這個例子中,使用 ".",表示游標所在的行。substitute() 函數的功能與 ":substitute" 命令類似。第一個參數是要執行替換的字串。第二個參數是模式,第三個是替換字串。最後,最後一個參數是標誌。setline() 函數會將由第一個參數指定的行設定為新的字串,即第二個參數。在這個範例中,游標下的行會被替換為 substitute() 的結果。因此,三個陳述式的效果等於:
:substitute/\a/*/g
當您在呼叫 substitute() 前後執行更多工作時,使用函數會變得很有趣。
字串操作:
string-functionsnr2char() 透過數字值取得字元 list2str() 從數字清單取得字串 char2nr() 取得字元的數值 str2list() 從字串取得數字清單 str2nr() 將字串轉換為數字 str2float() 將字串轉換為浮點數 printf() 根據 % 項目格式化字串 escape() 使用 '\' 跳脫字串中的字元 shellescape() 跳脫字串以用於 shell 命令 fnameescape() 跳脫檔案名稱以用於 Vim 命令 tr() 將字元從一個集合轉換到另一個集合 strtrans() 轉換字串以使其可列印 keytrans() 將內部鍵碼轉換為可由
:map 使用的形式 tolower() 將字串轉換為小寫 toupper() 將字串轉換為大寫 charclass() 字元的類別 match() 模式在字串中符合的位置 matchbufline() 緩衝區中模式的所有符合 matchend() 模式符合在字串中結束的位置 matchfuzzy() 模糊比對字串清單中的字串 matchfuzzypos() 模糊比對字串清單中的字串 matchstr() 字串中模式的符合 matchstrlist() 字串清單中模式的所有符合 matchstrpos() 字串中模式的符合和位置 matchlist() 類似 matchstr(),也傳回子符合 stridx() 長字串中短字串的第一個索引 strridx() 長字串中短字串的最後一個索引 strlen() 字串的長度(以位元組為單位) strcharlen() 字串的長度(以字元為單位) strchars() 字串中的字元數 strutf16len() 字串中的 UTF-16 碼元數 strwidth() 顯示時的字串大小 strdisplaywidth() 顯示時的字串大小,處理定位點 setcellwidths() 設定字元單元寬度覆寫 getcellwidths() 取得字元單元寬度覆寫 reverse() 反轉字串中字元的順序 substitute() 使用字串替換模式符合 submatch() 在 ":s" 和 substitute() 中取得特定的符合 strpart() 使用位元組索引取得字串的一部分 strcharpart() 使用字元索引取得字串的一部分 slice() 在 Vim9 腳本中使用字元索引取得字串的切片 strgetchar() 使用字元索引從字串取得字元 expand() 展開特殊關鍵字 expandcmd() 展開類似於為
:edit
完成的命令 iconv() 將文字從一種編碼轉換為另一種編碼 byteidx() 字串中字元的位元組索引 byteidxcomp() 類似 byteidx(),但會計算組合字元 charidx() 字串中位元組的字元索引 utf16idx() 字串中位元組的 UTF-16 索引 repeat() 重複字串多次 eval() 評估字串運算式 execute() 執行 Ex 命令並取得輸出 win_execute() 類似 execute(),但在指定的視窗中 trim() 從字串修剪字元 gettext() 查詢訊息翻譯
清單操作:
list-functionsget() 取得項目,不會因錯誤的索引而發生錯誤 len() 清單中的項目數 empty() 檢查清單是否為空 insert() 在清單中的某處插入項目 add() 將項目附加到清單 extend() 將清單附加到清單 extendnew() 建立新的清單並附加項目 remove() 從清單中移除一個或多個項目 copy() 建立清單的淺層複本 deepcopy() 建立清單的完整複本 filter() 從清單中移除選取的項目 map() 變更每個清單項目 mapnew() 建立包含變更項目的新清單 foreach() 將函數應用於清單項目 reduce() 將清單縮減為值 slice() 取得清單的切片 sort() 排序清單 reverse() 反轉清單中項目的順序 uniq() 移除重複的相鄰項目 split() 將字串分割成清單 join() 將清單項目合併成字串 range() 傳回包含數字序列的清單 string() 清單的字串表示 call() 使用清單作為參數呼叫函數 index() 清單或 Blob 中值的索引 indexof() 清單或 Blob 中運算式評估為 true 的索引 max() 清單中的最大值 min() 清單中的最小值 count() 計算值在清單中出現的次數 repeat() 重複清單多次 flatten() 平坦化清單 flattennew() 平坦化清單的複本
字典操作:
dict-functionsget() 取得字典中的一個項目,若鍵值錯誤則不會產生錯誤 len() 取得字典中項目數量 has_key() 檢查字典中是否存在某個鍵值 empty() 檢查字典是否為空 remove() 從字典中移除一個項目 extend() 將一個字典的項目加入到另一個字典中 extendnew() 建立一個新的字典並加入項目 filter() 從字典中移除選定的項目 map() 變更字典中的每個項目 mapnew() 建立一個包含變更項目之新字典 foreach() 將函式應用於字典項目 keys() 取得字典鍵值的列表 values() 取得字典數值的列表 items() 取得字典鍵值對的列表 copy() 建立字典的淺層副本 deepcopy() 建立字典的完整副本 string() 字典的字串表示 max() 字典中的最大值 min() 字典中的最小值 count() 計算數值出現的次數
浮點數運算:
float-functionsfloat2nr() 將浮點數轉換為數字 abs() 絕對值 (也適用於數字) round() 四捨五入 ceil() 無條件進位 floor() 無條件捨去 trunc() 移除小數點後的值 fmod() 除法的餘數 exp() 指數 log() 自然對數 (以 e 為底的對數) log10() 以 10 為底的對數 pow() x 的 y 次方值 sqrt() 平方根 sin() 正弦 cos() 餘弦 tan() 正切 asin() 反正弦 acos() 反餘弦 atan() 反正切 atan2() 反正切 sinh() 雙曲正弦 cosh() 雙曲餘弦 tanh() 雙曲正切 isinf() 檢查是否為無限大 isnan() 檢查是否為非數值
Blob 操作:
blob-functionsblob2list() 從 Blob 取得數字列表 list2blob() 從數字列表取得 Blob reverse() 反轉 Blob 中數字的順序
其他運算:
bitwise-functionand() 位元 AND invert() 位元反轉 or() 位元 OR xor() 位元 XOR sha256() SHA-256 雜湊 rand() 取得虛擬亂數 srand() 初始化 rand() 使用的種子
變數:
var-functionstype() 變數的型別 islocked() 檢查變數是否被鎖定 funcref() 取得函式參考的 Funcref function() 取得函式名稱的 Funcref getbufvar() 從特定緩衝區取得變數值 setbufvar() 在特定緩衝區設定變數 getwinvar() 從特定視窗取得變數 gettabvar() 從特定分頁取得變數 gettabwinvar() 從特定視窗及分頁取得變數 setwinvar() 在特定視窗設定變數 settabvar() 在特定分頁設定變數 settabwinvar() 在特定視窗及分頁設定變數 garbagecollect() 可能釋放記憶體
游標與標記位置:
cursor-functions mark-functions col() 游標或標記的欄數 virtcol() 游標或標記的螢幕欄數 line() 游標或標記的行數 wincol() 游標的視窗欄數 winline() 游標的視窗行數 cursor() 將游標定位到指定行/欄 screencol() 取得游標的螢幕欄數 screenrow() 取得游標的螢幕行數 screenpos() 螢幕上文字字元的行與欄 virtcol2col() 螢幕上文字字元的位元組索引 getcurpos() 取得游標位置 getpos() 取得游標、標記等的位置 setpos() 設定游標、標記等的位置 getmarklist() 全域/本機標記的列表 byte2line() 取得特定位元組數的行數 line2byte() 取得特定行數的位元組數 diff_filler() 取得某行之上填充行的數量 screenattr() 取得螢幕行/列的屬性 screenchar() 取得螢幕行/列的字元碼 screenchars() 取得螢幕行/列的字元碼 screenstring() 取得螢幕行/列的字串 charcol() 游標或標記的字元數 getcharpos() 取得游標、標記等的字元位置 setcharpos() 設定游標、標記等的字元位置 getcursorcharpos() 取得游標的字元位置 setcursorcharpos() 設定游標的字元位置
在目前緩衝區中處理文字:
text-functionsgetline() 從緩衝區取得一行或多行 getregion() 從緩衝區取得一段文字 getregionpos() 取得一段文字的位置列表 setline() 取代緩衝區中的一行 append() 在緩衝區中附加一行或多行 indent() 特定行的縮排 cindent() 根據 C 縮排方式縮排 lispindent() 根據 Lisp 縮排方式縮排 nextnonblank() 尋找下一個非空白行 prevnonblank() 尋找上一個非空白行 search() 尋找符合模式的內容 searchpos() 尋找符合模式的內容 searchcount() 取得游標之前/之後的符合數量 searchpair() 尋找開始/跳過/結束的另一端 searchpairpos() 尋找開始/跳過/結束的另一端 searchdecl() 搜尋名稱的宣告 getcharsearch() 回傳字元搜尋資訊 setcharsearch() 設定字元搜尋資訊
在另一個緩衝區中處理文字: getbufline() 從指定的緩衝區取得多行 getbufoneline() 從指定的緩衝區取得單行 setbufline() 取代指定緩衝區中的一行 appendbufline() 在指定的緩衝區附加多行 deletebufline() 從指定的緩衝區刪除多行
system-functions file-functions 系統函式與檔案操作: glob() 展開萬用字元 globpath() 在多個目錄中展開萬用字元 glob2regpat() 將 glob 模式轉換為搜尋模式 findfile() 在目錄列表中尋找檔案 finddir() 在目錄列表中尋找目錄 resolve() 找出捷徑指向的位置 fnamemodify() 修改檔案名稱 pathshorten() 縮短路徑中的目錄名稱 simplify() 簡化路徑但不改變其含義 executable() 檢查可執行程式是否存在 exepath() 可執行程式的完整路徑 filereadable() 檢查檔案是否可讀 filewritable() 檢查檔案是否可寫入 getfperm() 取得檔案的權限 setfperm() 設定檔案的權限 getftype() 取得檔案的類型 isabsolutepath() 檢查路徑是否為絕對路徑 isdirectory() 檢查目錄是否存在 getfsize() 取得檔案的大小 getcwd() 取得目前的工作目錄 haslocaldir() 檢查目前的視窗是否使用
:lcd 或
:tcd tempname() 取得暫存檔的名稱 mkdir() 建立新目錄 chdir() 變更目前的工作目錄 delete() 刪除檔案 rename() 重新命名檔案 system() 取得 shell 命令的字串結果 systemlist() 取得 shell 命令的列表結果 environ() 取得所有環境變數 getenv() 取得一個環境變數 setenv() 設定環境變數 hostname() 系統名稱 readfile() 將檔案讀入行的列表 readblob() 將檔案讀入 Blob readdir() 取得目錄中的檔案名稱列表 writefile() 將行的列表或 Blob 寫入檔案 filecopy() 將檔案
{from}
複製到
{to}
日期與時間:
date-functions time-functions getftime() 取得檔案的最後修改時間 localtime() 以秒為單位取得目前時間 strftime() 將時間轉換為字串 strptime() 將日期/時間字串轉換為時間 reltime() 精確取得目前或經過的時間 reltimestr() 將 reltime() 的結果轉換為字串 reltimefloat() 將 reltime() 的結果轉換為浮點數
buffer-functions window-functions arg-functions 緩衝區、視窗與引數列表: argc() 引數列表中的項目數 argidx() 引數列表中目前的位置 arglistid() 取得引數列表的 ID argv() 從引數列表取得一個項目 bufadd() 將檔案加入緩衝區列表 bufexists() 檢查緩衝區是否存在 buflisted() 檢查緩衝區是否存在且已列出 bufload() 確保已載入緩衝區 bufloaded() 檢查緩衝區是否存在且已載入 bufname() 取得特定緩衝區的名稱 bufnr() 取得特定緩衝區的編號 tabpagebuflist() 回傳分頁中的緩衝區列表 tabpagenr() 取得分頁的編號 tabpagewinnr() 類似 winnr(),但針對指定的分頁 winnr() 取得目前視窗的編號 bufwinid() 取得特定緩衝區的視窗 ID bufwinnr() 取得特定緩衝區的視窗編號 winbufnr() 取得特定視窗的緩衝區編號 win_findbuf() 尋找包含緩衝區的視窗 win_getid() 取得視窗的 ID win_gettype() 取得視窗的類型 win_gotoid() 跳至具有 ID 的視窗 win_id2tabwin() 從視窗 ID 取得分頁和視窗編號 win_id2win() 從視窗 ID 取得視窗編號 win_move_separator() 移動視窗垂直分隔線 win_move_statusline() 移動視窗狀態列 win_splitmove() 將視窗移動到另一個視窗的分割 getbufinfo() 取得包含緩衝區資訊的列表 gettabinfo() 取得包含分頁資訊的列表 getwininfo() 取得包含視窗資訊的列表 getchangelist() 取得變更列表項目的列表 getjumplist() 取得跳躍列表項目的列表 swapfilelist() 在
'directory' 中現有的交換檔列表 swapinfo() 關於交換檔的資訊 swapname() 取得緩衝區的交換檔路徑
命令列:
command-line-functionsgetcmdcomplpat() 取得目前命令列的補全模式 getcmdcompltype() 取得目前命令列補全的類型 getcmdline() 取得目前命令列輸入 getcmdprompt() 取得目前命令列提示 getcmdpos() 取得命令列中游標的位置 getcmdscreenpos() 取得命令列中游標的螢幕位置 setcmdline() 設定目前的命令列 setcmdpos() 設定命令列中游標的位置 getcmdtype() 回傳目前命令列的類型 getcmdwintype() 回傳目前命令列視窗的類型 getcompletion() 命令列補全符合項目的列表 fullcommand() 取得完整命令名稱
快速修復與位置列表:
quickfix-functionsgetqflist() 快速修復錯誤列表 setqflist() 修改快速修復列表 getloclist() 位置列表項目的列表 setloclist() 修改位置列表
插入模式補全:
completion-functionscomplete() 設定找到的符合項目 complete_add() 加入找到的符合項目 complete_check() 檢查是否應中止補全 complete_info() 取得目前補全資訊 pumvisible() 檢查彈出選單是否顯示 pum_getpos() 如果彈出選單可見,取得其位置和大小
摺疊:
folding-functionsfoldclosed() 檢查特定行是否有關閉的摺疊 foldclosedend() 類似 foldclosed(),但回傳最後一行 foldlevel() 檢查特定行的摺疊層級 foldtext() 為關閉的摺疊產生顯示行 foldtextresult() 取得為關閉的摺疊顯示的文字
拼字檢查:
spell-functionsspellbadword() 在游標處或之後尋找拼寫錯誤的單字 spellsuggest() 返回建議的拼寫修正 soundfold() 返回單字的同音異義詞
歷史紀錄:
history-functionshistadd() 將項目新增至歷史紀錄 histdel() 從歷史紀錄中刪除項目 histget() 從歷史紀錄中取得項目 histnr() 取得歷史紀錄列表的最高索引
互動:
interactive-functionsbrowse() 顯示檔案選擇器 browsedir() 顯示目錄選擇器 confirm() 讓使用者做出選擇 getchar() 從使用者取得字元 getcharmod() 取得最後輸入字元的修飾符 getmousepos() 取得最後已知的滑鼠位置 feedkeys() 將字元放入輸入佇列 input() 從使用者取得一行輸入 inputlist() 讓使用者從列表中選擇一個項目 inputsecret() 從使用者取得一行輸入,但不顯示 inputdialog() 在對話方塊中從使用者取得一行輸入 inputsave() 儲存並清除輸入佇列 inputrestore() 還原輸入佇列
GUI:
gui-functionsgetfontname() 取得目前使用的字型名稱 getwinpos() 取得 Vim 視窗的位置 getwinposx() 取得 Vim 視窗的 X 座標 getwinposy() 取得 Vim 視窗的 Y 座標 balloon_show() 設定氣泡內容 balloon_split() 將訊息分割成氣泡 balloon_gettext() 取得氣泡中的文字
Vim 伺服器:
server-functionsserverlist() 返回伺服器名稱列表 remote_startserver() 執行伺服器 remote_send() 將指令字元傳送至 Vim 伺服器 remote_expr() 在 Vim 伺服器中評估表達式 server2client() 將回覆傳送至 Vim 伺服器的用戶端 remote_peek() 檢查是否有來自 Vim 伺服器的回覆 remote_read() 從 Vim 伺服器讀取回覆 foreground() 將 Vim 視窗移至前景 remote_foreground() 將 Vim 伺服器視窗移至前景
視窗大小與位置:
window-size-functionswinheight() 取得特定視窗的高度 winwidth() 取得特定視窗的寬度 win_screenpos() 取得視窗的螢幕位置 winlayout() 取得索引標籤頁中視窗的佈局 winrestcmd() 返回還原視窗大小的指令 winsaveview() 取得目前視窗的檢視 winrestview() 還原已儲存的目前視窗檢視
對應與選單:
mapping-functionsdigraph_get() 取得
雙字母 digraph_getlist() 取得所有
雙字母 digraph_set() 註冊
雙字母 digraph_setlist() 註冊多個
雙字母 hasmapto() 檢查對應是否存在 mapcheck() 檢查是否存在匹配的對應 maparg() 取得對應的右側 (rhs) maplist() 取得所有對應的列表 mapset() 還原對應 menu_info() 取得關於選單項目的資訊 wildmenumode() 檢查是否啟用了 wildmode
符號:
sign-functionssign_define() 定義或更新符號 sign_getdefined() 取得已定義符號的列表 sign_getplaced() 取得已放置符號的列表 sign_jump() 跳至符號 sign_place() 放置符號 sign_placelist() 放置符號列表 sign_undefine() 取消定義符號 sign_unplace() 取消放置符號 sign_unplacelist() 取消放置符號列表
測試:
test-functionsassert_equal() 斷言兩個表達式的值相等 assert_equalfile() 斷言兩個檔案內容相等 assert_notequal() 斷言兩個表達式的值不相等 assert_inrange() 斷言表達式在範圍內 assert_match() 斷言模式符合值 assert_notmatch() 斷言模式不符合值 assert_false() 斷言表達式為假 assert_true() 斷言表達式為真 assert_exception() 斷言指令拋出例外狀況 assert_beeps() 斷言指令發出嗶聲 assert_nobeep() 斷言指令不會發出嗶聲 assert_fails() 斷言指令失敗 assert_report() 報告測試失敗
計時器:
timer-functionstimer_start() 建立計時器 timer_pause() 暫停或取消暫停計時器 timer_stop() 停止計時器 timer_stopall() 停止所有計時器 timer_info() 取得關於計時器的資訊 wait() 等待條件
標籤:
tag-functionstaglist() 取得匹配標籤的列表 tagfiles() 取得標籤檔案的列表 gettagstack() 取得視窗的標籤堆疊 settagstack() 修改視窗的標籤堆疊
提示緩衝區:
promptbuffer-functionsprompt_getprompt() 取得緩衝區的有效提示文字 prompt_setcallback() 設定緩衝區的提示回呼 prompt_setinterrupt() 設定緩衝區的中斷回呼 prompt_setprompt() 設定緩衝區的提示文字
暫存器:
register-functionsgetreg() 取得暫存器的內容 getreginfo() 取得關於暫存器的資訊 getregtype() 取得暫存器的類型 setreg() 設定暫存器的內容和類型 reg_executing() 返回正在執行的暫存器名稱 reg_recording() 返回正在錄製的暫存器名稱
內容堆疊:
ctx-functionsctxget() 從頂部返回給定索引的內容 ctxpop() 彈出並還原頂部內容 ctxpush() 推入給定的內容 ctxset() 設定從頂部開始給定索引的內容 ctxsize() 返回內容堆疊大小
其他:
various-functionsmode() 取得目前編輯模式 visualmode() 取得上次使用的視覺模式 exists() 檢查變數、函式等是否存在 has() 檢查 Vim 是否支援某項功能 changenr() 返回最近一次變更的編號 did_filetype() 檢查是否使用了 FileType 自動指令 eventhandler() 檢查是否由事件處理程式調用 getpid() 取得 Vim 的處理序 ID getscriptinfo() 取得已載入 Vim 指令碼的列表
libcall() 呼叫外部函式庫中的函式 libcallnr() 同上,但返回數值
undofile() 取得復原檔案的名稱 undotree() 返回緩衝區的復原樹狀結構的狀態
wordcount() 取得緩衝區的位元組/單字/字元計數
debugbreak() 中斷正在除錯的程式
Vim 允許您定義自己的函式。基本函式宣告如下所示
:function {name}({var1}, {var2}, ...)
: {body}
:endfunction
注意: 函式名稱必須以大寫字母開頭。
讓我們定義一個簡短的函式來返回兩個數字中較小的一個。它從這一行開始
:function Min(num1, num2)
這告訴 Vim 該函式名為 "Min",它接受兩個引數:「num1」和「num2」。您需要做的第一件事是檢查哪個數字較小
: if a:num1 < a:num2
特殊前綴 "a:" 告訴 Vim 該變數是函式引數。讓我們將變數 "smaller" 指定為較小的值
: if a:num1 < a:num2
: let smaller = a:num1
: else
: let smaller = a:num2
: endif
變數 "smaller" 是一個區域變數。在函式內使用的變數是區域變數,除非以 "g:"、"a:" 或 "s:" 等前綴。
注意: 若要從函式內部存取全域變數,您必須在其前面加上 "g:"。因此,函式內的 "g:today" 用於全域變數 "today",而 "today" 是另一個變數,它是函式的區域變數。
現在您可以使用 ":return" 陳述式將最小的數字返回給使用者。最後,您結束該函式
: return smaller
:endfunction
完整的函式定義如下所示
:function Min(num1, num2)
: if a:num1 < a:num2
: let smaller = a:num1
: else
: let smaller = a:num2
: endif
: return smaller
:endfunction
對於喜歡短函式的人,這個函式做同樣的事情
:function Min(num1, num2)
: if a:num1 < a:num2
: return a:num1
: endif
: return a:num2
:endfunction
使用者定義的函式與內建函式的呼叫方式完全相同。只有名稱不同。「Min」函式可以像這樣使用
:echo Min(5, 8)
現在才會執行函式,並且 Vim 會解譯這些程式碼。如果出現錯誤,例如使用未定義的變數或函式,您現在會收到錯誤訊息。定義函式時不會偵測到這些錯誤。
當函式到達 ":endfunction" 或在沒有引數的情況下使用 ":return" 時,函式會返回零。
若要重新定義已存在的函式,請對 ":function" 指令使用 !
:function! Min(num1, num2, num3)
使用範圍
":call" 指令可以指定行範圍。這可能有兩種含義。當使用 "range" 關鍵字定義函式時,它會自行處理行範圍。函式會傳遞變數 "a:firstline" 和 "a:lastline"。這些將具有函式呼叫的範圍中的行號。範例
:function Count_words() range
: let lnum = a:firstline
: let n = 0
: while lnum <= a:lastline
: let n = n + len(split(getline(lnum)))
: let lnum = lnum + 1
: endwhile
: echo "found " .. n .. " words"
:endfunction
您可以使用以下方式呼叫此函式
:10,30call Count_words()
它將執行一次並輸出單字數量。使用行範圍的另一種方式是定義沒有 "range" 關鍵字的函式。對於範圍中的每一行,都會呼叫一次該函式,並且游標位於該行中。範例
:function Number()
: echo "line " .. line(".") .. " contains: " .. getline(".")
:endfunction
如果您使用以下方式呼叫此函式
:10,15call Number()
則該函式將被呼叫六次。
可變數量的引數
Vim 允許您定義具有可變數量引數的函式。例如,以下指令定義了一個必須有 1 個引數 (start) 且最多可以有 20 個額外引數的函式
:function Show(start, ...)
變數 "a:1" 包含第一個選用引數,"a:2" 包含第二個,依此類推。變數 "a:0" 包含額外引數的數量。例如
:function Show(start, ...)
: echohl Title
: echo "start is " .. a:start
: echohl None
: let index = 1
: while index <= a:0
: echo " Arg " .. index .. " is " .. a:{index}
: let index = index + 1
: endwhile
: echo ""
:endfunction
這會使用 ":echohl" 指令指定以下 ":echo" 指令使用的高亮顯示。"echohl None" 會再次停止它。":echon" 指令與 ":echo" 的功能類似,但不會輸出換行符號。
您也可以使用 a:000 變數,它是所有 "..." 引數的列表。請參閱
a:000。
列出函式
":function" 指令會列出所有使用者定義函式的名稱和引數
:function
function Show(start, ...)
function GetVimIndent()
function SetSyn(name)
若要查看函式的作用,請使用其名稱作為 ":function" 的引數
:function SetSyn
1 if &syntax == ''
2 let &syntax = a:name
3 endif
endfunction
除錯
當您收到錯誤訊息或進行除錯時,行號會很有用。有關除錯模式,請參閱
debug-scripts。您也可以將
'verbose' 選項設定為 12 或更高,以查看所有函式呼叫。將其設定為 15 或更高,以查看每一行已執行的程式碼。
刪除函式
若要刪除 Show() 函式
:delfunction Show
當函式不存在時,您會收到錯誤訊息。
函式參考
有時候,讓變數指向一個函式或另一個函式可能很有用。您可以使用 function() 函式來執行此操作。它會將函式名稱轉換為參考
:let result = 0 " or 1
:function! Right()
: return 'Right!'
:endfunc
:function! Wrong()
: return 'Wrong!'
:endfunc
:
:if result == 1
: let Afunc = function('Right')
:else
: let Afunc = function('Wrong')
:endif
:echo call(Afunc, [])
請注意,儲存函數參照的變數名稱必須以大寫字母開頭。否則,它可能會與內建函數的名稱混淆。調用變數所參照的函數的方式是使用 call() 函數。它的第一個參數是函數參照,第二個參數是包含參數的 List(列表)。
函數參照最常與 Dictionary(字典)結合使用,下一節將對此進行說明。
到目前為止,我們使用了基本類型 String(字串)和 Number(數字)。Vim 也支援兩種複合類型:List(列表)和 Dictionary(字典)。
List(列表)是有順序的事物序列。這些事物可以是任何種類的值,因此您可以建立數字列表、列表的列表,甚至是混合項目的列表。要建立包含三個字串的列表:
:let alist = ['aap', 'mies', 'noot']
列表項目用方括號括起來,並以逗號分隔。要建立一個空列表:
:let alist = []
您可以使用 add() 函數向列表中新增項目:
:let alist = []
:call add(alist, 'foo')
:call add(alist, 'bar')
:echo alist
列表串接使用 + 號:
:echo alist + ['foo', 'bar']
或者,如果您想要直接擴展列表:
:let alist = ['one']
:call extend(alist, ['two', 'three'])
:echo alist
請注意,使用 add() 會產生不同的效果:
:let alist = ['one']
:call add(alist, ['two', 'three'])
:echo alist
add() 的第二個參數會作為單個項目新增。
FOR 迴圈
您可以使用列表做的其中一件好事是迭代它:
:let alist = ['one', 'two', 'three']
:for n in alist
: echo n
:endfor
這會迴圈遍歷列表 "alist" 中的每個元素,將值指派給變數 "n"。for 迴圈的一般形式是:
:for {varname} in {listexpression}
: {commands}
:endfor
若要迴圈執行特定次數,您需要一個特定長度的列表。range() 函數會為您建立一個:
:for a in range(3)
: echo a
:endfor
請注意,range() 產生的列表的第一個項目是零,因此最後一個項目比列表的長度少一。您也可以指定最大值、步幅,甚至可以向後移動:
:for a in range(8, 4, -2)
: echo a
:endfor
一個更有用的範例,迴圈遍歷緩衝區中的行:
:for line in getline(1, 20)
: if line =~ "Date: "
: echo matchstr(line, 'Date: \zs.*')
: endif
:endfor
這會檢查第 1 行到第 20 行(含)的內容,並回顯在其中找到的任何日期。
字典
字典儲存鍵值對。如果您知道鍵,則可以快速查詢值。字典使用大括號建立:
:let uk2nl = {'one': 'een', 'two': 'twee', 'three': 'drie'}
現在,您可以透過將鍵放在方括號中來查詢單字:
:echo uk2nl['two']
定義字典的一般形式是:
{<key> : <value>, ...}
空字典是不包含任何鍵的字典:
{}
字典的可能性有很多。也有各種用於它們的函數。例如,您可以取得鍵的列表並遍歷它們:
:for key in keys(uk2nl)
: echo key
:endfor
您會注意到這些鍵沒有排序。您可以對列表進行排序以取得特定的順序:
:for key in sort(keys(uk2nl))
: echo key
:endfor
但是您永遠無法取回定義項目的順序。為此,您需要使用列表,它會將項目儲存在有序的序列中。
字典函數
字典中的項目通常可以使用方括號中的索引來取得:
:echo uk2nl['one']
一種執行相同操作的方法,但沒有那麼多的標點符號:
:echo uk2nl.one
這僅適用於由 ASCII 字母、數字和底線組成的鍵。您也可以透過這種方式指派新值:
:let uk2nl.four = 'vier'
:echo uk2nl
{'three':drie,four:vier,one:een,two:twee}
現在來點特別的:您可以直接定義一個函數,並將它的參照儲存在字典中:
:function uk2nl.translate(line) dict
: return join(map(split(a:line), 'get(self, v:val, "???")'))
:endfunction
讓我們首先試用一下:
:echo uk2nl.translate('three two five one')
您注意到的第一個特別之處是 ":function" 行結尾的 "dict"。這會將函數標記為從字典中使用。"self" 局部變數將會參照該字典。現在讓我們分解複雜的 return 命令:
split(a:line)
split() 函數會取得一個字串,將它切成以空白分隔的單字,並傳回包含這些單字的列表。因此,在此範例中,它會傳回:
:echo split('three two five one')
此列表是 map() 函數的第一個引數。這會遍歷列表,評估其第二個引數,並將 "v:val" 設定為每個項目的值。這是使用 for 迴圈的捷徑。此命令:
:let alist = map(split(a:line), 'get(self, v:val, "???")')
等同於:
:let alist = split(a:line)
:for idx in range(len(alist))
: let alist[idx] = get(self, alist[idx], "???")
:endfor
get() 函數會檢查鍵是否存在於字典中。如果存在,則會擷取該值。如果不存在,則會傳回預設值,在此範例中為 '???'。這是一種處理可能不存在鍵且您不希望顯示錯誤訊息的情況的便捷方式。
join() 函數與 split() 執行相反的操作:它會將單字列表結合在一起,並在它們之間放置一個空格。split()、map() 和 join() 的這種組合是一種非常精簡的篩選單字行的方法。
物件導向程式設計
既然您可以在字典中放入值和函數,您實際上可以將字典當成物件使用。在上方,我們使用字典將荷蘭語翻譯成英文。我們可能想要對其他語言執行相同的操作。讓我們首先建立一個具有 translate 函數的物件(又稱字典),但沒有要翻譯的單字:
:let transdict = {}
:function transdict.translate(line) dict
: return join(map(split(a:line), 'get(self.words, v:val, "???")'))
:endfunction
它與上方的函數略有不同,使用 'self.words' 來查詢單字翻譯。但是我們沒有 self.words。因此,您可以將此稱為抽象類別。
現在,我們可以實例化一個荷蘭語翻譯物件:
:let uk2nl = copy(transdict)
:let uk2nl.words = {'one': 'een', 'two': 'twee', 'three': 'drie'}
:echo uk2nl.translate('three one')
以及一個德語翻譯器:
:let uk2de = copy(transdict)
:let uk2de.words = {'one': 'eins', 'two': 'zwei', 'three': 'drei'}
:echo uk2de.translate('three one')
您會看到 copy() 函數用於建立 "transdict" 字典的副本,然後變更該副本以新增單字。當然,原始的字典保持不變。
現在,您可以更進一步,使用您偏好的翻譯器:
:if $LANG =~ "de"
: let trans = uk2de
:else
: let trans = uk2nl
:endif
:echo trans.translate('one two three')
現在您可能會使用不受支援的語言。您可以覆寫 translate() 函數使其不執行任何動作:
:let uk2uk = copy(transdict)
:function! uk2uk.translate(line)
: return a:line
:endfunction
:echo uk2uk.translate('three one wladiwostok')
請注意,使用了 ! 來覆寫現有的函數參照。現在,當找不到可辨識的語言時,請使用 "uk2uk":
:if $LANG =~ "de"
: let trans = uk2de
:elseif $LANG =~ "nl"
: let trans = uk2nl
:else
: let trans = uk2uk
:endif
:echo trans.translate('one two three')
讓我們從一個範例開始:
:try
: read ~/templates/pascal.tmpl
:catch /E484:/
: echo "Sorry, the Pascal template file cannot be found."
:endtry
如果檔案不存在,":read" 命令將會失敗。此程式碼不會產生錯誤訊息,而是會捕獲錯誤並向使用者提供良好的訊息。
對於 ":try" 和 ":endtry" 之間的命令,錯誤會轉換為例外狀況。例外狀況是一個字串。如果發生錯誤,字串會包含錯誤訊息。而且每個錯誤訊息都有一個數字。在本例中,我們捕獲的錯誤包含 "E484:"。此數字保證保持不變(文字可能會變更,例如,可能會翻譯)。
當 ":read" 命令導致其他錯誤時,模式 "E484:" 不會與之比對。因此,這個例外狀況不會被捕獲,並導致顯示一般的錯誤訊息,而且執行會中止。
您可能會想要執行此操作:
:try
: read ~/templates/pascal.tmpl
:catch
: echo "Sorry, the Pascal template file cannot be found."
:endtry
這表示會捕獲所有錯誤。但是您將看不到有用的錯誤,例如 "E21: Cannot make changes,
'modifiable' is off"。
另一個有用的機制是 ":finally" 命令:
:let tmp = tempname()
:try
: exe ".,$write " .. tmp
: exe "!filter " .. tmp
: .,$delete
: exe "$read " .. tmp
:finally
: call delete(tmp)
:endtry
這會透過 "filter" 命令,篩選從游標到檔案結尾的行,而該命令會接受檔案名稱引數。無論篩選是否有效,":try" 和 ":finally" 之間發生錯誤,或是使用者按下
CTRL-C
取消篩選,都會一律執行 "call delete(tmp)"。這會確保您不會留下暫存檔。
以下是適用於 Vim 指令碼的項目摘要。它們也會在其他地方提及,但會形成一份良好的檢查清單。
行尾字元取決於系統。對於 Vim 指令碼,建議一律使用 Unix 檔案格式。然後使用換行字元分隔行。這也適用於任何其他系統。這樣一來,您可以將您的 Vim 指令碼從 MS-Windows 複製到 Unix,而且它們仍然可以運作。請參閱
:source_crnl。為確保已正確設定,請在寫入檔案之前執行此操作:
:setlocal fileformat=unix
當使用 "dos" 檔案格式時,會使用 CR-NL 這兩個字元來分隔行。CR 字元會造成各種問題,最好避免使用它。
空白字元
指令碼中允許使用空白行,而且會被忽略。
通常會忽略結尾的空白字元,但並非總是如此。包含它的其中一個命令是 map
。您必須注意這一點,它可能會導致難以理解的錯誤。一般解決方案是不使用結尾的空白字元,除非您真的需要它。
若要將空白字元包含在選項的值中,必須使用 "\" (反斜線) 來逸出,如下列範例所示:
:set tags=my\ nice\ file
相同的範例寫為:
:set tags=my nice file
將會發出錯誤,因為它會被解譯為:
:set tags=my
:set nice
:set file
字元 " (雙引號) 會開始註解。此字元之後 (含) 到行尾的所有內容都會被視為註解並忽略,但不會將註解納入考量的命令除外,如下列範例所示。註解可以在行上的任何字元位置開始。
對於某些命令的註解,有一個小「陷阱」。範例:
:abbrev dev development " shorthand
:map <F3> o#include " insert include
:execute cmd " do it
:!ls *.c " list C files
縮寫 "dev" 會展開為 development " shorthand'。
<F3>
的對應實際上會是 'o# ....' 之後的整行,包括 '" insert include'。"execute" 命令會產生錯誤。"!" 命令會將它之後的所有內容傳送至 shell,導致產生未比對的 '"' 字元的錯誤。":map"、":abbreviate"、":execute" 和 "!" 命令之後不能有註解(還有一些命令具有此限制)。對於 ":map"、":abbreviate" 和 ":execute" 命令,有一個技巧:
:abbrev dev development|" shorthand
:map <F3> o#include|" insert include
:execute cmd |" do it
使用 '|' 字元,命令會與下一個命令分開。而下一個命令只是一個註解。對於最後一個命令,您需要執行兩項操作:
:execute 和使用 '|':
:exe '!ls *.c' |" list C files
請注意,縮寫和對應中的 '|' 之前沒有空白字元。對於這些命令,會包含到行尾或 '|' 之間的所有字元。由於這種行為,您並不總是會看到已包含結尾的空白字元:
:map <F4> o#include
若要發現這些問題,您可以在編輯 vimrc 檔案時設定
'list' 選項。
在 Unix 系統中,有一種特殊的註解方式,可以讓 Vim 腳本變成可執行的。
#!/usr/bin/env vim -S
echo "this is a Vim script"
quit
單獨使用 "#" 指令會列出帶有行號的行。加入驚嘆號會使其變成什麼都不做,這樣您就可以添加 shell 命令來執行文件的其餘部分。
:#! -S
陷阱
以下範例會出現更大的問題
:map ,ab o#include
:unmap ,ab
這裡的 unmap 指令將無法運作,因為它嘗試取消對應 ",ab "。這不是一個已對應的序列。會出現一個錯誤,這很難識別,因為 ":unmap ,ab " 中結尾的空白字元是不可見的。
這與在 "unmap" 指令後使用註解的情況相同
:unmap ,ab " comment
這裡註解的部分將被忽略。但是,Vim 會嘗試取消對應 ',ab ',而該對應不存在。將其改寫為
:unmap ,ab| " comment
還原視圖
有時候您想要進行變更,然後回到游標所在的位置。若能還原相對位置也很好,這樣同一行會出現在視窗頂部。此範例會複製目前行,將其放置在檔案的第一行上方,然後還原視圖
map ,p ma"aYHmbgg"aP`bzt`a
這會做什麼
ma"aYHmbgg"aP`bzt`a
ma 在游標位置設定標記 a "aY 將目前行複製到暫存器 a Hmb 前往視窗頂部並設定標記 b gg 前往檔案的第一行 "aP 將複製的行放置在其上方 b 回到顯示畫面的頂部 zt 將視窗中的文字位置設定為先前狀態 a 回到儲存的游標位置
封裝
為了避免您的函式名稱與您從其他人取得的函式發生衝突,請使用此方案
在每個函式名稱之前加上唯一的字串。我通常使用縮寫。例如,「OW_」用於選項視窗函式。
將您的函式定義放在一個檔案中。設定一個全域變數來指示函式已載入。當再次執行該檔案時,請先卸載函式。範例
" This is the XXX package
if exists("XXX_loaded")
delfun XXX_one
delfun XXX_two
endif
function XXX_one(a)
... body of function ...
endfun
function XXX_two(b)
... body of function ...
endfun
let XXX_loaded = 1
==============================================================================
41.11 編寫外掛程式
write-plugin
您可以編寫 Vim 腳本,以便許多人都可以使用它。這稱為外掛程式。Vim 使用者可以將您的腳本放入他們的外掛程式目錄中,然後立即使用其功能
add-plugin。
實際上外掛程式有兩種型別
全域外掛程式:適用於所有類型的檔案。檔案類型外掛程式:僅適用於特定類型的檔案。
名稱
首先,您必須為您的外掛程式選擇一個名稱。外掛程式提供的功能應從其名稱中清楚地看出。而且,其他人不太可能編寫一個名稱相同但功能不同的外掛程式。請將名稱限制為 8 個字元,以避免在舊的 MS-Windows 系統上出現問題。
一個可以更正打字錯誤的腳本可以稱為「typecorr.vim」。我們將在此處將其作為範例使用。
為了讓外掛程式對所有人都能正常運作,它應遵循一些準則。這將逐步說明。完整的外掛程式範例在結尾處。
主體
讓我們從外掛程式的主體開始,也就是執行實際工作的行
14 iabbrev teh the
15 iabbrev otehr other
16 iabbrev wnat want
17 iabbrev synchronisation
18 \ synchronization
19 let s:count = 4
當然,實際清單應該長得多。
行號僅為了說明一些事項而新增,請勿將其放入您的外掛程式檔案中!
您可能會在外掛程式中新增新的更正,而且很快就會有幾個版本。當發布此檔案時,人們會想知道誰編寫了這個很棒的外掛程式,以及他們可以將意見發送給誰。因此,請在外掛程式的頂部放置標頭
1 " Vim global plugin for correcting typing mistakes
2 " Last Change: 2000 Oct 15
3 " Maintainer: Bram Moolenaar <[email protected]>
關於著作權和授權:由於外掛程式非常有用,並且幾乎不值得限制其發布,請考慮將您的外掛程式設定為公共領域,或使用 Vim
授權。在外掛程式頂部附近簡短地說明此事即可。範例
4 " License: This file is placed in the public domain.
行接續,避免副作用
use-cpo-save
在上面的第 18 行中,使用了行接續機制
line-continuation。設定了
'compatible' 的使用者在這裡會遇到麻煩,他們會收到錯誤訊息。我們不能直接重設
'compatible',因為這會產生很多副作用。為了避免這種情況,我們將把
'cpoptions' 選項設定為其 Vim 預設值,並稍後還原它。這樣就可以使用行接續,並使腳本適用於大多數人。做法如下
11 let s:save_cpo = &cpo
12 set cpo&vim
..
42 let &cpo = s:save_cpo
43 unlet s:save_cpo
我們先將
'cpoptions' 的舊值儲存在 s:save_cpo 變數中。在外掛程式結束時,此值會還原。
請注意,使用了腳本局部變數
s:var。全域變數可能已用於其他用途。對於僅在腳本中使用的內容,請始終使用腳本局部變數。
不載入
使用者有可能並非總是想要載入此外掛程式。或者系統管理員已將其放入系統範圍的外掛程式目錄中,但使用者有他們自己想要使用的外掛程式。然後使用者必須有機會停用載入此特定外掛程式。這將使其成為可能
6 if exists("g:loaded_typecorr")
7 finish
8 endif
9 let g:loaded_typecorr = 1
這也可以避免腳本載入兩次時,因為重新定義函式而導致錯誤訊息,並導致新增兩次的自動指令出現問題。
建議名稱以「loaded_」開頭,然後是外掛程式的檔案名稱,文字上也是如此。「g:」只是為了避免在函式中使用變數時發生錯誤而加上(沒有「g:」它會是函式的局部變數)。
使用「finish」會停止 Vim 讀取檔案的其餘部分,這比在整個檔案周圍使用 if-endif 快得多。
對應
現在讓我們讓外掛程式更有趣:我們將新增一個對應,為游標下的單字新增更正。我們可以為此對應選擇一個按鍵序列,但使用者可能已經將其用於其他用途。為了讓使用者定義外掛程式中對應使用的按鍵,可以使用
<Leader>
項目
22 map <unique> <Leader>a <Plug>TypecorrAdd;
「<Plug>TypecorrAdd;」會執行工作,稍後會詳細說明。
使用者可以將「mapleader」變數設定為他們希望此對應開始使用的按鍵序列。因此,如果使用者已執行
let mapleader = "_"
對應將定義「_a」。如果使用者沒有這樣做,則會使用預設值,即反斜線。然後將定義「\a」的對應。
但是,如果使用者想要定義自己的按鍵序列怎麼辦?我們可以透過此機制允許這樣做
21 if !hasmapto('<Plug>TypecorrAdd;')
22 map <unique> <Leader>a <Plug>TypecorrAdd;
23 endif
這會檢查是否存在「<Plug>TypecorrAdd;」的對應,並且僅在沒有的情況下定義從「<Leader>a」開始的對應。然後使用者有機會將其放入他們的 vimrc 檔案中
map ,c <Plug>TypecorrAdd;
然後對應的按鍵序列將是「,c」,而不是「_a」或「\a」。
片段
如果腳本變得更長,您通常會想要將工作分成幾個片段。您可以為此使用函式或對應。但您不希望這些函式和對應干擾其他腳本中的函式和對應。例如,您可以定義函式 Add(),但另一個腳本可能會嘗試定義相同的函式。為了避免這種情況,我們透過在函式前面加上「s:」來定義腳本的局部函式。
我們將定義一個新增新打字更正的函式
30 function s:Add(from, correct)
31 let to = input("type the correction for " .. a:from .. ": ")
32 exe ":iabbrev " .. a:from .. " " .. to
..
36 endfunction
現在我們可以在此腳本中呼叫函式 s:Add()。如果另一個腳本也定義了 s:Add(),則它會是該腳本的局部函式,並且只能從定義它的腳本中呼叫。也可以有全域 Add() 函式(沒有「s:」),這又是另一個函式。
<SID>
可以與對應一起使用。它會產生腳本 ID,該 ID 會識別目前的腳本。在我們的打字更正外掛程式中,我們像這樣使用它
24 noremap <unique> <script> <Plug>TypecorrAdd; <SID>Add
..
28 noremap <SID>Add :call <SID>Add(expand("<cword>"), 1)<CR>
因此,當使用者鍵入「\a」時,會叫用此序列
\a -> <Plug>TypecorrAdd; -> <SID>Add -> :call <SID>Add()
如果另一個腳本也對應了
<SID>
Add,它會取得另一個腳本 ID,因此會定義另一個對應。
請注意,我們在這裡使用 <SID>
Add() 而不是 s:Add()。這是因為對應是由使用者鍵入的,因此在腳本外部。<SID>
會轉換為腳本 ID,以便 Vim 知道在哪個腳本中尋找 Add() 函式。
這有點複雜,但外掛程式必須與其他外掛程式一起運作。基本規則是您在對應中使用 <SID>
Add(),而在其他位置(腳本本身、自動指令、使用者指令)中使用 s:Add()。
我們也可以新增一個選單項目來執行與對應相同的操作
26 noremenu <script> Plugin.Add\ Correction <SID>Add
建議使用「Plugin」選單來新增外掛程式的選單項目。在此情況下,僅使用一個項目。當新增更多項目時,建議建立子選單。例如,對於提供 CVS 操作的外掛程式,可以使用「Plugin.CVS」,例如「Plugin.CVS.checkin」、「Plugin.CVS.checkout」等。
請注意,在第 28 行中,使用了「:noremap」來避免任何其他對應造成問題。例如,有人可能已經重新對應了「:call」。在第 24 行中,我們也使用「:noremap」,但我們希望重新對應「<SID>Add」。這就是為什麼在這裡使用「<script>」的原因。這僅允許腳本的局部對應。
:map-<script> 在第 26 行中,對「:noremenu」執行了相同的操作。
:menu-<script>
<SID>
和 <Plug>
都用於避免鍵入的按鍵對應干擾僅要從其他對應使用的對應。請注意使用 <SID>
和 <Plug>
之間的差異
<Plug>
在腳本外部可見。它用於使用者可能想要將按鍵序列對應到的對應。<Plug>
是一個特殊程式碼,鍵入的按鍵永遠不會產生。為了使其他外掛程式不太可能使用相同的字元序列,請使用以下結構:<Plug>
腳本名稱 對應名稱。在我們的範例中,腳本名稱為「Typecorr」,對應名稱為「Add」。我們新增一個分號作為終止符。這會產生「<Plug>TypecorrAdd;」。只有腳本名稱和對應名稱的第一個字元為大寫,以便我們可以看到對應名稱的開始位置。
<SID>
是腳本 ID,是腳本的唯一識別碼。在內部,Vim 會將 <SID>
轉換為「<SNR>123_」,其中「123」可以是任何數字。因此,函式「<SID>Add()」在一個腳本中的名稱為「<SNR>11_Add()」,而在另一個腳本中的名稱為「<SNR>22_Add()」。如果您使用「:function」指令取得函式清單,您可以看到這一點。<SID>
在對應中的轉換完全相同,這就是您可以從對應呼叫腳本局部函式的方式。
使用者指令
現在讓我們新增一個使用者指令來新增更正
38 if !exists(":Correct")
39 command -nargs=1 Correct :call s:Add(<q-args>, 0)
40 endif
僅在沒有相同名稱的指令時,才會定義使用者指令。否則,我們在這裡會收到錯誤。使用「:command!」覆寫現有的使用者指令不是一個好主意,這可能會讓使用者懷疑為什麼他們自己定義的指令不起作用。
:command
腳本變數
當變數以 "s:" 開頭時,它是一個腳本變數。它只能在腳本內部使用。在腳本之外,它是不可見的。這可以避免在不同腳本中使用相同變數名稱的問題。這些變數將在 Vim 運行期間保留。並且當再次引用相同腳本時,會使用相同的變數。
s:var
有趣的是,這些變數也可以用於在腳本中定義的函數、自動指令和使用者命令中。在我們的範例中,我們可以加入幾行程式碼來計算更正的次數
19 let s:count = 4
..
30 function s:Add(from, correct)
..
34 let s:count = s:count + 1
35 echo s:count .. " corrections now"
36 endfunction
首先,在腳本本身中將 s:count 初始化為 4。當稍後呼叫 s:Add() 函數時,它會遞增 s:count。函數從何處被呼叫並不重要,因為它是在腳本中定義的,它將使用此腳本中的區域變數。
結果
這是最終的完整範例
1 " Vim global plugin for correcting typing mistakes
2 " Last Change: 2000 Oct 15
3 " Maintainer: Bram Moolenaar <[email protected]>
4 " License: This file is placed in the public domain.
5
6 if exists("g:loaded_typecorr")
7 finish
8 endif
9 let g:loaded_typecorr = 1
10
11 let s:save_cpo = &cpo
12 set cpo&vim
13
14 iabbrev teh the
15 iabbrev otehr other
16 iabbrev wnat want
17 iabbrev synchronisation
18 \ synchronization
19 let s:count = 4
20
21 if !hasmapto('<Plug>TypecorrAdd;')
22 map <unique> <Leader>a <Plug>TypecorrAdd;
23 endif
24 noremap <unique> <script> <Plug>TypecorrAdd; <SID>Add
25
26 noremenu <script> Plugin.Add\ Correction <SID>Add
27
28 noremap <SID>Add :call <SID>Add(expand("<cword>"), 1)<CR>
29
30 function s:Add(from, correct)
31 let to = input("type the correction for " .. a:from .. ": ")
32 exe ":iabbrev " .. a:from .. " " .. to
33 if a:correct | exe "normal viws\<C-R>\" \b\e" | endif
34 let s:count = s:count + 1
35 echo s:count .. " corrections now"
36 endfunction
37
38 if !exists(":Correct")
39 command -nargs=1 Correct :call s:Add(<q-args>, 0)
40 endif
41
42 let &cpo = s:save_cpo
43 unlet s:save_cpo
第 33 行尚未解釋。它將新的更正應用於游標下的單字。使用
:normal 命令來使用新的縮寫。請注意,即使函數是從使用 ":noremap" 定義的映射中呼叫的,這裡也會展開映射和縮寫。
為您的外掛程式撰寫一些說明文件也是一個好主意。尤其是在其行為可以由使用者變更時。請參閱
add-local-help 以了解它們是如何安裝的。
這是一個簡單的外掛程式說明檔案範例,名為 "typecorr.txt"
1 *typecorr.txt* Plugin for correcting typing mistakes
2
3 If you make typing mistakes, this plugin will have them corrected
4 automatically.
5
6 There are currently only a few corrections. Add your own if you like.
7
8 Mappings:
9 <Leader>a or <Plug>TypecorrAdd;
10 Add a correction for the word under the cursor.
11
12 Commands:
13 :Correct {word}
14 Add a correction for {word}.
15
16 *typecorr-settings*
17 This plugin doesn't have any settings.
實際上,只有第一行的格式很重要。它會從說明檔案中提取出來,放入第一行的 "LOCAL ADDITIONS:" 區段。加入您的說明檔案後,執行 ":help" 並檢查條目是否排列整齊。
您可以在說明檔案的 ** 內部加入更多標籤。但請小心不要使用現有的說明標籤。您可能會在大多數標籤中使用外掛程式的名稱,例如範例中的 "typecorr-settings"。
建議使用 || 中的其他說明部分的參考。這讓使用者可以輕鬆找到相關的說明。
如果您的檔案類型尚未被 Vim 偵測到,您應該在單獨的檔案中建立一個檔案類型偵測程式碼片段。它通常採用自動指令的形式,當檔案名稱符合模式時會設定檔案類型。範例
au BufNewFile,BufRead *.foo set filetype=foofoo
將此單行檔案以 "ftdetect/foofoo.vim" 的名稱寫入
'runtimepath' 中出現的第一個目錄。對於 Unix,這將是 "~/.config/nvim/ftdetect/foofoo.vim"。慣例是使用檔案類型的名稱作為腳本名稱。
外掛程式中要使用的特殊項目摘要
s:name 腳本的區域變數。
<SID>
腳本 ID,用於腳本的區域映射和函數。
hasmapto() 函數,用於測試使用者是否已為腳本提供的功能定義了映射。
<Leader>
"mapleader" 的值,使用者將其定義為外掛程式映射的起始按鍵。
:map <unique>
如果映射已存在,則發出警告。
:noremap <script>
僅使用腳本的區域映射,而非全域映射。
exists(":Cmd") 檢查使用者命令是否已存在。
首先閱讀上方關於全域外掛程式的章節
41.11。其中所述的所有內容也適用於檔案類型外掛程式。這裡有一些額外的說明。最重要的一點是,檔案類型外掛程式只應對目前的緩衝區產生影響。
停用
如果您正在撰寫要供多人使用的檔案類型外掛程式,他們需要有機會停用載入它。將此程式碼放在外掛程式的頂部
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin = 1
也需要使用它來避免針對同一個緩衝區執行兩次相同的外掛程式(當使用不帶引數的 ":edit" 命令時會發生)。
現在,使用者可以透過建立僅包含此行的檔案類型外掛程式,完全停用載入預設外掛程式
let b:did_ftplugin = 1
這需要檔案類型外掛程式目錄在
'runtimepath' 中位於 $VIMRUNTIME 之前!
如果您想使用預設外掛程式,但覆寫其中一個設定,您可以在腳本中寫入不同的設定
setlocal textwidth=70
現在將其寫入 "after" 目錄,以便在分發的 "vim.vim" ftplugin
after-directory 之後引用它。對於 Unix,這將是 "~/.config/nvim/after/ftplugin/vim.vim"。請注意,預設外掛程式會設定 "b:did_ftplugin",但在此處會忽略它。
選項
為了確保檔案類型外掛程式只會影響目前的緩衝區,請使用
:setlocal
命令來設定選項。並且只設定緩衝區的區域選項(請參閱選項的說明以檢查這一點)。當對全域選項或視窗的區域選項使用
:setlocal 時,該值將會針對許多緩衝區變更,而這不是檔案類型外掛程式應該執行的操作。
當選項的值是旗標或項目的清單時,請考慮使用 "+=" 和 "-=" 來保留現有值。請注意,使用者可能已經變更了選項值。通常,先重設為預設值,然後再變更是個好主意。範例
:setlocal formatoptions& formatoptions+=ro
映射
為了確保映射僅在目前的緩衝區中運作,請使用
:map <buffer>
命令。這需要與上面說明的兩步驟映射結合使用。以下是在檔案類型外掛程式中定義功能的範例
if !hasmapto('<Plug>JavaImport;')
map <buffer> <unique> <LocalLeader>i <Plug>JavaImport;
endif
noremap <buffer> <unique> <Plug>JavaImport; oimport ""<Left><Esc>
使用
hasmapto() 來檢查使用者是否已定義到
<Plug>
JavaImport; 的映射。如果沒有,則檔案類型外掛程式會定義預設映射。這以
<LocalLeader> 開始,這讓使用者可以選擇他們想要讓檔案類型外掛程式映射開始使用的按鍵。預設值是反斜線。"<unique>" 用於在映射已存在或與現有映射重疊時發出錯誤訊息。
:noremap 用於避免使用者定義的任何其他映射干擾。您可能想要使用 ":noremap
<script>
" 來允許重新映射此腳本中定義的以
<SID>
開頭的映射。
使用者必須有機會停用檔案類型外掛程式中的映射,而無需停用所有內容。以下說明了如何針對郵件檔案類型外掛程式執行此操作的範例
" Add mappings, unless the user didn't want this.
if !exists("no_plugin_maps") && !exists("no_mail_maps")
" Quote text by inserting "> "
if !hasmapto('<Plug>MailQuote;')
vmap <buffer> <LocalLeader>q <Plug>MailQuote;
nmap <buffer> <LocalLeader>q <Plug>MailQuote;
endif
vnoremap <buffer> <Plug>MailQuote; :s/^/> /<CR>
nnoremap <buffer> <Plug>MailQuote; :.,$s/^/> /<CR>
endif
使用兩個全域變數:
no_plugin_maps 停用所有檔案類型外掛程式的映射,
no_mail_maps 停用 "mail" 檔案類型的映射
使用者命令
若要為特定檔案類型加入使用者命令,使其僅在一個緩衝區中使用,請對
:command 使用 "-buffer" 引數。範例
:command -buffer Make make %:r.s
變數
對於其所屬類型的每個緩衝區,都會引用檔案類型外掛程式。本地腳本變數
s:var 將在所有調用之間共享。如果您想要一個特定於一個緩衝區的變數,請使用本地緩衝區變數
b:var。
函數
定義函數時,只需要執行一次。但是,每次開啟具有此檔案類型的檔案時,都會引用檔案類型外掛程式。此結構確保函數只會定義一次
:if !exists("*s:Func")
: function s:Func(arg)
: ...
: endfunction
:endif
當使用者執行 ":setfiletype xyz" 時,應該復原先前檔案類型的效果。將 b:undo_ftplugin 變數設定為將復原檔案類型外掛程式中設定的命令。範例
let b:undo_ftplugin = "setlocal fo< com< tw< commentstring<"
\ .. "| unlet b:match_ignorecase b:match_words b:match_skip"
對選項名稱後面的 "<" 使用 ":setlocal" 會將選項重設為其全域值。這通常是重設選項值的最佳方式。
若要復原縮排腳本的效果,應相應設定 b:undo_indent 變數。
檔案名稱
.../ftplugin/stuff.vim .../ftplugin/stuff_foo.vim .../ftplugin/stuff/bar.vim
"stuff" 是檔案類型,"foo" 和 "bar" 是任意名稱。
檔案類型外掛程式中要使用的特殊項目摘要
<LocalLeader>
"maplocalleader" 的值,使用者將其定義為檔案類型外掛程式映射的起始按鍵。
:map <buffer>
定義一個緩衝區的區域映射。
:noremap <script>
僅重新映射此腳本中定義的以 <SID>
開頭的映射。
:setlocal 僅為目前的緩衝區設定選項。
:command -buffer 定義一個緩衝區的區域使用者命令。
exists("*s:Func") 檢查函數是否已定義。
最簡單的方法是查看範例。此命令將編輯所有預設的編譯器外掛程式
:next $VIMRUNTIME/compiler/*.vim
使用
:next 前往下一個外掛程式檔案。
關於這些檔案有兩個特別之處。第一是允許使用者覆蓋或擴充預設檔案的機制。預設檔案以
:if exists("current_compiler")
: finish
:endif
:let current_compiler = "mine"
當您編寫編譯器檔案並將其放入您的個人執行目錄(例如,Unix 上的 ~/.config/nvim/compiler)時,您可以設定 "current_compiler" 變數來使預設檔案跳過這些設定。
:CompilerSet第二個機制是針對 ":compiler!" 使用 ":set",針對 ":compiler" 使用 ":setlocal"。Vim 為此定義了 ":CompilerSet" 使用者命令。這是一個範例
CompilerSet errorformat& " use the default 'errorformat'
CompilerSet makeprg=nmake
當您為 Vim 發行版或系統範圍的執行目錄編寫編譯器外掛程式時,請使用上述機制。如果使用者外掛程式已經設定了 "current_compiler",則不會執行任何操作。
當您編寫編譯器外掛程式以覆蓋預設外掛程式的設定時,請不要檢查 "current_compiler"。此外掛程式應該最後載入,因此它應該位於
'runtimepath' 結尾的目錄中。對於 Unix,這可能是 ~/.config/nvim/after/compiler。
外掛程式可能會增長並變得相當長。啟動延遲可能會變得明顯,而您幾乎不使用該外掛程式。那麼是時候使用快速載入外掛程式了。
基本概念是外掛程式會載入兩次。第一次載入會定義提供功能的使用者命令和映射。第二次載入會定義實現該功能的功能。
快速載入意味著載入腳本兩次,這聽起來可能很奇怪。我們的意思是,它第一次載入時速度很快,將腳本的大部分內容延遲到第二次載入,而第二次載入只有在您實際使用它時才會發生。當您總是使用該功能時,它實際上會變慢!
以下範例展示了如何完成
" Vim global plugin for demonstrating quick loading
" Last Change: 2005 Feb 25
" Maintainer: Bram Moolenaar <[email protected]>
" License: This file is placed in the public domain.
if !exists("s:did_load")
command -nargs=* BNRead call BufNetRead(<f-args>)
map <F19> :call BufNetWrite('something')<CR>
let s:did_load = 1
exe 'au FuncUndefined BufNet* source ' .. expand('<sfile>')
finish
endif
function BufNetRead(...)
echo 'BufNetRead(' .. string(a:000) .. ')'
" read functionality here
endfunction
function BufNetWrite(...)
echo 'BufNetWrite(' .. string(a:000) .. ')'
" write functionality here
endfunction
當腳本第一次載入時,不會設定 "s:did_load"。 "if" 和 "endif" 之間的命令將會執行。這會以
:finish 命令結束,因此不會執行腳本的其餘部分。
第二次載入腳本時, "s:did_load" 存在,並且執行 "endif" 之後的命令。這會定義(可能很長的)BufNetRead() 和 BufNetWrite() 函數。
如果您將此腳本放入您的外掛程式目錄中,Vim 將在啟動時執行它。以下是發生的事件順序
1. 在啟動時載入腳本時,會定義 "BNRead" 命令,並映射
<F19>
按鍵。會定義
FuncUndefined 自動命令。":finish" 命令會導致腳本提早終止。
2. 使用者輸入 BNRead 命令或按下 <F19>
按鍵。將呼叫 BufNetRead() 或 BufNetWrite() 函數。
3. Vim 找不到該函數,並觸發
FuncUndefined 自動命令事件。由於模式 "BufNet*" 符合呼叫的函數,因此將執行命令 "source fname"。 "fname" 將等於腳本的名稱,無論它位於何處,因為它來自展開 "<sfile>" (請參閱
expand())。
4. 腳本再次載入, "s:did_load" 變數存在,並且定義了函數。
某些功能將在多個地方需要。當這超過幾行時,您會想要將其放入一個腳本中並從多個腳本中使用它。我們將這個腳本稱為函式庫腳本。
可以手動載入函式庫腳本,只要您避免在它已經完成載入時再次載入。您可以使用
exists() 函數來執行此操作。範例
if !exists('*MyLibFunction')
runtime library/mylibscript.vim
endif
call MyLibFunction(arg)
在這裡,您需要知道 MyLibFunction() 是在
'runtimepath' 中其中一個目錄下的 "library/mylibscript.vim" 腳本中定義的。
為了使這更容易一些,Vim 提供了自動載入機制。然後,範例如下
call mylib#myfunction(arg)
這樣簡單多了,不是嗎?Vim 將識別函數名稱,並且在未定義時,在
'runtimepath' 中搜尋腳本 "autoload/mylib.vim"。該腳本必須定義 "mylib#myfunction()" 函數。
您可以將許多其他函數放入 mylib.vim 腳本中,您可以自由地在函式庫腳本中組織您的函數。但是您必須使用函數名稱,其中 '#' 之前的部分與腳本名稱相符。否則,Vim 將不知道要載入哪個腳本。
如果您真的很有興趣並編寫了很多函式庫腳本,您可能想要使用子目錄。範例
call netlib#ftp#read('somefile')
對於 Unix,用於此的函式庫腳本可能是
~/.config/nvim/autoload/netlib/ftp.vim
其中函數的定義如下
function netlib#ftp#read(fname)
" Read the file fname through ftp
endfunction
請注意,定義函數的名稱與用於呼叫函數的名稱完全相同。並且最後一個 '#' 之前的部分與子目錄和腳本名稱完全匹配。
您可以使用相同的機制來處理變數
let weekdays = dutch#weekdays
這將載入腳本 "autoload/dutch.vim",其中應包含類似
let dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag',
\ 'donderdag', 'vrijdag', 'zaterdag']
延伸閱讀:
autoload。
Vim 腳本可以在任何系統上使用。可能沒有 tar 或 gzip 命令。如果您想要將檔案打包在一起和/或壓縮它們,建議使用 "zip" 公用程式。