挺多人问怎么在 Vim 中搭建 C/C++ 开发环境,我本来想找篇文章发给人家,结果网上看了一圈,要不就是内容太过陈旧,要不就是太过零碎,不成体系。2018 年了,Vim 8 发布已经一年半,各大 Linux 发行版和 Mac OS X自带的 Vim 都已经跟进到 8了,不少文章还在介绍一些十年前的老方法。于是有了这篇文章。
那如何高效的再 Vim 8 中开发 C/C++ 项目呢?假设你已经有一定 Vim 使用经验,并且折腾过 Vim 配置,能够相对舒适的在 Vim 中编写其他代码的时候,准备在 Vim 开始 C/C++ 项目开发,或者你已经用 Vim 编写了几年 C/C++ 代码,想要更进一步,让自己的工作更加顺畅的话,本文就是为你准备的:
插件管理
为什么把插件管理放在第一个来讲呢?这是比较基本的一个东西,如今 Vim 下熟练开发的人,基本上手都有 20-50 个插件,遥想十年前,Vim里常用的插件一只手都数得过来。过去我一直使用老牌的 Vundle 来管理插件,但是随着插件越来越多,更新越来越频繁,Vundle 这种每次更新就要好几分钟的东西实在是不堪重负了,在我逐步对 Vundle 失去耐心之后,我试用了 vim-plug ,用了两天以后就再也回不去 Vundle了,它支持全异步的插件安装,安装50个插件只需要一分钟不到的时间,这在 Vundle 下面根本不可想像的事情,插件更新也很快,不像原来每次更新都可以去喝杯茶去,最重要的是它支持插件延迟加载:
" 定义插件,默认用法,和 Vundle 的语法差不多
Plug 'junegunn/vim-easy-align'
Plug 'skywind3000/quickmenu.vim'
" 延迟按需加载,使用到命令的时候再加载或者打开对应文件类型才加载
Plug 'scrooloose/nerdtree', { 'on': 'NERDTreeToggle' }
Plug 'tpope/vim-fireplace', { 'for': 'clojure' }
" 确定插件仓库中的分支或者 tag
Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }
Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }
定义好插件以后一个::PlugInstall
命令就并行安装所有插件了,比 Vundle 快捷不少,关键是 vim-plug 只有单个文件,正好可以放在我 github 上的 vim 配置仓库中,每次需要更新 vim-plug 时只需要 :PlugUpgrade
,即可自我更新。
抛弃 Vundle 切换到 vim-plug 以后,不仅插件安装和更新快了一个数量级,大量的插件我都配置成了延迟加载,Vim 启动速度比 Vundle 时候提高了不少。使用 Vundle 的时候一旦插件数量超过30个,管理是一件很痛苦的事情,而用了 vim-plug 以后,50-60个插件都轻轻松松。
符号索引
现在有好多 ctags 的代替品,比如 gtags, etags 和 cquery。然而我并不排斥 ctags,因为他支持 50+ 种语言,没有任何一个符号索引工具有它支持的语言多。同时 Vim 和 ctags 集成的相当好,用它依赖最少,大量基础工作可以直接通过 ctags 进行,然而到现在为止,我就没见过几个人把 ctags 用对了的。
就连配置文件他们都没写对,正确的 ctags 配置应该是:
set tags=./.tags;,.tags
这里解释一下,首先我把 tag 文件的名字从 tags
换成了 .tags
,前面多加了一个点,这样即便放到项目中也不容易污染当前项目的文件,删除时也好删除,gitignore 也好写,默认忽略点开头的文件名即可。
前半部分 ./.tags;
代表在文件的所在目录下(不是 :pwd
返回的 Vim 当前目录)查找名字为 .tags
的符号文件,后面一个分号代表查找不到的话向上递归到父目录,直到找到 .tags
文件或者递归到了根目录还没找到,这样对于复杂工程很友好,源代码都是分布在不同子目录中,而只需要在项目顶层目录放一个 .tags
文件即可;逗号分隔的后半部分 .tags
是指同时在 Vim 的当前目录(:pwd
命令返回的目录,可以用 :cd ..
命令改变)下面查找 .tags
文件。
最后请更新你的 ctags,不要再使用老旧的 Exuberant Ctags,这货停止更新快十年了,请使用最新的 Universal CTags 代替之,它在 Exuberant Ctags 的基础上继续更新迭代了近十年,如今任然活跃的维护着,功能更强大,语言支持更多。
(注意最新版 universal ctags 调用时需要加一个 –output-format=e-ctags,输出格式才和老的 exuberant ctags 兼容否则会有 windows 下路径名等小问题)。
自动索引
过去写几行代码又需要运行一下 ctags 来生成索引,每次生成耗费不少时间。如今 Vim 8 下面自动异步生成 tags 的工具有很多,这里推荐最好的一个:vim-gutentags ,这个插件主要做两件事情:
确定文件所属的工程目录,即文件当前路径向上递归查找是否有 .git
, .svn
, .project
等标志性文件(可以自定义)来确定当前文档所属的工程目录。
检测同一个工程下面的文件改动,能会自动增量更新对应工程的 .tags
文件。每次改了几行不用全部重新生成,并且这个增量更新能够保证 .tags
文件的符号排序,方便 Vim 中用二分查找快速搜索符号。
vim-gutentags 需要简单配置一下:
" gutentags 搜索工程目录的标志,碰到这些文件/目录名就停止向上一级目录递归
let g:gutentags_project_root = ['.root', '.svn', '.git', '.hg', '.project']
" 所生成的数据文件的名称
let g:gutentags_ctags_tagfile = '.tags'
" 将自动生成的 tags 文件全部放入 ~/.cache/tags 目录中,避免污染工程目录
let s:vim_tags = expand('~/.cache/tags')
let g:gutentags_cache_dir = s:vim_tags
" 配置 ctags 的参数
let g:gutentags_ctags_extra_args = ['--fields=+niazS', '--extra=+q']
let g:gutentags_ctags_extra_args += ['--c++-kinds=+px']
let g:gutentags_ctags_extra_args += ['--c-kinds=+px']
有了上面的设置,你平时基本感觉不到 tags 文件的生成过程了,只要文件修改过,gutentags 都在后台为你默默打点是否需要更新数据文件,你根本不用管,还会帮你:
setlocal tags+=...
为当前文件添加上对应的 tags 文件的路劲而不影响其他文件。得益于 Vim 8 的异步机制,你可以任意随时使用 ctags 相关功能,并且数据库都是最新的。需要注意的是,gutentags 需要靠上面定义的 project_root 里的标志,判断文件所在的工程,如果一个文件没有托管在 .git/.svn 中,gutentags 找不到工程目录的话,就不会为该野文件生成 tags,这也很合理。想要避免的话,你可以在你的野文件目录中放一个名字为 .root
的空白文件,主动告诉 gutentags 这里就是工程目录。
最后啰嗦两句,少用 CTRL-]
直接在当前窗口里跳转到定义,多使用 CTRL-W ]
用新窗口打开并查看光标下符号的定义,或者 CTRL-W }
使用 preview 窗口预览光标下符号的定义。
我自己还写过不少关于 ctags 的 vimscript,例如在最下面命令行显示函数的原型而不用急着跳转,或者重复按 ALT+;
在 preview 窗口中轮流查看多个定义,不切走当前窗口,不会出一个很长的列表让你选择,有兴趣可以刨我的 vim dotfiles 。
编译运行
再 Vim 8 以前,编译和运行程序要么就让 vim 傻等着结束,不能做其他事情,要么切到一个新的终端下面去单独运行编译命令和执行命令,要么开个 tmux 左右切换。如今新版本的异步模式可以让这个流程更加简化,这里我们使用 AsyncRun 插件,简单设置下:
Plug 'skywind3000/asyncrun.vim'
" 自动打开 quickfix window ,高度为 6
let g:asyncrun_open = 6
" 任务结束时候响铃提醒
let g:asyncrun_bell = 1
" 设置 F10 打开/关闭 Quickfix 窗口
nnoremap <F10> :call asyncrun#quickfix_toggle(6)<cr>
该插件可以在后台运行 shell 命令,并且把结果输出到 quickfix 窗口:
最简单的编译单个文件,和 sublime 的默认 build system 差不多,我们定义 F9 为编译单文件:
Continue reading →