你好,游客 登录 注册 搜索
背景:
阅读新闻

Vim之tags 详解

[日期:2017-01-09] 来源:Linux社区  作者:pangchol [字体: ]

下面是今天的最后一个自动索引功能的实现. 我废话真的太多了, 写博客都写累了....休息中....

最后一个查找跳转类型的数据库文件是lookupfile这个插件.  实际上这个插件在很大程度上是多余的, 首先是cscope提供了文件查找功能. 其次是如果使用FuzzyFinder插件可以实现借助tags文件来查找项目文件. 不过鉴于lookupfile这个插件遇见的较早, 功能简单, 查找优美, 设置好了之后使用也很方便又不和任何其他插件的冲突, 我还是永久的留下了这个插件. 下面是这个插件的相关设置过程:

到http://vim-scripts.org/vim/scripts.html 搜索lookupfile下载并安装该插件.

编写生成lookupfile索引文件的脚本, 这个我也是从网络上抄来的, 内容可以如下:

#!/bin/sh
# generate tag file for lookupfile plugin
echo -e "!_TAG_FILE_SORTED\t2\t/2=foldcase/" > name.tags
find `pwd` -type f \( -name "*.[ch]"  -o -name "*.[ch]pp" \) -printf "%f\t%p\t1\n"|sort -f >>name.tags

其中的name.tags是生成的的索引文件的名字, 你可以修改成任何期它的名字, 不过需要注意一定要使用一个后缀名, 不可起如nametags这样的名字, 这在后面的会说明原因.这里的第二句命令find 后面使用了'pwd'参数 因此生成的name.tags中存放的是文件的绝对路径, 如果不使用'pwd'参数,将使用相对路径,这将出现的问题和FuzzyFinder使用相对路径的tags文件相同的--在根目录的子目录搜索到文件后无法正确的跳转.

 

将上面的内容保存为.sh后缀的文件(如:nametags.sh),将这个shell文件移动的到一个目录下并将该目录添加到环境变量中去. 个人建议是在~/.vim目录下建立一个bin文件夹, 将nametags.sh移动到这里, 以后只要是和编程有关的shell文件或自建的批处理等文件都可以放到这里来, 这样在日常备份的时候只要将~/.vim 文件夹和~/.vimrc文件打包就可以备份很多开发环境了, 本这这个原则, 我个人开发相关的一些其他的内容(如vim的补全列表, 标准项目模板, 个人使用的函数库等)都会放在.vim文件夹中.bin文件夹下面存在放的都是需要执行的文件, 因此在第一次使用之前,重装系统或移动到一台上使用的时候需要做如下两个操作:

->将bin目录下所有的文件赋予执行权限: sudo chmod 775 ~/.vim/bin/*

->将~/.vim/bin目录添加到环境变量中去. 这个操作有很多中方式, 每个linux版本也不是太一样, 可以自行百度解决.

 

  nametags.sh在赋予可执行权限和添加环境变量操作之后就可以在任何目录执行了, 执行的结果是nametags.sh 索引当前目录的所有的子目录, 将.c .h等源文件排序后添加到当前目录的name.tags文件来.这name.tags相当与tags文件, 只是他的功能只能用来查找项目文件而已.

  在项目根目录执行nametags.sh之后我们需要向ctags和cscope一样通过vimrc来告诉vim的lookupfile插件那里去找这个项目文件的索引,当然, 我们也需要实现vim自动对name.tags文件的自动递归查找. 这个功能在网络上可就没有现成的方法了, 因为很少有人会想到这个需求, 同时实现起来也很困难, 因为没有多少人对vim了解到会编写脚步的地步, 我至今还不太会编写比较复杂的vim脚本文件. 好在大多数复杂的功能都已经被大神和前辈们实现了, 而我们需要做的就是让这些功能更加的便捷并定制成符合自己需求的样子就可以了.

  在看过了lookupfile的帮助文档和查阅了一些vim脚步资料之后, 在我不断的尝试性的编写下竟然让我实现了lookupfile的递归查找name.tags的功能. 这个算的上是我完全独立实现的功能啦. 有纪念意义哦....

  在vimrc中添加如下两条语句,将实现lookupfile的自动递归查找name.tags文件:

let g:name_file=findfile("name.tags", ".;")
let g:LookupFile_TagExpr='g:name_file'

  第一句的findfile函数实现了递归查找,注意:findfile函数似乎有一个神奇的特点, 如果你查找的是诸如name.tags这种带有后缀名的文件, 那么它会返回一个绝对路径的文件名字符串,可是如果你查找的是诸如nametags这样没有后缀名的文件, findfile依然可以正确的找到这个文件, 但返回的东西就很坑爹了, 直接是nametags, 路径全部被吞掉了!!!

   第二句是将第一句查找的结果连同路径一起赋值给LookupFile_TagExpr变量, 这里似乎必须在name_file前面添加一个g:或s:才能正确的被vim识别.

为更家便捷的使用lookupfile查询文件,我在vimrc设置了如下的对lookupfile的设置和快捷键的映射并从网络找到了一个可以实现lookupfile查找时忽略大小写的功能函数:

let g:LookupFile_MinPatLength=2
let g:LookupFile_PreserveLastPattern=0
let g:LookupFile_PreservePatternHistory=1
let g:LookupFile_AlwaysAcceptFirst=1
let g:LookupFile_AllowNewFiles=0
let g:indentLine_color_gui='#4e5656'

nmap ,l :LUTags<cr> "最常用
nmap ,kk :LUBufs<cr>
nmap ,kl :LUWalk<cr>

"LookupFile搜索时不区分大小写
func! LookupFile_IgnoreCaseFunc(pattern)
 let _tags = &tags
 try
 let &tags = eval(g:LookupFile_TagExpr)
 let newpattern = '\c' . a:pattern
 let tags = taglist(newpattern)
 catch
 echohl ErrorMsg | echo "Exception: " . v:exception | echohl NONE
 return ""
 finally
 let &tags = _tags
 endtry
 let files = map(tags, 'v:val["filename"]')
 return files
endfunc
let g:LookupFile_LookupFunc = 'LookupFile_IgnoreCaseFunc'

  由于这三个索引功能的文件都不会在项目文件发生修改的时候自动更新, 因此需要我们手动的在需要更新的时候一个一个的更新他们,事实上这样的更新在我们查看别人的项目和linux内核文件的时候并不是太过频繁,有时甚至很少. 例如在查看linux内核源文件的时候我几乎只需要建立一次索引就可以了, 重点这是一个很幸运的事情, 因为linux的内核文件是相当的庞大的, 对它建立tags和cscope.out以及name.tags文件加一起更新一次就需要近十来分钟. 这三个数据库文件都不支持增量更新. 即便我们只改动了整个项目的很小的一部分内核, 重新构建tags的时候依然需要对整个项目进去完全的检索. 从这一点来看ctags和cscope并不适合超大型项目的频繁构建. 之所以说这是一个幸运是因为我们很少会参与诸如linux内核这样的项目构建. 即便公司真的有一个超大的项目, 分配到我们身上的大多是很小的一部分或一个模块功能. ctags和cscope建立的速度还相当快了, linux内核产生的ctags可以达到100多兆, cscope.out更是达到300-400兆. 这样的数据在几分钟内完成,可想而知, 不是超级巨型的项目, tags建立基本可以在十妙内完成.

  现在还剩下最后一个问题, 虽然既然我们很幸运的可以正常使用他们, 但是在开发一个常规项目的时候频繁的重新构建tags,cscope.out和name.tags是正常的操作之一, 这些索引数据都是通过命令建立的.每个命令又不是那么简短. 编码时,如果希望刷新tags的时候就跑到项目根目录挨个执行一通他们的命令肯定是一件没几个人能接受的事情. 好在这些tags的构建名利几乎都是固定的. vim提供了直接执行shell命令的能力. 再加上快捷键映射功能和项目的递归特性. 我们便有办法将tags的更新简化到一键操作, 一键操作是一个很了不起的能力, 因为除了全自动的操作, 应该没有比一键操作还要简洁高效的了.

  下面是实现ctags, cscope,和lookupfile的一键刷新功能的设置:

  ->在项目的根目录新建一个空的文件名为TOP的文件. 这个文件用来标识项目根目录所在位置.(你可以将这个空文件做为你的项目标准模板的一部分)

  ->在vimrc中添加如下的函数和设置:

 

"nmap <f12> <esc>:call Go_top()<cr>:!ctags -R --c++-kinds=+p --fields=+iaS
"\ --extra=+q $PWD<cr>:call Do_CsTag()<cr>:!nametags.sh<cr><cr>:call Go_curr()<cr>
"imap <f12> <esc>:call Go_top()<cr>:!ctags -R --c++-kinds=+p --fields=+iaS
"\ --extra=+q $PWD<cr>:call Do_CsTag()<cr>:!nametags.sh<cr><cr>:call Go_curr()<cr>
"nmap <a-f12> <esc>:!ctags -R --fields=+lS $PWD<cr><cr>
 "\:!cscope -Rbkq<cr><cr>:!nametags.sh<cr><cr>
"imap <a-f12> <esc>:!ctags -R --fields=+lS $PWD<cr><cr>
 "\:!cscope -Rbkq<cr><cr>:!nametags.sh<cr><cr>

nmap <f12> <esc>:call Go_top()<cr>:!ctags -R --fields=+lS $PWD<cr><cr>
 \:!cscope -Rbkq<cr><cr>:!nametags.sh<cr><cr>:call Go_curr()<cr>
imap <f12> <esc>:call Go_top()<cr>:!ctags -R --fields=+lS $PWD<cr><cr>
 \:!cscope -Rbkq<cr><cr>:!nametags.sh<cr><cr>:call Go_curr()<cr>

func! Go_top()
  wall
  let g:Curr_dir=getcwd()
  let i = 1
  while i < 10
    if filereadable("TOP")
      return
    else
      cd ..
     let i += 1
    endif
  endwhile
  exec 'cd'.g:Curr_dir

endfunc

func! Go_curr()
  exec 'cd'.g:Curr_dir
endfunc

func! Do_CsTag()
  silent! exec "!find . -name '*.h' -o -name '*.c' -o -name '*.cpp'
  \ -o -name 'Makefile' -o -name 'makefile' -o -name 'make*'
  \ -o -name '*.cc' -o -name '*.C'-o -name '*.s'-o -name '*.S'>cscope.files"
  silent! exec"!cscope -Rbkq -i cscope.files"
  silent! exec"!cscope -Rbkq"
endfunc

说明:

  ->前面几行用 " 注视掉的内容是对C++的支持, 在使用C++的时候可以解除他们的注释并将下面C语言使用的映射添加注释.

  ->整个对<F12>键的映射在看完上面的介绍之后应该可以看懂, 需要注意的是vim中命令模式下执行!开头的内容会被送往vim所在的shell中执行并且很多时候在终端执行完vim传递的命令之后终端会给出一个提示说明执行完毕,并要求键入一个enter键才能返回到vim,vim的映射中的<cr>可以被自动发送到终端中去, 因此只要在调用终端命令之后在额外的添加一个<cr>就可以实现让vim自动在终端中键入一个<enter>键. 另外, 其实这里的映射本来完全可以封装在一个函数中完成的. 可是我自己无法实现在vim脚步的函数中向终端发送<enter>键的功能, 因此不得已才通过这种在映射中添加<cr>的方式实现自动交互的功能.

  ->映射中在执行ctags -R的时候额外的传递了一个$PWD变量, 这个变量在vim表示的是vim的当前工作目录, 由于在执行ctags前已经调用Go_top()将当前目录更改为项目的根目录, 所以这里传递的实际上就项目的跟目录, 这个变量的作用在之前说过, 它可是让ctags生成的文件使用绝对路径, 这会保证几乎所有调用的tags的插件都可以正常的工作.

  ->Go_top()函数首先记录下当前目录位置然后递归的查找当前目录所以父目录下是否存在TOP文件, 如果存在就定位到那个目录并返回, 如果不存在通过之前记录的目录返回到之前的目录, 这样做的好处是的即便没有找到TOP标识,vim的当前目标也不至于跑到系统的根目录上去.

  ->Go_curr()函数实现vim在成功找到TOP标识并在TOP所在目录执行完所有命令之后通过g:Curr_dir记录的全局变量来让vim的当前目录中回到执行命令前的状态.

  ->Do_Cstags()函数是一个可选函数, 他的功能是可以在生成cscope的时候过滤掉一些不需要建立索引的文件. 事实上, 这个功能并是经常用到, 因为项目文件夹中的的文件大多和项目相关. 不相关的一些说明文件cscope也可能对其建立索引.为了让所有可能建立索引的特殊文件(如:makefile)也可以实现跳转.我自己并没有过滤任何文件.

  当上面所有的设置都正确工作了之后, 恭喜, vim和tags相关的配置就算告一段落了. 以后你要做的就是在项目根目录建立TOP文件.在任何项目目录中打开vim按下f12键, vim将会自动为你建立所需的所有tags文件. 之后只要你在任意项目目录中打开vim属于这个项目的所有tags文件都会被vim自动加载并正确工作. 在你编辑代码的任何时刻, 你都可以通过一个f12键迅速的更新整个项目的所有tags索引. 你可以通过ctrl+]和ctrl+t来调用ctags, 在普通模式下通过"f"打头快捷键调用cscope查询,通过 ",l" 来调用lookupfile查询项目文件. 这篇文章中提到到所有功能最终被浓缩到一个f12键上. 这种一劳永逸并可以大幅提高效率的感觉会让你觉得付出的一切的是那么的值得. 这也是为什么我愿意花掉整个下午的时间来写这篇博客的原因. 希望这几千的说明可以被更多愿意使用vim的人看到. 也希望有其他的vim爱好者能够和我更多的交流学习....

  最后顺便提及一下曾经研究过的另一个tags工具--gnu global 这个工具好像是一个日本人开发的, 曾经在水木社区上看到比较全面的使用方法, 按照上面的配置, global工具的确可以被vim识别并使用, 但由于global使用的是二进制存在, 内部tags数据又使用的是相对路径, 这将出现上面提到过两次的相同问题--只有在项目的根目录使用global搜索, vim才能正确的跳转. gnu global最大的优点是支持增量更新, 大型项目修改后global的更几乎是瞬间完成. global在查询速度上也很快, 查询的结果比ctags要全面但相对与cscope要稍微弱一点. 在所有优点的背后由于我没有找到让他产生绝对路径数据库的办法. 最终在折腾了几天之后放弃了使用.

本文永久更新链接地址http://www.linuxidc.com/Linux/2017-01/139378.htm

linux
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数

       

评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款