Skip to content

Latest commit

 

History

History
1905 lines (1250 loc) · 108 KB

README.md

File metadata and controls

1905 lines (1250 loc) · 108 KB



vim-galore logo



version: original tranlsation: japanese translation: portuguese

Licensed under CC BY-SA 4.0.


掘金翻譯計劃

Vim from zero to hero - Vim 從入門到精通

for i in *md; do cat $i |opencc > tmp ; cat tmp> $i ;done
declare -a temp=(); for i in *md; chardetect $i |grep -iq 'GB'&& temp+=$i; for j in $temp ; iconv -f GBK -t UTF8 $j >tmp && cat tmp > $j ; unset temp ;rm tmp

簡介

什麼是 Vim?

Vim 是一個歷史悠久的文本編輯器,可以追溯到 qedBram Moolenaar 於 1991 年發佈初始版本。

該項目託管在 vim.org

獲取 Vim:用包管理器安裝或者直接到 vim.org 下載

對於 Windows 用戶,可以從 我的網盤 下載。該版本可輕易添加 pythonpython3lua 等支持,只需要安裝 python、lua 即可。

討論使用相關問題最好使用 vim_use 郵件列表或者使用 IRC(Freenode) 的 #vim 頻道。

歡迎加入我們的中文討論羣:QQ

項目在 Github 上開發,項目討論請訂閱 vim_dev 郵件列表。

通過閱讀 Why, oh WHY, do those #?@! nutheads use vi? 來對 Vim 進行大致的瞭解。

返回主目錄 ⤴️

Vim 哲學

Vim 採用模式編輯的理念,即它提供了多種模式,按鍵在不同的模式下作用不同。你可以在 普通模式 下瀏覽文件,在 插入模式 下插入文本,在 可視模式 下選擇行,在 命令模式 下執行命令等等。起初這聽起來可能很複雜,但是這有一個很大的優點:不需要通過同時按住多個鍵來完成操作,大多數時候你只需要依次按下這些按鍵即可。越常用的操作,所需要的按鍵數量越少。

和模式編輯緊密相連的概念是“操作符”和“動作”。操作符 開始一些行爲,例如:修改,刪除,或者選擇文本。之後你要用一個 動作 來指定需要操作的文本區域。比如,要改變括號內的文本,需要執行 ci( (讀做 change inner parentheses);刪除整個段落的內容,需要執行 dap (讀做:delete around paragraph)。

如果你能看見 Vim 老司機操作,你會發現他們使用 Vim 腳本語言就如同鋼琴師彈鋼琴一樣。複雜的操作只需要幾個按鍵就能完成。他們甚至不用刻意去想,因爲這已經成爲肌肉記憶了。這減少認識負荷並幫助人們專注於實際任務。

返回主目錄 ⤴️

入門

Vim 自帶一個交互式的教程,內含你需要了解的最基礎的信息,你可以通過終端運行以下命令打開教程:

$ vimtutor

不要因爲這個看上去很無聊而跳過,按照此教程多練習。你以前用的 IDE 或者其他編輯器很少是有“模式”概念的,因此一開始你會很難適應模式切換。但是你 Vim 使用的越多,肌肉記憶 將越容易形成。

Vim 基於一個 vi 克隆,叫做 Stevie,支持兩種運行模式:"compatible" 和 "nocompatible"。在兼容模式下運行 Vim 意味着使用 vi 的默認設置,而不是 Vim 的默認設置。除非你新建一個用戶的 vimrc 或者使用 vim -N 命令啓動 Vim,否則就是在兼容模式下運行 Vim!請大家不要在兼容模式下運行 Vim。

下一步

  1. 創建你自己的 vimrc
  2. 在第一週準備備忘錄
  3. 通讀基礎章節瞭解 Vim 還有哪些功能。
  4. 按需學習!Vim 是學不完的。如果你遇到了問題,先上網尋找解決方案,你的問題可能已經被解決了。Vim 擁有大量的參考文檔,知道如何利用這些參考文檔很有必要:獲取離線幫助
  5. 瀏覽附加資源

最後一個建議:使用插件之前,請先掌握 Vim 的基本操作。很多插件都只是對 Vim 自帶功能的封裝。

返回主目錄 ⤴️

精簡的 vimrc

用戶的 vimrc 配置文件可以放在 ~/.vimrc,或者爲了更好的分離放在 ~/.vim/vimrc,後者更便於通過版本控制軟件備份和同步整個配置,比方說 Github。

你可以在網上找到許多精簡的 vimrc 配置文件,我的版本可能並不是最簡單的版本,但是我的版本提供了一套我認爲良好的,非常適合入門的設置。

最終你需要閱讀完那些設置,然後自行決定需要使用哪些。:-)

精簡的 vimrc 地址:minimal-vimrc

如果你有興趣,這裏是我(原作者)的 vimrc

建議:大多數插件作者都維護不止一個插件並且將他們的 vimrc 放在 Github 上展示(通常放在叫做 "vim-config" 或者 "dotfiles" 的倉庫中),所以當你發現你喜歡的插件時,去插件維護者的 Github 主頁看看有沒有這樣的倉庫。

返回主目錄 ⤴️

我正在使用什麼樣的 Vim

使用 :version 命令將向你展示當前正在運行的 Vim 的所有相關信息,包括它是如何編譯的。

第一行告訴你這個二進制文件的編譯時間和版本號,比如:7.4。接下來的一行呈現 Included patches: 1-1051,這是補丁版本包。因此你 Vim 確切的版本號是 7.4.1051。

另一行顯示着一些像 Tiny version without GUI 或者 Huge version with GUI 的信息。很顯然這些信息告訴你當前的 Vim 是否支持 GUI,例如:從終端中運行 gvim 或者從終端模擬器中的 Vim 內運行 :gui 命令。另一個重要的信息是 TinyHuge。Vim 的特性集區分被叫做 tinysmallnormalbig and huge,所有的都實現不同的功能子集。

:version 主要的輸出內容是特性列表。+clipboard 意味這剪貼板功能被編譯支持了,-clipboard 意味着剪貼板特性沒有被編譯支持。

一些功能特性需要編譯支持才能正常工作。例如:爲了讓 :prof 工作,你需要使用 huge 模式編譯的 Vim,因爲那種模式啓用了 +profile 特性。

如果你的輸出情況並不是那樣,並且你是從包管理器安裝 Vim 的,確保你安裝了 vim-xvim-x11vim-gtkvim-gnome 這些包或者相似的,因爲這些包通常都是 huge 模式編譯的。

你也可以運行下面這段代碼來測試 Vim 版本以及功能支持:

" Do something if running at least Vim 7.4.42 with +profile enabled.
if (v:version > 704 || v:version == 704 && has('patch42')) && has('profile')
  " do stuff
endif

相關幫助:

:h :version
:h feature-list
:h +feature-list
:h has-patch

返回主目錄 ⤴️

備忘錄

爲了避免版權問題,我只貼出鏈接:

或者在 Vim 中快速打開備忘錄:vim-cheat40

返回主目錄 ⤴️

基礎

緩衝區,窗口,標籤

Vim 是一個文本編輯器。每次文本都是作爲緩衝區的一部分顯示的。每一份文件都是在他們自己獨有的緩衝區打開的,插件顯示的內容也在它們自己的緩衝區中。

緩衝區有很多屬性,比如這個緩衝區的內容是否可以修改,或者這個緩衝區是否和文件相關聯,是否需要同步保存到磁盤上。

窗口 是緩衝區上一層的視窗。如果你想同時查看幾個文件或者查看同一文件的不同位置,那樣你會需要窗口。

請別把他們叫做 分屏 。你可以把一個窗口分割成兩個,但是這並沒有讓這兩個窗口完全 分離

窗口可以水平或者豎直分割並且現有窗口的高度和寬度都是可以被調節設置的,因此,如果你需要多種窗口布局,請考慮使用標籤。

標籤頁 (標籤)是窗口的集合。因此當你想使用多種窗口布局時候請使用標籤。

簡單的說,如果你啓動 Vim 的時候沒有附帶任何參數,你會得到一個包含着一個呈現一個緩衝區的窗口的標籤。

順帶提一下,緩衝區列表是全局可見的,你可以在任何標籤中訪問任何一個緩衝區。

返回主目錄 ⤴️

已激活、已載入、已列出、已命名的緩衝區

用類似 vim file1 的命令啓動 Vim 。這個文件的內容將會被加載到緩衝區中,你現在有一個已載入的緩衝區。如果你在 Vim 中保存這個文件,緩衝區內容將會被同步到磁盤上(寫回文件中)。

由於這個緩衝區也在一個窗口上顯示,所以他也是一個已激活的緩衝區。如果你現在通過 :e file2 命令加載另一個文件,file1 將會變成一個隱藏的緩衝區,並且 file2 變成已激活緩衝區。

使用 :ls 我們能夠列出所有可以列出的緩衝區。插件緩衝區和幫助緩衝區通常被標記爲不可以列出的緩衝區,因爲那並不是你經常需要在編輯器中編輯的常規文件。通過 :ls! 命令可以顯示被放入緩衝區列表的和未被放入列表的緩衝區。

未命名的緩衝區是一種沒有關聯特定文件的緩衝區,這種緩衝區經常被插件使用。比如 :enew 將會創建一個無名臨時緩衝區。添加一些文本然後使用 :w /tmp/foo 將他寫入到磁盤,這樣這個緩衝區就會變成一個已命名的緩衝區

返回主目錄 ⤴️

參數列表

全局緩衝區列表是 Vim 的特性。在這之前的 vi 中,僅僅只有參數列表,參數列表在 Vim 中依舊可以使用。

每一個通過 shell 命令傳遞給 Vim 的文件名都被記錄在一個參數列表中。可以有多個參數列表:默認情況下所有參數都被放在全局參數列表下,但是你可以使用 :arglocal 命令去創建一個新的本地窗口的參數列表。

使用 :args 命令可以列出當前參數。使用 :next:previous:first:last 命令可以在切換在參數列表中的文件。通過使用 :argadd:argdelete 或者 :args 等命令加上一個文件列表可以改變參數列表。

偏愛緩衝區列表還是參數列表完全是個人選擇,我的印象中大多數人都是使用緩衝區列表的。

然而參數列表在有些情況下被大量使用:批處理 使用 :argdo! 一個簡單的重構例子:

:args **/*.[ch]
:argdo %s/foo/bar/ge | update

這條命令將替換掉當前目錄下以及當前目錄的子目錄中所有的 C 源文件和頭文件中的“foo”,並用“bar”代替。

相關幫助::h argument-list

返回主目錄 ⤴️

按鍵映射

使用 :map 命令家族你可以定義屬於你自己的快捷鍵。該家族的每一個命令都限定在特定的模式下。從技術上來說 Vim 自帶高達 12 中模式,其中 6 種可以被映射。另外一些命令作用於多種模式:

  遞歸     非遞歸     模式                          
:map :noremap normal, visual, operator-pending
:nmap :nnoremap normal
:xmap :xnoremap visual
:cmap :cnoremap command-line
:omap :onoremap operator-pending
:imap :inoremap insert

例如:這個自定義的快捷鍵只在普通模式下工作。

:nmap <space> :echo "foo"<cr>

使用 :nunmap <space> 可以取消這個映射。

對於更少數,不常見的模式(或者他們的組合),查看 :h map-modes

到現在爲止還好,對新手而言有一個問題會困擾他們::nmap遞歸執行的!結果是,右邊執行可能的映射。

你自定義了一個簡單的映射去輸出“Foo”:

:nmap b :echo "Foo"<cr>

但是如果你想要映射 b (回退一個單詞)的默認功能到一個鍵上呢?

:nmap a b

如果你敲擊a,我們期望着光標回退到上一個單詞,但是實際情況是“Foo”被輸出到命令行裏!因爲在右邊,b 已經被映射到別的行爲上了,換句話說就是 :echo "Foo"<cr>

解決此問題的正確方法是使用一種 非遞歸 的映射代替:

:nnoremap a b

經驗法則:除非遞歸是必須的,否則總是使用非遞歸映射。

通過不給一個右值來檢查你的映射。比如:nmap 顯示所以普通模式下的映射,:nmap <leader> 顯示所有以 <leader> 鍵開頭的普通模式下的映射。

如果你想禁止用標準映射,把他們映射到特殊字符 <nop> 上,例如::noremap <left> <nop>

相關幫助:

:h key-notation
:h mapping
:h 05.3

返回主目錄 ⤴️

映射前置鍵

映射前置鍵(Leader 鍵)本身就是一個按鍵映射,默認爲 \。我們可以通過在 map 中調用 <leader> 來爲把它添加到其他按鍵映射中。

nnoremap <leader>h :helpgrep<space>

這樣,我們只需要先按 \ 然後連續按 \h 就可以激活這個映射 :helpgrep<space>。如果你想通過先按 空格 鍵來觸發,只需要這樣做:

let mapleader = ' '
nnoremap <leader>h :helpgrep<space>

另外,還有一個叫 <localleader> 的,可以把它理解爲局部環境中的 <leader>,默認值依然爲 \。當我們需要只對某一個條件下(比如,特定文件類型的插件)的緩衝區設置特別的 <leader> 鍵,那麼我們就可以通過修改當前環境下的 <localleader> 來實現。

注意:如果你打算設置 Leader 鍵,請確保在設置按鍵映射之前,先設置好 Leader 鍵。如果你先設置了含有 Leader 鍵的映射,然後又修改了 Leader 鍵,那麼之前映射內的 Leader 鍵是不會因此而改變的。你可以通過執行 :nmap <leader> 來查看普通模式中已綁定給 Leader 鍵的所有映射。

請參閱 :h mapleader:h maploacalleader 來獲取更多幫助。

返回主目錄 ⤴️

寄存器

寄存器就是存儲文本的地方。我們常用的「複製」操作就是把文本存儲到寄存器,「 粘貼」 操作就是把文本從寄存器中讀出來。順便,在 Vim 中複製的快捷鍵是 y,粘貼的快捷鍵是 p

Vim 爲我們提供瞭如下的寄存器:

類型 標識 讀寫者 是否爲只讀 包含的字符來源
Unnamed " vim 最近一次的複製或刪除操作 (d, c, s, x, y)
Numbered 09 vim 寄存器 0: 最近一次複製。寄存器 1: 最近一次刪除。寄存器 2: 倒數第二次刪除,以此類推。對於寄存器 19,他們其實是隻讀的最多包含 9 個元素的隊列。這裏的隊列即爲數據類型 queue
Small delete - vim 最近一次行內刪除
Named az, AZ 用戶 如果你通過複製操作存儲文本至寄存器 a,那麼 a 中的文本就會被完全覆蓋。如果你存儲至 A,那麼會將文本添加給寄存器 a,不會覆蓋之前已有的文本
Read-only :.% vim :: 最近一次使用的命令,.: 最近一次添加的文本,%: 當前的文件名
Alternate buffer # vim 大部分情況下,這個寄存器是當前窗口中,上一次訪問的緩衝區。請參閱 :h alternate-file 來獲取更多幫助
Expression = 用戶 複製 VimL 代碼時,這個寄存器用於存儲代碼片段的執行結果。比如,在插入模式下複製 <c-r>=5+5<cr>,那麼這個寄存器就會存入 10
Selection +* vim *+剪貼板 寄存器
Drop ~ vim 最後一次拖拽添加至 Vim 的文本(需要 "+dnd" 支持,暫時只支持 GTK GUI。請參閱 :help dnd:help quote~
Black hole _ vim 一般稱爲黑洞寄存器。對於當前操作,如果你不希望在其他寄存器中保留文本,那就在命令前加上 _。比如,"_dd 命令不會將文本放到寄存器 "1+*
Last search pattern / vim 最近一次通過 /?:global 等命令調用的匹配條件

只要不是隻讀的寄存器,用戶都有權限修改它的內容,比如:

:let @/ = 'register'

這樣,我們按 n 的時候就會跳轉到單詞"register" 出現的地方。

有些時候,你的操作可能已經修改了寄存器,而你沒有察覺到。請參閱 :h registers 獲取更多幫助。

上面提到過,複製的命令是 y,粘貼的命令是 p 或者 P。但請注意,Vim 會區分「字符選取」與「行選取」。請參閱 :h linewise 獲取更多幫助。

行選取: 命令 yyY 都是複製當前行。這時移動光標至其他位置,按下 p 就可以在光標下方粘貼複製的行,按下 P 就可以在光標上方粘貼至複製的行。

字符選取: 命令 0yw 可以複製第一個單詞。這時移動光標至其他位置,按下 p 就可以在當前行、光標後的位置粘貼單詞,按下 P 就可以在當前行、光標前的位置粘貼單詞。

將文本存到指定的寄存器中: 命令 "aY 可以將當前行復制,並存儲到寄存器 a 中。這時移動光標至其他位置,通過命令 "AY 就可以把這一行的內容擴展到寄存器 a 中,而之前存儲的內容也不會丟失。

爲了便於理解和記憶,建議大家現在就試一試上面提到的這些操作。操作過程中,你可以隨時通過 :reg 來查看寄存器的變化。

有趣的是: 在 Vim 中,y 是複製命令,源於單詞 "yanking"。而在 Emacs 中,"yanking" 代表的是粘貼(或者說,重新插入剛纔刪掉的內容),而並不是複製。

返回主目錄 ⤴️

範圍

範圍 (Ranges) 其實很好理解,但很多 Vim 用戶的理解不到位。

  • 很多命令都可以加一個數字,用於指明操作範圍
  • 範圍可以是一個行號,用於指定某一行
  • 範圍也可以是一對通過 ,; 分割的行號
  • 大部分命令,默認只作用於當前行
  • 只有 :write:global 是默認作用於所有行的

範圍的使用是十分直觀的。以下爲一些例子(其中,:d:delete 的縮寫):

命令 操作的行
:d 當前行
:.d 當前行
:1d 第一行
:$d 最後一行
:1,$d 所有行
:%d 所有行(這是 1,$ 的語法糖)
:.,5d 當前行至第 5 行
:,5d 同樣是當前行至第 5 行
:,+3d 當前行及接下來的 3 行
:1,+3d 第一行至當前行再加 3 行
:,-3d 當前行及向上的 3 行(Vim 會彈出提示信息,因爲這是一個保留的範圍)
:3,'xdelete 第三行至標註 爲 x 的那一行
:/^foo/,$delete 當前行以下,以字符 "foo" 開頭的那一行至結尾
:/^foo/+1,$delete 當前行以下,以字符 "foo" 開頭的那一行的下一行至結尾

需要注意的是,; 也可以用於表示範圍。區別在於,a,bb 是以當前行作爲參考的。而 a;bb 是以 a 行作爲參考的。舉個例子,現在你的光標在第 5 行。這時 :1,+1d 會刪除第 1 行至第 6 行,而 :1;+1d 會刪除第 1 行和第 2 行。

如果你想設置多個尋找條件,只需要在條件前加上 /,比如:

:/foo//bar//quux/d

這就會刪除當前行之後的某一行。定位方式是,先在當前行之後尋找第一個包含 "foo" 字符的那一行,然後在找到的這一行之後尋找第一個包含 "bar" 字符的那一行,然後再在找到的這一行之後尋找第一個包含 "quux" 的那一行。刪除的就是最後找到的這一行。

有時,Vim 會在命令前自動添加範圍。舉個例子,如果你先通過 V 命令進入行選取模式,選中一些行後按下 : 進入命令模式,這時候你會發現 Vim 自動添加了 '<,'> 範圍。這表示,接下來的命令會使用之前選取的行號作爲範圍。但如果後續命令不支持範圍,Vim 就會報錯。爲了避免這樣的情況發生,有些人會設置這樣的按鍵映射::vnoremap foo :<c-u>command,組合鍵 Ctrl + u 可以清除當前命令行中的內容。

另一個例子是在普通模式中按下 !!,命令行中會出現 :.!。如果這時你如果輸入一個外部命令,那麼當前行的內容就會被這個外部命令的輸出替換。你也可以通過命令 :?^$?+1,/^$/-1!ls 把當前段落的內容替換成外部命令 ls 的輸出,原理是向前和向後各搜索一個空白行,刪除這兩個空白行之間的內容,並將外部命令 ls 的輸出放到這兩個空白行之間。

請參閱以下兩個命令來獲取更多幫助:

:h cmdline-ranges
:h 10.3

返回主目錄 ⤴️

標註

你可以使用標註功能來標記一個位置,也就是記錄文件某行的某個位置。

標註 設置者 使用
a-z 用戶 僅對當前的一個文件生效,也就意味着只可以在當前文件中跳轉
A-Z 用戶 全局標註,可以作用於不同文件。大寫標註也稱爲「文件標註」。跳轉時有可能會切換到另一個緩衝區
0-9 viminfo 0 代表 viminfo 最後一次被寫入的位置。實際使用中,就代表 Vim 進程最後一次結束的位置。1 代表 Vim 進程倒數第二次結束的位置,以此類推

如果想跳轉到指定的標註,你可以先按下 ' / g' 或者 ` / g` 然後按下標註名。

如果你想定義當前文件中的標註,可以先按下 m 再按下標註名。比如,按下 mm 就可以把當前位置標註爲 m。在這之後,如果你的光標切換到了文件的其他位置,只需要通過 'm 或者 \m即可回到剛纔標註的行。區別在於,'m會跳轉回被標記行的第一個非空字符,而`m會跳轉回被標記行的被標記列。根據 viminfo 的設置,你可以在退出 Vim 的時候保留小寫字符標註。請參閱:h viminfo-'` 來獲取更多幫助。

如果你想定義全局的標註,可以先按下 m 再按下大寫英文字符。比如,按下 mM 就可以把當前文件的當前位置標註爲 M。在這之後,就算你切換到其他的緩衝區,依然可以通過 'M\M` 跳轉回來。

關於跳轉,還有以下的方式:

按鍵 跳轉至
'[ `[ 上一次修改或複製的第一行或第一個字符
'] `] 上一次修改或複製的最後一行或最後一個字符
'< `< 上一次在可視模式下選取的第一行或第一個字符
'> `> 上一次在可視模式下選取的最後一行或最後一個字符
'' `' 上一次跳轉之前的光標位置
'" `" 上一次關閉當前緩衝區時的光標位置
'^ `^ 上一次插入字符後的光標位置
'. `. 上一次修改文本後的光標位置
'( `( 當前句子的開頭
') `) 當前句子的結尾
'{ `{ 當前段落的開頭
'} `} 當前段落的結尾

標註也可以搭配 範圍 一起使用。前面提到過,如果你在可視模式下選取一些文本,然後按下 :,這時候你會發現命令行已經被填充了 :'<,'>。對照上面的表格,現在你應該明白了,這段代表的就是可視模式下選取的範圍。

請使用 :marks 命令來顯示所有的標註,參閱 :h mark-motions 來獲取關於標註的更多幫助。

返回主目錄 ⤴️

補全

Vim 在插入模式中爲我們提供了多種補全方案。如果有多個補全結果,Vim 會彈出一個菜單供你選擇。

常見的補全有標籤、項目中引入的模塊或庫中的方法名、文件名、字典及當前緩衝區的字段。

針對不同的補全方案,Vim 爲我們提供了不同的按鍵映射。這些映射都是在插入模式中通過 Ctrl + x 來觸發:

映射 類型 幫助文檔
<c-x><c-l> 整行 :h i^x^l
<c-x><c-n> 當前緩衝區中的關鍵字 :h i^x^n
<c-x><c-k> 字典(請參閱 :h 'dictionary')中的關鍵字 :h i^x^k
<c-x><c-t> 同義詞字典(請參閱 :h 'thesaurus')中的關鍵字 :h i^x^t
<c-x><c-i> 當前文件以及包含的文件中的關鍵字 :h i^x^i
<c-x><c-]> 標籤 :h i^x^]
<c-x><c-f> 文件名 :h i^x^f
<c-x><c-d> 定義或宏定義 :h i^x^d
<c-x><c-v> Vim 命令 :h i^x^v
<c-x><c-u> 用戶自定義補全(通過 'completefunc' 定義) :h i^x^u
<c-x><c-o> Omni Completion(通過 'omnifunc' 定義) :h i^x^o
<c-x>s 拼寫建議 :h i^Xs

儘管用戶自定義補全與 Omni Completion 是不同的,但他們做的事情基本一致。共同點在於,他們都是一個監聽當前光標位置的函數,返回值爲一系列的補全建議。用戶自定義補全是由用戶定義的,基於用戶的個人用途,因此你可以根據自己的喜好和需求隨意定製。而 Omni Completion 是針對文件類型的補全,比如在 C 語言中補全一個結構體(struct)的成員(members),或者補全一個類的方法,因而它通常都是由文件類型插件設置和調用的。

如果你設置了 'complete' 選項,那麼你就可以在一次操作中採用多種補全方案。這個選項默認包含了多種可能性,因此請按照自己的需求來配置。你可以通過 <c-n> 來調用下一個補全建議,或通過 <c-p> 來調用上一個補全建議。當然,這兩個映射同樣可以直接調用補全函數。請參閱 :h i^n:h 'complete' 來獲得更多幫助。

如果你想配置彈出菜單的行爲,請一定要看一看 :h 'completeopt' 這篇幫助文檔。默認的配置已經不錯了,但我個人(原作者)更傾向於把 "noselect" 加上。

請參閱以下文檔獲取更多幫助:

:h ins-completion
:h popupmenu-keys
:h new-omni-completion

返回主目錄 ⤴️

動作,操作符,文本對象

動作也就是指移動光標的操作,你肯定很熟悉 hjkl,以及 wb。但其實,/ 也是一個動作。他們都可以搭配數字使用,比如 2?the<cr> 可以將光標移動到倒數第二個 "the" 出現的位置。

以下會列出一些常用的動作。你也可以通過 :h navigation 來獲取更多的幫助。

操作符是對某個區域文本執行的操作。比如,d~gU> 都是操作符。這些操作符既可以在普通模式下使用,也可以在可視模式下使用。在普通模式中,順序是先按操作符,再按動作指令,比如 >j。在可是模式中,選中區域後直接按操作符就可以,比如 Vjd

與動作一樣,操作符也可以搭配數字使用,比如 2gUw 可以將當前單詞以及下一個單詞轉成大寫。由於動作和操作符都可以搭配數字使用,因此 2gU2w 與執行兩次 gU2w 效果是相同的。

請參閱 :h operator 來查看所有的操作符。你也可以通過 :set tildeop 命令把 ~ 也變成一個操作符

值得注意的是,動作是單向的,而文本對象是雙向的。文本對象不僅作用於符號(比如括號、中括號和大括號等)標記的範圍內,也作用於整個單詞、整個句子等其他情況。

文本對象不能用於普通模式中移動光標的操作,因爲光標還沒有智能到可以向兩個方向同時跳轉。但這個功能可以在可視模式中實現,因爲在對象的一端選中的情況下,光標只需要跳轉到另一端就可以了。

文本對象操作一般用 ia 加上對象標識符操作,其中 i 表示在對象內(英文 inner)操作,a 表示對整個對象(英文 around)操作,這時開頭和結尾的空格都會被考慮進來。舉個例子,diw 可以刪除當前單詞,ci( 可以改變括號中的內容。

文本對象同樣可以與數字搭配使用。比如,像 ((( ))) 這樣的文本,假如光標位於最內層的括號上或最內層的括號內,那麼 d2a( 將會刪除從最內層開始的兩對括號,以及他們之間的所有內容。其實,d2a( 這個操作等同於 2da(。在 Vim 的命令中,如果有兩處都可以接收數字作爲參數,那麼最終結果就等同於兩個數字相乘。在這裏,da( 都是可以接收參數的,一個參數是 1,另一個是 2,我們可以把它們相乘然後放到最前面。

請參閱 :h text-objects 來獲取更多關於文本對象的幫助。

返回主目錄 ⤴️

自動命令

在特定的情況下,Vim 會傳出事件。如果你想針對這些事件執行回調方法,那麼就需要用到自動命令這個功能。

如果沒有了自動命令,那你基本上是用不了 Vim 的。自動命令一直都在執行,只是很多時候你沒有注意到。不信的話,可以執行命令 :au ,不要被結果嚇到,這些是當前有效的所有自動命令。

請使用 :h {event} 來查看 Vim 中所有事件的列表,你也可以參考 :h autocmd-events-abc 來獲取關於事件的更多幫助。

一個很常用的例子,就是針對文件類型執行某些設置:

autocmd FileType ruby setlocal shiftwidth=2 softtabstop=2 comments-=:#

但是緩衝區是如何知道當前的文件中包含 Ruby 代碼呢?這其實是另一個自動命令檢測的到的,然後把文件類型設置成爲 Ruby,這樣就觸發了上面的 FileType 事件。

在配置 vimrc 的時候,一般第一行加進去的就是 filetype on。這就意味着,Vim 啓動時會讀取 filetype.vim 文件,然後根據文件類型來觸發相應的自動命令。

如果你勇於嘗試,可以查看下 :e $VIMRUNTIME/filetype.vim,然後在輸出中搜索 "Ruby"。這樣,你就會發現其實 Vim 只是通過文件擴展名 .rb 判斷某個文件是不是 Ruby 的。

注意:對於相同事件,如果有多個自動命令,那麼自動命令會按照定義時的順序執行。通過 :au 就可以查看它們的執行順序。

au BufNewFile,BufRead *.rb,*.rbw setf ruby

BufNewFileBufRead 事件是被寫在 Vim 源文件中的。因此,每當你通過 :e 或者類似的命令打開文件,這兩個事件都會觸發。然後,就是讀取 filetype.vim 文件來判斷打開的文件類型。

簡單來說,事件和自動命令在 Vim 中的應用十分廣泛。而且,Vim 爲我們留出了一些易用的接口,方便用戶配置適合自己的事件驅動回調。

請參閱 :h autocommand 來獲取更多幫助

返回主目錄 ⤴️

變更歷史,跳轉歷史

在 Vim 中,用戶最近 100 次的文字改動都會被保存在變更歷史中。如果在同一行有多個小改動,那麼 Vim 會把它們合併成一個。儘管內容改動會合並,但作用的位置還是會只記錄下最後一次改動的位置。

在你移動光標或跳轉的時候,每一次的移動或跳轉前的位置會被記錄到跳轉歷史中。類似地,跳轉歷史也可以最多保存 100 條記錄。對於每個窗口,跳轉記錄是獨立的。但當你分離窗口時(比如使用 :split 命令),跳轉歷史會被複制過去。

Vim 中的跳轉命令,包括 '`G/?nN%()[[]]{}:s:tagLMH 以及開始編輯一個新文件的命令。

列表 顯示所有條目 跳轉到上一個位置 跳轉到下一個位置
跳轉歷史 :jumps [count]<c-o> [count]<c-i>
變更歷史 :changes [count]g; [count]g,

如果你執行第二列的命令顯示所有條目,這時 Vim 會用 > 標記來爲你指示當前位置。通常這個標記位於 1 的下方,也就代表最後一次的位置。

如果你希望關閉 Vim 之後還保留這些條目,請參閱 :h viminfo-' 來獲取更多幫助。

注意:上面提到過,最後一次跳轉前的位置也會記錄在標註中,也可以通過連按 ``'' 跳轉到那個位置

請參閱以下兩個命令來獲取更多幫助:

:h changelist
:h jumplist

返回主目錄 ⤴️

內容變更歷史記錄

Vim 會記錄文本改變之前的狀態。因此,你可以使用「撤銷」操作 u 來取消更改,也可以通過「重做」操作 Ctrl + r 來恢復更改。

值得注意的是,Vim 採用 tree 數據結構來存儲內容變更的歷史記錄,而不是採用 queue。你的每次改動都會成爲存儲爲樹的節點。而且,除了第一次改動(根節點),之後的每次改動都可以找到一個對應的父節點。每一個節點都會記錄改動的內容和時間。其中,「分支」代表從任一節點到根節點的路徑。當你進行了撤銷操作,然後又輸入了新的內容,這時候就相當於創建了分支。這個原理和 git 中的 branch(分支)十分類似。

考慮以下這一系列按鍵操作:

ifoo<esc>
obar<esc>
obaz<esc>
u
oquux<exc>

那麼現在,Vim 中會顯示三行文本,分別是 "foo"、"bar" 和 "quux"。這時候,存儲的樹形結構如下:

     foo(1)
       /
    bar(2)
   /      \
baz(3)   quux(4)

這個樹形結構共包含四次改動,括號中的數字就代表時間順序。

現在,我們有兩種方式遍歷這個樹結構。一種叫「按分支遍歷」,一種叫「按時間遍歷」。

撤銷 u 與重做 Ctrl + r 操作是按分支遍歷。對於上面的例子,現在我們有三行字符。這時候按 u 會回退到 "bar" 節點,如果再按一次 u 則會回退到 "foo" 節點。這時,如果我們按下 Ctrl + r 就會前進至 "bar" 節點,再按一次就回前進至 "quux" 節點。在這種方式下,我們無法訪問到兄弟節點(即 "baz" 節點)。

與之對應的是按時間遍歷,對應的按鍵是 g-g+。對於上面的例子,按下 g- 會首先回退到 "baz" 節點。再次按下 g- 會回退到 "bar" 節點。

命令/按鍵 執行效果
[count]u:undo [count] 回退到 [count] 次改動之前
[count]<c-r>:redo [count] 重做 [count] 次改動
U 回退至最新的改動
[count]g-:earlier [count]? 根據時間回退到 [count] 次改動之前。"?" 爲 "s"、"m"、"h"、"d" 或 "f"之一。例如,:earlier 2d 會回退到兩天之前。:earlier 1f 則會回退到最近一次文件保存時的內容
[count]g+:later [count]? 類似 g-,但方向相反

內容變更記錄會儲存在內存中,當 Vim 退出時就會清空。如果需要持久化存儲內容變更記錄,請參閱備份文件,交換文件,撤銷文件以及viminfo文件的處理章節的內容。

如果你覺得這一部分的內容難以理解,請參閱 undotree,這是一個可視化管理內容變更歷史記錄的插件。類似的還有 vim-mundo

請參閱以下鏈接獲取更多幫助:

:h undo.txt
:h usr_32

返回主目錄 ⤴️

全局位置信息表,局部位置信息表

在某一個動作返回一系列「位置」的時候,我們可以利用「全局位置信息表」和「局部位置信息表」來存儲這些位置信息,方便以後跳轉回對應的位置。每一個存儲的位置包括文件名、行號和列號。

比如,編譯代碼是出現錯誤,這時候我們就可以把錯誤的位置直接顯示在全局位置信息表,或者通過外部抓取工具使位置顯示在局部位置信息表中。

儘管我們也可以把這些信息顯示到一個空格緩衝區中,但用這兩個信息表顯示的好處在於接口調用很方便,而且也便於瀏覽輸出。

Vim 中,全局位置信息表只能有一個,但每一個窗口都可以有自己的局部位置信息表。這兩個信息表的外觀看上去很類似,但在操作上會稍有不同。

以下爲兩者的操作比較:

動作 全局位置信息表 局部位置信息表
打開窗口 :copen :lopen
關閉窗口 :cclose :lclose
下一個條目 :cnext :lnext
上一個條目 :cprevious :lprevious
第一個條目 :cfirst :lfirst
最後一個條目 :clast :llast

請參閱 :h :cc 以及底下的內容,來獲取更多命令的幫助。

應用實例: 如果我們想用 grep 遞歸地在當前文件夾中尋找某個關鍵詞,然後把輸出結果放到全局位置信息表中,只需要這樣:

:let &grepprg = 'grep -Rn $* .'
:grep! foo
<grep output - hit enter>
:copen

執行了上面的代碼,你就能看到所有包含字符串 "foo" 的文件名以及匹配到的相關字段都會顯示在全局位置信息表中。

返回主目錄 ⤴️

你可以在 Vim 中錄製一系列按鍵,並把他們存儲到寄存器中。對於一些需要臨時使用多次的一系列操作,把它們作爲宏保存起來會顯著地提升效率。對於一些複雜的操作,建議使用 Vim 腳本來實現。

  • 首先,按下 q,然後按下你想要保存的寄存器,任何小寫字母都可以。比如我們來把它保存到 q 這個寄存器中。按下 qq,你會發現命令行裏已經顯示了 "recording @q"。
  • 如果你已經錄製完成,那麼只需要再按一次 q 就可以結束錄製。
  • 如果你想調用剛纔錄製的宏,只需要 [count]@q
  • 如果你想調用上一次使用的宏,只需要 [count]@@

實例1

一個插入字符串 "abc" 後換行的宏,重複調用十次:

qq
iabc<cr><esc>
q
10@q

(對於上面這個功能,你同樣可以通過如下的按鍵: oabc 然後 ESC 然後 10. 來實現)。

實例2

一個在每行前都加上行號的宏。從第一行開始,行號爲 1,後面依次遞增。我們可以通過 Ctrl + a 來實現遞增的行號,在定義宏的時候,它會顯示成 ^A

qq
0yf jP0^A
q
1000 @q

這裏能實現功能,是因爲我們假定了文件最多隻有 1000 行。但更好的方式是使用「遞歸」宏,它會一直執行,知道不能執行爲止:

qq
0yf jP0^A@q
q
@q

(對於上面這個插入行號的功能,如果你不願意使用宏,同樣可以通過這段按鍵操作來實現::%s/^/\=line('.') . '. ')。

這裏向大家展示瞭如何不用宏來達到相應的效果,但要注意,這些不用宏的實現方式只適用於這些簡單的示例。對於一些比較複雜的自動化操作,你確實應該考慮使用宏。

請參閱以下文檔獲取更多幫助:

:h recording
:h 'lazyredraw'

返回主目錄 ⤴️

顏色主題

顏色主題可以把你的 Vim 變得更漂亮。Vim 是由多個組件構成的,我們可以給每一個組件都設置不同的文字顏色、背景顏色以及文字加粗等等。比如,我們可以通過這個命令來設置背景顏色:

:highlight Normal ctermbg=1 guibg=red

執行後你會發現,現在背景顏色變成紅色了。請參閱 :h :highlight 來獲取更多幫助。

其實,顏色主題就是一系列的 :highlight 命令的集合。

事實上,大部分顏色主題都包含兩套配置。一套適用於例如 xterm 和 iTerm 這樣的終端環境(使用前綴 cterm),另一套適用於例如 gvim 和 MacVim 的圖形界面環境(使用前綴 gui)。對於上面的例子,ctermbg 就是針對終端環境的,而 guibg 就是針對圖形界面環境的。

如果你下載了一個顏色主題,並且在終端環境中打開了 Vim,然後發現顯示的顏色與主題截圖中差別很大,那很可能是配置文件只設置了圖形界面環境的顏色。反之同理,如果你使用的是圖形界面環境,發現顯示顏色有問題,那就很可能是配置文件只設置了終端環境的顏色。

第二種情況(圖形界面環境的顯示問題)其實不難解決。如果你使用的是 Neovim 或者 Vim 7.4.1830 的後續版本,可以通過打開真彩色設置來解決顯示問題。這就可以讓終端環境的 Vim 使用 GUI 的顏色定義,但首先,你要確認一下你的終端環境和環境內的組件(比如 tmux)是否都支持真彩色。可以看一下這篇文檔,描述的十分詳細。

請參閱以下文檔或鏈接來獲取更多幫助:

返回主目錄 ⤴️

摺疊

每一部分文字(或者代碼)都會有特定的結構。對於存在結構的文字和代碼,也就意味着它們可以按照一定的邏輯分割成不同區域。Vim 中的摺疊功能,就是按照特定的邏輯把文字和代碼摺疊成一行,並顯示一些簡短的描述。摺疊功能涉及到很多操作,而且摺疊功能可以嵌套使用。

在 Vim 中,有以下 6 中摺疊類型:

摺疊方式 概述
diff 在「比較窗口」中摺疊未改變的文本
expr 使用 'foldexpr' 來創建新的摺疊邏輯
indent 基於縮進摺疊
manual 使用 zfzF:fold 來自定義摺疊
marker 根據特定的文本標記摺疊(通常用於代碼註釋)
syntax 根據語法摺疊,比如摺疊 if 代碼塊

注意:摺疊功能可能會顯著地影響性能。如果你在使用摺疊功能的時候出現了打字卡頓之類的問題,請考慮使用 FastFold 插件。這個插件可以讓 Vim 按需更新摺疊內容,而不是一直調用。

請參閱以下文檔獲取更多幫助:

:h usr_28
:h folds

會話

如果你保存了當前的「視圖」(請參閱 :h :mkview),那麼當前窗口、配置和按鍵映射都會被保存下來(請參閱 :h :loadview)。

「會話」就是存儲所有窗口的相關設置,以及全局設置。簡單來說,就是給當前的 Vim 運行實例拍個照,然後把相關信息存儲到會話文件中。存儲之後的改動就不會在會話文件中顯示,你只需要在改動後更新一下會話文件就可以了。

你可以把當前工作的「項目」存儲起來,然後可以在不同的「項目」之間切換。

現在就來試試吧。打開幾個窗口和標籤,然後執行 :mksession Foo.vim。如果你沒有指定文件名,那就會默認保存爲 Session.vim。這個文件會保存在當前的目錄下,你可以通過 :pwd 來顯示當前路徑。重啓 Vim 之後,你只需要執行 :source Foo.vim,就可以恢復剛纔的會話了。所有的緩衝區、窗口布局、按鍵映射以及工作路徑都會恢復到保存時的狀態。

其實 Vim 的會話文件就只是 Vim 命令的集合。你可以通過命令 :vs Foo.vim 來看看會話文件中究竟有什麼。

你可以決定 Vim 會話中究竟要保存哪些配置,只需要設置一下 'sessionoptions' 就可以了。

爲了方便開發,Vim 把最後一次調用或寫入的會話賦值給了一個內部變量 v:this_session

請參閱以下文檔來獲取更多幫助:

:h Session
:h 'sessionoptions'
:h v:this_session

局部化

以上提到的很多概念,都有一個局部化(非全局)的版本:

全局 局部 作用域 幫助文檔
:set :setlocal 緩衝區或窗口 :h local-options
:map :map <buffer> 緩衝區 :h :map-local
:autocmd :autocmd * <buffer> 緩衝區 :h autocmd-buflocal
:cd :lcd 窗口 :h :lcd
:<leader> :<localleader> 緩衝區 :h maploacalleader

變量也有不同的作用域,詳細內容請參考 Vim scripting 的文檔

用法

獲取離線幫助

Vim 自帶了一套很完善的幫助文檔,它們是一個個有固定排版格式的文本文件,通過標籤可以訪問這些文件的特定位置。

在開始之前先讀一下這個章節::help :help。執行這個命令以後會在新窗口打開 $VIMRUNTIME/doc/helphelp.txt 文件並跳轉到這個文件中 :help 標籤的位置。

一些關於幫助主題的簡單規則:

  • 用單引號把文本包起來表示選項,如::h 'textwidth'
  • 以小括號結尾表示 VimL 函數,如::h reverse()
  • 以英文冒號開頭表示命令,如::h :echo

使用快捷鍵 <c-d> (這是 ctrl+d)來列出所有包含你當前輸入的內容的幫助主題。如::h tab<c-d> 會列出所有包含 tab 主題,從 softtabstopsetting-guitablabel (譯者注:根據安裝的插件不同列出的選項也會不同)。

你想查看所有的 VimL 方法嗎?很簡單,只要輸入::h ()<c-d> 就可以了。你想查看所有與窗口相關的函數嗎?輸入 :h win*()<c-d>

相信你很快就能掌握這些技巧,但是在剛開始的時候,你可能對於該通過什麼進行查找一點線索都沒有。這時你可以想象一些與要查找的內容相關的關鍵字,再讓 :helpgrep 來幫忙。

:helpgrep backwards

上面的命令會在所有的幫助文件中搜索“backwards”,然後跳轉到第一個匹配的位置。所有的匹配位置都會被添加到全局位置信息表,用 :cp / :cn 可以在匹配位置之間進行切換。或者用 :copen 命令來打開全局位置信息表,將光標定位到你想要的位置,再按 回車就可以跳轉到該匹配項。詳細說明請參考 :h quickfix

獲取離線幫助(補充)

這個列表最初發表在 vim_dev,由 @chrisbra 編輯的,他是 Vim 開發人員中最活躍的一個。

經過一些微小的改動後,重新發布到了這裏。


如果你知道你想要找什麼,使用幫助系統的搜索會更簡單一些,因爲搜索出的主題都帶有固定的格式。

而且幫助系統中的主題包含了你當前使用的 Vim 版本的所特有特性,而網上那些已經過時或者是早期發佈的話題是不會包含這些的。

因此學習使用幫助系統以及它所用的語言是很有必要的。這裏是一些例子(不一定全,我有可能忘了一些什麼)。

(譯者注:下面列表中提及的都是如何指定搜索主題以便快速準確的找到你想要的幫助)

  1. 選項要用單引號引起來。用 :h 'list' 來查看列表選項幫助。只有你明確的知道你要找這麼一個選項的時候纔可以這麼做,不然的話你可以用 :h options.txt 來打開所有選項的幫助頁面,再用正則表達式進行搜索,如:/width。某些選項有它們自己的命名空間,如::h cpo-a:h cpo-A:h cpo-b 等等。

  2. 普通模式的命令不能用冒號作爲前綴。使用 :h gt 來轉到“gt”命令的幫助頁面。

  3. 正則表達式以“/”開頭,所以 :h /\+ 會帶你到正則表達式中量詞“+”的幫助頁面。

  4. 組合鍵經常以一個字母開頭表示它們可以在哪些模式中使用。如::h i_CTRL-X 會帶你到插入模式下的 CTRL-X 命令的用法幫助頁面,這是一個自動完成類的組合鍵。需要注意的是某些鍵是有固定寫法的,如Control鍵寫成CTRL。還有,查找普通模式下的組合鍵幫助時,可以省略開頭的字母“n”,如::h CTRL-A。而 :h c_CTRL-A(譯者注:原文爲 :h c_CRTL-R,感覺改爲 A 更符合上下文語境)會解釋 CTRL-A 在命令模式下輸入命令時的作用;:h v_CTRL-A 說的是在可見模式下把光標所在處的數字加 1;:h g_CTRL-A 則說的是 g 命令(你需要先按 "g" 的命令)。這裏的 "g" 代表一個普通的命令,這個命令總是與其它的按鍵組合使用才生效,與 "z" 開始的命令相似。

  5. 寄存器是以 "quote" 開頭的。如::h quote: (譯者注:原文爲:h quote,感覺作者想以":"來舉例)來查看關於":"寄存器的說明。

  6. 關於 Vim 腳本(VimL)的幫助都在 :h eval.txt 裏。而某些方面的語言可以使用 :h expr-X 獲取幫助,其中的 'X' 是一個特定的字符,如::h expr-! 會跳轉到描述 VimL 中'!'(非)的章節。另外一個重要提示,可以使用 :h function-list 來查看所有函數的簡要描述,列表中包括函數名和一句話描述。

  7. 關於映射都可以在 :h map.txt 中找到。通過 :h mapmode-i 來查找 :imap 命令的相關信息;通過 :h map-topic 來查找專門針對映射的幫助(譯者注:topic 爲一個佔位符,正如上面的字符 'X' 一樣,在實際使用中需要替換成相應的單詞)(如::h :map-local 查詢本地buffer的映射,:h map-bar 查詢如何在映射中處理'|')。

  8. 命令定義用 "command-" 開頭,如用 :h command-bar 來查看自定義命令中'!'的作用。

  9. 窗口管理類的命令是以 "CTRL-W" 開頭的,所以你可以用 :h CTRL-W_* 來查找相應的幫助(譯者注:'*'同樣爲佔位符)(如::h CTRL-W_p 查看切換到之前訪問的窗口命令的解釋)。如果你想找窗口處理的命令,還可以通過訪問 :h windows.txt 並逐行向下瀏覽,所有窗口管理的命令都在這裏了。

  10. 執行類的命令以":"開頭,即::h :s 講的是 ":s" 命令。

  11. 在輸入某個話題時按 CTRL-D,讓 Vim 列出所有的近似項輔助你輸入。

  12. :helpgrep 在所有的幫助頁面(通常還包括了已安裝的插件的幫助頁面)中進行搜索。參考 :h :helpgrep 來了解如何使用。當你搜索了一個話題之後,所有的匹配結果都被保存到了全局位置信息表(或局部位置信息表)當中,可以通過 :copen:lopen 打開。在打開的窗口中可能通過 / 對搜索結果進行進一步的過濾。

  13. :h helphelp 裏介紹瞭如何使用幫助系統。

  14. 用戶手冊。它採用了一種對初學者更加友好的方式來展示幫助話題。用 :h usr_toc.txt 打開目錄(你可能已經猜到這個命令的用處了)。瀏覽用戶手冊能幫助你找出某些你想了解的話題,如你可以在第24章看到關於“複合字符”以及“輸入特殊字符”的講解(用 :h usr_24.txt 可以快速打開相關章節)。

  15. 高亮分組的幫助以 hl- 開頭。如::h hl-WarningMsg 說的是警告信息分組的高亮。

  16. 語法高亮以:syc- 開頭,如::h :syn-conceal 講的是 :syn 命令的對於隱藏字符是如何顯示的。

  17. 快速修復命令以 :c 開頭,而位置列表命令以 :l 開頭。

  18. :h BufWinLeave 講的是 BufWinLeave 自動命令。還有,:h autocommand-events (譯者注:原文是 :h autocommands-events,但是沒有該幫助)講的是所有可用的事件。

  19. 啓動參數都以“-”開頭,如::h -f 會告訴你 Vim 中 “-f” 參數的作用。

  20. 額外的特性都以“+”開頭,如::h +conceal 講的是關於隱藏字符的支持。

  21. 錯誤代碼可以在幫助系統中直接查到。:h E297 會帶你到關於這一錯誤的詳細解釋。但是有時並沒有轉到錯誤描述,而是列出了經常導出這一錯誤的 Vim 命令,如 :h E128 (譯者注:原文爲:h hE128,但是並沒有該幫助)會直接跳轉到 :function 命令。

  22. 關於包含的語法文件的文檔的幫助話題格式是 :h ft-*-syntax。如::h ft-c-syntax 說的就是C語言語法文件以及它所提供的選項。有的語法文件還會帶有自動完成(:h ft-php-omni)或文件類型插件(:h ft-tex-plugin)相關的章節可以查看。

另外在每個幫助頁的頂端通常會包含一個用戶文檔鏈接(更多的從從用戶的角度出發來主角命令的功能和用法,不涉及那麼多細節)。如::h pattern.txt 裏包含了 :h 03.9:h usr_27 兩個章節的鏈接。

獲取在線幫助

如果你遇到了無法解決的問題,或者需要指引的話,可以參考 Vim 使用郵件列表。 IRC 也是一個很不錯的資源。 Freenode 上的 #vim 頻道很龐大,並且裏面有許多樂於助人的人。

如果你想給 Vim 提交 Bug 的話,可以使用 vim_dev 郵件列表。

執行自動命令

你可以觸發任何事件,如::doautocmd BufRead

用戶自定義事件

對於插件而言,創建你自己的自定義事件有時非常有用。

function! Chibby()
    " A lot of stuff is happening here.
    " And at last..
    doautocmd User ChibbyExit
endfunction

現在你插件的用戶可以在Chibby執行完成之後做任何他想做的事情:

autocmd User ChibbyExit call ChibbyCleanup()

順便提一句,如果在使用 :autocmd:doautocmd 時沒有捕捉異常,那麼會輸出 "No matching autocommands" 信息。這也是爲什麼許多插件用 silent doautocmd ... 的原因。但是這也會有不足,那就是你不能再在 :autocmd 中使用 echo "foo" 了,取而代之的是你要使用 unsilent echo "foo" 來輸出。

這就是爲什麼要在觸發事件之前先判斷事件是否存在的原因,

if exists('#User#ChibbyExit')
  doautocmd User ChibbyExit
endif

幫助文檔::h User

事件嵌套

默認情況下,自動命令不能嵌套!如果某個自動命令執行了一個命令,這個命令再依次觸發其它的事件,這是不可能的。

例如你想在每次啓動 Vim 的時候自動打開你的 vimrc 文件:

autocmd VimEnter * edit $MYVIMRC

當你啓動 Vim 的時候,它會幫你打開你的 vimrc 文件,但是你很快會注意到這個文件沒有任何的高亮,儘管平時它是正常可以高亮的。

問題在於你的非嵌套自動命令 :edit 不會觸發“BufRead”事件,所以並不會把文件類型設置成“vim”,進而 $VIMRUNTIME/syntax/vim.vim 永遠不會被引入。詳細信息請參考::au BufRead *.vim。要想完成上面所說的需求,使用下面這個命令:

autocmd VimEnter * nested edit $MYVIMRC

幫助文檔::h autocmd-nested

剪切板

如果你想在沒有GUI支持的Unix系統中使用 Vim 的 'clipboard' 選項,則需要 +clipboard 以及可選的 +xterm_clipboard 兩個特性支持。

幫助文檔:

:h 'clipboard'
:h gui-clipboard
:h gui-selections

另外請參考:持續粘貼(爲什麼我每次都要設置 'paste' 模式

剪貼板的使用(Windows, OSX)

Windows 自帶了剪貼板,OSX 則帶了一個粘貼板

在這兩個系統中都可以用大家習慣用的 ctrl+c / cmd+c 複製選擇的文本,然後在另外一個應用中用 ctrl+v / cmd+v 進行粘貼。

需要注意的是複製的文本已經被髮送到了剪貼板,所以你在粘貼複製的內容之前關閉這個應用是沒有任何問題的。

每次複製的時候,都會向剪貼板寄存器 * 中寫入數據。 而在 Vim 中分別使用 "*y"*p 來進行復制(yank) 和 粘貼(paste)。

如果你不想每次操作都要指定 * 寄存器,可以在你的 vimrc 中添加如下配置:

set clipboard=unnamed

通常情況下複製/刪除/放入操作會往 " 寄存器中寫入數據,而加上了上面的配置之後 * 寄存器也會被寫入同樣數據,因此簡單的使用 yp 就可以複製粘貼了。

我再說一遍:使用上面的選項意味着每一次的複製/粘貼,即使在同一個 Vim 窗口裏,都會修改剪貼板的內容。你自己決定上面的選項是否適合。

如果你覺得輸入 y 還是太麻煩的話,可以使用下面的設置把在可視模式下選擇的內容發送到剪貼板:

set clipboard=unnamed,autoselect
set guioptions+=a

幫助文檔:

:h clipboard-unnamed
:h autoselect
:h 'go_a'

剪貼板的使用(Linux, BSD, ...)

如果你的系統使用了 X 圖形界面,事情會變得有一點不同。X 圖形界面實現了 X 窗口系統協議, 這個協議在1987年發佈的主版本11,因此 X 也通常被稱爲 X11。

在 X10 版本中,剪貼緩衝區被用來實現像 clipboard 一樣由 X 來複制文本,並且可以被所有的程序訪問。現在這個機制在 X 中還存在,但是已經過時了,很多程序都不再使用這一機制。

近年來數據在程序之間是通過選擇進行傳遞的。一共有三種選擇,經常用到的有兩種:PRIMARY 和 CLIPBOARD。

選擇的工作工模大致是這樣的:

Program A:<ctrl+c>
Program A:聲稱對 CLIPBOARD 的所有權
Program B:<ctrl+v>
Program B:發現CLIPBOARD的所有權被Program A持有
Program B:從Program A請求數據
Program A:響應這個請求併發送數據給Program B
Program B:從Program A接收數據並插入到窗口中
選擇 何時使用 如何粘貼 如何在 Vim 中訪問
PRIMARY 選擇文本 鼠標中鍵, shift+insert * 寄存器
CLIPBOARD 選擇文本並按 ctrl+c ctrl+v +寄存器

注意:X 服務器並不會保存選擇(不僅僅是 CLIPBOARD 選擇)!因此在關閉了相應的程序後,你用 ctrl+c 複製的內容將丟失。

使用 "*p 來貼粘 PRIMARY 選擇中的內容,或者使用 "+y1G 來將整個文件的內容複製到 CLIPBOARD 選擇。

如果你需要經常訪問這兩個寄存器,可以考慮使用如下配置:

set clipboard^=unnamed          " * 寄存器
" 或者
set clipboard^=unnamedplus      " + 寄存器

^= 用來將設置的值加到默認值之前,詳見::h :set^=

這會使得所有複製/刪除/放入操作使用 *+ 寄存器代替默認的未命令寄存器 "。之後你就可以直接使用 yp 訪問你的X選擇了。

幫助文檔:

:h clipboard-unnamed
:h clipboard-unnamedplus

打開文件時恢復光標位置

如果沒有這個設置,每次打開文件時光標都將定位在第一行。而加入了這個設置以後,你就可以恢復到上次關閉文件時光標所在的位置了。

將下面的配置添加到你的 vimrc 文件:

autocmd BufReadPost *
    \ if line("'\"") > 1 && line("'\"") <= line("$") |
    \   exe "normal! g`\"" |
    \ endif

這是通過判斷之前的光標位置是否存在(文件可能被其它程序修改而導致所記錄的位置已經不存在了),如果存在的話就執行 g`" (轉到你離開時的光標位置但是不更改跳轉列表)。

這需要使用 viminfo 文件::h viminfo-

臨時文件

根據選項的不同, Vim 最多會創建4種工作文件。

備份文件

你可以讓 Vim 在將修改寫入到文件之前先備份原文件。默認情況下, Vim 會保存一個備份文件但是當修改成功寫入後會立即刪除它(:set writebackup)。如果你想一直保留這個備份文件的話,可以使用 :set backup。而如果你想禁用備份功能的話,可以使用 :set nobackup nowritebackup

咱們來看一下上次我在 vimrc 中改了什麼:

$ diff ~/.vim/vimrc ~/.vim/files/backup/vimrc-vimbackup
390d389
< command! -bar -nargs=* -complete=help H helpgrep <args>

幫助文檔::h backup

交換文件

假設你有一個非常棒的科幻小說的構思。在按照故事情節已經寫了好幾個小時幾十萬字的時候..忽然停電了!而那時你纔想起來你上次保存 ~/來自外太空的邪惡入侵者.txt 是在.. 好吧,你從來沒有保存過。

但是並非沒有希望了!在編輯某個文件的時候, Vim 會創建一個交換文件,裏面保存的是對當前文件所有未保存的修改。自己試一下,打開任意的文件,並使用 :swapname 獲得當前的交換文件的保存路徑。你也可以將 :set noswapfile 加入到 vimrc 中來禁用交換文件。

默認情況下,交換文件會自動保存在被編輯文件所在的目錄下,文件名以 .file.swp 後綴結尾,每當你修改了超過 200 個字符或是在之前 4 秒內沒有任何動作時更新它的內容,在你不再編輯這個文件的時候會被刪除。你可以自己修改這些數字,詳見::h 'updatecount':h 'updatetime'

而在斷電時,交換文件並不會被刪除。當你再次打開 vim ~/來自外太空的邪惡入侵者.txt 時, Vim 會提示你恢復這個文件。

幫助文檔::h swap-file:h usr_11

撤銷文件

內容變更歷史記錄是保存在內存中的,並且會在 Vim 退出時清空。如果你想讓它持久化到磁盤中,可以設置 :set undofile。這會把文件 ~/foo.c 的撤銷文件保存在 ~/foo.c.un~

幫助文檔::h 'undofile':h undo-persistence

viminfo 文件

備份文件、交換文件和撤銷文件都是與文本狀態相關的,而 viminfo 文件是用來保存在 Vim 退出時可能會丟失的其它的信息的。包括歷史記錄(命令歷史、搜索歷史、輸入歷史)、寄存器內容、標註、緩衝區列表、全局變量等等。

默認情況下,viminfo 被保存在 ~/.viminfo

幫助文檔::h viminfo:h 'viminfo'

臨時文件管理設置示例

如果你跟我一樣,也喜歡把這些文件放到一個位置(如:~/.vim/files)的話,可以使用下面的配置:

" 如果文件夾不存在,則新建文件夾
if !isdirectory($HOME.'/.vim/files') && exists('*mkdir')
  call mkdir($HOME.'/.vim/files')
endif

" 備份文件
set backup
set backupdir   =$HOME/.vim/files/backup/
set backupext   =-vimbackup
set backupskip  =
" 交換文件
set directory   =$HOME/.vim/files/swap//
set updatecount =100
" 撤銷文件
set undofile
set undodir     =$HOME/.vim/files/undo/
" viminfo 文件
set viminfo     ='100,n$HOME/.vim/files/info/viminfo

注意:如果你在一個多用戶系統中編輯某個文件時, Vim 提示你交換文件已經存在的話,可能是因爲有其他的用戶此時正在編輯這個文件。而如果將交換文件放到自己的home目錄的話,這個功能就失效了。因此服務器非常不建議將這些文件修改到HOME目錄,避免多人同時編輯一個文件,卻沒有任何警告。

編輯遠程文件

Vim 自帶的 netrw 插件支持對遠程文件的編輯。實際上它將遠程的文件通過 scp 複製到本地的臨時文件中,再用那個文件打開一個緩衝區,然後在保存時把文件再複製回遠程位置。

下面的命令在你本地的 VIM 配置與 SSH 遠程服務器上管理員想讓你使用的配置有衝突時尤其有用:

:e scp://bram@awesome.site.com/.vimrc

如果你已經設置了 ~/.ssh/config,SSH 會自動讀取這裏的配置:

Host awesome
    HostName awesome.site.com
    Port 1234
    User bram

如果你的 ~/.ssh/config 中有以上的內容,那麼下面的命令就可以正常執行了:

:e scp://awesome/.vimrc

可以用同樣的方法編輯 ~/.netrc, 詳見::h netrc-netrc

確保你已經看過了 :h netrw-ssh-hack:h g:netrw_ssh_cmd

另外一種編輯遠程文件的方法是使用 sshfs,它會用 FUSE 來掛載遠程的文件系統到你本地的系統當中。

插件管理

Pathogen是第一個比較流行的插件管理工具。實際上它只是修改了 runtimepath:h 'rtp') 來引入所有放到該目錄下的文件。你需要自己克隆插件的代碼倉庫到那個目錄。

真正的插件管理工具會在 Vim 中提供幫助你安裝或更新插件的命令。以下是一些常用的插件管理工具:

多行編輯

這是一種可以同時輸入多行連續文本的技術。參考這個示例

<c-v> 切換到可視塊模式。然後向下選中幾行,按 IA (譯者注:大寫字母,即 shift+i 或 shift+a)然後開始輸入你想要輸入的文本。

在剛開始的時候可能會有些迷惑,因爲文本只出現在了當前編輯的行,只有在當前的插入動作結束後,之前選中的其它行纔會出現插入的文本。

舉一個簡單的例子:<c-v>3jItext<esc>

如果你要編輯的行長度不同,但是你想在他們後面追加相同的內容的話,可以試一下這個:<c-v>3j$Atext<esc>

有時你可能需要把光標放到當前行末尾之後,默認情況下你是不可能做到的,但是可能通過設置 virtualedit 選項達到目的:

set virtualedit=all

設置之後 $10l90| 都會生效,即使超過了行尾的長度。

詳見 :h blockwise-examples。在開始的時候可能會覺得有些複雜,但是它很快就會成爲你的第二天性的。

如果你想探索更有趣的事情,可以看看多光標

使用外部程序和過濾器

免責聲明:Vim 是單線程的,因此在 Vim 中以前端進程執行其它的程序時會阻止其它的一切。當然你可以使用 Vim 程序接口,如Lua,並且使用它的多線程支持,但是在那期間, Vim 的處理還是被阻止了。Neovim 添加了任務 API 解決了此問題。

(據說 Bram 正在考慮在 Vim 中也添加任務控制。如果你使用了較新版本的的 Vim ,可以看一下 :helpgrep startjob。)

使用 :! 啓動一個新任務。如果你想列出當前工作目錄下的所有文件,可以使用 :!ls。 用 | 來將結果通過管道重定向,如::!ls -l | sort | tail -n5

沒有使用範圍時(譯者注:範圍就是 :! 之間的內容,. 表示當前行,+4 表示向下偏移4行,$ 表示最末行等,多行時用 , 將它們分開,如 .,$ 表示從當前行到末行),:! 會顯示在一個可滾動的窗口中(譯者注:在 GVim 和在終端裏運行的結果稍有不同)。相反的,如果指定了範圍,這些行會被過濾。這意味着它們會通過管道被重定向到過濾程序的 stdin,在處理後再通過過濾程序的 stdout 輸出,用輸出結果替換範圍內的文本。例如:爲接下來的5行文本添加行號,可以使用:

:.,+4!nl -ba -w1 -s' '

由於手動添加範圍很麻煩, Vim 提供了一些輔助方法以方便的添加範圍。如果需要經常帶着範圍的話,你可以在可見模式中先選擇,然後再按 : (譯者注:選中後再按 ! 更方便)。還可以使用 ! 來取用一個motion的範圍,如 !ipsort (譯者注:原文爲 !ip!sort ,但經過實驗發現該命令執行報錯,可能是因爲 Vim 版本的原因造成的,新版本使用 ip 選擇當前段落後自動在命令後添加了 ! ,按照作者的寫法來看,可能之前的版本沒有自動添加 ! )可以將當前段落的所有行按字母表順序進行排序。

一個使用過濾器比較好的案例是Go語言。它的縮進語法非常個性,甚至還專門提供了一個名爲 gofmt 的過濾器來對Go語言的源文件進行正確的縮進。Go語言的插件通常會提供一個名爲 :Fmt 的函數,這個函數就是執行了 :%!gofmt 來對整個文件進行縮進。

人們常用 :r !prog 將prog程序的插入放到當前行的下面,這對於腳本來說是很不錯的選擇,但是在使用的過程中我發現 !!ls 更加方便,它會用輸出結果替換當前行的內容。(譯者注:前面命令中的 prog 只是個佔位符,在實際使用中需要替換成其它的程序,如 :r !ls,這就與後面的 !!ls 相對應了,兩者唯一的不同是第一個命令不會覆蓋當前行內容,但是第二個命令會)

幫助文檔:

:h filter
:h :read!

Cscope

Cscope 的功能比 ctags 要完善,但是隻支持C(通過設置cscope.files後同樣支持C++以及Java)。

鑑於Tag文件只是知道某個符號是在哪裏定義的,cscope的數據庫裏的數據信息就多的多了:

  • 符號是在哪裏定義的?
  • 符號是在哪裏被使用的?
  • 這個全局符號定義了什麼?
  • 這個變量是在哪裏被賦值的?
  • 這個函數在源文件的哪個位置?
  • 哪些函數調用了這個函數?
  • 這個函數調用了哪些函數?
  • "out of space"消息是從哪來的?
  • 在目錄結構中當前的源文件在哪個位置?
  • 哪些文件引用了這個頭文件?

1. 構建數據庫

在你項目的根目錄執行下面的命令:

$ cscope -bqR

這條命令會在當前目錄下創建三個文件:cscope{,.in,.po}.out 。把它們想象成你的數據庫。

不幸的時 cscope 默認只分析 *.[c|h|y|l] 文件。如果你想在Java項目中使用 cscope ,需要這樣做:

$ find . -name "*.java" > cscope.files
$ cscope -bq

2. 添加數據庫

打開你新創建的數據庫連接:

:cs add cscope.out

檢查連接已經創建成功:

:cs show

(當然你可以添加多個連接。)

3. 查詢數據庫

:cs find <kind> <query>

如::cs find d foo 會列出 foo(...) 調用的所有函數。

Kind 說明
s symbol:查找使用該符號的引用
g global:查找該全局符號的定義
c calls:查找調用當前方法的位置
t text:查找出現該文本的位置
e egrep:使用 egrep 搜索當前單詞
f file:打開文件名
i includes:查詢引入了當前文件的文件
d depends:查找當前方法調用的方法

推薦一些比較方便的映射,如:

nnoremap <buffer> <leader>cs :cscope find s  <c-r>=expand('<cword>')<cr><cr>
nnoremap <buffer> <leader>cg :cscope find g  <c-r>=expand('<cword>')<cr><cr>
nnoremap <buffer> <leader>cc :cscope find c  <c-r>=expand('<cword>')<cr><cr>
nnoremap <buffer> <leader>ct :cscope find t  <c-r>=expand('<cword>')<cr><cr>
nnoremap <buffer> <leader>ce :cscope find e  <c-r>=expand('<cword>')<cr><cr>
nnoremap <buffer> <leader>cf :cscope find f  <c-r>=expand('<cfile>')<cr><cr>
nnoremap <buffer> <leader>ci :cscope find i ^<c-r>=expand('<cfile>')<cr>$<cr>
nnoremap <buffer> <leader>cd :cscope find d  <c-r>=expand('<cword>')<cr><cr>

所以 :tag (或 <c-]>)跳轉到標籤定義的文件,而 :cstag 可以達到同樣的目的,同時還會打開 cscope 的數據庫連接。'cscopetag' 選項使得 :tag 命令自動的像 :cstag 一樣工作。這在你已經使用了基於標籤的映射時會非常方便。

幫助文檔::h cscope

MatchIt

由於 Vim 是用 C 語言編寫的,因此許多功能都假設使用類似 C 語言的語法。默認情況下,如果你的光標在 {#endif , 就可以使用 % 跳轉到與之匹配的 }#ifdef

Vim 自帶了一個名爲 matchit.vim 的插件,但是默認沒有啓用。啓用後可以用 % 在HTML相匹配的標籤或 VimL 的 if/else/endif 塊之間進行跳轉,它還帶來了一些新的命令。

在 Vim 8 中安裝

" vimrc
packadd! matchit

在 Vim 7 或者更早的版本中安裝

"vimrc
runtime macros/matchit.vim

由於matchit的文檔很全面,我建議安裝以後執行一次下面的命令:

:!mkdir -p ~/.vim/doc
:!cp $VIMRUNTIME/macros/matchit.vim ~/.vim/doc
:helptags ~/.vim/doc

簡短的介紹

至此這個插件已經可以使用了。 參考 :h matchit-intro 來獲得支持的命令以及 :h matchit-languages 來獲得支持的語言。

你可以很方便的定義自己的匹配對,如:

autocmd FileType python let b:match_words = '\<if\>:\<elif\>:\<else\>'

之後你就可以在任何的 Python 文件中使用 % (向前)或 g% (向後)在這三個片斷之間跳轉了。

幫助文檔:

:h matchit-install
:h matchit
:h b:match_words

技巧

聰明地使用 n 和 N

nN 的實際跳轉方向取決於使用 / 還是 ? 來執行搜索,其中 / 是向後搜索,? 是向前搜索。一開始我(原作者)覺得這裏很難理解。

如果你希望 n 始終爲向後搜索,N 始終爲向前搜索,那麼只需要這樣設置:

nnoremap <expr> n  'Nn'[v:searchforward]
nnoremap <expr> N  'nN'[v:searchforward]

聰明地使用命令行歷史

我(原作者)習慣用 Ctrl + pCtrl + n 來跳轉到上一個/下一個條目。其實這個操作也可以用在命令行中,快速調出之前執行過的命令。

不僅如此,你會發現 其實更智能。如果命令行中已經存在了一些文字,我們可以通過按方向鍵來匹配已經存在的內容。比如,命令行中現在是 :echo,這時候我們按 ,就會幫我們補全成 :echo "Vim rocks!"(前提是,之前輸入過這段命令)。

當然,Vim 用戶都不願意去按方向鍵,事實上我們也不需要去按,只需要設置這樣的映射:

cnoremap <c-n> <down>
cnoremap <c-p> <up>

這個功能,我(原作者)每天都要用很多次。

智能 Ctrl-l

Ctrl + l 的默認功能是清空並「重新繪製」當前的屏幕,就和 :redraw! 的功能一樣。下面的這個映射就是執行重新繪製,並且取消通過 /? 匹配字符的高亮,而且還可以修復代碼高亮問題(有時候,由於多個代碼高亮的腳本重疊,或者規則過於複雜,Vim 的代碼高亮顯示會出現問題)。不僅如此,還可以刷新「比較模式」(請參閱 :help diff-mode)的代碼高亮:

nnoremap <leader>l :nohlsearch<cr>:diffupdate<cr>:syntax sync fromstart<cr><c-l>

禁用錯誤報警聲音和圖標

set noerrorbells
set novisualbell
set t_vb=

請參閱 Vim Wiki: Disable beeping

快速移動當前行

有時,我(原作者)想要快速把當前行上移或下移一行,只需要這樣設置映射:

nnoremap [e  :<c-u>execute 'move -1-'. v:count1<cr>
nnoremap ]e  :<c-u>execute 'move +'. v:count1<cr>

這個映射,同樣可以搭配數字使用,比如連續按下 2 ] e 就可以把當前行向下移動兩行。

快速添加空行

nnoremap [<space>  :<c-u>put! =repeat(nr2char(10), v:count1)<cr>'[
nnoremap ]<space>  :<c-u>put =repeat(nr2char(10), v:count1)<cr>

設置之後,連續按下 5 [ 空格 在當前行上方插入 5 個空行。

快速編輯自定義宏

這個功能真的很實用!下面的映射,就是在一個新的命令行窗口中讀取某一個寄存器(默認爲 *)。當你設置完成後,只需要按下 回車 即可讓它生效。

在錄製宏的時候,我經常用這個來更改拼寫錯誤。

nnoremap <leader>m  :<c-u><c-r><c-r>='let @'. v:register .' = '. string(getreg(v:register))<cr><c-f><left>

只需要連續按下 leader m 或者 " leader m 就可以調用了。

請注意,這裏之所以要寫成 <c-r><c-r> 是爲了確保 <c-r> 執行了。請參閱 :h c_^R^R

快速跳轉到源(頭)文件

這個技巧可以用在多種文件類型中。當你從源文件或者頭文件中切換到其他文件的時候,這個技巧可以設置「文件標記」(請參閱 :h marks),然後你就可以通過連續按下 ' C 或者 ' H 快速跳轉回去(請參閱 :h 'A)。

autocmd BufLeave *.{c,cpp} mark C
autocmd BufLeave *.h       mark H

注意:由於這個標記是設置在 viminfo 文件中,因此請先確認 :set viminfo? 中包含了 :h viminfo-'

在 GUI 中快速改變字體大小

印象中,我(原作者)記得一下代碼是來自 tpope's 的配置文件:

command! Bigger  :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)+1', '')
command! Smaller :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)-1', '')

根據模式改變光標類型

我(原作者)習慣在普通模式下用塊狀光標,在插入模式下用條狀光標(形狀類似英文 "I" 的樣子),然後在替換模式中使用下劃線形狀的光標。

if empty($TMUX)
  let &t_SI = "\<Esc>]50;CursorShape=1\x7"
  let &t_EI = "\<Esc>]50;CursorShape=0\x7"
  let &t_SR = "\<Esc>]50;CursorShape=2\x7"
else
  let &t_SI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=1\x7\<Esc>\\"
  let &t_EI = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=0\x7\<Esc>\\"
  let &t_SR = "\<Esc>Ptmux;\<Esc>\<Esc>]50;CursorShape=2\x7\<Esc>\\"
endif

原理很簡單,就是讓 Vim 在進入和離開插入模式的時候,輸出一些序列,請參考 escape sequence。Vim 與終端之間的中間層,比如 tmux 會處理並執行上面的代碼。

但上面這個還是有一個缺點的。終端環境的內部原理不盡相同,對於序列的處理方式也稍有不同。因此,上面的代碼可能無法在你的環境中運行。甚至,你的運行環境也有可能不支持其他光標形狀,請參閱你的 Vim 運行環境的文檔。

好消息是,上面這個代碼,可以在 iTerm2 中完美運行。

防止水平滑動的時候失去選擇

如果你選中了一行或多行,那麼你可以用 <> 來調整他們的縮進。但在調整之後就不會保持選中狀態了。

你可以連續按下 g v 來重新選中他們,請參考 :h gv。因此,你可以這樣來配置映射:

xnoremap <  <gv
xnoremap >  >gv

設置好之後,在可視模式中使用 >>>>> 就不會再出現上面提到的問題了。

重新載入保存文件

通過自動命令,你可以在保存文件的同時觸發一些其他功能。比如,如果這個文件是一個配置文件,那麼就重新載入;或者你還可以對這個文件進行代碼風格檢查。

autocmd BufWritePost $MYVIMRC source $MYVIMRC
autocmd BufWritePost ~/.Xdefaults call system('xrdb ~/.Xdefaults')

更加智能的當前行高亮

我(原作者)很喜歡「當前行高亮」(請參閱 :h cursorline)這個功能,但我只想讓這個效果出現在當前窗口,而且在插入模式中關閉這個效果:

autocmd InsertLeave,WinEnter * set cursorline
autocmd InsertEnter,WinLeave * set nocursorline

更快的關鍵字補全

關鍵字補全(<c-n><c-p>)功能的工作方式是,無論 'complete' 設置中有什麼,它都會嘗試着去補全。這樣,一些我們用不到的標籤也會出現在補全列表中。而且,它會掃描很多文件,有時候運行起來非常慢。如果你不需要這些,那麼完全可以像這樣把它們禁用掉:

set complete-=i   " disable scanning included files
set complete-=t   " disable searching tags

改變顏色主題的默認外觀

如果你想讓狀態欄在顏色主題更改後依然保持灰色,那麼只需要這樣設置:

autocmd ColorScheme * highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE

同理,如果你想讓某一個顏色主題(比如 "lucius")的狀態欄爲灰色(請使用 :echo color_name 來查看當前可用的所有顏色主題):

autocmd ColorScheme lucius highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE

命令

下面的命令都比較有用,最好了解一下。用 :h :<command name> 來了解更多關於它們的信息,如::h :global

:global 和 :vglobal - 在所有匹配行執行命令

在所有符合條件的行上執行某個命令。如: :global /regexp/ print 會在所有包含 "regexp" 的行上執行 print 命令(譯者注:regexp 有正則表達式的意思,該命令同樣支持正則表達式,在所有符合正則表達式的行上執行指定的命令)。

趣聞:你們可能都知道老牌的 grep 命令,一個由 Ken Thompson 編寫的過濾程序。它是幹什麼用的呢?它會輸出所有匹配指定正則表達式的行!現在猜一下 :global /regexp/ print 的簡寫形式是什麼?沒錯!就是 :g/re/p 。 Ken Thompsom 在編寫 grep 程序的時候是受了 vi :global 的啓發。(譯者注: https://robots.thoughtbot.com/how-grep-got-its-name)

既然它的名字是 :global,理應僅作用在所有行上,但是它也是可以帶範圍限制的。假設你想使用 :delete 命令刪除從當前行到下一個空行(由正則表達式 ^$ 匹配)範圍內所有包含 "foo" 的行:

:,/^$/g/foo/d

如果要在所有 匹配的行上執行命令的話,可以使用 :global! 或是它的別名 :vglobal ( V 代表的是 inVerse )。

:normal 和 :execute - 腳本夢之隊

這兩個命令經常在 Vim 的腳本里使用。

藉助於 :normal 可以在命令行裏進行普通模式的映射。如::normal! 4j 會令光標下移 4 行(由於加了"!",所以不會使用自定義的映射 "j")。

需要注意的是 :normal 同樣可以使用範圍數(譯者注:參考 :h range:h :normal-range 瞭解更多),故 :%norm! Iabc 會在所有行前加上 "abc"。

藉助於 :execute 可以將命令和表達式混合在一起使用。假設你正在編輯一個 C 語言的文件,想切換到它的頭文件:

:execute 'edit' fnamemodify(expand('%'), ':r') . '.h'

(譯者注:頭文件爲與與源文件同名但是擴展名爲 .h 的文件。上面的命令中 expand 獲得當前文件的名稱,fnamemodify 獲取不帶擴展名的文件名,再連上 '.h' 就是頭文件的文件名了,最後在使用 edit 命令打開這個頭文件。)

這兩個命令經常一起使用。假設你想讓光標下移 n 行:

:let n = 4
:execute 'normal!' n . 'j'

重定向消息

許多命令都會輸出消息,:redir 用來重定向這些消息。它可以將消息輸出到文件、寄存器或是某個變量中。

" 將消息重定向到變量 `neatvar` 中
:redir => neatvar
" 打印所有寄存器的內容
:reg
" 結束重定向
:redir END
" 輸出變量
:echo neatvar
" 惡搞一下,我們把它輸出到當前緩衝區
:put =neatvar

再 Vim 8 中,可以更簡單的方式即位:

:put =execute('reg')

(譯者注:原文最後一條命令是 :put =nicevar 但是實際會報變量未定義的錯誤) (實測 neovim/vim8 下沒問題)

幫助文檔::h :redir

調試

常規建議

如果你遇到了奇怪的行爲,嘗試用這個命令重現它:

vim -u NONE -N

這樣會在不引用vimrc(默認設置)的情況下重啓vim,並且在 nocompatible 模式下(使用vim默認設置而不是vi的)。(搜索 :h --noplugin 命令瞭解更多啓動加載方式)

如果仍舊能夠出現該錯誤,那麼這極有可能是vim本身的bug,請給 vim_dev 發送郵件反饋錯誤,多數情況下問題不會立刻解決,你還需要進一步研究

許多插件經常會提供新的(默認的/自動的)操作。如果在保存的時候發生了,那麼請用 :verb au BufWritePost 命令檢查潛在的問題

如果你在使用一個插件管理工具,將插件行註釋調,再進行調試。

問題還沒有解決?如果不是插件的問題,那麼肯定是你的自定義的設置的問題,可能是你的 options 或 autocmd 等等。

到了一行行代碼檢查的時候了,不斷地排除縮小檢查範圍知道你找出錯誤,根據二分法的原理你不會花費太多時間的。

在實踐過程中,可能就是這樣,把 :finish 放在你的 vimrc 文件中間,Vim會跳過它之後的設置。如果問題還在,那麼問題就出在:finish之前的設置中,再把:finish放到前一部分設置的中間位置。否則問題就出現在它後面的半部分設置,那麼就把:finish放到後半部分的中間位置。不斷的重複即可找到。

調整日誌等級

Vim現在正在使用的另一個比較有用的方法是增加debug信息輸出詳細等級。現在Vim支持9個等級,可以用:h 'verbose'命令查看。

:e /tmp/foo
:set verbose=2
:w
:set verbose=0

這可以顯示出所有引用的文件、沒有變化的文件或者各種各樣的作用於保存的插件。

如果你只是想用簡單的命令來提高等級,也是用 :verbose ,放在其他命令之前,通過計數來指明等級,默認是1.

:verb set verbose
"  verbose=1
:10verb set verbose
"  verbose=10

通常用等級1來顯示上次從哪裏設置的選項

:verb set ai?
"      Last set from ~/.vim/vimrc

一般等級越高輸出信息月詳細。但是不要害怕,亦可以把輸出導入到文件中:

:set verbosefile=/tmp/foo | 15verbose echo "foo" | vsplit /tmp/foo

你可以一開始的時候就打開verbosity,用 -V 選項,它默認設置調試等級爲10。 例如:vim -V5

查看啓動日誌

查看運行時日誌

vim 腳本調試

語法文件調試

雜項

附加資源

Vim 配置集合

內置插件

將 Control 映射到 CapsLock

復活節彩蛋

爲何使用 hjkl

常見問題

編輯小文件時很慢

有兩個因素對性能影響非常大:

  1. 過於複雜的 正則表達式 。尤其是 Ruby 的語法文件,以前會造成性能下降。(見調試語法文件

  2. 屏幕重繪 。有一些功能會強制重繪所有行。

典型肇事者 原因 解決方案
:set cursorline 會導致所有行重繪 :set nocursorline
:set cursorcolumn 會導致所有行重繪 :set nocursorcolumn
:set relativenumber 會導致所有行重繪 :set norelativenumber
:set foldmethod=syntax 如果語法文件已經很慢了,這隻會變得更慢 :set foldmethod=manual:set foldmethod=marker 或者使用快速摺疊插件
:set synmaxcol=3000 由於內部表示法,Vim 處理比較長的行時會有問題。讓它高亮到 3000 列…… :set synmaxcol=200
matchparen.vim Vim 默認加載的插件,用正則表達式查找配對的括號 禁用插件::h matchparen

注意:只有在你真正遇到性能問題的時候才需要做上面的調整。在大多數情況下使用上面提到的選項是完全沒有問題的。

編輯大文件的時候很慢

Vim 處理大文件最大的問題就是它會一次性讀取整個文件。這麼做是由於緩衝區的內部機理導致的(在 vim_dev 中討論)。

如果只是想查看的話,tail hugefile | vim - 是一個不錯的選擇。

如果你能接受沒有語法高亮,並且禁用所有插件和設置的話,使用:

$ vim -u NONE -N

這將會使得跳轉變快很多,尤其是省去了基於很耗費資源的正則表達式的語法高亮。你還可以告訴 Vim 不要使用交換文件和 viminfo 文件,以避免由於寫這些文件而造成的延時:

$ vim -n -u NONE -i NONE -N

簡而言之,儘量避免使用 Vim 寫過大的文件。

持續粘貼(爲什麼我每次都要設置 'paste' 模式)

持續粘貼模式讓終端模擬器可以區分輸入內容與粘貼內容。

你有沒有遇到過往 Vim 裏粘貼代碼之後被搞的一團糟?

這在你使用 cmd+vshirt-insertmiddle-click 等進行粘貼的時候纔會發生。因爲那樣的話你只是向終端模擬器扔了一大堆的文本。 Vim 並不知道你剛剛是粘貼的文本,它以爲你在飛速的輸入。於是它想縮進這些行但是失敗了。

這明顯不是個問題,如果你用 Vim 的寄存器粘貼,如:"+p ,這時 Vim 就知道了你在粘貼,就不會導致格式錯亂了。

使用 :set paste 就可以解決這個問題正常進行粘貼。見 :h 'paste':h 'pastetoggle' 獲取更多信息。

如果你受夠了每次都要設置 'paste' 的話,看看這個能幫你自動設置的插件:bracketed-paste

點此查看該作者對於這個插件的更多描述。

Neovim 嘗試把這些變得更順暢,如果終端支持的話,它會自動開啓持續粘貼模式,無須再手動進行切換。

在終端中按 ESC 後有延時

如果你經常使用命令行,那麼肯定要接觸 終端模擬器 ,如 xterm、gnome-terminal、iTerm2 等等(與實際的終端不同)。

終端模擬器與他們的祖輩一樣,使用 轉義序列 (也叫 控制序列 )來控制光標移動、改變文本顏色等。轉義序列就是以轉義字符開頭的 ASCII 字符串(用脫字符表示法表示成 ^[ )。當遇到這樣的字符串後,終端模擬器會從終端信息數據庫中查找對應的動作。

爲了使用問題更加清晰,我會先來解釋一下什麼是映射超時。在映射存在歧義的時候就會產生映射超時:

:nnoremap ,a :echo 'foo'<cr>
:nnoremap ,ab :echo 'bar'<cr>

上面的例子中兩個映射都能正常工作,但是當輸入 ,a 之後,Vim 會延時1秒,因爲它要確認用戶是否還要輸入那個 b

轉義序列會產生同樣的問題:

  • <esc> 作爲返回普通模式或取消某個動作的按鍵而被大量使用
  • 光標鍵使用轉義序列進行的編碼
  • Vim 期望 Alt (也叫作 Mate Key )會發送一個正確的 8-bit 編碼的高位,但是許多終端模擬器並不支持這個(也可能默認沒有啓用),而只是發送一個轉義序列作爲代替。

你可以這樣測試上面所提到的事情: vim -u NONE -N 然後輸入 i<c-v><left> ,你會看到一個以 ^[ 開頭的字符串,表明這是一個轉義序列,^[ 就是轉義字符。

簡而言之,Vim 在區分錄入的 <esc> 和轉義序列的時候需要一定的時間。

默認情況下,Vim 用 :set timeout timeoutlen=1000,就是說它會用 1 秒的時間來區分有歧義的映射 以及 按鍵編碼。這對於映射來說是一個比較合理的值,但是你可以自行定義按鍵延時的長短,這是解決該問題最根本的辦法:

set timeout           " for mappings
set timeoutlen=1000   " default value
set ttimeout          " for key codes
set ttimeoutlen=10    " unnoticeable small value

:h ttimeout 裏你可以找到一個關於這些選項之間關係的小表格。

而如果你在 tmux 中使用 Vim 的話,別忘了把下面的配置加入到你的 ~/.tmux.conf文件中:

set -sg escape-time 0

無法重複函數中執行的搜索

  • 在命令中的搜索(/:substitute 等)內容會改變“上次使用的搜索內容”。(它保存在/寄存器中,用 :echo @/ 可以輸出它裏面的內容)
  • 簡單的文本變化可以通過 . 重做。(它保存在 . 寄存器,用 :echo @. 可以輸出它的內容)

而在你在函數中進行這些操作的時候,一切就會變得不同。因此你不能用 N/n 查找某個函數剛剛查找的內容,也不能重做函數中對文本的修改。

幫助文檔::h function-search-undo

主題列表

插件列表

Neovim

加入我們

可以協助我們覈對翻譯,或者從章節列表中認領章節進行翻譯。

致謝: