ctags使用详解
ctags 简介
ctags 最先是用来生成C代码的tags文件,后来扩展成可以生成各类语言的tags, 有些语言也有专有的tags生成工具(比如java的jtags, python的 ptags)。虽然Linux下面没有文件扩展名一说,但是这并不代表某些软件不需要文件扩展名。ctags正是 根据文件的扩展名以及文件名的形式来确定该文件中是何种语言,从而使用正确的分析器。 查看ctags支持那些语言ctags --list-maps
对应Linux Vimer来说,代码提示是个很重要的特性,而ctags正式解决这一问题的方式。 ctags(Generate tag files for source code)是vim下方便代码阅读的工具。尽管ctags也可以支持其它编辑器,但是它正式支持的只有VIM。很多Linux发行版都会默认安装ctags,但是Ubuntu没有,apt install exuberant-ctags
。
ctags 参数
--list-maps
ctags可以根据文件的扩展名以及文件名的形式确定该文件是何种语言。用--list-maps查看默认情况下支持那些语言,每一种支持的语言支持的文件扩展名是什么。
ctags --list-maps
--list-kinds
查看c++语言可以识别哪些语法元素。
ctags --list-kinds=c++
查看默认支持所有语言可以识别的语法元素
ctags --list-kinds
--c++-kinds
ctags识别很多元素,但未必全都记录,例如“函数声明”这一语法元素默认是不记录的。可以控制ctags记录的语法元素的种类,例如,下面的命令要求记录c++文件中的函数声明,包括各种外部和前向声明:
ctags -R --c++-kinds=+px
--langmap
可以指定ctags用特定语言的分析器来分析某种扩展名的文件或者名字符合特定模式的文件。例如如下命令告知ctags,以inl为扩展名的文件是c++文件。
ctags --langmap=c++:+.inl –R
-h
有些头文件的扩展名不是.h或.hpp。以下的命令指定.inc文件也是头文件。
ctags -h +.inc
--fields
tag对语法元素的描述取决于语法元素的种类。可以在命令行中指定显示哪些描述,以哪种格式显示等。比如下面的命令。
" i 表示如果有继承,要标明父类;
" a 表示如果是类的成员,要标明其access属性(即是public的还是private的);
" S 表示如果是函数,要标明函数的signature;
" K 表示要显示语法元素类型的全称;
" z 表示在显示语法元素的类型时,使用格式kind:type。
ctags -R --fields=+aiKSz
--extra
ctags在记录成员函数时,默认情况下tag的名字只包括该函数的名字,不包括类名,这样很多不同类但同名的函数所对应的tag名字都一样。于是在VIM中使用函数名来定位时就会出现很多选择,很麻烦。如果想用包括类名的函数全名进行定位,就得要求ctags记录全名。
ctags --extra=+q
--exclude
可以要求ctags不要扫描某些目录或文件,比如下面的命令:
ctags --exclude=lex.yy.cc
浏览代码
为哪些文件生成tags
我们写代码的时候,不使用系统库函数几乎是不可能的。在IDE中,我们#include <stdio.h>
很自然就找到对应的头文件,但是Windows下一切理所应当的东西在Linux下都会变得十分不直观。Linux下头文件在/usr/[local]/include
下面,如果你是个内核开发者或者驱动开发者, The /usr/src/linux-headers-$(uname -r)/include
下面也有各种头文件,对应新手来说这的确很让人困惑。参考。简单地说,如果你只是开发用户态程序,像游戏后台。面向glibc和系统调用编程,那么/usr/[local]/include
完全足够你是用,这个目录下的代码是C标准库的一部分,他是提供了系统调用接口。而 The /usr/src/linux-headers-$(uname -r)/include
是/lib/modules/$(uname -r)/build
的符号链接,他们特定于某个Linux内核版本的,开发内核态代码需要用到他们,对应大多数程序员来说几乎是触及不到这里的。
如果你足够细心,你会发现/usr/[local]/include
下面有很多重名但是分布在不同路径的文件,像下面这样(所有文件相对于目录/usr/include而言)
./asm-generic/unistd.h
./linux/unistd.h
./unistd.h
./x86_64-linux-gnu/sys/unistd.h
./x86_64-linux-gnu/bits/unistd.h
./x86_64-linux-gnu/asm/unistd.h
./stdlib.h
./x86_64-linux-gnu/bits/stdlib.h
./c++/7/stdlib.h
./c++/7/tr1/stdlib.h
./c++/7/cmath
./c++/7/ext/cmath
./c++/7/tr1/cmath
./asm-generic/termios.h
./linux/termios.h
./x86_64-linux-gnu/sys/termios.h
./x86_64-linux-gnu/bits/termios.h
./x86_64-linux-gnu/asm/termios.h
./termios.h
./linux/time.h
./time.h
./x86_64-linux-gnu/sys/time.h
./x86_64-linux-gnu/bits/time.h
他们是简单的文件拷贝吗?很显然不是。参考
对于处在/usr/include顶层的文件来说,他们是体系无关的,而字目录中同名的文件是体系有关的,文件重名也是为了以后修改方便。
头文件有了,那代码实现呢?对于标准库的代码,都是以库的形式呈现的,那么库文件放在了哪里呢?gcc -dumpmachine
输出当前使用的体系结构。是由如下命令查看gcc搜索路径
cpp -v /dev/null -o /dev/null
针对C代码
cpp -x c++ -v /dev/null -o /dev/null
针对C++代码
以我所使用的Ubuntu 1904为例,C输出如下信息
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/8/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/8/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
可以看到gcc查找库文件的搜索路径。x86_64-linux-gnu就是gcc -dumpmachine
输出结果。
如何查看代码
以先描述的方法只对那些使用 Exuberant Ctags 的人有效,如果你使用其他版本的ctags,不保证方法可用。
当我们使用ctags为 C/C++ 生成tags文件的时候,我们可能会这样做
ctags -R .
或者你需要关于tags的更多信息
ctags -R --c++-kinds=+p --fields=+iaS --extra=+q .
无论使用哪一个命令,生成的tags文件所描述的符号信息都只针对当前项目源代码树中的文件,而不包含任何外部文件的符号信息,例如标准头文件(例如stdio.h,stdlib.h)等,因此Vim无法在外部头文件中找到符号。
很自然的一个想法,首先为任何外部头文件生成一个tags文件,然后让Vim读取生成的tags文件和为当前项目源代码树生成的tags文件。 例如,Linux/Unix下位系统头文件生成tags文件
ctags -R --c++-kinds=+p --fields=+iaS --extra=+q /usr/include
上述命令通常需要执行很长时间,最终它会提供1个很大的tags文件,Vim会浪费大量时间在tags文件中搜索符号。 为了解决这个问题,引出另一个想法。
为什么我们必须生成一个包含系统头文件中所有符号的tags文件? 如果只为与项目相关的头文件生成tags文件,速度岂不是会快很多。我们可以先搜索代码中include引入的那些头文件,然后使用ctags为这些头文件和源文件生成tags文件,这样tags文件就会变得更小。
#!/bin/bash
# ./ctags_with_dep.sh file1.c file2.c ... to generate a tags file for these files.
gcc -M "$@" | sed -e 's/[\\ ]/\n/g' | \
sed -e '/^$/d' -e '/\.o:[ \t]*$/d' | \
ctags -L - --c++-kinds=+p --fields=+iaS --extra=+q
这个脚本将首先使用gcc -M来输出一组源码中include的头文件。但是,ctags不能直接使用其输出,因此该脚本使用sed命令过滤输出。最后,此脚本使用管道将文件列表放到ctags程序的stdin中,-L的作用就是让ctags从stdin读取文件列表。
除/usr/include标准头文件之外,如果你还想保护其他头文件,只需要稍稍修改下脚本,使用-I参数拽顶欲包含的目录。例如,你想要包含在~/include目录中的一些头文件,如下修改脚本
gcc -M -I ~/include "$@" | sed -e 's/[\\ ]/\n/g' | \
sed -e '/^$/d' -e '/\.o:[ \t]*$/d' | \
ctags -L - --c++-kinds=+p --fields=+iaS --extra=+q