Vim之tags 详解
本章要说的是和vim的tags相关的内容. 之所以在跳转之后就说明tags是因为这个功能相当的重要和实用. 好的东西自然是需要提前分享的.
首先, 要说的是关于vim使用ctags, cscope的相关教程, 网络上已经有相当详尽的文章可以搜索到. 这里不会在重复大多数网络上可以搜索到的入门教程了. 并且在此提醒阅读本篇博客的读者, 如果你现在对ctags和cscope等并不了解, 那么请先在网络里google所有和他们相关的教程, 花上大约至少一天上的时间认真研究他们的基本用法. 等到对他们有了基本的了解之后再回来看这里的说明你将会有更大的收获.
好了, 废话我就不再多说, 下面进入真题, 首先要说的是ctags是一个linux上很普遍的源码分析工具, 可以将代码中的函数变量等定义的位置记录在一个名称为tags的文件. 类似于数据库记录功能. tags文件的产出最简单的方法是在需要生成tags的工程项目的根目录下执行ctags -R命令, 这会调用tags递归的扫描当前目录以及所有子目录中可以被tags识别的文件所以文件数据信息都会汇集到tags文件中.这里总结一下几个本人暂时知道的几个需要注意的地方:
->Ubuntu在默认系统环境中已经集成了ctags功能, 但这个ctags并不是完整的版本, 为了保证vim可正常的使用tags建议安装完整的ctags支持, ubuntu完整的ctags被重名为exuberant-ctags但依然可以通过ctags索引到, 安装命令如下:
sudo apt-get install ctags
->如果你的vim有使用echofunc插件来显示函数的参数定义, 那么在使用ctags生产索引文件时需要使用如下附加参数:
ctags -R --fields=+lS
->ctags默认生成的索引文件只包含了对C语言的语法分析, 如果你需要ctags支持对C++语法分析. 需要使用下面的命令:
ctags -R --c++-kinds=+p --fields=+iaS --extra=+q
->如果你在C语言编写的代码中使用上面提到的C++命令生成tags, 那么你将惊讶的发现, 当你希望通过ctags跳转到光标下函数定义的地方的时候, vim总是跳转到这个函数定义的地方, 原因是ctags的C++命令增加了额外的语法分析以便支持C++更加复杂的语法结构, 这种额外的语法分析用在C语言中的时候就会出现跳转默认定位到函数声明的地方.
->ctags默认是支持C和C++的由于本人暂时没有做过其他语言的开发工作, 所以不清楚ctags是否支持其他编程语言.
->请不要每次生成tags的时候都使用上面的命令, 包括以后其他的很多功能的实现都是如此, 命令是实现一个功能的原型, 如果仅仅使用命令在类unix(windows下面也是如此)的操作系统上实现常用的操作和功能, 那么你的操作将会变得很低效. 个人认为vim的使用也有这个规律, 最普遍的操作最好不要使用长长的命令来实现. 取而代之的方法是设置快捷键和命令的简写映射.
vim中的tags使用
vim配置tags使用的过程是, 先在终端中生成一个项目的tags文件, 再在vimrc中告诉vim哪里去找这个tags. 假如我们有一个项目在/home/boddy/hello/下面, 并且这个目录下已经生成tags文件.那么只要在vimrc文件中添加如下语句就可以让vim在每次启动的时候自动找到这tags了:
set tags+=/home/boddy/holle/tags
如果你有多个tags需要使用,可以重复上面的语句, 也可以在同一个语句中加入多个路径,每个路径用","隔开.
这种方法是我最初使用tags的的方式, 缺点显而易见, 每当我们开始新的项目的时候,不得不重新修改set tags后面的路径, 虽然这样的修改并不麻烦, 也不需要经常操作. 但不免还是让人觉得太死板不智能.
为了让vim对tags的支持更加自动话, 首先想到的方法是在vimrc中添加 settags+=./tags语句, 这样当前目录下的tags就不需要我们手动添加了.但我们不可能永远在项目的根目录操作. 如果在项目的子目录里操作, 这个方法将会失效. 事实上, 任何一个项目文件夹都有一个显著的特点: 任何子目录递归向上都可以到达项目的根目录. 这个特点将会在下面的实践中大显身手, 让我们从各种和项目整体相关的操作中解脱出来. 这包括ctags cscope lookupfile等相关索引文件的自动添加和随时更新. 在项目任意子目录下的任意时刻对项目的自动make和make clean等. 这些最频繁操作要么是自动完成的要么是通过vimrc的map功能映射到不同的快捷键上, 这会让我们的vim具有所以IDE所具有的便捷性的同时保持了vim在编码方面无比的高校性和在功能定制方便的无比的可定制性. 我想这就是我为什么宁愿选择折腾vim而没有使用成熟的IDE的原因.
上面提到工程项目的目录递归特性, 实际上, 我们需要使用一个tags最典型的需求是希望在一个项目中快速的找到函数,变量,宏等定义的地方. 这些查找大多是在项目文件夹内完成. 因此, 只要我们能上vim实现对当前所在目录的递归查找功能, 这个需求就会满足. 只要项目的根目录下存在tags文件, 任何时候任在任何一个项目的子目录下使用vim都可以正确的找到这个tags, 同时如果我们的电脑中存在多个项目, 当我们切换到另一个项目的子目录的时候, vim递归查找到永远是当前项目根目录下面的tags(前提是tags存在于项目根目录), 无意间就实现tags的自动切换功能.
实现vim对tags的自动递归查找其实很简单, 因为vim已经实现了这个功能, 只是默认没有开启. 在vimrc添加下面两行配置, 就会是见证奇迹的时刻:
set autochdir
set tags=tags;
set autochdir表示自动切换目录的意思, set tags=tags;表示自动查找, 这两句同时设置vim即可实现递归的tags查找, 注意: set tags=tags;这一句的最后有一个分号, 这个分号是不能省略的. vim的配置文件使用的是vim自己的脚步语言. 这里是少数几个在行尾需要使用分号的地方之一.
最后需要说明的一点: ctags在默认的命令下生成的tags中使用的是相对路径的存放所有查找结果, 这在多数情况下是一个优点, 因为相对路径不依赖于项目的根目录所在位置. 这样在整个项目转移到别的位置的时候, 相对路径的tags依然可以正常的实现跳转. 不过相对路径的tags并不是没有缺点, 如果你的vim中使用了FuzzyFinder来作为查找项目文件的工具, 你将惊讶的发现如果你在项目根目录的子目录下执行项目文件查找,在找到了想要的文件并最后回车跳转的时候如果tags使用的是相对路径, 这一步将会失败, 因为FuzzyFinder无法正确的通过当前的目录(不是项目根目录)加上tags中的相对路径计算出正确的文件位置. 解决的办法是在ctags生产tags文件的时候使用绝对路径, 使用方法是在ctags -R 的命令后面添加项目根目录的绝对路径, 如: ctags -R /home/boddy/hello/ , 使用绝对路径可以保证tags在任何调用他的工具中正确的找到文件位置. 还有一个需要注意的是, 如果现在有下面的需求,我们需要对/usr/include下面所以文件和子目录bits/做tags处理, 如果你按照上面的语法推导应该得出命令为(当前位置已经在/usr/include): ctags * bits/ /usr/include/, 可是让我们失望的是这个样执行没有出错但产生的结果竟然是相对路径, 实际上这种情况下生成绝对路径的命令写法应该是: ctags /usr/include/* /usr/include/bits/* . 使用绝对路径唯一的缺点是如果项目移动了, tags必需重新生成, 鉴于我们的项目在自己的电脑中移动的需求很小的同时重新生成一tags的时间也就是几秒钟的事情(只要不是超级大的项目,tags生成还是很快的), 我个人选择了在任何时候使用ctags都使用绝对路径, 当然这个功能是在vim中通过参数自动实现的, 本文的最后将会提到.
好了, vim对tags的自动查找功能实现了, 只是一个开始哦. 下面的介绍将会进一步增强vim在代码跳转和搜索上的能力.
ctags的优点是使用简单, 生成的tags文件比较小, 使用时对tags的检索也相对比较快, 对c语言的函数和宏定义跳转相当准确高效. 他的缺点功能相对单一, 没办法实现对一个关键字出现位置的统计, 一个函数被调用的位置统计, 局部变量的定义跳转往往没有效果等. 我们在编码的时候除了随时查看函数定义的需求以外, 另一个比较常用的需求是对一个函数在项目中出现位置查找. 这个功能ctags是没有的, 为了让vim实现这个功能, 我们需要借助另一tags索引工具:cscope
网络上对cscope的用法说明也很多, cscope的使用要不ctags复杂的不少,因为一样的, 请先自行google cscope相关的教程提前了解.
cscope的用法和上面的ctags的用法很相似, 不同的地方有:
->生成索引文件的命令不同: cscope -Rbkq
-> 索引文件的名字不同: 一共有三个, 主要的文件是cscope.out, 另外两个cscope.in.out cscope.po.out 是在生成命令中使用q参数才会有的文件, 这两个文件可以加速cscope的查找速度, 注意 : windows下面也可以使用tags和ecscope的,但cscope的-q参数并不支持, 因为windows下面将不会有cscope.in.out 和 cscope.po.out文件
->vim中用法不同: ctags 默认在vim中典型的用法是ctrl+] 跳转到光标下的关键字的定义处, ctrl+t跳转回来. cscope在vim下面并没有映射快捷键. 因为cscope的查找模式有近八种之多, 因此和vim配合使用的时候,默认是通过一组:cs find 开头的命令来实现的. cscope的官方网站上有一份在vim中使用的建议快捷键映射配置. 个人觉得cscope虽然查找方式众多, 但真正经常用到的也就三到四个. 可以试着将其分配到容易操作的键位上来. 官方的那个映射还是稍微有点难按了点.
->ubuntu默认系统下完全不支持cscope, 需要通过下面的命令安装cscope:
sudo apt-get install cscope
cscope是一个ctags的增强版本, 可以提供上面提到的各种查找功能. 其实我们完全可以不用ctags而只用cscope的, 因为cscope可以做到所有ctags的功能. 但出于ctags在c语言函数等定义上跳转的高效和准确性. 我个人是两个同时使用的, 定义的跳转通过ctrl+]和ctrl+t实现, 只要在需要其他查找的时候才会动用:cs find 命令.
好了, 既然实现了ctags的自动查找, cscope自然也要实现, 不能输入人家ctags啊. 不过在vim中实现cscope的递归查找就比较麻烦. 首先要注意的一点是:简单的在vimrc添加cscope路径有两种写法:
linux的终端版本的vim7.3中需要使用如下的写法vim才能正确的识别cscope的tags文件
cs add /home/boddy/hello/cscope.out /home/boddy/hello/
第二个参数是告诉vim cscope的索引文件中记录的数据的相对路径的起始位置在哪里, 大多情况下的这个位置是我们项目的根目录. 因为我们告诉了vim这个相对路径的起始位置, 因此cscope默认使用相对路径产生的数据库总是可以正确的跳转.
windows下面的gvim7.3需要使用如下的写法gvim才能正确识别cscope
cs a E:\project\hello\cscope.out
这里不需要添加相对路径的起始位置, 具体原因不清楚, 可能windows下的gvim默认把cscope.out所在路径解析成相对路径的起始路径了吧. 不过就是这个问题导致我在windows下面使用vim在很长一段时间中没能正确的使用cscope.
上面说的注意事项是我在vim学习之初遇到的问题, 下面实现的递归索引功能将不会关注这个问题, 但依然需要注意这个事项.
vim中实现cscope的递归查找有两种方式, 要么自己在vimrc中写一个简单函数让vim启动的时候去递归查找并添加cscope.out, 要么使用插件. 通过简单函数的实现在网络上可以搜索到, 同时我在最初也是用这种方法实现的, 不过在后来的某一天偶然让我发现了vim有一个实现递归查找cscope的插件后就删除了这个在vimrc中的函数,并使用插件来实现. 这样可是简化vimrc的篇幅的同时别人写的插件肯定比简单的函数要完善.
到下面的网站中搜索autoload_cscope.vim即可下载到这个插件.
http://vim-scripts.org/vim/scripts.html
针对这个插件有如下的说明:
这个插件只有一个脚步文件, 放到你的~/.vim/plugin目下即可使用, 该插件默认情况下是在我们打开.h/.c/.cpp文件的时候才会自动递归查找并添加cscope.out文件的,个人觉得既然我们在已经在项目子目录下了, 大多时候都是希望cscope可用的, 即便我们是在编辑一个.txt文件我们可能希望手动搜索一个当前项目的关键字等. 另外即便是cscope.out文件我们用不到,但将其添加到当前编辑文件中来在性能上几乎是没有影响的. 因此, 我人为的修改了这个插件,以便让它实现在打开任何文件的时候(包括新建空文件)都递归查找cscope.out
修改方法如下:
打开autoload_cscope.vim定位到文件的最后几行,你将看到:
au BufEnter *.[chly] call <SID>Cycle_csdb() | call <SID>Cycle_macros_menus()
au BufEnter *.cc call <SID>Cycle_csdb() | call <SID>Cycle_macros_menus()
au BufUnload *.[chly] call <SID>Unload_csdb() | call <SID>Cycle_macros_menus()
au BufUnload *.cc call <SID>Unload_csdb() | call <SID>Cycle_macros_menus()
将他们修改为下面内容即可
au BufEnter * call <SID>Cycle_csdb() | call <SID>Cycle_macros_menus()
au BufUnload * call <SID>Unload_csdb() | call <SID>Cycle_macros_menus()
另外可以在vimrc中选择性的添加如下内容:
set nocst "在cscope数据库添加成功的时候不在命令栏现实提示信息.
set cspc=6 "cscope的查找结果在格式上最多显示6层目录.
let g:autocscope_menus=0 "关闭autocscope插件的快捷健映射.防止和我们定义的快捷键冲突.
"cscope相关的快捷键映射
nmap ff <c-]> "ff映射到ctrl+],这将中调用ctags的数据库跳转,在速度上会快一点. 如果发现ff无法实现跳转,可以试着使用fg, 这个会调用cscope的数据库实现跳转.
nmap ss <c-t> "ss映射到ctrl+t , 在使用ff跳转的使用通过ss跳转回来,纯属个人在功能映射上的多余之举.
"s:查找即查找C语言符号出现的地方
nmap fs :cs find s <C-R>=expand("<cword>")<CR><CR>
"g:查找函数、宏、枚举等定义的位置
nmap fg :cs find g <C-R>=expand("<cword>")<CR><CR>
"c:查找光标下的函数被调用的地方
nmap fc :cs find c <C-R>=expand("<cword>")<CR><CR>
"t: 查找指定的字符串出现的地方
nmap ft :cs find t <C-R>=expand("<cword>")<CR><CR>
"e:egrep模式查找,相当于egrep功能
nmap fe :cs find e <C-R>=expand("<cword>")<CR><CR>
"f: 查找文件名,相当于lookupfile
nmap fn :cs find f <C-R>=expand("<cfile>")<CR><CR>
"i: 查找当前文件名出现过的地方
nmap fi :cs find i <C-R>=expand("<cfile>")<CR><CR>
"d: 查找本当前函数调用的函数
nmap fd :cs find d <C-R>=expand("<cword>")<CR><CR>
好了, cscope的自动化使用到此也大功告成,如果你想在vim中查看auto_cscope是否成功为你自动加载了cscope.out,可以在vim中执行 ":cs s" (这s是show的缩写) 来查看与当前vim窗口关联的所有cscope文件的信息.