ctags使⽤详解(转载)
⼀. ctags是⼲什么的
我⽤的是Exuberant Ctags,在Windows上使⽤,就⼀个可执⾏⽂件,⾮常绿⾊,可在sourceforge下载。
⼆. ctags可以识别哪些语⾔,是如何识别的
ctags识别很多语⾔,可以⽤如下命令来查看:
ctags --list-languages=c++
还可以识别⾃定义语⾔,具体没研究过。
ctags是可以根据⽂件的扩展名以及⽂件名的形式来确定该⽂件中是何种语⾔,从⽽使⽤正确的分析器。可以使⽤如下命令来查看默认哪些扩展名对应哪些语⾔:
ctags --list-maps
还可以指定ctags⽤特定语⾔的分析器来分析某种扩展名的⽂件或者名字符合特定模式的⽂件。例如如下命令告知ctags,以inl为扩展名的⽂件是c++⽂件。
ctags --langmap=c++:+.inl –R
并不⼗分清楚ctags使⽤何种技术来解析内容,估计包括正则表达式、词法分析、语法分析等等。但ctags不是编译器也不是预处理器,它的解析能⼒是有限的。例如它虽然可以识别宏定义,但对于使⽤了宏的语句的识别还是有缺陷的,在⼀些稍微正规点的代码(例如ACE的库或VC的头⽂件等)中的某些常规的宏使⽤⽅式会导致ctags⽆法识别,或者识别错误,从⽽使得ctags没有记录user想记录的内容,或者记录下的信息不准确。另⼀⽅⾯ctags也有聪明的⼀⾯,例如在cpp⽂件中扫描到static的全局变量时,ctags会记录这个变量,⽽且还会标明说这个变量是局限于本⽂件的,同样的定义,如果放在h⽂件中,ctags则不会标明说这个变量是局限于本⽂件的,因为ctags认为h⽂件是头⽂件的⼀种,会被其他⽂件include,所以在其他⽂件中可能会⽤到该h⽂件⾥定义的这个全局变量。
三. ctags可以识别和记录哪些语法元素
怎么显示文件的扩展名可以⽤如下命令查看ctags可以识别的语法元素:
ctags --list-kinds
或者单独查看可以识别的c++的语法元素
ctags --list-kinds=c++
ctags识别很多元素,但未必全都记录,例如“函数声明”这⼀语法元素默认是不记录的,可以控制ctags记录的语法元素的种类。如下命令要求ctags记录c++⽂件中的函数声明和各种外部和前向声明:
ctags -R --c++-kinds=+px
四. ctags是怎么记录的
不管⼀次扫描多少⽂件,⼀条ctags命令把记录的内容都记到⼀个⽂件⾥去,默认是当前⽬录的tags⽂件,当然这是可以更改的。
每个语法元素对应⽂件⾥的⼀⾏,学名叫tag entry。
1)开头是tag的名字,其实也就是语法元素的名字,例如记录的是函数的话则tag名就是函数名,记录的是类的话,tag名就是类名。2)接下来是⼀个tab。
3)接下来是语法元素所在的⽂件名。
4)⼜是⼀个tab。
5)⼀条“命令”。这个要解释⼀下意义:ctags所记录的内容的⼀个功能就是要帮助像vi这样的编辑器快速定位到语法元素所在的⽂件中去。前⾯已经记录了语法元素所在的⽂件,这条命令的功能就是⼀旦在vi中打开语法元素所在的⽂件,并且执⾏了该“命令”后,vi的光标就能定位到语法元素在⽂件中的具体位置。所以该“命令”的内容⼀般分两种,⼀种是⼀个正则表达式的搜索命令,⼀种是第⼏⾏的指向命令。默认让ctags在记录时⾃⾏选择命令的种类,选择的依据不详,可以通过命令⾏参数来强制ctags使⽤某种命令,这⾥就不多谈了。
6)对于本tag entry(简称tag)所对应的语法元素的描述,例如语法元素的类型等。具体内容和语法元素的种类密切相关。显⽰哪些描述,显⽰的格式等都是可以在命令⾏指定的。例如如下命令要求描述信息中要包含:a表⽰如果语法元素的类的成员的话,要标明
其access(即是public的还是private的);i表⽰如果有继承,标明⽗类;K表⽰显⽰语法元素的类型的全称;S表⽰如果是函数,标明函数的signature;z表⽰在显⽰语法元素的类型是使⽤kind:type的格式。
ctags -R --fields=+aiKSz
ctags除了记录上述的各种内容之外,还可以在tags⽂件中记录本次扫描的各个⽂件,⼀个⽂件名对应⼀个tag entry。默认是不记录的,要强制记录要是使⽤如下命令:
ctags –R --extra=+f
还可以强制要求ctags做这样⼀件事情——如果某个语法元素是类的⼀个成员,当然ctags默认会给其记录⼀个tag entry(说⽩了就是
在tags⽂件⾥写⼀⾏),可以要求ctags对同⼀个语法元素再记⼀⾏。举⼀个例⼦来说明:假设语法元素是⼀个成员函数,ctags默认记录
的tag entry中的tag的名字就是该函数的名字(不包括类名作为前缀),⽽我们强制要求ctags多记的那个tag entry的tag的名字是包含了类明作为前缀的函数的全路径名。这样做有什么好处见下⽂分析。强制ctags给类的成员函数多记⼀⾏的命令为:
ctags -R --extra=+q
五. vi⼤概是怎样使⽤ctags⽣成的tags⽂件的
估计vi是这样使⽤tags⽂件的:我们使⽤vi来定位某个tag时,vi根据我们输⼊的tag的名字在tags⽂件中⼀⾏⾏查,判断每⼀⾏tag entry
的tag名字(即每⾏的开头)是否和⽤户给出的相同,如果相同就认为到⼀条记录,最后vi显⽰所有到的记录,或者根据这些记录直接跳转到对应⽂件的特定位置。
考虑到ctags记录的内容和⽅式,出现同名的tag entry是很常见的现象,例如函数声明和函数定义的tag名字是⼀样的,重载函数的tag名字是⼀样的等等。vi只是使⽤tag名字来搜索,还没智能到可以根据函数的signature来选择相应的tag entry。vi只能简单的显⽰tag entry的内容
给user,让user⾃⾏选择。
ctags在记录成员函数时默认是把函数的名字(仅仅是函数的名字,不带任何类名和namespace作为前缀)作为tag的名字的,这样就导致很多不同类但同名的函数所对应的tag entry的名字都是⼀样的,这样user在vi中使⽤函数名来定位时就会出现暴多选择,挑选起来⼗分⿇
烦。user可能会想在vi中⽤函数的全路径名来进⾏定位,但这样做会失败,因为tags⽂件中没有对应名字的tag entry。要满⾜⽤户的这种⼼思,就要求ctags在记录时针对类的成员多记录⼀条tag entry,该tag entry和已有的tag entry的内容都相同,除了tag的名字不同,该tag entry 的名字是类的成员的全路径名(包括了命名空间和类名)。这就解释了ctags的--extra=+q这样⼀条命令⾏选项(见四)。
六.我的⼀条ctags命令
ctags-R --languages=c++ --langmap=c++:+.inl -h +.inl --c++-kinds=+px--fields=+aiKSz --extra=+q -- --
exclude=
命令太长了,折成两⾏了,可以考虑把命令的各个参数写到⽂件⾥去了(具体做法就不谈了)。
1.
-R
表⽰扫描当前⽬录及所有⼦⽬录(递归向下)中的源⽂件。并不是所有⽂件ctags都会扫描,如果⽤户没有特别指明,则ctags根据⽂件的扩展名来决定是否要扫描该⽂件——如果ctags可以根据⽂件的扩展名可以判断出该⽂件所使⽤的语⾔,则ctags会扫描该⽂件。
2.
--languages=c++
只扫描⽂件内容判定为c++的⽂件——即ctags观察⽂件扩展名,如果扩展名对应c++,则扫描该⽂件。反之如果某个⽂件叫aaa.py(⽂件),则该⽂件不会被扫描。
3.
--langmap=c++:+.inl
告知ctags,以inl为扩展名的⽂件是c++语⾔写的,在加之上述2中的选项,即要求ctags以c++语法扫描以inl为扩展名的⽂件。
4.
-h +.inl
告知ctags,把以inl为扩展名的⽂件看作是头⽂件的⼀种(inl⽂件中放的是inline函数的定义,本来就是为了被include的)。这样ctags在扫描inl⽂件时,就算⾥⾯有static的全局变量,ctags在记录时也不会标明说该变量是局限于本⽂件的(见第⼀节描述)。
5.
--c++-kinds=+px
记录类型为函数声明和前向声明的语法元素(见第三节)。
6.
--fields=+aiKSz
控制记录的内容(见第四节)。
7.
--extra=+q
让ctags额外记录⼀些东西(见第四、五节)。
8.
-- --exclude=
告知ctags不要扫描名字是这样的⽂件。还可以控制ctags不要扫描指定⽬录,这⾥就不细说了。
9.
-f tagfile:指定⽣成的标签⽂件名,默认是tags. tagfile指定为 - 的话,输出到标准输出。
七.本⽂内容来源
Exuberant Ctags附带的帮助⽂档(ctags.html)。
========================================补充===========================================
"ctags"是⼀个独⽴的程序,绝⼤多数Unix系统上都会预装这个程序。
1、使⽤tags
tag是什么?⼀个位置。它记录了关于⼀个标识符在哪⾥被定义的信息,⽐如C或C++程序中的⼀个函数定义。这种tag聚集在⼀起被放⼊⼀个tags⽂件。这个⽂件可以让Vim能够从任何位置起跳达到tag所指⽰的位置-标识符被定义的位置。
下⾯的命令可以为当前⽬录下的所有C程序⽂件⽣成对应的tags⽂件:
(shell command) ctags *.c
现在你在Vim中要跳到⼀个函数的定义(如startlist)就可以⽤下⾯的命令:
(ex command) :tag startlist
这个命令会带你到函数"startlist"的定义处,哪怕它是在另⼀个⽂件中。
CTRL+] 命令会取当前光标下的word作为tag的名字并直接跳转。这使得在⼤量C程序中进⾏探索更容易⼀些。假设你正看函数"write block",发现它调⽤了⼀个叫"write line"的函数,这个函数是⼲什么的呢?你可以把光标置于"write_line"上,按下CTRL+] 即可。如果"write_line"函数⼜调⽤了 "write_ char".你当然⼜要知道这个函数⼜是什么功能。同时,置光标于"write_char"上按下CTRL+]。现在你位于函数"write_char"的定义处。
":tags"命令会列出现在你就已经到过哪些tag了:
(ex command):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命令会跳到你前⼀次的tag处。在上例中它会带你到调⽤了"write_char"的"write_line"函数的地⽅。CTRL+T可以带⼀个命令记数, 以此作为往回跳的次数, 你已经向前跳过了,现在正在往回跳,我们再往前跳⼀次。下⾯的命令可以直接跳转到当前tag序列的最后:
(ex command) :tag
你也可以给它⼀个前辍, 让它向前跳指定的步长. ⽐如":3tag"。CTRL+T也可以带⼀个前辍。这些命令可以让你向下深⼊⼀个函数调⽤树(使⽤CTRL+]), 也可以回溯跳转(使⽤CTRL+T). 还可以随时⽤":tags"看你当前的跳转历史记录。
2、分隔窗⼝
":tag"命令会在当前窗⼝中载⼊包含了⽬标函数定义的⽂件。但假设你不仅要查看新的函数定义,还要同时保留当前的上下⽂呢?你可以在":tag"后使⽤⼀个分隔窗⼝命令":split"。Vim还有⼀个⼀举两得的命令:
(ex command) :stag tagname
要分隔当前窗⼝并跳转到光标下的tag:
(normal mode command) CTRL+W+]
如果同时还指定了⼀个命令记数, 它会被当作新开窗⼝的⾏⾼.
3、多个tags⽂件
如果你的源⽂件位于多个⽬录下,你可以为每个⽬录都建⼀个tags⽂件。Vim会在使⽤某个⽬录下的tags⽂件进⾏跳转时只在那个⽬录下跳转。
要使⽤更多tags⽂件,可以通过改变'tags'选项的设置来引⼊更多的tags⽂件。如:
(ex command) :set tags=./tags, ./../tags, ./*/tags
这样的设置使Vim可以使⽤当前⽬录下的tags⽂件,上⼀级⽬录下的tags⽂件,以及当前⽬录下所有层级的⼦⽬录下的tags⽂件。这样可能会引⼊很多的tags⽂件,但还有可能不敷其⽤。⽐如说你正在编辑"~/proj/src"下的⼀个⽂件,但⼜想使⽤"~/proj/sub/tags"作为 tags⽂件。对这种Vim情况提供了⼀种深度搜索⽬录的形式。如下:(ex command) :set tags=~/proj/**/tags
4、单个tags⽂件
Vim在搜索众多的tags⽂件时,你可能会听到你的硬盘在咔嗒咔嗒拼命地叫。显然这会降低速度。如果这样还不如花点时间⽣成⼀个⼤⼀点的tags⽂件。这需要⼀个功能丰富的ctags程序,⽐如上⾯提到的那个。它有⼀个参数可以搜索整个⽬录树:
(shell command)cd ~/proj
ctags -R
⽤⼀个功能更强的ctags的好处是它能处理多种类型的⽂件。不光是C和C++源程序,也能对付Eiffel或者是Vim脚本。你可以参考ctags程序的⽂件调整⾃⼰的需要。现在你只要告诉Vim你那⼀个tags⽂件在哪就⾏了:
(ex command) :set tags=~/proj/tags
5、同名tag
当⼀个函数被多次重载(或者⼏个类⾥都定义了⼀些同名的函数),":tag"命令会跳转到第⼀个符合条件的。如果当前⽂件中就有⼀个匹配的,那⼜会优先使⽤它。当然还得有办法跳转到其它符合条件的tag去:
(ex command) :tnext
重复使⽤这个命令可以发现其余的同名tag。如果实在太多,还可以⽤下⾯的命令从中直接选取⼀个:
(ex command) :tselect tagname
Vim会提供给你⼀个选择列表,例如:(Display)
# 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()
Enter nr of choice (<CR> to abort):
现在你只需键⼊相应的数字(位于第⼀栏的)。其它栏中的信息是为了帮你作出决策的。在多个匹配的tag之间移动,可以使⽤下⾯这些命令:
(ex command):tfirst go to first match
:[count]tprevious go to [count] previous match
:[count]tnext go to [count] next match
:tlast go to last match
如果没有指定[count],默认是1。
6、tag的名字
命令补齐真是避免键⼊⼀个长tag名的好办法。只要输⼊开头的⼏个字符然后按下制表符:
(ex command) :tag write_<Tab>
Vim 会为你补全第⼀个符合的tag名。如果还不合你意,接着按制表符直到到你要的。有时候你只记得⼀个tag名的⽚段,或者有⼏个tag开头相同。这⾥你可以⽤⼀个模式匹配来告诉Vim你要的tag。
假设你想跳转到⼀个包含"block"的tag。⾸先键⼊命令:(ex command) :tag /block。现在使⽤命令补齐:按<Tab>。Vim会到所有包含"block"的tag并先提供给你第⼀个符合的。"/"告诉Vim下⾯的名字不是⼀五⼀⼗的tag名,⽽是⼀个搜索模式。通常的搜索技巧都可以⽤在这⾥。⽐如你有⼀个tag以"write "开始:(ex command) :tselect / ^write_,"^"表⽰这个tag以"write_"开始。不然在半中间出现write的tag也会被搜索到。同样"$"可以⽤于告诉Vim要查的tag如何结束。
7、tags的浏览器
CTRL+]可以直接跳转到以当前光标下的word为tag名的地⽅去,所以可以在⼀个tag列表中使⽤它。下⾯是⼀个例⼦。⾸先建⽴⼀个标识符的列表(这需要⼀个好的ctags):
(shell command) ctags --c-types=f -f functions *.c
现在直接启动Vim, 以⼀个垂直分隔窗⼝的编辑命令打开⽣成的⽂件:
(shell command) vim:vsplit functions
这个窗⼝中包含所有函数名的列表。可能会有很多内容,但是你可以暂时忽略它。⽤⼀个":setlocal ts=99"命令清理⼀下显⽰。在该窗⼝
中,定义这样的⼀个映射:
(ex command):nnoremap <buffer> <CR> 0ye<C-W>w:tag <C-R>"<CR>
现在把光标移到你想要查看其定义的函数名上,按下回车键,Vim就会在另⼀个窗⼝中打开相应的⽂件并定位到到该函数的定义上。
8、其它相关主题
设置'ignorecase'也可以让tag名的处理忽略掉⼤⼩写。'tagsearch'选项告诉Vim当前参考的tags⽂件是否是排序过的。默认情况假设该⽂件是排序过的,这会使tag的搜索快⼀些,但如果tag⽂件实际上没有排序就会在搜索时漏掉⼀些tag。
'taglength'告诉Vim⼀个tag名字中有效部分的字符个数。例:
#include <stdio.h>
int very_long_variable_1;
int very_long_variable_2;
int very_long_variable_3;
int very_long_variable_4;
int main()
{
very_long_variable_4 = very_long_variable_1 *
very_long_variable_2;
}
对于上⾯这段代码, 4个变量长度都为20, 如果将'taglength'设为10, 则:
(ex command):tag very_long_variable_4
会匹配到4个tag,⽽不是1个,光标停留在very_long_variable_1所在⾏上,因为被搜索的tag部分只有前⾯的10个字符: "very_long_",相应的显⽰是(是gvim中⽂版的真正显⽰,不是翻译的):
(Display)到tag: 1/4 或更多
转⾃:
发布评论