1. 程式人生 > >將VIM打造成強大的IDE

將VIM打造成強大的IDE

【正文】

開始前,我假設你:0)具備基本的 vim 操作能力,清楚如何開啟/編輯/儲存文件、命令與插入模式間切換;1)希望將 vim 打造成 C/C++ 語言的 IDE,而非其他語言。

關於 vim 的優點,你在網上能查到 128+ 項,對我而言,只有兩項:0)所想即所得,讓手輸入的速度跟上大腦思考的速度,1)所需即所獲,只有你想不到的功能、沒有實現不了的外掛。希望獲得前者的能力,你需要兩本教程深入學習,《Practical Vim: Edit Text at the Speed of Thought》和《vim user manual》;要想擁有後者的能力,通讀本文 -。-#。對於 vim 的喜愛,獻上溼哥哥以表景仰之情:

vi 之大道如我心之禪,
vi 之漫路即為禪修,
vi 之命令禪印於心,
未得此道者視之怪誕,
與之為伴者洞其真諦,
長修此道者鉅變人生。
作:[email protected]
譯:[email protected]

言歸正傳,說說 vim 用於程式碼編寫提供了哪些直接和間接功能支撐。vim 使用者手冊中,50% 的例子都是在講 vim 如何高效編寫程式碼,由此可見,vim 是一款面向於程式設計師的編輯器,即使某些功能 vim 無法直接完成,藉助其豐富的外掛資源,必定可以達成目標,這就是所需即所獲。 我是個目標驅動的信奉者,本文內容,我會先給出優秀 C/C++ IDE 應具備哪些功能,再去探索如何通過 vim 的操作或外掛來達到目標。最終至少要像這個樣子:

(圖形環境下 IDE 總攬)
(純字元模式下 IDE 總攬)

0 vim 必知會

在正式開始前先介紹幾個 vim 的必知會,這不是關於如何使用而是如何配置 vim 的要點,這對理解後續相關配置非常有幫助。

0.1 .vimrc 檔案

.vimrc 是控制 vim 行為的配置檔案,位於 ~/.vimrc,不論 vim 視窗外觀、顯示字型,還是操作方式、快捷鍵、外掛屬性均可通過編輯該配置檔案將 vim 調教成最適合你的編輯器。

很多人之所以覺得 vim 難用,是因為 vim 缺少預設設定,甚至安裝完後你連配置檔案自身都找不到,不進行任何配置的 vim 的確難看、難用。不論用於程式碼還是普通文字編輯,有必要將如下基本配置加入 .vimrc 中。

字首鍵。各類 vim 外掛幫助文件中經常出現 ,即,字首鍵。vim 自帶有很多快捷鍵,再加上各類外掛的快捷鍵,大量快捷鍵出現在單層空間中難免引起衝突,為緩解該問題,引入了字首鍵 ,這樣,鍵 r 可以配置成 r、r、r 等等多個快捷鍵。字首鍵是 vim 使用率較高的一個鍵(最高的當屬 Esc),選一個最方便輸入的鍵作為字首鍵,將有助於提高編輯效率。找個無須眼睛查詢、無須移動手指的鍵 —— 分號鍵,挺方便的,就在你右手小指處:

" 定義快捷鍵的字首,即<Leader>
let mapleader=";"

既然字首鍵是為快捷鍵服務的,那隨便說下快捷鍵設定原則:不同快捷鍵儘量不要有同序的相同字元。比如,e 執行操作 0 和 eb 執行操作 1,在你鍵入 e 後,vim 不會立即執行操作 0,而是繼續等待使用者鍵入 b,即便你只想鍵入 e,vim 也不得不花時間等待輸入以確認是哪個快捷鍵,顯然,這讓 e 響應速度變慢。ea 和 eb 就沒問題。

檔案型別偵測。允許基於不同語言載入不同外掛(如,C++ 的語法高亮外掛與 python 的不同):

" 開啟檔案型別偵測
filetype on
" 根據偵測到的不同型別載入對應的外掛
filetype plugin on

快捷鍵。把 vim(非外掛)常用操作設定成快捷鍵,提升效率:

" 定義快捷鍵到行首和行尾
nmap lb 0
nmap le $
" 設定快捷鍵將選中文字塊複製至系統剪貼簿
vnoremap <Leader>y "+y
" 設定快捷鍵將系統剪貼簿內容貼上至 vim
nmap <Leader>p "+p
" 定義快捷鍵關閉當前分割視窗
nmap <Leader>q :q<CR>
" 定義快捷鍵儲存當前視窗內容
nmap <Leader>w :w<CR>
" 定義快捷鍵儲存所有視窗內容並退出 vim
nmap <Leader>WQ :wa<CR>:q<CR>
" 不做任何儲存,直接退出 vim
nmap <Leader>Q :qa!<CR>
" 依次遍歷子視窗
nnoremap nw <C-W><C-W>
" 跳轉至右方的視窗
nnoremap <Leader>lw <C-W>l
" 跳轉至方的視窗
nnoremap <Leader>hw <C-W>h
" 跳轉至上方的子視窗
nnoremap <Leader>kw <C-W>k
" 跳轉至下方的子視窗
nnoremap <Leader>jw <C-W>j
" 定義快捷鍵在結對符之間跳轉,助記pair
nmap <Leader>pa %

其他。搜尋、vim 命令補全等設定:

" 開啟實時搜尋功能
set incsearch
" 搜尋時大小寫不敏感
set ignorecase
" 關閉相容模式
set nocompatible
" vim 自身命令列模式智慧補全
set wildmenu

以上的四類配置不僅影響 vim,而且影響外掛是否能正常執行。很多外掛不僅要在 .vimrc 中新增各自特有的配置資訊,還要增加 vim 自身的配置資訊,在後文的各類外掛介紹中,我只介紹對應外掛特有配置資訊,當你發現按文中介紹操作後外掛未生效,很可能是 vim 自身配置資訊未新增,所以一定要把上述配置拷貝至到你的 .vimrc 中,再對照本文介紹一步步操作。.vimrc 完整配置資訊參見附錄,每個配置項都有對應註釋。另外,由於有些外掛還未來得及安裝,在你實驗前面的外掛是否生效時,vim 可能有報錯資訊提示,先別理會,安裝完所有外掛後自然對了。

0.2 .vim/ 目錄

.vim/ 目錄是存放所有外掛的地方。vim 有一套自己的指令碼語言 vimscript,通過這種指令碼語言可以實現與 vim 互動,達到功能擴充套件的目的。一組 vimscript 就是一個 vim 外掛,vim 的很多功能都由各式外掛實現。此外,vim 還支援 perl、python、lua、ruby 等主流指令碼語言編寫的外掛,前提是 vim 原始碼編譯時增加 ---enable-perlinterp、--enable-pythoninterp、--enable-luainterp、--enable-rubyinterp 等選項。vim.org 和 github.com 有豐富的外掛資源,任何你想得到的功能,如果 vim 無法直接支援,那一般都有對應的外掛為你服務,有需求時可以去逛逛。

vim 外掛目前分為 *.vim 和 *.vba 兩類,前者是傳統格式的外掛,實際上就是一個文字檔案,通常 someplugin.vim(外掛指令碼)與 someplugin.txt(外掛幫助檔案)並存在一個打包檔案中,解包後將 someplugin.vim 拷貝到 ~/.vim/plugin/ 目錄,someplugin.txt 拷貝到 ~/.vim/doc/ 目錄即可完成安裝,重啟 vim 後剛安裝的外掛就已經生效,但幫助檔案需執行 :helptags ~/.vim/doc/ 才能生效,可通過 :h someplugin 檢視外掛幫助資訊。傳統格式外掛需要解包和兩次拷貝才能完成安裝,相對較繁瑣,所以後來又出現了 *.vba 格式外掛,安裝便捷,只需在 shell 中依次執行如下命令即可:

vim someplugin.vba
:so %
:q

不論是直接拷貝外掛到目錄,還是通過 *.vba 安裝,都不便於外掛解除安裝、升級,後來又出現了管理外掛的外掛 pathogen,後文介紹。

後面就正式開始了嘍,文中前後內容順序敏感,請依次查閱。

1 原始碼安裝編輯器 vim

發行套件的軟體源中預編譯的 vim 要麼不是最新版本,要麼功能有閹割,有必要升級成全功能的最新版,當然,原始碼安裝必須滴。

cd ~/downloads/vim74/
./configure --with-features=huge --enable-rubyinterp --enable-pythoninterp --with-python-config-dir=/usr/lib/python2.7/config/ --enable-perlinterp --enable-gui=gtk2 --enable-cscope --prefix=/usr --enable-luainterp 
ake VIMRUNTIMEDIR=/usr/share/vim/vim74 && make install

其中,--enable-rubyinterp、--enable-pythoninterp、--enable-perlinterp、--enable-luainterp 等分別表示支援 ruby、python、perl、lua 編寫的外掛,--enable-gui=gtk2 表示生成 gvim,--enable-cscope 支援 cscope,--with-python-config-dir=/usr/lib/python2.7/config/ 指定 python 路徑(先自行安裝 python 的標頭檔案 python-devel),這幾個特性非常重要,影響後面各類外掛的使用。注意,你得預先安裝相關依賴庫的標頭檔案,python-devel、python3-devel、ruby-devel、libX11-devel、gtk-devel、gtk2-devel、gtk3-devel,如果缺失,原始碼構建過程雖不會報錯,但構建完成後的 vim 很可能丟失相關功能。構建完成後執行在 vim 中執行

:echo has('python')

若輸出 1 則表示構建出的 vim 已支援 python,反之,0 則不支援。

2 外掛管理

既然本文主旨在於講解如何通過外掛將 vim 打造成中意的 C/C++ IDE,那麼高效管理外掛是首要解決的問題。

vim 自身希望通過在 .vim/ 目錄中預定義子目錄管理所有外掛(比如,子目錄 doc/ 存放外掛幫助文件、plugin/ 存放通用外掛指令碼),vim 的各外掛打包文件中通常也包含上述兩個(甚至更多)子目錄,使用者將外掛打包文件中的對應子目錄拷貝至 .vim/ 目錄即可完成外掛的安裝。一般情況下這種方式沒問題,但我等重度外掛使用者,.vim/ 將變得混亂不堪,至少存在如下幾個問題:

  • 外掛名字衝突。所有外掛的幫助文件都在 doc/ 子目錄、外掛指令碼都在 plugin/ 子目錄,同個名字空間下必然引發名字衝突;
  • 外掛解除安裝麻煩。你需要先知道 doc/ 和 plugin/ 子目錄下哪些檔案是屬於該外掛的,再逐一刪除,容易多刪/漏刪。

我希望每個外掛在 .vim/ 下都有各自獨立子目錄,這樣需要升級、解除安裝外掛時,直接找到對應外掛目錄變更即可。pathogen 為此而生,它突破了 vim 只能識別 .vim/doc/、.vim/plugin/ 等等路徑的限制,你可以在按外掛名建立獨立目錄,然後將外掛打包檔提取至各自外掛目錄中。通常來說,你需要先建立 ~/.vim/bundle/ 目錄,bundle/ 就是以後存放各外掛目錄的父目錄。

安裝:先清空 .vim/ 下的所有檔案(備份?);建立目錄 ~/.vim/bundle/pathogen/autoload/;下載 pathogen.vim(https://github.com/tpope/vim-pathogen )至 ~/.vim/bundle/pathogen/autoload/。

設定:接下來在 .vimrc 增加相關配置資訊:

# 將 pathogen 自身也置於獨立目錄中,需指定其路徑 
runtime bundle/pathogen/autoload/pathogen.vim
# 執行 pathogen
execute pathogen#infect()

使用:比如要安裝新外掛 plugin_name,先在 ~/.vim/bundle/ 下建立目錄 plugin_name/,然後到 vim 官網下載 plugin_name 壓縮包並解壓至 ~/.vim/bundle/plugin_name/ 即可,注意不要重複包含多次 plugin_name/ 目錄,如,~/.vim/bundle/plugin_name/plugin_name/。要解除安裝外掛,直接刪除 plugin_name/ 外掛目錄即可。另外,通過 pathogen 管理外掛後,相較以前有幾點變化:

  • 切勿通過發行套件自帶的軟體管理工具安裝任何外掛,不然 .vim/ 又要混亂了;
  • pathogen 無法安裝配色主題風格,只能將主題外掛手工放置於 ~/.vim/colors/;
  • 安裝 *.vba 型別外掛:
:e plugin_name.vba 
:!mkdir -p ~/.vim/bundle/plugin_name 
:UseVimball ~/.vim/bundle/plugin_name 
  • 生成幫助文件:
:Helptags 

非特殊情況,後文介紹到的外掛不再累述如何安裝。

此外,你得注意外掛的下載源。相同外掛在 vim.org 和 github.com 上都能找到,有些外掛在 vim.org 上是最新版,有些又在 github.com 上更新,比如,indexer 外掛,在 vim.org上的版本是 4.15(http://www.vim.org/scripts/script.php?script_id=3221 ),而在 github.com 上的卻是 1.2(https://github.com/shemerey/vim-indexer ),所以我建議先去作者個人網站上找,沒有再在 vim.org 和 github.com 上比較哪個的最新。甚至,同在 github.com 上都有很多重名外掛,自己得稍微花時間確認下,本文中出現的外掛,我都會附上最新版下載地址。還有,外掛更新頻率較高,差不多每隔一季你應該看看哪些外掛有推出新版本!

3 介面美化

玉不琢不成器,vim 不配不算美。剛安裝好的 vim 樸素得嚇人,這是與我同時代的軟體麼?


(預設 vim 介面)

就我的審美觀而言,至少有幾個問題:語法高亮太單薄、主題風格太簡陋、視窗元素太冗餘、輔助資訊太欠缺。

3.1 主題風格

一套好的配色方案絕對會影響你的編碼效率,vim 內建了 10 多種配色方案供你選擇,GUI 下,可以通過選單(Edit -> Color Scheme)試用不同方案,字元模式下,需要你手工調整配置資訊,再重啟 vim 檢視效果(csExplorer 外掛,可在字元模式下不用重啟即可檢視效果)。不滿意,可以去http://vimcolorschemetest.googlecode.com/svn/html/index-c.html 慢慢選。我自認為“閱美無數”,目前最夯三甲:

前面說過,pathogen 無法安裝主題外掛,請將主題外掛(僅 *.vim 檔案而非外掛目錄,即,solarized.vim、molokai.vim、phd.vim)拷貝至 ~/.vim/colors/,然後在 .vimrc 中設定選用其作為主題:

" 配色方案
set background=dark
colorscheme solarized
"colorscheme molokai
"colorscheme phd

其中,不同主題都有暗/亮色系之分,這樣三種主題六種風格,久不久換一換,給你不一樣的心情:


(solarized 主題風格)

3.2 營造專注氛圍

如今的 UX 設計講究的是內容至上,從 GNOME3 的變化就能看出。編輯器介面展示的應全是程式碼,不應該有工具條、選單、滾動條浪費空間的元素,另外,程式設計是種精神高度集中的腦力勞動,不應出現閃爍游標、花哨滑鼠這些分散注意力的東東。配置如下:

" 禁止游標閃爍
set gcr=a:block-blinkon0
" 禁止顯示滾動條
set guioptions-=l
set guioptions-=L
set guioptions-=r
set guioptions-=R
" 禁止顯示選單和工具條
set guioptions-=m
set guioptions-=T

重啟 vim 後效果如下:


(去除冗餘視窗元素)

還容易分神?好吧,我們把 vim 弄成全屏模式。vim 自身無法實現全屏,必須藉助第三方工具 wmctrl,一個控制視窗 XYZ 座標、視窗尺寸的命令列工具。先自行安裝 wmctrl,再在 .vimrc 中增加如下資訊:

" 將外部命令 wmctrl 控制視窗最大化的命令列引數封裝成一個 vim 的函式
fun! ToggleFullscreen()
    call system("wmctrl -ir " . v:windowid . " -b toggle,fullscreen")
endf
" 全屏開/關快捷鍵
map <silent> <F11> :call ToggleFullscreen()<CR>
" 啟動 vim 時自動全屏
autocmd VimEnter * call ToggleFullscreen()

上面是一段簡單的 vimscript 指令碼,外部命令 wmctrl 及其命令列引數控制將指定視窗 windowid(即,vim)全屏,繫結快捷鍵 F11 實現全屏/視窗模式切換(linux 下各 GUI 軟體約定使用 F11 全屏,最好遵守約定),最後配置啟動時自動全屏。

3.3 新增輔助資訊

去除了冗餘元素讓 vim 介面清爽多了,為那些實用輔助資訊騰出了空間。游標當前位置、顯示行號、高亮當前行/列等等都很有用:

" 總是顯示狀態列
set laststatus=2
" 顯示游標當前位置
set ruler
" 開啟行號顯示
set number
" 高亮顯示當前行/列
set cursorline
set cursorcolumn
" 高亮顯示搜尋結果
set hlsearch

效果如下:


(新增輔助資訊)

3.4 其他美化

預設字型不好看,挑個自己喜歡的,前提是你得先安裝好該字型。中文字型,我喜歡飽滿方正的(微軟雅黑),英文字型喜歡圓潤的(Consolas),vim 無法同時使用兩種字型,怎麼辦?有人制作釋出了一款中文字型用微軟雅黑、英文字型用 Consolas 的混合字型 —— yahei consolas hybrid 字型,號稱最適合中國程式設計師使用的字型,效果非常不錯(本文全文采用該字型)。在 .vimrc 中設定下:

" 設定 gvim 顯示字型
set guifont=YaHei\ Consolas\ Hybrid\ 11.5

其中,由於字型名存在空格,需要用轉義符“\”進行轉義;最後的 11.5 用於指定字型大小。

程式碼折行也不太美觀,禁止掉:

" 禁止折行
set nowrap
" 設定狀態列主題風格
let g:Powerline_colorscheme='solarized256'

效果如下:


(介面美化最終效果)

圖中,中英文混合字型看著是不是很舒服哈;增強後的狀態列,不僅介面漂亮多了,而且多了好些輔助資訊(所在函式名、檔案編碼格式、檔案型別)。

4 程式碼分析

閱讀優秀開源專案原始碼是提高能力的重要手段,營造舒適、便利的閱讀環境至關重要。

4.1 語法高亮

程式碼只有一種顏色的編輯器,就好像紅綠燈只有一種顏色的路口,全然無指引。現在已是千禧年後的十年了,早已告別上世紀六、七十年代黑底白字的時代,即使在字元模式下程式設計(感謝偉大的 fbterm),我也需要語法高亮。所幸 vim 自身支援語法高亮,只需顯式開啟即可:

" 開啟語法高亮功能
syntax enable
" 允許用指定語法高亮配色方案替換預設方案
syntax on

效果如下:


(語法高亮)

上圖中 STL 容器模板類 unordered_multimap 並未高亮,對滴,vim 對 C++ 語法高亮支援不夠好(特別是 STL、C++11 新增元素),必須藉由外掛 stl.vim 進行增強,下載(http://www.vim.org/scripts/script.php?script_id=4293 )後拷貝至 ~/.vim/bundle/STL-Syntax/after/syntax/cpp/,重啟即可。效果如下:


(增強 C++11 及 STL 的語法高亮)

4.2 程式碼縮排

C/C++ 中的程式碼執行流由複合語句控制,如 if(){} 判斷複合語句、for(){} 迴圈符號語句等等,這勢必出現大量縮排。縮排雖然不影響語法正確性,但對提升程式碼清晰度有不可替代的功效。

在 vim 中有兩類縮排表示法,一類是用 1 個製表符('\t'),一類是用多個空格(' ')。兩者並無本質區別,只是原始碼檔案儲存的字元不同而已,但,縮排視覺化外掛對兩類縮排顯示方式不同,前者只能顯示為粗塊,後者可顯示為細條,就我的審美觀而言,選後者。增加如下配置資訊:

" 自適應不同語言的智慧縮排
filetype indent on
" 將製表符擴充套件為空格
set expandtab
" 設定編輯時製表符佔用空格數
set tabstop=4
" 設定格式化時製表符佔用空格數
set shiftwidth=4
" 讓 vim 把連續數量的空格視為一個製表符
set softtabstop=4

其中,注意下 expandtab、tabstop 與 shiftwidth、softtabstop、retab:

  • expandtab,把製表符轉換為多個空格,具體空格數量參考 tabstop 和 shiftwidth 變數;
  • tabstop 與 shiftwidth 是有區別的。tabstop 指定我們在插入模式下輸入一個製表符佔據的空格數量,linux 核心編碼規範建議是 8,看個人需要;shiftwidth 指定在進行縮排格式化原始碼時製表符佔據的空格數。所謂縮排格式化,指的是通過 vim 命令由 vim 自動對原始碼進行縮排處理,比如其他人的程式碼不滿足你的縮排要求,你就可以對其進行縮排格式化。縮排格式化,需要先選中指定行,要麼鍵入 = 讓 vim 對該行進行智慧縮排格式化,要麼按需鍵入多次 < 或 > 手工縮排格式化;
  • softtabstop,如何處理連續多個空格。因為 expandtab 已經把製表符轉換為空格,當你要刪除製表符時你得連續刪除多個空格,該就是告訴 vim 把連續數量的空格視為一個製表符,即,只刪一個字元即可。通常應將這tabstop、shiftwidth、softtabstop 三個變數設定為相同值;

另外,你總會閱讀其他人的程式碼吧,他們對製表符定義規則與你不同,這時你可以手工執行 vim 的 retab 命令,讓 vim 按上述規則重新處理製表符與空格關係。

很多編碼規範建議縮排(程式碼巢狀類似)最多不能超過 4 層,但難免有更多層的情況,縮排一多,我那個暈啊:


(多層縮排)
" 隨 vim 自啟動
let g:indent_guides_enable_on_vim_startup=1
" 從第二層開始視覺化顯示縮排
let g:indent_guides_start_level=2
" 色塊寬度
let g:indent_guides_guide_size=1
" 快捷鍵 i 開/關縮排視覺化
:nmap <silent> <Leader>i <Plug>IndentGuidesToggle

重啟 vim 效果如下:


(不連續的縮排視覺化)

斷節?Indent Guides 通過識別製表符來繪製縮排連線線,斷節處是空行,沒有製表符,自然繪製不出來,算是個小 bug,但瑕不掩瑜,有個小技巧可以解決,換行-空格-退格:


(完美視覺化縮排)

4.3 程式碼摺疊

有時為了去除干擾,集中精力在某部分程式碼片段上,我會把不關注部分程式碼摺疊起來。vim 自身支援多種摺疊:手動建立摺疊(manual)、基於縮排進行摺疊(indent)、基於語法進行摺疊(syntax)、未更改文字構成摺疊(diff)等等,其中,indent、syntax 比較適合程式設計,按需選用。增加如下配置資訊:

" 基於縮排或語法進行程式碼摺疊
"set foldmethod=indent
set foldmethod=syntax
" 啟動 vim 時關閉摺疊程式碼
set nofoldenable

操作:za,開啟或關閉當前摺疊;zM,關閉所有摺疊;zR,開啟所有摺疊。效果如下:


(程式碼摺疊)

4.4 介面與實現快速切換

我習慣把類的介面和實現分在不同檔案中,常會出現在介面檔案(MyClass.h)和實現檔案(MyClass.cpp)中來回切換的操作。你當然可以先分別開啟介面檔案和實現檔案,再手動切換,但效率不高。我希望,假如在介面檔案中,vim 自動幫我找到對應的實現檔案,當鍵入快捷鍵,可以在當前視窗中開啟對應實現檔案,也可以在當前視窗中分裂一個子視窗顯示對應實現檔案。

" *.cpp 和 *.h 間切換
nmap <Leader>ch :A<CR>
" 子視窗中顯示 *.cpp 或 *.h
nmap <Leader>sch :AS<CR>

這樣,鍵入 ;ch 就能在實現檔案和介面檔案間切換,鍵入 ;sch 子視窗中將顯示實現檔案/介面檔案。如下圖所示:


(介面檔案與實現檔案切換)

上圖中,初始狀態先打開了介面檔案 MyClass.h,鍵入 ;ch 後,vim 在新 buffer 中開啟實現檔案 MyClass.cpp,並在當前視窗中顯示;再次鍵入 ;ch 後,當前視窗切回介面檔案;鍵入 ;sch 後,當前視窗分裂了一個子視窗顯示實現檔案。

a.vim 實現原理很簡單,基於檔名進行關聯,比如,a.vim 能識別 my_class.h 與 my_class.cpp,而無法識別 my_class.h 與 your_class.cpp。所以,你在命名檔案時得注意下。

4.5 程式碼收藏

原始碼分析過程中,常常需要在不同程式碼間來回跳轉,我需要“收藏”分散在不同處的程式碼行,以便需要檢視時能快速跳轉過去,這時,vim 的書籤(mark)功能派上大用途了。

vim 書籤的使用很簡單,在你需要收藏的程式碼行鍵入 mm,這樣就收藏好了,你試試,沒反應?不會吧,難道你 linux 核心編譯引數有問題,或者,vim 的編譯引數沒給全,讓我想想,別急,喔,對了,你是指看不到書籤?對對對,書籤本來就看不到吖。這可不行,小二,來個讓書籤視覺化的外掛,親,來了,visual mark (https://github.com/vim-scripts/Visual-Mark ),記得好評。

visual mark 使用快捷鍵 mm 建立/刪除書籤,F2 正向遍歷書籤,Shift + F2 逆向遍歷,不太方便,得改;另外,書籤顏色不好看,得調。看看幫助如何配置,昏,沒幫助,得,直接改它的原始碼吧。找到 ~/.vim/bundle/Visual-Mark/plugin/visualmark.vim,將

map <unique> <F2> <Plug>Vm_goto_next_sign
map <unique> <s-F2> <Plug>Vm_goto_prev_sign

替換成

map <unique> mn <Plug>Vm_goto_next_sign
map <unique> mp <Plug>Vm_goto_prev_sign

這樣,mn 正向遍歷書籤、mp 逆向遍歷;再將

if &bg == "dark" 
 highlight SignColor ctermfg=white ctermbg=blue guifg=white guibg=RoyalBlue3 
else 
 highlight SignColor ctermbg=white ctermfg=blue guibg=grey guifg=RoyalBlue3 
endif

替換成

if &bg == "dark" 
 highlight SignColor ctermfg=white ctermbg=blue guifg=#FD971F guibg=#1D1D1D
else 
 highlight SignColor ctermbg=white ctermfg=blue guifg=LightGreen guibg=DarkRed
endif

你可以根據自己喜好提取喜歡顏色的 RGB(推薦,提色工具 gpick,色卡http://www.colorschemer.com/schemes/ ),按上例設定即可。提醒下,RGB 的字首是 # 而非 0X,別慣性思維 ._.

效果如下:


(視覺化書籤)

另外,我雖然選用了 visual mark,但不代表它完美了,對我而言,存在兩個硬傷:一是,建立的書籤無法儲存,下次開啟該檔案後又得重新視窗;一是,無法在不同檔案的書籤間跳轉。前者可藉由 vim 的 session 和 viminfo 特性解決,詳見後文“環境恢復”節,後者無解,只能先切換檔案再跳轉書籤。

4.6 程式碼導航

假設你正在分析某個開源專案原始碼,在 main.cpp 中遇到呼叫函式 func(),想要檢視它如何實現,一種方式:在 main.cpp 中查詢 -> 若沒有在工程內查詢 -> 找到後開啟對應檔案 -> 檔案內查詢其所在行 -> 移動游標到該行 -> 分析完後切換會先前檔案,不僅效率太低更要命的是影響我的思維連續性。我需要另外高效的方式,就像真正函式呼叫一樣:游標選中呼叫處的 func() -> 鍵入某個快捷鍵自動轉換到 func() 實現處 -> 鍵入某個鍵又回到 func() 呼叫處,這就是所謂的程式碼導航。

先了解下什麼是標籤(tag)。這可厲害了,標籤可謂是現代 IDE 的基石之一,沒有它,類/函式/物件列表、程式碼補全、程式碼導航、函式原型提示等等功能是不可能實現的。程式碼中的類、結構、類成員、函式、物件、巨集這些元素就是標籤,每個標籤有它自己的名字、定義、型別、所在檔案中的行位置、所在檔案的路徑等等屬性。

編譯環節之一就是提取標籤,但由於編譯器並未把生成的標籤輸出至文字,後來出現了專門用於生成標籤的工具 Exuberant Ctags (http://ctags.sourceforge.net/ ,有牆,後簡稱 ctags)。ctags,最初只支援生成 C/C++ 語言的標籤,目前已支援 41 種語言,具體列表執行如下命令獲取:

ctags --list-languages

學習知識最好方式就是動手實踐。我們以 main.cpp、my_class.h、my_class.cpp 三個檔案為例。

第一步,準備程式碼檔案。建立演示目錄 /data/workplace/example/、庫子目錄 /data/workplace/example/lib/,建立如下內容的 main.cpp:

#include <iostring> 
#include <string> 
#include "lib/my_class.h" 
using namespace std; 
int g_num = 128; 
// 過載函式 
static void 
printMsg (char ch) 
{ 
    std::cout << ch << std::endl; 
} 
int 
main (void) 
{ 
    // 區域性物件
    const string    name = "yangyang.gnu"; 
    // 類 
    MyClass one; 
    // 成員函式 
    one.printMsg(); 
    // 使用區域性物件 
    cout << g_num << name << endl; 
    return  (EXIT_SUCCESS); 
} 

建立如下內容的 my_class.h:

#pragma once 
class MyClass 
{ 
    public: 
        void printMsg(void);     
    private: 
        ; 
};

建立如下內容的 my_class.cpp:

#include "my_class.h" 
// 過載函式 
static void 
printMsg (int i) 
{ 
    std::cout << i << std::endl; 
} 
void 
MyClass::printMsg (void) 
{ 
    std::cout << "I'M MyClass!" << std::endl; 
}

第二步,生成標籤檔案。現在執行 ctags 生成標籤檔案:

cd /data/workplace/example/
ctags -R --c++-kinds=+p+l+x+c+d+e+f+g+m+n+s+t+u+v --fields=+liaS --extra=+q --language-force=c++

命令列引數較多,主要關注 --c++-kinds,ctags 預設並不會提取所有標籤,執行

ctags --list-kinds=c++ 

可看到 ctags 支援生成標籤型別的全量列表:

c classes
d macro definitions
e enumerators (values inside an enumeration)
f function definitions
g enumeration names
l local variables [off]
m class, struct, and union members
n namespaces
p function prototypes [off]
s structure names
t typedefs
u union names
v variable definitions
x external and forward variable declarations [off]

其中,標為 off 的區域性物件、函式宣告、外部物件等型別預設不會生成標籤,所以我顯式加上所有型別。執行完後,example/ 下多了個檔案 tags,內容大致如下:

!_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
!_TAG_PROGRAM_AUTHOR Darren Hiebert /[email protected]orge.net/
!_TAG_PROGRAM_NAME Exuberant Ctags //
!_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/
!_TAG_PROGRAM_VERSION 5.8 //
MyClass lib/my_class.h /^class MyClass $/;" c
MyClass::printMsg lib/my_class.cpp /^MyClass::printMsg (void) $/;" f class:MyClass signature:(void)
MyClass::printMsg lib/my_class.h /^ void printMsg(void);$/;" p class:MyClass access:public signature:(void)
endl lib/my_class.cpp /^ std::cout << "I'M MyClass!" << std::endl;$/;" m class:std file:
endl lib/my_class.cpp /^ std::cout << i << std::endl;$/;" m class:std file:
endl main.cpp /^ cout << g_num << name << endl;$/;" l
endl main.cpp /^ std::cout << ch << std::endl;$/;" m class:std file:
g_num main.cpp /^int g_num = 128;$/;" v
main main.cpp /^main (void) $/;" f signature:(void)
name main.cpp /^ const string name = "yangyang.gnu";$/;" l
one main.cpp /^ MyClass one;$/;" l
printMsg lib/my_class.cpp /^MyClass::printMsg (void) $/;" f class:MyClass signature:(void)
printMsg lib/my_class.cpp /^printMsg (int i) $/;" f file: signature:(int i)
printMsg lib/my_class.h /^ void printMsg(void);$/;" p class:MyClass access:public signature:(void)
printMsg main.cpp /^ one.printMsg();$/;" p file: signature:()
printMsg main.cpp /^printMsg (char ch) $/;" f file: signature:(char ch)
std::endl lib/my_class.cpp /^ std::cout << "I'M MyClass!" << std::endl;$/;" m class:std file:
std::endl lib/my_class.cpp /^ std::cout << i << std::endl;$/;" m class:std file:
std::endl main.cpp /^ std::cout << ch << std::endl;$/;" m class:std file:

其中,! 開頭的幾行是 ctags 生成的軟體資訊忽略之,下面的就是我們需要的標籤,每個標籤項至少有如下欄位(命令列引數不同標籤項的欄位數不同):標籤名、標籤所在的檔名(也是檔案路徑)、標籤項所在行的內容、標籤型別(如,l 表示區域性物件),另外,如果是函式,則有函式簽名欄位,如果是成員函式,則有訪問性欄位等等。

第三步,引入標籤檔案。就是讓 vim 知曉標籤檔案的路徑。在 /data/workplace/example/ 目錄下用 vim 開啟 main.cpp,在 vim 中執行如下目錄引入標籤檔案 tags:

:set tags+=/data/workplace/example/tags

既然 vim 有個專門的命令來引入標籤,說明 vim 能識別標籤。雖然標籤檔案中並無行號,