Usr_29

Nvim :help 頁面,由 產生,來源為 source,使用 tree-sitter-vimdoc 解析器。


VIM 使用者手冊 - 作者:Bram Moolenaar
在程式碼中移動
Vim 的創作者是一位電腦程式設計師。Vim 包含許多輔助編寫程式的功能,這並不令人意外。跳轉以尋找識別符的定義和使用位置。在單獨的視窗中預覽宣告。下一章將有更多內容。
29.1 使用標籤 29.2 預覽視窗 29.3 在程式碼中移動 29.4 尋找全域識別符 29.5 尋找區域識別符
下一章:usr_30.txt 編輯程式 前一章:usr_28.txt 折疊 目錄:usr_toc.txt

使用標籤

什麼是標籤?它是識別符定義的位置。例如,C 或 C++ 程式中的函式定義。標籤清單保存在標籤檔案中。Vim 可以使用它直接從任何位置跳轉到標籤,也就是識別符定義的位置。要為目前目錄中的所有 C 檔案產生標籤檔案,請使用以下命令
ctags *.c
"ctags" 是一個獨立的程式。大多數 Unix 系統已經安裝了它。如果您還沒有安裝它,可以在以下位置找到 Universal ctags:https://ctags.io
建議使用 Universal ctags,Exuberant ctags 已停止開發。
現在當您在 Vim 中,想要跳轉到函式定義時,可以使用以下命令跳轉
:tag startlist
即使函式 "startlist" 位於另一個檔案中,此命令也會找到它。CTRL-] 命令會跳轉到游標下的單字的標籤。這使得探索複雜的 C 程式碼變得容易。例如,假設您在函式 "write_block" 中。您可以看到它呼叫了 "write_line"。但是 "write_line" 做什麼?將游標放在 "write_line" 的呼叫上,然後按下 CTRL-],您就會跳轉到此函式的定義。"write_line" 函式呼叫 "write_char"。您需要找出它做了什麼。因此,您將游標放在 "write_char" 的呼叫上,然後按下 CTRL-]。現在您位於 "write_char" 的定義處。
+-------------------------------------+
|void write_block(char **s; int cnt)  |
|{                                      |
|   int i;                              |
|   for (i = 0; i < cnt; ++i)              |
|      write_line(s[i]);              |
|}            |                              |
+-----------|-------------------------+
            |
     CTRL-] |
            |         +----------------------------+
            +--> |void write_line(char *s)    |
                 |{                              |
                 |   while (*s != 0)              |
                 |        write_char(*s++);     |
                 |}          |                      |
                 +--------|-------------------+
                          |
                   CTRL-] |
                          |    +------------------------------------+
                          +--> |void write_char(char c)                    |
                               |{                                    |
                               |    putchar((int)(unsigned char)c); |
                               |}                                    |
                               +------------------------------------+
":tags" 命令會顯示您遍歷過的標籤清單
:tags
# TO tag FROM line in file/text
1 1 write_line 8 write_block.c
2 1 write_char 7 write_line.c
>
現在要返回。CTRL-T 命令會回到上一個標籤。在上面的範例中,您會回到 "write_line" 函式中呼叫 "write_char" 的位置。此命令接受計數參數,指示要跳回多少個標籤。您已經前進,現在又後退。讓我們再次前進。以下命令會跳轉到清單頂端的標籤
:tag
您可以在其前面加上計數,並向前跳轉該數量的標籤。例如:「:3tag」。CTRL-T 也可以在前面加上計數。因此,這些命令允許您使用 CTRL-] 向下瀏覽呼叫樹,並使用 CTRL-T 向上返回。使用 ":tags" 來找出您在哪裡。

分割視窗

":tag" 命令會將目前視窗中的檔案替換為包含新函式的檔案。但是,假設您不僅想看到舊函式,還想看到新函式?您可以使用 ":split" 命令分割視窗,然後使用 ":tag" 命令。Vim 有一個簡寫命令可以同時執行這兩項操作
:stag tagname
要分割目前視窗並跳轉到游標下的標籤,請使用此命令
CTRL-W ]
如果指定了計數,則新視窗將具有該高度的行數。

更多標籤檔案

當您在多個目錄中有檔案時,您可以在每個目錄中建立標籤檔案。然後,Vim 將只能跳轉到該目錄中的標籤。要尋找更多標籤檔案,請設定 'tags' 選項,以包含所有相關的標籤檔案。範例
:set tags=./tags,./../tags,./*/tags
這會在與目前檔案相同的目錄中、高一層的目錄中以及所有子目錄中尋找標籤檔案。這相當多的標籤檔案,但可能仍然不夠。例如,當編輯 "~/proj/src" 中的檔案時,您將找不到標籤檔案 "~/proj/sub/tags"。對於這種情況,Vim 提供了一個選項,可以在整個目錄樹中搜尋標籤檔案。範例
:set tags=~/proj/**/tags

一個標籤檔案

當 Vim 必須在許多位置搜尋標籤檔案時,您可能會聽到硬碟發出嘎嘎聲。速度可能會有點慢。在這種情況下,最好在產生一個大型標籤檔案時花費這些時間。您可能會在夜間執行此操作。這需要上述的 Universal 或 Exuberant ctags 程式。它提供一個引數來搜尋整個目錄樹
cd ~/proj
ctags -R .
這樣做的好處是 Universal/Exuberant ctags 可以識別各種檔案類型。因此,這不僅適用於 C 和 C++ 程式,也適用於 Eiffel 甚至 Vim 指令碼。請參閱 ctags 文件以進行調整。現在您只需要告訴 Vim 您的大型標籤檔案在哪裡
:set tags=~/proj/tags

多重符合

當一個函式被多次定義(或一個方法在多個類別中)時,":tag" 命令將跳轉到第一個。如果目前檔案中有符合項,則首先使用該符合項。現在您可以使用以下命令跳轉到同一標籤的其他符合項
:tnext
重複此操作以尋找其他符合項。如果有很多,您可以選擇要跳轉到哪一個
:tselect tagname
Vim 將向您顯示一個選項清單
# pri kind tag file
1 F f mch_init os_amiga.c
mch_init()
2 F f mch_init os_mac.c
mch_init()
3 F f mch_init os_msdos.c
mch_init(void)
4 F f mch_init os_riscos.c
mch_init()
輸入選擇的編號 (<CR> 終止)
您現在可以輸入您要跳轉到的符合項的編號(在第一欄中)。其他欄中的資訊會讓您清楚知道符合項的定義位置。
要在符合的標籤之間移動,可以使用以下命令
:tfirst 跳轉到第一個符合項 :[count]tprevious 跳轉到 [count] 個上一個符合項 :[count]tnext 跳轉到 [count] 個下一個符合項 :tlast 跳轉到最後一個符合項
如果省略 [count],則使用 1。

猜測標籤名稱

命令列完成是避免輸入長標籤名稱的好方法。只需輸入開頭部分,然後按下 <Tab>
:tag write_<Tab>
您將獲得第一個符合項。如果它不是您想要的,請按 <Tab> 直到找到正確的符合項。有時您只知道函式名稱的一部分。或者您有許多以相同字串開頭但結尾不同的標籤。然後您可以告訴 Vim 使用模式來尋找標籤。假設您要跳轉到包含 "block" 的標籤。首先輸入此內容
:tag /block
現在使用命令列完成:按下 <Tab>。Vim 將找到所有包含 "block" 的標籤,並使用第一個符合項。標籤名稱前的 "/" 告訴 Vim,後面的內容不是文字標籤名稱,而是模式。您可以在此處使用搜尋模式的所有項目。例如,假設您要選擇一個以 "write_" 開頭的標籤
:tselect /^write_
"^" 指定標籤以 "write_" 開頭。否則,它也會在標籤名稱的中間找到。同樣,結尾的 "$" 可確保模式與標籤的結尾匹配。
標籤瀏覽器
由於 CTRL-] 會將您帶到游標下識別符的定義,因此您可以使用識別符名稱清單作為目錄。以下是一個範例。首先建立識別符清單(這需要 Universal 或 Exuberant ctags)
ctags --c-types=f -f functions *.c
現在啟動 Vim 而不開啟檔案,並在 Vim 中以垂直分割視窗編輯此檔案
vim
:vsplit functions
該視窗包含所有函式的清單。還有其他一些內容,但您可以忽略它們。執行 ":setlocal ts=99" 來稍微清理一下。在此視窗中,定義一個映射
:nnoremap <buffer> <CR> 0ye<C-W>w:tag <C-R>"<CR>
將游標移動到包含您要前往的函式的行。現在按下 <Enter>。Vim 將轉到另一個視窗並跳轉到選定的函式。
要忽略標籤名稱中的大小寫,您可以設定 'ignorecase',同時將 'tagcase' 保留為 "followic",或將 'tagcase' 設定為 "ignore"。
'tagbsearch' 選項會指出標籤檔案是否已排序。預設情況下,假設標籤檔案已排序,這會使標籤搜尋速度快很多,但如果標籤檔案未排序,則無法運作。
'taglength' 選項可用於告知 Vim 標籤中有效字元的數量。

29.2 預覽視窗

當您編輯包含函式呼叫的程式碼時,您需要使用正確的引數。要了解要傳遞哪些值,您可以查看函式的定義方式。標籤機制對此非常有效。最好在另一個視窗中顯示定義。為此,可以使用預覽視窗。要開啟預覽視窗以顯示函式 "write_char"
:ptag write_char
Vim 將開啟一個視窗,並跳轉到標籤 "write_char"。然後它會將您帶回原始位置。因此,您可以繼續輸入,而無需使用 CTRL-W 命令。如果函式的名稱出現在文字中,您可以使用以下命令在預覽視窗中取得其定義
CTRL-W }
有一個指令碼會自動顯示游標下單字定義的文字。請參閱 CursorHold-example
要關閉預覽視窗,請使用此命令
:pclose
要在預覽視窗中編輯特定檔案,請使用 ":pedit"。這對於編輯標頭檔案非常有用,例如
:pedit defs.h
最後,可以使用 ":psearch" 在目前檔案和任何包含的檔案中尋找單字,並在預覽視窗中顯示符合項。當使用您沒有標籤檔案的程式庫函式時,這特別有用。範例
:psearch popen
這會在預覽視窗中顯示 "stdio.h" 檔案,並包含 popen() 函式的原型。
FILE        *popen __P((const char *, const char *));
您可以使用 'previewheight' 選項來指定預覽視窗開啟時的高度。

29.3 在程式碼中移動

由於程式碼是結構化的,Vim 可以識別其中的項目。可以使用特定的命令來移動。C 程式通常包含像這樣的結構:
#ifdef USE_POPEN
    fd = popen("ls", "r")
#else
    fd = fopen("tmp", "w")
#endif
但它們可能會更長,甚至有巢狀結構。將游標放在 "#ifdef" 上,然後按下 %。Vim 會跳到 "#else"。再次按下 % 會將您帶到 "#endif"。再按一次 % 會將您帶回 "#ifdef"。當結構是巢狀時,Vim 會找到匹配的項目。這是一個檢查您是否忘記 "#endif" 的好方法。當您在 "#if" - "#endif" 內部時,您可以使用以下命令跳到它的開頭:
[#
如果您不是在 "#if" 或 "#ifdef" 之後,Vim 會發出嗶聲。要向前跳到下一個 "#else" 或 "#endif",請使用:
]#
這兩個命令會跳過它們遇到的任何 "#if" - "#endif" 區塊。範例:
#if defined(HAS_INC_H)
a = a + inc();
# ifdef USE_THEME
a += 3;
# endif
set_width(a);
當游標在最後一行時,"[#" 會移動到第一行。中間的 "#ifdef"
"#endif" 區塊會被跳過。

在程式碼區塊中移動

在 C 程式碼中,區塊會用 {} 包圍。這些區塊可能會很長。要移動到最外層區塊的開頭,請使用 "[[" 命令。使用 "][" 來尋找結尾。這假設 "{" 和 "}" 位於第一欄。[{ 命令會移動到目前區塊的開頭。它會跳過同一層級的 {} 對。"}]" 會跳到結尾。概述如下:
function(int a) +-> { | if (a) | +-> { [[ | | for (;;) --+ | | +-> { | | [{ | | foo(32); | --+ | | [{ | if (bar(a)) --+ | ]} | +-- | +-- break; | ]} | | | } <-+ | | ][ +-- foobar(a) | | } <-+ | } <-+
當撰寫 C++ 或 Java 時,最外層的 {} 區塊是類別。下一層的 {} 是方法。當您在類別內部時,可以使用 "[m" 來尋找前一個方法的開頭。"]m" 會尋找下一個方法的開頭。
此外,"[]" 會向後移動到函式的結尾,而 "]]" 會向前移動到下一個函式的開頭。函式的結尾是由第一欄的 "}" 定義的。
int func1(void) { return 1; +----------> } | [] | int func2(void) | +-> { | [[ | if (flag) start +-- +-- return flag; | ][ | return 2; | +-> } ]] | | int func3(void) +----------> { return 3; }
別忘了您也可以使用 "%" 在匹配的 ()、{} 和 [] 之間移動。即使它們相隔很多行,也有效。

在括號中移動

[( 和 ]) 命令的工作方式與 [{ 和 ]} 類似,但它們操作的是 () 對,而不是 {} 對。
[(
<-------------------------------- <-------
if (a == b && (c == d || (e > f)) && x > y)
--------------> -------------------------------->
])

在註解中移動

要移回註解的開頭,請使用 "[/"。使用 "]/ " 向前移動到註解的結尾。這僅適用於 /* - */ 註解。
   +->          +-> /*
   |    [/ |    * A comment about      --+
[/ |          +--  * wonderful life.        | ]/
   |               */                      <-+
   |
   +--               foo = bar * 3;              --+
                                         | ]/
                /* a short comment */  <-+

29.4 尋找全域識別碼

您正在編輯一個 C 程式,並想知道變數是否宣告為 "int" 或 "unsigned"。使用 "[I" 命令可以快速找到這個資訊。假設游標在 "column" 這個單字上。輸入:
[I
Vim 會列出它找到的匹配行。不僅在目前檔案中,也在所有包含的檔案中(以及包含在其中的檔案等等)。結果如下所示:
structs.h
 1:   29     unsigned     column;    /* column number */
與使用標籤或預覽視窗相比,其優勢在於會搜尋包含的檔案。在大多數情況下,這樣可以找到正確的宣告。即使標籤檔案已過時,或者您沒有包含檔案的標籤,也是如此。然而,要讓 "[I" 發揮作用,必須滿足一些條件。首先,'include' 選項必須指定檔案的包含方式。預設值適用於 C 和 C++。對於其他語言,您必須變更它。

尋找包含的檔案

Vim 會在 'path' 選項指定的位置尋找包含的檔案。如果缺少目錄,則會找不到某些包含檔案。您可以使用以下命令來發現這個情況:
:checkpath
它會列出找不到的包含檔案。也會列出找到的檔案所包含的檔案。以下是輸出的範例:
--- 在路徑中找不到的包含檔案 ---
<io.h>
vim.h -->
<functions.h>
<clib/exec_protos.h>
"io.h" 檔案由目前檔案包含,但找不到。"vim.h" 可以找到,因此 ":checkpath" 會進入這個檔案並檢查它包含的內容。"functions.h" 和 "clib/exec_protos.h" 檔案由 "vim.h" 包含,但找不到。
注意:Vim 不是編譯器。它不識別 "#ifdef" 陳述式。這表示每個 "#include" 陳述式都會被使用,即使它在 "#if NEVER" 之後出現。
要修正找不到的檔案,請將目錄加入 'path' 選項。尋找相關資訊的好地方是 Makefile。請留意包含 "-I" 項目的行,例如 "-I/usr/local/X11"。要加入這個目錄,請使用:
:set path+=/usr/local/X11
當有很多子目錄時,可以使用 "*" 萬用字元。範例:
:set path+=/usr/*/include
這會在 "/usr/local/include" 以及 "/usr/X11/include" 中尋找檔案。
當處理一個包含整個巢狀樹的包含檔案專案時,"**" 項目會很有用。這會在所有子目錄中向下搜尋。範例:
:set path+=/projects/invent/**/include
這會在以下目錄中尋找檔案:
/projects/invent/include
/projects/invent/main/include
/projects/invent/main/os/include
等等。
還有更多的可能性。請查閱 'path' 選項以取得資訊。如果您想查看實際找到哪些包含檔案,請使用以下命令:
:checkpath!
您會得到一個(非常長的)包含檔案清單,它們包含的檔案等等。為了縮短清單,Vim 會為先前找到的檔案顯示 "(已列出)",並且不會再次列出其中的包含檔案。
跳到匹配項
"[I" 會產生一個只包含一行的清單。如果您想仔細查看第一個項目,可以使用以下命令跳到該行:
[<Tab>
您也可以使用 "[ CTRL-I",因為 CTRL-I 與按下 <Tab> 相同。
"[I" 產生的清單在每行的開頭都有一個數字。如果您想跳到第一個項目以外的另一個項目,請先輸入數字:
3[<Tab>
會跳到清單中的第三個項目。請記住,您可以使用 CTRL-O 跳回您開始的位置。
[i 只列出第一個匹配項 ]I 只列出游標下方的項目 ]i 只列出游標下方第一個項目

尋找定義的識別碼

"[I" 命令會尋找任何識別碼。要僅尋找使用 "#define" 定義的巨集,請使用:
[D
同樣地,這會在包含的檔案中搜尋。'define' 選項指定定義 "[D" 項目的行的外觀。您可以變更它,使其適用於 C 或 C++ 以外的其他語言。與 "[D" 相關的命令有:
[d 只列出第一個匹配項 ]D 只列出游標下方的項目 ]d 只列出游標下方第一個項目

29.5 尋找本機識別碼

"[I" 命令會搜尋包含的檔案。要在目前檔案中搜尋,並跳到游標下的單字第一次使用的地方:
gD
提示:跳到定義處。此命令對於尋找在本機宣告的變數或函式(在 C 術語中為 "static")非常有用。範例(游標在 "counter" 上):
   +->   static int counter = 0;
   |
   |     int get_counter(void)
gD |     {
   |             ++counter;
   +--             return counter;
         }
要進一步限制搜尋範圍,並且只在目前的函式中搜尋,請使用此命令:
gd
這會回到目前函式的開頭,並尋找游標下單字的第一個出現位置。實際上,它會向後搜尋到第一欄中 "{" 上方的空白行。從那裡開始,它會向前搜尋識別碼。範例(游標在 "idx" 上):
        int find_entry(char *name)
        {
   +->            int idx;
   |
gd |            for (idx = 0; idx < table_len; ++idx)
   |                if (strcmp(table[idx].name, name) == 0)
   +--                    return idx;
        }
下一章:usr_30.txt 編輯程式碼
版權:請參閱 manual-copyright vim:tw=78:ts=8:noet:ft=help:norl
主選單
命令索引
快速參考