Usr_29

Nvim :help 页面,生成源文件,使用 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 系统都已安装。如果您还没有,可以在以下位置找到通用 ctags: https://ctags.io
通用 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 必须在很多地方搜索标签文件时,您可能会听到磁盘咔嗒作响。它可能会变得有点慢。在这种情况下,最好在生成一个大的标签文件时花费这段时间。您可能在晚上进行此操作。这需要上面提到的通用或 Exuberant ctags 程序。它提供了一个参数来搜索整个目录树
cd ~/proj
ctags -R .
这样做的好处是,通用/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-] 将您带到光标下标识符的定义,因此您可以使用标识符名称列表作为目录。这是一个例子。首先创建一个标识符列表(这需要通用或 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" 进入此文件并检查它包含的内容。由 "vim.h" 包含的 "functions.h" 和 "clib/exec_protos.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 会为之前找到的文件显示 "(Already listed)",并且不会在其中再次列出包含的文件。
跳转到匹配项
"[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
命令索引
快速参考