vi/vim使用进阶: 使用标签(tag)文件

<< 返回vim使用进阶: 目录

本节所用命令的帮助入口:

:help 'tags'
:help :tag
:help :tags
:help CTRL-]
:help CTRL-T
:help vimgrep
:help cw
:help pattern 

尽管相关的文章已经很多了,但tag文件实在是太有用了,所以还是啰嗦一次。

Tag文件(标签文件)无疑是开发人员的利器之一,有了tag文件的协助,你可以在vim查看函数调用关系,类、结构、宏等的定义,可以在任意标签中跳转、返回……相信使用过Source Insight的人对这些功能并不陌生,而在vim中,此功能的实现依赖于tag文件。

对于程序来说,Tag文件中保存了诸如函数、类、结构、宏等的名字,它们所处的文件,以及如何通过Ex命令跳转到这些标签。它是一个纯文本文件,因此你可以手工的编辑它,也可以使用脚本对其进行操作。

通常我们使用名为ctags的程序来生成这样的tag文件。vim能直接使用ctags程序所生成的tag文件。在UNIX系统下的ctags功能比较少,所以一般我们使用Exuberant Ctags(在大多数Linux系统上,它是缺省的ctags程序),它能够支持多达33种程序语言,足以满足我们开发的需要了。这里是它的中文手册。如果你的系统上未安装此程序,请到http://ctags.sourceforge.net下载。

emacs则使用etags来生成tag文件,如果希望vim也能支持etags的tag文件格式,需要编译vim时加入”+emacs_tags“选项。在这篇文章介绍了编译vim的方法。

Tag文件需要遵循一定的格式,由Exuberant Ctags生成的tag文件,缺省是如下格式:

{tagname} {TAB} {tagfile} {TAB} {tagaddress} {term} {field} ..  
  • {tagname} – 标识符名字,例如函数名、类名、结构名、宏等。不能包含制表符。
  • {tagfile} – 包含 {tagname} 的文件。它不能包含制表符。
  • {tagaddress} – 可以定位到 {tagname}光标位置的 Ex 命令。通常只包含行号或搜索命令。出于安全的考虑,vim会限制其中某些命令的执行。
  • {term} – 设为 ;” ,这是为了兼容Vi编辑器,使Vi忽略后面的{field}字段。
  • {field} .. – 此字段可选,通常用于表示此{tagname}的类型是函数、类、宏或是其它。

在{tagname}、{tagfile}和{tagaddress}之间,采用制表符(TAB符,即C语言中的”\t”)分隔,也就是说{tagname}、{tagfile}的内容中不能包含制表符。

Tag文件的开头可以包含以”!_TAG_”开头的行,用来在tag文件中加入其它信息。vim能够识别两种这样的标记,经常用到的是”_TAG_FILE_SORTED”标记,例如:

!_TAG_FILE_SORTED<Tab>1<Tab>{anything} 

上面这个标记说明tag文件是经过排序的,并且排序时区分了大小写,对排序的tag,vim会使用二分法来进行查找,大大加快了查找速度;如果值为0,则表示tag文件未经排序;如果值为2,则表示tag文件是忽略大小写排序的。

之所以在这里介绍tag文件的格式,是因为我们在后面提到的lookupfile插件中,会自己生成tag文件。

虽然ctags有为数众多的选项,但通常我们所使用的非常简单。还是以vim 7.0的代码为例,我们执行:

cd ~/src/vim70
ctags –R src 

上面这条命令会在~/src/vim70/目录下生成一个名为tags的文件,这个文件中包含~/src/vim70/src/目录下所有.c、.h文件中的标签。它一个文本文件,你可以用vim打开它看一下。此文件缺省按区分字母大小写排序,所以直接可以被vim使用。

现在我们进入vim,执行下面的命令:

:cd ~/src/vim70	"切换当前目录为~/src/vim70
:set tags=tags	"设置tags选项为当前目录下的tags文件 

现在,我们设置好了tags选项,接下来我们使用它:

:tag VimMain

你会看到vim打开了src/main.c文件,并把光标定位到第167行VimMain上。

我们知道,一般主程序的函数名为main,如果你尝试下面的命令:

:tag main
# pri kind tag               file
1 F   f    main              src/xxd/xxd.c
main(argc, argv)
2 FS  d    main              src/if_python.c
46
Choice number (<Enter> cancels): 

这里并没有src/main.c文件,怎么回事呢?这是因为ctags并不是编译器,它在处理编译预处理指令受到局限,因此并没有生成src/main.c中main()函数的标签。你可以在生成tag文件时给ctags指定参数来解决这个问题。见ctags手册

或者你可以用”:grep“或”:vimgrep“来查找main(这篇文章讲解grep及vimgrep的用法):

:cd ~/src/vim70
:vimgrep /\<main\>/ src/*.c
:cw 

这时下面的quickfix窗口将显示出来,在quickfix窗口中找到我们想跳转的位置(本例中是src/main.c),按回车,就可以跳到对应的位置了。

如果你只记得部分的标签名,那么可以使用”tag“命令的搜索模式,你可以输入一个vim正则表达式来表示你所查找的名字,如:

:tag /\C\<\k\+ain\>
# pri kind tag               file
1 F   f    VimMain           src/main.c
VimMain
2 F   d    bindtextdomain    src/vim.h
483
3 F   d    bindtextdomain    src/vim.h
502
4 F   d    bindtextdomain    src/vim.h
504
5 F   f    main              src/xxd/xxd.c
main(argc, argv)
6 F   d    textdomain        src/vim.h
488
7 F   d    textdomain        src/vim.h
510
8 F   d    textdomain        src/vim.h
512
9 FS  d    bindtextdomain    src/gui_gtk.c
54
10 FS  d    bindtextdomain    src/gui_gtk_x11.c
37
11 FS  f    cmdsrv_main       src/main.c
cmdsrv_main(argc, argv, serverName_arg, serverStr)
12 FS  d    main              src/if_python.c
46
13 FS  d    textdomain        src/gui_gtk.c
51
14 FS  d    textdomain        src/gui_gtk_x11.c
34
Choice number (<Enter> cancels): 

这表示我想查找一个以一个或多个keyword开始的标签,此标签以ain做为结尾,在查找时区分大小写。要读懂这个正则表达式,请”:help pattern“。

vim会保存一个跳转的标签栈,以允许你在跳转到一个标签后,再跳回来,可以使用”:tags“命令查找你处于标签栈的哪个位置。

我们经常用到的tag跳转命令见下(一般只需要知道CTRL-]和CTRL-T就可以了):

:tag {ident}            "跳转到指定的标签
:tags                   "显示标签栈
CTRL-]                  "跳转到当前光标下的标签
CTRL-T                  "跳到标签栈中较早的标签 

如果想了解更多命令,可以”:help 29.1“(强烈建议程序员完整的阅读usr_29.txt和usr_30.txt)。

如果想更深入了解tag命令和相关知识,可以”:help tagsrch“。

我之前写的一篇关于ctags和cscope的文章,参见:Vim + Cscope/Ctags

[参考文档]

<< 返回vim使用进阶: 目录

35 thoughts on “vi/vim使用进阶: 使用标签(tag)文件”

  1. (以下为CSDN评论的转帖)

    #zvane 发表于2007-03-10 22:41:52 IP:
    写的很不错,小弟正在学习用vim写程序方面的东西.十分感谢作者的这几篇文章.收益真是太大.

    #flyincosmic 发表于2007-05-03 00:11:42 IP: 220.192.42.*
    如何获得多匹配结果显示的窗口,如果找到2个以上的结果,只会在底部有一行1/n……这样的通知,甚至都没办法移动到下一个匹配的结果,
    vimgrep也是如此

    #easwy 发表于2007-05-04 12:44:10 IP: 219.133.160.*
    你的VIM是不是工作在兼容模式下?执行一下:set nocompatible,然后再试

    移动到下一个tag,使用命令:tn,移动到上一个,使用命令:tp
    如果想列出vimgrep找到的所有行,使用:cw命令,你可以看一下quickfix那篇文章。

    #flyincosmic 发表于2007-05-07 00:00:10 IP: 220.192.41.*
    一直是nocp的

    #flyincosmic 发表于2007-05-09 00:06:40 IP: 220.192.40.*
    lz是否弄错了,ctags并不支持直接recurse目录,还是得使用文件名或者通配符啊
    我试了–language-force=c++
    和–langmap=c++,奇怪的是后者却不行
    而list language和map的时候都有c++的

    #easwy 发表于2007-05-09 09:14:38 IP: 213.70.90.*
    ctags的用法可以参考我翻译的ctags手册:
    http://easwy.com/blog/20090211/111.html

  2. (以下为CSDN评论的转帖)

    #wen727 发表于2008-05-29 14:17:06 IP: 119.0.34.*
    因为自动补全要先设置好编程语言才能补全,但是我想在HTML中嵌套PHP语言,这样就不能完成PHP自动补全功能。请问应该如何设置,能实现在编辑HTML中实现PHP代码自动补全功能谢谢!

    2008-06-10 13:16:39作者回复
    你可以把文件类型设成PHP试一下。就是定义两个快捷键,在编辑HTML时把filetype设为HTML,编辑PHP时把文件类型设为PHP (未经测试)

    #ajee 发表于2008-06-14 01:14:25 IP: 116.227.119.*
    确实是只出现1/n,无法显示所有的,怎么回事

    2008-06-16 09:53:48作者回复
    我没有遇到过这个问题,你用:version命令看一下vim的版本,把这些信息连同你的vimrc一起放上来看看。

    #wen727 发表于2008-06-14 23:55:31 IP: 119.0.36.*
    谢谢!

    #xxx 发表于2008-09-06 21:44:28 IP: 219.139.246.*
    楼主感觉你的文章贴一些啥的命令的介绍太多了 ,看的人真叫一个不耐烦,其实完全没必要求多求全,把常用的整理出来就行了,说句老实话,没人会用到所有功能,看别人总结的ctags就几句,感觉实用多了,如下:
    熟练的使用ctags仅需记住下面七条命令:(很简单吧,呵呵)
    1. $ ctags –R * ($ 为Linux系统Shell提示符)
    2. $ vi –t tag (请把tag替换为您欲查找的变量或函数名)
    3. :ts (ts 助记字:tags list, “:”开头的命令为VI中命令行模式命令)
    4. :tp (tp 助记字:tags preview)—此命令不常用,可以不用记
    5. :tn (tn 助记字:tags next) —此命令不常用,可以不用记
    6. Ctrl + ]
    7. Ctrl + T

  3. 你好,
    我按照你的方法安装了vim72(和vimgdb72)。但是发现:在gdb的command-line里面输入命令如”file test”后,并没有相关的程序加载进来,我看不见任何的程序代码出现在窗口里面,但是输出窗口是有的,里面显示内容为:
    Vim |+gdb| level 3 mode

    GNU gdb 6.8-debian
    Copyright (C) 2008 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law. Type “show copying”
    and “show warranty” for details.
    This GDB was configured as “i486-linux-gnu”.
    (gdb) file test
    Reading symbols from /usr/bin/test…(no debugging symbols found)…done.
    (gdb)
    请问,如何调用出相关的程序代码出来?谢谢!

  4. @Randy
    Reading symbols from /usr/bin/test…(no debugging symbols found)…done.

    这句话显示,你在编译时,没有把调试信息编译进来
    在用gcc编译时,加上”-g”选项试一下

  5. @Easwy
    不好意思,低级错误了。
    我刚刚试了下,发现还是不行,看不见源代码,输出窗口为:
    Vim |+gdb| level 3 mode

    GNU gdb 6.8-debian
    Copyright (C) 2008 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law. Type “show copying”
    and “show warranty” for details.
    This GDB was configured as “i486-linux-gnu”.
    (gdb) file t
    Reading symbols from /home/randyqiu/桌面/t…done.
    (gdb)
    我找了下config.h,发现有错误,不知道是不是这个引起的:
    conftest.c:9:28: error: ac_nonexistent.h: No such file or directory
    configure:3024: $? = 1
    configure: failed program was:
    | /* confdefs.h. */
    | #define PACKAGE_NAME “”
    …………………………………………………还有:
    /usr/bin/ld: cannot find -lsocket
    collect2: ld returned 1 exit status
    configure:5887: $? = 1
    configure: failed program was:
    | /* confdefs.h. */
    | #define PACKAGE_NAME “”
    | #define PACKAGE_TARNAME “”
    | #define PACKAGE_VERSION “”
    | #define PACKAGE_STRING “”
    | #define PACKAGE_BUGREPORT “”
    | #define UNIX 1
    | #define STDC_HEADERS 1
    | #define HAVE_SYS_WAIT_H 1
    | #define HAVE_SELINUX 1
    | #define FEAT_NORMAL 1
    | #define USE_XSMP_INTERACT 1
    | #define FEAT_CSCOPE 1
    | /* end confdefs.h. */
    | #define PACKAGE_TARNAME “”
    | #define PACKAGE_VERSION “”
    | #define PACKAGE_STRING “”
    | #define PACKAGE_BUGREPORT “”
    | #define UNIX 1
    | /* end confdefs.h. */
    ……………………………………………………还有:
    /usr/bin/ld: cannot find -lXpm
    ……………………………………………………还有;
    conftest.c:55:21: error: X11/xpm.h: No such file or directory
    ………………………………………………………………………………还有:
    conftest.c:79:28: error: sys/systeminfo.h: No such file or directory
    ……………………………………………………………………………….还有:
    conftest.c:80:24: error: sys/stream.h: No such file or directory
    …………………………………………….还有几个我没列出来,,
    为什么有这么多的错误还能编译通过?而且我也手动./configure,也没问题,都很顺利的通过了,,
    谢谢,麻烦你了!

  6. @Randy
    Reading symbols from /home/randyqiu/桌面/t…done.

    这句话显示,你已经加载了调试文件,接下来就可以调试了。configure的错误不用管,只要你能编译通过就行了
    你还没开始调试,当然看不到源代码窗口了 ^_^

  7. @Easwy
    555,总觉得和你帖出来的窗口有点不一样。
    1.我首先进入vim;
    2.然后空格,调出command-line;
    3.输入file test(test是我的可执行文件);
    (我觉得你是这个时候就可以看到command-line下面有个窗口来显示代码,然后我好方便移动到具体的代码行去设置断点之类的)
    4.如果我接着设置一个断点:b main,然后:r 进行调试的话,还是没有代码在command-line下面的窗口显示出来。
    当然,如果我在command-line里面输入list的话,gdb输出窗口是有代码出来的。
    不晓得我有没有说明白^-^:
    我看到你的教程里面的窗口有如下几个(从上到下):
    gdb输出窗口;
    command-line;
    代码窗口(你的教程里面,其窗口名是sample.c,我的就显示No Name,当然也没有内容);
    最后还有一个状态栏。
    请老大指教指教!是不是我哪里理解错了,还是什么没添加?

  8. @Randy
    明白你的问题了。
    在用file命令加载被调试程序时,此时不会自动显示源代码的。我一般是自己打开相应文件来设置断点。
    当断点停下来以后,再接着单步跟踪时,vimgdb会根据所执行的代码来打开相应的源文件,把它显示在vi窗口中。

  9. 请教大家一个问题:
    以前用vim tag时,当搜索到多个匹配时不会直接跳转,而是会在下面的小窗口list出来,让你选择跳转。可是现在换了家公司后,下面的list窗口不出来而是直接跳转到第一个匹配处,可是这样很不方便,如果有几十个匹配的话,得一个个跳转过去查看。想让回到以前的模式,该怎样设置vim?

    请问有人知道解决方案吗?

  10. 是啊,是用”ctrl-]”时。

    那该怎么办呀?这样好不方便啊!

    不知道是什么原因,我还一直以为是配置文件的问题呢,依稀记得在这里第一次用的时候好像出来过下面list的小窗口呢,后来不知道是不是我开始用vim配置文件后就不行了,没有很清晰的印象。(之前home目录中没有.vimrc的)
    这边是从自己的windows机器登录到linux终端工作,所以linux的权限很少。

    另外,斑竹,你的“定阅本站”链接好像有点问题吧?本来想定阅的,希望这样可以及时知道回复,可是好像定阅不了。 :)

  11. 还有另外一个问题:
    cd ~/src/vim70
    ctags –R src
    上面这条命令会在~/src/vim70/目录下生成一个名为tags的文件,这个文件中包含~/src/vim70/src/目录下所有.c,.h文件中的标签。

    tags文件中如果要包含其他类型文件中的标签该怎么办呢?不知道公司有的cpp源文件为什么后缀为.src

  12. @在路上
    vim 6.3版本已经比较老了,现在最新的版本是vim 7.3了
    你可以自己编译一个vim 7.3然后再试一下此功能,可以把vim安装在自己的home目录的,我在vimgdb那篇文章里介绍了如何编译vim

    我试了定阅功能,貌似在我这里没问题。

  13. 刚开始用的时候貌似还可以,今天发现打开文件居然报错
    taglist: failed to generate tags for……
    ctags的版本也是最新的,无解~···

  14. Pingback: Vim+Cscope | Cnfn
  15. Pingback: 【vim】 | Anan
  16. 請教下Easwy,
    Tag stack是否也能像session一樣被保存, 等到下次開啟vim再載入呢?

    用ctags追蹤最方便的就是有stack可供轉跳及定位了,
    但每次關閉vim後, 就算載入session, 但tag stack的資訊都不會被保留的情況下, 又得重新追蹤, 有點困擾啊.

    1. @Evan, tag 历史不会保存,如果你需要保存的话,试着用 :redir 命令把 :tags 命令的输出记录下来,不过这只能给你提供一些提示信息,你无法根据它进行跳转

    1. quickfix列表就是做这个用的,在vim里输入 :h quickfix.txt
      把这篇文档读完,你就可以找到适合你的方法了。我的建议是,如果你能在vim中直接make,就直接在vim里make;如果不能,那就把make输出的错误重定向到一个文件,然后导入成vim的quickfix列表。

发表评论

电子邮件地址不会被公开。 必填项已用*标注