­

一開始看不上親密接觸後又讓你欲罷不能的 vim

  • 2019 年 11 月 21 日
  • 筆記

每一個程式設計師都有一款心心念叨的程式碼編輯器。而在眾多妖顏魅惑的編輯器偶像團體前,vim 就像個不加粉飾的農村姑娘,咋一看是那樣樸實無華,難有傾心。但只要走近一點,來個親密接觸,又會被她的似水柔情所俘獲。

這是青筆親身經歷。公司從騰訊空降技術總監,時不時會分享一些他的開發經驗。其中說到編輯器,他的臉上總會泛起淡淡桃花,「我就喜歡 vim!」 。儘管我們曾一致向他引薦當下紅極一時集美貌與才華於一身的 vscode 。他卻依然心有所屬,不為所動。

多少次,我百思不得其解,這個「相貌平平」的 vim 到底有何魔力,能牢牢扣住總監的心弦。在我真正走進她的內心世界,多次晝夜相伴之後。我最終也被她的清新脫俗所拜倒。

接下來就讓我們一起來了解這名神秘的「女子」。

1. vim 歷史

以下是網上找到一張圖。如對 vim 發展歷史感興趣可以參考維基百科的詞條 Vim (text editor)

https://en.wikipedia.org/wiki/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將游標向左移動一個字:

如果想近一步擴大游標單次移動的範圍,就要用到按句子和段落來前後移動來。兩對圓括弧()分別將游標向後和向前移動一個句子,對應兩個花括弧{}分別將游標向後和向前移動一個段落。

  • ( : 向後移動一個句子,游標定位在句子開始
  • ) : 向前移動一個句子,游標定位在句子開始
  • { : 向後移動一個段落,游標定位在段落開始
  • } : 向前移動一個段落,游標定位在段落開始

命令模式按下)(向前和向後移動一個句子:

命令模式按下}{向前和向後移動一個段落:

此外你還可以在螢幕所見範圍內進行快速移動游標。分別使用大寫的HML。但是這三種定位並不是很精確,通常用做快速大範圍級定位,然後再使用前面的命令進行更加精確的定位。讀者可以自行嘗試。

  • H: 將游標定位到螢幕頂部一行的最左端
  • M: 將游標定位到螢幕中間一行
  • L: 將游標定位到螢幕的底部一行

還有一種我們非常熟悉的應用場景,就是在調試程式拋出異常時,通常會顯示出異常產生的行號,這時就需要根據行號快速將游標定位到指定的位置。這個命令相比前面的命令要複雜一點,它需要先輸入代表行號的一個或多個數字,然後按下大寫字母G來完成。例如將游標定位到文件的第 80 行。需要先在命令模式輸入80,然後快速按下大寫字母G

4.2 刪除/撤銷

將刪除和撤銷兩個操作放在一起講,其中一個原因是考慮到可能因為還不知道如何撤銷刪除操作,而害怕嘗試刪除操作的心理(儘管我們已經事先做了備份,但這應該是一種普通的心理和人性害怕失去是同樣的道理)。我們知道可以撤銷,因此在刪除這件事上就有了「安全感」。也許你不是這麼認為,但是這樣還是能幫助我們更好的練習和記憶(我們可以循環往複地練習刪除撤銷)。

和移動游標一樣,刪除也可以按不同粒度進行。如刪除單個字元,字,行,句子,段落以及螢幕首尾。刪除操作由用字母d加上表示刪除範圍的標識符構成。同時刪除具體範圍還受游標當前所在的具體位置決定。例如刪除字使用字母組合dw,如果游標出現在單詞hello的第二個字元e上,此時在命令模式連續按下dw,將刪除從e開始之後的整個字,但是會e前面的h不會刪除,刪除後的結果就是還剩一個字母h。其他粒度的刪除,也遵循相似的規律。

刪除單個字元

刪除單個字元有兩者方法。兩者等效,都是刪除游標所在的字元,但是使用x更簡單一些,因為只需要輸入一個字母。

  • x
  • dl

刪除字

  • dw: 從游標所在位置開始,刪除到字的末尾(包含游標所在位置的字元)
  • db: 與dw相反方向刪除,即刪除游標所在位置前面的字元(不包含游標所在位置的字元)

刪除行

  • dd: 刪除游標所在的行
  • 3dd: 刪除從游標開始的3行,當然這是一個例子,可以更改前面的數字刪除任意數量的行

一次刪除3行:

刪除到行尾

如果你不想刪除整行。而是從游標開始到行尾的字元,可以直接使用一個大寫D來實現。

使用大寫字母D刪除從游標位置開始到行尾的字元:

從行首刪除

與刪除到行尾對應的是使用d0從行首刪除:

d^與d$

如果對正則表達式熟悉,應該很容易猜到兩者的含義。也是刪除行首和行尾,但是與前面的 d0D 所不同的是不刪除行首和行尾的空格。

  • 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 這位眉目清新又純潔善良的「姑娘」,並且從此對她有了一份獨有的愛意,算是本文創作的最大收穫所在^^。