現代的專案開發,很少在從無到有打造,大部分都是基於現有的程式之上繼續開發或維護,進入專案之後,通常第一件事就是大量閱讀程式碼理清專案的整個脈絡,才開始著手寫 code,所以好的程式碼導覽技巧將會帶你上天堂。
我將大部分的程式碼導覽情境大致拆成了三種情況:
- 基本的跳轉:
#if
#endif
中間的程式碼太長,想要跳轉到成對的#if
#endif
- cursor 在 {…} block 裡, 這個 block 又臭又長,想快速跳轉 block 的開頭和結尾
- 註解長篇大論,想快速跳到註解的開頭或結尾
- 尋找專案下檔案 (可透過設定 path option 解決)
- 跳轉到標頭檔
- 跳轉到指定檔案
- 使用 source code tagging system 快速挑轉到 function 定義及特定 symbol 等等: 專案相當大的時候,source code tagging system 相當好用,可以快速找到定位,也不會花掉太多的資源,比起 LSP 的跳轉定義,是較輕量的選擇。
本篇依據上述三種情境,一一講述。
常用跳轉操作
%
- 跳到成對的括號
- 如果註解是
/*...*/
,跳到成對的註解符號 - 在成對的
#if
,#ifdef
,#else
,#elif
,#endif
之中跳轉,這相當重要,如果 code 很長, if else macro 又是巢狀的,這招可以讓你少迷路好幾次
[{
]}
[{
:若 cursor 在 {…} 區塊中,跳到此區塊的 {]}
:若 cursor 在 {…} 區塊中,跳到此區塊的 }
[*
]*
[*
:找到上一個 /*]*
:找到下一個 */
[#
]#
用法和[{
]}
類似,但是針對的是#
在 line2 區塊[{
跳到 line1,]}
跳到 line3 在 line4 區塊[{
跳到 line3,]}
跳到 line5 在 line6 區塊[{
跳到 line5,]}
跳到 line7#ifdef TEST // line2 #elif TEST2 // line4 #else // line6 #endif
若 cursor 在 #else…#endif 區塊中,跳到 #else
透過 path 設定,快速查找相關檔案
看看以下圖片,將 cursor 移動到程式碼標頭檔的位置 (stdio.h),按下 gf
,bang! 你跳到 stdio.h 標頭檔了
再來看看 :find
指令,我想找出 stdlib.h 標頭檔,使用 :find stdlib.h
, 就跳到 stdlib.h 啦
我們來看看 vim 到底怎麼實現這個魔法的:
其實 vim 是藉由 path
這個 option 去搜尋的 (詳見 :help 'path'
),path 這個值由許多路徑組成並由逗號隔開,在 nix 系統裡 vim 對 path 預設的值是
.,/usr/include,,
可以看到三個值:
.
: 當前檔案所在的目錄/usr/include
: 很顯然找到gf
找到的 stdio.h 和:find
找到的 stdlib.h 是由這個目錄找出來的- empty string:工作目錄 (current work directory, 通常是 vim 開啟的位置,如果你沒用
:cd
的話)
所以可以把自己常用的標頭檔所在目錄加入 path ! 舉個例子 /usr/local/include
也是很常放標頭檔的路徑,所以我把它加入到 path
:set path+=/usr/local/include
註: .
和 empty string 的差別請看:is-vims-default-path-option-redundant
**
算是一個蠻好用的值,它會對路徑做 recursive 的解析,你可能會使用到它,詳細的規則可以看 :help starstar
Source code tagging system 和 vim
2020 年 Linux kernel 原始碼規模已經達到 2780 萬行,規模如此之龐大,Linux 的程式開發人員到底是怎麼開發的,隨便想找一個 function 定義都相當吃力,總不能用 grep 慢慢去找吧!這時候 source code tagging system 就派上用場了,它可以對專案所有的 symbol 產生索引檔,需要查找目標時就藉由這些索引檔快速查找位置。概念其實就是字典的索引,字典這麼大一本不會有人從頭掃到尾,都是直接翻到字典最後面的索引快速找到目標。
這些 source code tagging system,通常會產生索引檔並且提供一個介面,讓編輯器使用它們快速在程式碼內定位。
接下來將逐一介紹 cscope, gtags, ctags 等等三個 source code tagging system 在 vim 裡的基本使用方式。
Cscope
Cscope 簡介
cscope 官網聲稱可以做到以下事情:
- 查詢所有使用到這個 symbol 的地方
- 查詢全域變數
- 查詢 function 在哪被呼叫
- 查詢這個 function 呼叫了誰
- 使用字串查詢找到目標
- 使用 egrep pattern 尋找目標
- 快速尋找專案下檔案
- 查看哪個檔案 include 當前檔案
cscope 指令常用選項
- b:只產生索引檔案,不進入互動查詢介面
- q:產生 cscope.in.out 和 cscope.po.out文件,加快 cscope 的索引速度
- k:產生索引檔案时,不搜尋 /usr/include
- R:遞迴搜尋子目錄
Cscope 和 vim
在專案目錄產生索引檔案:
$ cscope -bkqR
會產生三個檔案 cscope.in.out
cscope.out
cscope.po.out
進入 vim 之後可以透過 cs
查看 cscope 在 vim 裡的使用方式
讓 vim 知道 cscope 的路徑
:set cscopeprg=/path/to/cscope
指定 cscope 索引檔案,讓 vim 知道要用哪個索引檔案查詢:
:cs add cscope.out
接下來就可以使用以下方式查詢 (xxx 為想搜尋的目標):
:cs find a xxx
查尋 xxx 被賦值的地方:cs find c xxx
查尋所有呼叫 xxx function 的地方:cs find d xxx
查尋 xxx function 呼叫的所有函數:cs find e xxx
使用 egrep pattern 查詢:cs find f xxx
找出 xxx 檔案在哪裡:cs find g xxx
查尋 xxx 所定義的地方 (通常最常用的是這個):cs find i xxx
查尋所有 inlcude xxx 檔案的地方:cs find s xxx
查尋所有 xxx symbol 出現的地方:cs find t xxx
查詢 xxx 字串出現的地方,其實搜尋效果就類似 grep
記得查詢完的結果可以用 :cw
, :copen
在 quickfix window 列出:
GNU GLOBAL (gtags)
由 GNU GLOBAL 由 GNU 所開發的 soure code tagging system,本身支持四種語言,透過 Pygments 和 Universal Ctags 的 parser 可以支持其他 25 種語言,支援 incremental update,原始碼更動後,只會對改變的地方索引,不會重新索引,這對龐大的專案有很大的幫助。另外 gtags 提供了 cscope 相容的互動介面,叫做 gtags-cscope,讓你用起來像 cscope,但背後其實是 gtags 的索引檔案。
在專案目錄產生索引檔案:
$ gtags
讓 vim 知道 gtags-cscope 路徑:
:set cscopeprg=/path/to/gtsgs-cscope
指定 gtags 索引檔案,讓 vim 知道要用哪個索引檔案查詢:
:cs add GTAGS
借下來就可以用和 cscope 一樣的方式查詢,除了 :cs find d
,其他皆支援
Ctags
Ctags 簡介
最早的 Ctag 在 BSD 上實現,版本變革的歷史脈絡我不是相當清楚,查不太到資料。 現在普及的版本是 Exuberant Ctags,一開始和 vim 是一包軟體,一起發布,但在 vim6 之後獨立成兩個專案,從這裏可看出它一定和 vim 兼容良好,但此版本在 2009 年之後已不再維護。建議使用Exuberant Ctags 所 fork 的版本 Universal Ctags,目前還在維護和開發當中。
Ctags 和 vim
在專案下生成索引檔案,可以看到目錄裡有 tags
檔案
$ ctags -R *
指定 tags 索引檔案,讓 vim 知道要用哪個索引檔案查詢:
:set tags=tags
再來就可以使用:
Ctrl + ]
: 跳到定義,如果有多個匹配則會跳到第一個Ctrl + T
: 跳回原本的地方
每次用 ctrl + ]
跳轉到定義,vim 就會把這位置推到 tag stack 裡,每次 ctrl + T
跳回原來的地方,vim 就會從 tag stack 裡 pop 位置出來,我們可以使用 :tags
看到 tag stack 裡的內容。