一開始看不上親密接觸後又讓你欲罷不能的 vim
每一個程式設計師都有一款心心念叨的程式碼編輯器。而在眾多妖顏魅惑的編輯器偶像團體前,vim 就像個不加粉飾的農村姑娘,咋一看是那樣樸實無華,難有傾心。但只要走近一點,來個親密接觸,又會被她的似水柔情所俘獲。
這是青筆親身經歷。公司從騰訊空降技術總監,時不時會分享一些他的開發經驗。其中說到編輯器,他的臉上總會泛起淡淡桃花,“我就喜歡 vim!” 。儘管我們曾一致向他引薦當下紅極一時集美貌與才華於一身的 vscode 。他卻依然心有所屬,不為所動。
多少次,我百思不得其解,這個“相貌平平”的 vim 到底有何魔力,能牢牢扣住總監的心絃。在我真正走進她的內心世界,多次晝夜相伴之後。我最終也被她的清新脫俗所拜倒。
接下來就讓我們一起來瞭解這名神祕的“女子”。
1. vim 歷史
以下是網上找到一張圖。如對 vim 發展歷史感興趣可以參考維基百科的詞條 Vim (text editor)
。
早期的 UNIX 作業系統上標準的編輯器是 ed ,這是一個面向行的編輯器,只能顯示當前編輯的行。1976年,Bill Joy 在 ed 上做了擴充套件,使之支援了全屏顯示和編輯,命名為 ex 。ex 是 ed 的超集,通過在 ex 輸入命令 vi 來啟動擴充套件功能。後來這個命令由於經常使用而被獨立出來,也就是現在 Linux (包括 macOS)系統預裝的命令列編輯器 vi 。直到 1991 年,Bram Moolenaar 才開發了 vi 的改進版(improvement),命名為 VIM (寓意:Vi IMprovement),同年 11 月 4 日釋出了 vim 的第一個版本。下圖來自 vim.org 網站首頁的一條新聞,也就是去年的11月4日迎來了 vim 的27歲生日。
2. 內建教程
vim 自帶了一個面向初學者開發的教程。可通過獨立的子程式 vimtutor
啟動教程。
如果系統已經安裝了 vim (macOS 預裝了 vim,部分 linux 發行版可能需要手動安裝),可以在直接在終端輸入 vimtutor
命令開啟教程。
vimtutor
複製程式碼
教程實際也是使用 vim 開啟系統上的一個文字檔案。檔案通常在 /usr/share/vim/vim80/tutor/
目錄下。當然,這個目錄下有很多個檔案,分別是不同語言版本的教程。使用 vimtutor
的不同之處是能自動開啟使用者所在地的語言版本。
上面看到的簡體中文版實際就是檔案 /usr/share/vim/vim80/tutor/tutor.zh_cn.utf-8
因此,你可以將其複製一份放到當前目錄下,再使用 vim 開啟,用做練習,隨意編輯檔案內容。
cp /usr/share/vim/vim80/tutor/tutor.zh_cn.utf-8 ./
vim tutor.zh_cn.utf-8
複製程式碼
但如果按照教程來練習,個人用起來感覺最舒服的方式是:同時開啟兩個終端,一個使用 vimtutor 開啟本地教程用於閱覽,另一個將教程的英文版複製到當前目錄,然後使用 vim 在複製的英文版上進行編輯練習(因為我們實際應用場景大多是英文環境,包括編輯程式碼和配置檔案)。
複製英文版教程並進行編輯練習:
cp /usr/share/vim/vim80/tutor/tutor ./
vim tutor
複製程式碼
3. 工作模式
對於習慣了 GUI 編輯器的開發者,剛開始接觸 vim ,很容易被 vim 的工作模式搞得一頭霧水,而且很可能就此放棄使用 vim 。但如果熟悉了,就覺得一切也理所當然。按照維基百科的介紹,vim 一共有 12 種不同模式,但只有 6 種基本模式,剩下 6 種都是基本模式的變種。但實際我們最常接觸的只有兩類模式:命令模式和編輯模式。其中命令模式又細分為,Normal(預設)模式和 Cmdline (底部命令列) 模式。而 Normal 模式是啟動 vim 進入的預設模式,並且在任何模式下,按 ESC
(鍵盤左上角)鍵都將回到這個模式,作用類似於手機的 Home 鍵。因此,當你開始迷糊自己當前處在什麼模式下,最好的辦法就是按 ESC
回到 Normal 模式。我們使用 vim 的絕大多數命令都在 Normal 模式執行,而 Cmdline(命令列)模式通常只在退出和儲存時使用,因此,如不特別說明,當我們在說命令模式時,就是指代 Normal 模式。此外,本文將 Cmdline 統稱為命令列模式。
下圖是三種模式的切換方式:
如果要簡單概括這三種模式的使用場景,那就是:在命令模式中執行遊標導航,複製貼上刪除,撤銷重做,以及查詢替換等操作;在編輯模式下,編寫程式碼或進行創作;在命令列模式模式下,輸入 q
退出,輸入w
儲存,輸入wq
儲存並退出。當然這裡還不是全部的操作,但是熟悉這些操作,基本就能將 vim 應用得得心應手了。
4. 命令模式
4.1 移動遊標
要使用 vim 熟練地進行程式碼和文字的編輯,首先需要能夠精確快速的將遊標定位到要編輯的地方。最基本的就是上下左右移動遊標了。這些操作當然可以使用鍵盤的四個方向鍵來完成。但是更常用的是使用字母輸入區域連續的四個按鍵 h,j,k,l 。事實上,當你按照標準的手勢接觸鍵盤,你右手的食指觸碰的就是 j 鍵(按鍵有個凸起)。因此,在你保持正常輸入的手勢下,順其自然地敲動食指,就能將遊標往下移動一行,而緊挨著的中指接觸的是 k 鍵,進行的是和 j 相反的操作,即將遊標向上移動一行,你會發現這樣用起來相當順手,這就理解為什麼要使用字母按鍵來進行遊標移動操作了。當然這是在命令模式下,按下字母鍵不會作為輸入而改變編輯內容。
-
h
: 向左移動一個字元 -
l
: 向右移動一個字元 -
j
: 向下移動一行 -
k
: 向上移動一行
雖然可以使用 j,k 左右兩邊的 h 和 l 來實現向左和向右移動遊標。這在一次只移動一個字元時,也還是很方便。但是如果向左或向右移動一個字(word,英文中空格和標點符號隔開的單詞,標點符號也算一個字)時,還使用 h 和 l 就顯得有些笨拙了。向左(向後)和向右(向前)移動一個字的方法如下:
-
w
: 向右或向前移動一個字,遊標定位在字的首字元 -
b
: 向左或向後移動一個字,遊標定位在字的首字元
命令模式按下字母w
將遊標向右移動一個字:
命令模式按下字母b
將遊標向左移動一個字:
如果想近一步擴大遊標單次移動的範圍,就要用到按句子和段落來前後移動來。兩對圓括弧(
和)
分別將遊標向後和向前移動一個句子,對應兩個花括弧{
和}
分別將遊標向後和向前移動一個段落。
-
(
: 向後移動一個句子,遊標定位在句子開始 -
)
: 向前移動一個句子,遊標定位在句子開始 -
{
: 向後移動一個段落,遊標定位在段落開始 -
}
: 向前移動一個段落,遊標定位在段落開始
命令模式按下)
和(
向前和向後移動一個句子:
命令模式按下}
和{
向前和向後移動一個段落:
此外你還可以在螢幕所見範圍內進行快速移動遊標。分別使用大寫的H
,M
,L
。但是這三種定位並不是很精確,通常用做快速大範圍級定位,然後再使用前面的命令進行更加精確的定位。讀者可以自行嘗試。
-
H
: 將遊標定位到螢幕頂部一行的最左端 -
M
: 將遊標定位到螢幕中間一行 -
L
: 將遊標定位到螢幕的底部一行
還有一種我們非常熟悉的應用場景,就是在除錯程式丟擲異常時,通常會顯示出異常產生的行號,這時就需要根據行號快速將遊標定位到指定的位置。這個命令相比前面的命令要複雜一點,它需要先輸入代表行號的一個或多個數字,然後按下大寫字母G
來完成。例如將遊標定位到檔案的第 80 行。需要先在命令模式輸入80,然後快速按下大寫字母G
。
4.2 刪除/撤銷
將刪除和撤銷兩個操作放在一起講,其中一個原因是考慮到可能因為還不知道如何撤銷刪除操作,而害怕嘗試刪除操作的心理(儘管我們已經事先做了備份,但這應該是一種普通的心理和人性害怕失去是同樣的道理)。我們知道可以撤銷,因此在刪除這件事上就有了“安全感”。也許你不是這麼認為,但是這樣還是能幫助我們更好的練習和記憶(我們可以迴圈往復地練習刪除撤銷)。
和移動遊標一樣,刪除也可以按不同粒度進行。如刪除單個字元,字,行,句子,段落以及螢幕首尾。刪除操作由用字母d
加上表示刪除範圍的識別符號構成。同時刪除具體範圍還受遊標當前所在的具體位置決定。例如刪除字使用字母組合dw
,如果游標出現在單詞hello
的第二個字元e
上,此時在命令模式連續按下d
和w
,將刪除從e
開始之後的整個字,但是會e
前面的h
不會刪除,刪除後的結果就是還剩一個字母h
。其他粒度的刪除,也遵循相似的規律。
刪除單個字元
刪除單個字元有兩者方法。兩者等效,都是刪除遊標所在的字元,但是使用x
更簡單一些,因為只需要輸入一個字母。
x
dl
刪除字
-
dw
: 從遊標所在位置開始,刪除到字的末尾(包含遊標所在位置的字元) -
db
: 與dw
相反方向刪除,即刪除遊標所在位置前面的字元(不包含遊標所在位置的字元)
刪除行
-
dd
: 刪除遊標所在的行 -
3dd
: 刪除從遊標開始的3行,當然這是一個例子,可以更改前面的數字刪除任意數量的行
一次刪除3行:
刪除到行尾
如果你不想刪除整行。而是從遊標開始到行尾的字元,可以直接使用一個大寫D
來實現。
使用大寫字母D
刪除從遊標位置開始到行尾的字元:
從行首刪除
與刪除到行尾對應的是使用d0
從行首刪除:
d^與d$
如果對正則表示式熟悉,應該很容易猜到兩者的含義。也是刪除行首和行尾,但是與前面的 d0
與 D
所不同的是不刪除行首和行尾的空格。
-
d^
: 刪除遊標前面知道行首的字元,不包含行首的空格 -
d$
: 刪除從遊標開始知道行尾的字元,不包含行尾的空格
刪除句子和段落
熟悉了前面的刪除,以下刪除句子和段落也可以以此類推。唯一區別僅僅在於刪除粒度不同。
-
d(
: 從句首刪除 -
d)
: 刪除到句尾 -
d{
: 從段首刪除 -
d}
: 刪除到段尾
替換:刪除並輸入
說到刪除不得不再提到替換。因為兩者就像兄弟一般,實際替換像刪除的哥哥,只比弟弟刪除多做了一步。就是在刪除後緊接著進入編輯模式,在已刪除的地方進行編輯。這兩個組合動作不就是替換嘛。需要注意的還有,替換會改變當前工作模式,也就是進入下文會講到的編輯模式,因此在完成輸入後,需要按ESC
重新回到命令模式。與刪除d
對應的替換命令是c
。因此將刪除裡講到的所有操作中的d
換成c
就是對應的替換操作,當然執行後的細節會有些差異。但是這種差異在你親自試過後就很容易理解,因此不作過多論述。讀者可以自行嘗試。
撤銷操作
撤銷操作很簡單,命令模式下,按下小寫字母u
(undo)即可。可以連續按多次,以快速撤銷多個歷史操作。
4.3 複製/貼上
複製 y
複製使用字母y
。它的用法和刪除操作d
很像,不同的是y
是複製而不是刪除。
下面以複製行為例。其他粒度的複製類比刪除操作d
即可。
與dd
刪除當前行對應的複製當前行操作是yy
,當然也可以複製多行,和刪除多行格式相似。在yy
前加上要複製的行數。例如3yy
複製從遊標所在行開始的三行。
-
yy
: 複製遊標所在的行 -
3yy
: 複製從遊標開始的3行,這裡只是例子,實際可以是任意行數
貼上 p
貼上命令就簡單多了。它沒有沒有那麼多的粒度區分,而只需要將已經複製的內容插入到當前遊標之後或者之前的位置。分別使用小寫的p
和大寫的P
。
-
p
: 將複製的內容插入到遊標之後 -
P
: 將複製的內容插入到遊標之前
4.4 查詢字串
在命令模式輸入斜槓/
(向下查詢)或問號?
(向上查詢),遊標定位到終端底部一行,輸入要查詢的字串,再按回車(RETURN),首先定位到一個找到的字串,接著按小寫的n
查詢下一個,按大寫的N
查詢前一個。
5. 進入編輯模式
上面介紹的所有命令都是在命令模式下進行的。如果要開始編寫程式碼或其他文字編輯工作,就需要從命令列模式進入編輯模式,否則輸入的字母(除能進入編輯模式的字母外)都將視作命令,而不會編輯到到檔案中。
進入編輯模式有以下6種方式,區別僅在於進入編輯模式後,遊標所在的位置的不同。
-
i
: 在當前遊標前插入 -
I
: 在行首插入 -
a
: 在當前遊標後追加 -
A
: 在行尾追加 -
o
: 在當前遊標所在行之後新增新行 -
O
: 在當前遊標所在行之前新增新行
6. 命令列模式
在命令模式下,按下冒號:
,在終端底部出現輸入行,表示當前是命令列模式。在命令列模式可以使用 vim 自身支援的很多命令。如前文已經談到的最常用用到的就是退出和儲存。
-
:q
+ RETURN: 退出編輯 -
:w
+ RETURN: 儲存不退出 -
:wq
+ RETURN: 儲存並退出
常用的還有對 vim 進行設定。以下命令都需要在輸入完後按下回車鍵 RETURN 才能執行(和在終端執行命令方式差不多)。
-
:syntax on
: 開啟語法高亮 -
:set number
: 顯示行號 -
:set tabstop=4
: 設定 Tab 鍵寬度 -
:set expandtab
: 使用空格替代 Tab -
:set softtabstop=4
: 設定軟體 Tab (自動Tab)寬度為 4 -
:set shiftwidth=4
: 設定自動縮排寬度為 4 -
:set autoindent
: 開啟自動縮排,通常用於編寫程式
當然以上對 vim 的設定僅僅影響當前開啟的 vim 。要想每次開啟都使用同樣的設定,需要將設定命令統一儲存到 vim 的配置檔案 ~/.vimrc
中,方法下一節將講到。
此外,可以使用help
來獲取幫助。例如輸入:help user-manual
+ RETURN 檢視使用者手冊。
如果說以上都是 vim 內部執行命令,那麼在命令列模式下,實際也可以呼叫外部 shell 的命令。方法是以感嘆號!
開頭,標識感嘆號!
之後的命令是一條外部 shell 的命令。如檢視當前目錄下的檔案,:!ls -l
。第一次按下 RETURN 會隱藏當前編輯區域,顯示終端介面,第二次按下 RETURN 再次回到 vim 編輯介面。
7. 配置檔案
前文已經講到如果想要每次開啟 vim 都使用同樣和介面和設定,需要將設定命令統一儲存在配置檔案中。在 linux 系統(含macOS)推薦儲存的路徑是 ~/.vimrc
。如果從來沒有設定過,這個檔案可能還不存在。這就需要我們先建立一個。為了不覆蓋已有的,我們可以使用如下命令:
test -f ~/.vimrc || vim ~/.vimrc
複製程式碼
這條 shell 命令會先判斷 ~/.vimrc
是否存在,不存在就新建一個空白的配置檔案,並使用 vim 開啟這個新建的配置檔案。
可選的方式也可以複製一份系統的上的配置檔案,然後在此基礎上新增和覆蓋預設配置。
test -f ~/.vimrc || cp /usr/share/vim/vimrc ~/.vimrc
複製程式碼
以下是青筆自己的配置檔案參考:
colorscheme default " 設定顏色主題
syntax on " 語法高亮
filetype on " 檢測檔案的型別
set number " 顯示行號
set ruler " 在編輯過程中,在右下角顯示遊標位置的狀態行
set laststatus=2 " 顯示狀態列 (預設值為 1,無法顯示狀態列)
set statusline=\ %<%F[%1*%M%*%n%R%H]%=\ %y\ %0(%{&fileformat}\ %{&encoding}\ %c:%l/%L%) " 設定在狀態行顯示的資訊
set tabstop=4 " Tab鍵的寬度
set expandtab " 使用空格替換Tab
set softtabstop=4
set shiftwidth=4 " 統一縮排為4
set autoindent " vim使用自動對齊,也就是把當前行的對齊格式應用到下一行(自動縮排)
set cindent " (cindent是特別針對 C語言語法自動縮排)
set smartindent " 依據上面的對齊格式,智慧的選擇對齊方式,對於類似C語言編寫上有用
set scrolloff=3 " 遊標移動到buffer的頂部和底部時保持3行距離
set incsearch " 輸入搜尋內容時就顯示搜尋結果
set hlsearch " 搜尋時高亮顯示被找到的文字
set foldmethod=indent " 設定縮排摺疊
set foldlevel=99 " 設定摺疊層數
nnoremap <space> @=((foldclosed(line('.')) < 0) ? 'zc' : 'zo')<CR>
" 用空格鍵來開關摺疊
" 自動跳轉到上次退出的位置
if has("autocmd")
au BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif
endif
複製程式碼
關於配置檔案的更多資訊,也可通過 vim 內建的幫助手冊來檢視。與配置相關的手冊為 usr_05
。在命令列模式輸入:help usr_05
(冒號用於進入命令列模式,輸入後回車)即可開啟該手冊。
總結
本文從 vim 的發展歷史開始,以具備能夠熟練駕馭 vim 編輯器來滿足日常程式碼編寫的基本操作為主線,講解了在 vim 中進行模式切換,遊標導航,刪除,撤銷,替換,複製,貼上,插入文字,以及配置編輯器外觀設定等必要技能。當然,vim 支援的操作和命令還有很多。本文只介紹到基礎部分。但是卻是我們進行程式碼編輯最常用到的功能。熟練掌握後,可以進一步檢視手冊學習更多的技巧。檢視手冊方式上文已經給出。即在命令列模式輸入::help user-manual
再回車。
如果本文能讓你重新認識了 vim 這位眉目清新又純潔善良的“姑娘”,並且從此對她有了一份獨有的愛意,算是本文創作的最大收穫所在^^。
本文首發公號