Vim+Ctags

2022-02-10

vim 下想实现code中 function/class 等 definition跳转. 比较简单的方法就是使用ctags 分析代码, 然后根据生成的tags table文件, 来做code index 的定位. 目前比较流行的 code definition/references 查询是LSP, 需要架设一个 language service backend, 与之通信. 这里先记录下使用ctags的方法.

1. Ctags

Ctags 用于根据Code 生成 index 文件( ctags table), 其中标注所有 identifer 的文件位置信息.

{tagname}\t{tagfile}\t{tagaddress}

Ctags 有其他变种, 比如 Emacs 下有 Etags (也支持 Ctags)

arch 安装Ctags tool: yay -S ctags

2. Vim + Ctags

2.1 生成 Tags文件

首先需要先 generate ctags file, 在 project root dir 下:

	ctags -R .
  • -R 表示 recursively, 该遍历目录下所有子文件夹, 递归下去
  • . 表示 filepath, 即当前文件夹

此时会在当前文件夹下生成 tags 文件, 稍微打开看一下, 可以看到类似

 Greetings    hello/greetings.go    /^func Greetings()...

指定了idenfier Greetings 所在的文件名即位置 vim 打开调用Greettings 方法的文件, 在调用出按 Ctrl-] 即会跳转到所在文件.

2.2 配置vim指向Tags文件

一般会将tags文件放在各个project 的目录下, 这样方便区分. 但其中涉及到, 如何让vim寻找到当前project的tags文件, 以及当所处位置不在project 根目录, 例如 子目录下时, 如何找到所处project的tags文件.

vim 使用 option tags 来指定tags文件的寻找路径. 可以vim 内:echo &tags 来查看当前的 tags .值. 我自己的配置需求是:

  • 不进入到VCS(version control system), 如git, hg 等的代码库中. (可以通过设置ignore来避免, 但是每个project配置有点麻烦)
  • 当所处位置并非project根目录时, 可以找到当前project 的tags文件. 基于以上, 有如下配置:
  1. 生成Ctags时需要增加
    ctags -R --tag-relative -f ./git/tags .                                                                                     
    
    • --tag-relative 表示index文件路径是tags文件的相对位置
    • -f 表示生成的tags文件output路径

这样生成的tags文件在根目录.git/下, 其中index路径就会是tags的相对路径:

 Greetings    ../hello/greetings.go    /^func Greetings()...
  1. 配置vimrc
    # .vimrc
    let my_workspace = "/home/myname/workspace/"
    set tags=.git/tags;eval(my_workspace),.hg/tags;eval(my_workspace),.local/tags;eval(my_workspace) 
    # 可以:help file-searching 了解vim的路径search配置语法
    

此处配置tags值意思为, 从当前路径, 一直向上到 workspace/ 目录, 寻找 .git(hg/local)/tags 文件, 找到即为 tags文件. 这样就解决了当前位置处于project子目录下时, 无法定位到tags文件, 以及文件相对路径的问题.

2.3 自动化

我也不想每次更改code文件后, 再cd到根目录手动执行

ctags -R --tag-relative -f ./git/tags .                                                                                     

如果用的不是git(公司workflow) 可能是hg, 还需要更改命令 因此有需求:

  • 每次更改write操作时, 生成tags文件
  • 判断当前的文件夹结构, 指定tags文件的位置
  • 不能卡

解决方法: vimrc 中定义

# .vimrc
let my_workspace = "/home/myname/workspace/"
set tags=.git/tags;eval(my_workspace),.hg/tags;eval(my_workspace),.local/tags;eval(my_workspace) 

function ReloadTags()  
 let my_workspace = "/home/myname/workspace/"  
 let dirs = ['.git', '.hg', '.local'] # .local 为自定义习惯 
 let tags_dir = '.'  
 for dir in dirs  
   let founddir= finddir(dir, './;'..my_workspace, 1)  
   if strlen(founddir) > 0    
     let tags_dir = founddir  
   endif  
 endfor  
 exec 'silent! !ctags -R --tag-relative -f '..tags_dir..'/tags . &'  
endfunction

ReloadTags() 方法判断当前的project VCS环境, 在对应的VCS local目录下 (.git/.hg), 生成 tags文件. 其中运行的命令 ` exec ‘silent! !ctags -R –tag-relative -f ‘..tags_dir..’/tags . &silent 让vim不显示后续的输出, 末尾 &` 意为 background运行.

同时可以添加autocmd

autocmd BufWritePost *.go call ReloadTags()

go文件在Write操作后, 会运行ReloadTags()方法, 更新project目录下 .git/.hg 下的tags文件 也可以自己map keys 去手动 toggle.

4. 参考

https://kulkarniamit.github.io/whatwhyhow/howto/use-vim-ctags.html