【写在前面】
以下的所有内容主要参照:
https://github.com/yangyangwithgnu/use_vim_as_ide 。
原blog作者写的非常用心,建议大家都去看看。(个人觉得通过这个blog大大提升了学习vim配置环境的效率,因此给作者捐了顿外卖的钱)这个随笔就是学习上述blog内容的辅助记录,并不能代替学习原著;把学习过程中遇到的一些坑列出来,初步做一下知识沉淀。
以前我个人的学习理念是“重剑无锋、大巧不工”,瞧不上这些快捷键之类的“奇技淫巧”。但是,打磨完了vim的开发环境后,我改变了这个看法:知识可以厚,但是顺手的工具必须要用心去一点点儿打磨锋利,提高工作效率的‘量变’会带来能力水平的‘质变’。
另,本人纯vim初学者,因此学习过程中肯定存在吃不透甚至错误的地方。一方面后续会自我不断改进,另一方面也非常欢迎大家拍砖指点。
【系统环境 & 内容提纲】
我用操作系统环境:
mac OS X Yosemite 10.10.3
脚本环境用的zsh:
搭建完成后的编辑环境如下:
可以看到:标签(函数变量)、工程目录、自动补全、状态栏提示、语法分析错误提示都集成在了里面。在实现了一个IDE的主要功能同时,VIM还可以让你最大程度自由定制个人的开发环境,快捷键等等。
全文的提纲汇总如下:
(一)重新编译安装vim7.4
(二)编辑界面显示美化插件
(三)安装pathogen插件管理工具
(四)语法高亮、代码缩进、代码折叠、强命名下的接口与实现快速切换
(五)基于标签的代码导航
(六)内容查找、内容替换、批量注释
(七)代码模板
(八)代码自动补全
(九)工程文件浏览 & 多文档编辑
(十)静态语法分析器
(十一)快速移动功能
下面对上述十一块内容逐一说明,各个部分有前后逻辑限制,改变顺序不保证每个功能都能实现正确。
【步入正题】
(一)重新编译安装vim7.4
我的mac上自带的vim版本是7.3,但有个极其重要的自动补全插件YouCompletetMe需要的vim版本是7.4,因此必须再装一套高版本vim。
vim7.4压缩包地址:ftp://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2
基本上按照blog中步骤执行如下命令(假设上述文件解压到了~/Download/vim74中):
cd ~/Downloads/vim74 ./configure --with-features=huge --enable-rubyinterp --enable-pythoninterp --with-python-config-dir=/usr/lib/python2.7/config/ --enable-perlinterp --enable-gui=gtk2 --enable-cscope --prefix=/usr --enable-luainterp make
报错:
简单分析一下error,应该是命名冲突了。google了http://zhouyichu.com/vim/Vim-in-Mac.html。
解决方案是在vim74/src/os_unix.h中加入#include <AvailabilityMacros.h>。Done。
再执行
sudo make install
输入root密码后,Done。
再输入vim,版本就升级到7.4了
===================================================================
注意./configure的参数中
--prefix=/usr
是vim安装路径的,如果不想替换系统原有的,就可以把vim安装到自定义的路径下。再使用alias命令,在.bashrc中加一句
alias vi="vim安装路径/vim"。
再注意,如果你跟我一样用了zsh这类的bash加强版,就需要在~/.zshrc中再加一句 source ~/.bashrc,如下图
这样你每次输入vi启动的才是vim7.4,否则zsh还是会默认调用原来系统自定义的vim。
===================================================================
另外,要想成功编译安装vim,还需要python-devel、python3-devel、ruby-devel、libX11-devel、gtk-devel、gtk2-devel、gtk3-devel、ncurses-devel等支持。(我在服务器端安装的时候,就遇到了ncurses没有的情况,这个后续再去解决)
(二)编辑界面显示美化插件
(由于pathogen无法管理界面美化插件,因此界面美化单独拎出来)
1. 界面配色
(1)建立.vim/colors/文件夹,并将三个配色的vim文件copy到.vim/colors/文件夹下面,如下图所示
(2).vimrc增加配置,使界面美化生效
在.vimrc中添加的命令如下
" 配色方案 set background=dark "colorscheme solarized colorscheme molokai "colorscheme phd
仅仅这样还不够,此时vim的编辑环境还是黑白的,还要让vim开启语法高亮并且能够应用配色文件。需要继续在.vimrc中添加下面的配置;
syntax enable
syntax on
这样,再次打开vim的编辑环境,语法高亮配色就体现出来了:
2. 添加辅助信息
为了防止编程过程中串行了,在设置一些辅助信息如下:
" 总是显示状态栏 set laststatus=2 " 显示光标当前位置 set ruler " 开启行号显示 set number " 高亮显示当前行/列 set cursorline set cursorcolumn " 高亮显示搜索结果 set hlsearch
" 禁止折行 set nowrap
添加了这些信息后,可以看到vim编辑界面如下(光标、高亮、行号、状态栏都有了):
经过上述的配置,现在vim的编辑环境有了颜色,有了行号。但仅仅是一个带语法颜色提示的txt,其他强大的功能还要借助于各种插件。
要想管理好各类插件,就要使用下面一节介绍的pathogen插件管理工具。
(三)安装pathogen插件管理工具
用pathogen来管理vim插件的特色是:.vim/bundle/下每个插件有独立的文件夹,插件之间几乎是完全解耦。
这样带来的好处是:在添加、删除某个插件时,几乎不影响其他的插件。
1. 安装pathogen
pathogen虽说是管理vim插件的,但是它自身就是一个插件。安装过程如下。
(1)在~/.vim路径下执行如下命令
(2)下载pathogen.vim到autoload
https://raw.githubusercontent.com/tpope/vim-pathogen/master/autoload/pathogen.vim
执行如下命令:
curl -o pathogen.vim https://raw.githubusercontent.com/tpope/vim-pathogen/master/autoload/pathogen.vim
如下:
(3)在.vimrc中增加配置信息
一是告诉vim,pathogen在哪;二是告诉vim执行pathogen的infect命令
" 将 pathogen 自身也置于独立目录中,需指定其路径 runtime bundle/pathogen/autoload/pathogen.vim " 运行 pathogen execute pathogen#infect()
2. 用pathogen工具引入Powerline插件
以状态栏增强工具Powerline插件的安装过程为例,体验一下pathogen管理插件的便捷。
简单来说用pathogen引入插件分两步:
(1)进入.vim/bundle/路径下:如果有git管理的,可以直接“git clone 插件所在的git的url”;如果没有git,就建立一个新的文件夹,再下载插件的zip包,解压到新建的这个文件夹下。
(2)在.vimrc中针对该插件的使用进行配置
下面看看Powerline如何搞:
Powerline插件的链接:https://github.com/Lokaltog/vim-powerline
(1)进入.vim/bundle/路径下,执行如下命令:
(2)原教程还推荐了个设置:
" 设置状态栏主题风格 let g:Powerline_colorscheme='solarized256'
但是感觉不用这个设置,Powerline也表现的不错,所以我也没有在.vimrc中配置。效果如下图所示:
红框框里面就是Powerline强化后的状态栏。而且在normal visual insert不同的状态下可以显示不同的功能,可以自行试试。
(四)语法高亮、代码缩进、代码折叠、强命名下的接口与实现快速切换
1. 语法高亮
由于目标是打造c/c++的vim环境,因此为了支持c++ STL的元素高亮。可以借助stl.vim插件补强vim的c++语法高亮功能。
(1)新建~/.vim/bunlde/STL-Syntax/after/syntax/cpp/文件夹
(2)下载stl.vim到~/.vim/bunlde/STL-Syntax/after/syntax/cpp/文件夹下
重启vim,我们再打开一个cpp文件:
可以看到unordered_map这个关键字支持变色了(但是也看到了有些弊端,比如line22的begin和end都是一般的变量,也被识别为了关键字,这个后续希望能找到改进的方法)
同时,我们看到上面的一些代码在缩进管理上是有问题的(line14 line22);这对c++还好,但是python这类对缩进严格要求的语言就不行。至此,引出了代码缩进的设置。
2. 代码缩进
vim中有两种缩进表示法,一类是用1个制表符' ',一类是用多个空格' '。
缩进可视化插件对两类缩进显示的方式不同:' '只能显示为粗块,而' '可以显示伟细条。
在.vimrc中增加如下的配置:
" 自适应不同语言的智能缩进 filetype indent on " 将制表符扩展为空格 set expandtab " 设置编辑时制表符占用空格数 set tabstop=4 " 设置格式化时制表符占用空格数 set shiftwidth=4 " 让 vim 把连续数量的空格视为一个制表符 set softtabstop=4
设置完成,重启vim后,我们再用vim打开的上面的cpp文件:
可以看到,代码老老实实的按照规矩缩进回去了。
阅读代码的时候,经常会遇到for while if 等等包含的代码行数比较多的情况。因此需要讲相同缩进的代码关联起来,便于阅读。
首先需要引进vim-indent-guides这个插件,用pathogen引入,不再赘述。(由于在我的mac上,一直觉得这个插件的使用效果一般,就不上图了)
3. 代码折叠
需要在.vimrc中配置如下的内容:
" 代码折叠 "set foldmethod=indent "基于缩进的代码折叠 set foldmethod=syntax "基于语法的代码折叠 " 启动 vim 时不折叠代码 set nofoldenable
还是打开上述cpp文件,均在NORMAL模式下进行操作。
命令:za
效果:全都折叠上了
命令:zr
效果:每次输入zr打开一层折叠
命令:zR
效果:把所有折叠都打开了(不上图了)
命令:za
效果:把一层代码折上
命令:za
效果:把当前折上的代码代开(这里连续使用za可以比较方便的折叠、打开一个代码段)
折叠代码部分的结论就是:za命令最好用;想折哪段就za,再想打开这段代码再za(个人感觉za按键位置比较舒服,所以一个za命令就足够了)
4.强命名下的接口与实现快速切换
习惯上c++中实现和接口是分开的:如my_class.cpp存放实现,my_class.h存放接口,并且二者在同一个目录下。如下:
目的就是实现同名的.cpp和.h跳转。
先安装插件:https://github.com/vim-scripts/a.vim
再在.vimrc中如下配置:
" *.cpp 和 *.h 间切换 nmap <Leader>ch :A<CR> " 子窗口中显示 *.cpp 或 *.h nmap <Leader>sch :AS<CR>
用vim打开my_class.cpp文件,不断输入“;ch”就可以在my_class.cpp和my_class.h中切换。
注意,这里必须要求.cpp和.h文件在同一个文件夹中并且命名相同;因此这种强命名条件使得切换的作用非常有限。这个插件现在并不太推荐,至于有多大作用还得看工作中的场景吧。
(五)基于标签的代码导航
标签(tag)的概念非常关键,原教程中评价标签是现代IDE的基石之一,深以为然。
什么是标签?
代码中的类、结构、类成员、函数、对象、宏这些元素就是标签,每个标签有它自己的名字、定义、类型、所在文件中的行的位置、所在文件的路径等属性。
编译环节之一就是提取标签,但由于编译器并未把生成的标签输出到文本中,后来出现了专门用于生成标签的工具Exuberant Ctags(就是现在常说的ctags)。
如果你的机器上没有ctags,那么请去这里下载http://ctags.sourceforge.net,并参照教程(http://blog.csdn.net/duguteng/article/details/7412652)去安装。
ctags与vim有什么关系?
其实ctags与vim完全就是两个东西,二者原本各司其职,但基于标签的代码导航把二者联系在了一起。
要想让vim支持基于标签的导航,大体上需要完成如下两件事情:
(1)给代码文件生成tags(先得生成标签原料)
(2)在.vimrc中增加些配置(让vim知道上哪去找标签,以及一些辅助配置)
下面通过实际的例子来体会下ctags与vim的结合。
####################
2015.10.14更新内容:
在server上编辑c程序的时候,处理了两个与标签有关的问题。
先说两个问题,再统一记录解决方案。
(1)一个遗留问题:为什么在.vimrc中引入系统标签(/usr/include),vim就变得超级慢呢?
原因是,当时为了图省事,直接在/usr/include路径下用“ctags -R *”来生成的标签。注意,-R是递归生成标签。我们先来看看/usr/include路径下有多少文件:
这么多系统文件(甚至有些不是系统文件)并不需要都生成标签,我们看都生成标签,产生的标签有多少:
总共19W个标签。对于这种系统库函数的标签生成策略,最好不要是这种暴力的方式,很多根本用到的内容的标签都生成了,结果就是降低了检索速率。
(2)发现一个新问题:为什么即使我用了ctag -R *这种暴力生成标签的策略,还是有些函数包含不进来呢?
比如,我已经include进来了signal.h,并且用了ycm的标签补全策略,但是还是不能补全出来最基本的signal函数。
两个问题的解决方法都可以在下面的blog中找到:http://blog.csdn.net/zklth/article/details/7027798
(1)生成标签的时候,不能直接ctag -R,而是需要哪个文件夹下面内容的标签,就把哪个文件夹包含进来。
(2)ctags默认的生成标签策略,是不会考虑__THROW、__attribute_pure__、__nonnull __attribute__这些内容的,因此在执行ctags命令的时候,必须把这些内容用-I给包含进来。
具体的解决方法已经ctags命令主要参考blog中的资料如下:
重新用ctags生成标签,新标签文件放在.vim目录下。最后signal可以看到补全出来了:
今天的这个补全问题,并没有拖太长的时间。想起几天前学习ctags的时候,本想糊弄糊弄过去算了,但还是本着认真些的态度研究了一下。
解决今天这个问题的时候,就得到了回报了。有些知识并不是特别复杂,但是多想一些就会给后面解决问题带来很大的便利。
插播结束。
####################
ctags的应用实例(参照原教程)。
(1)建立test_tags文件夹,再建立test_tags/lib/子文件夹。
(2)在test_tags目录下,建立main.cpp
(3)在test_tags/lib目录下,建立my_class.h, my_class.cpp
回到test_tags目录下,执行如下命令(先执行命令看结果,回过头来再分析):
ctags -R --c++-kinds=+p+l+x+c+d+e+f+g+m+n+s+t+u+v --fields=+liaS --extra=+q --language-force=c++
这时候,在test_tags目录下,多了一个tags文件:
打开tags文件,执行:set list命令:
每一行代表一条标签记录,且一行中各个字段间用 分割:
标签名 标签所在的文件路径 标签所在的行内容 标签类型 标签的语言 ...
这里的标签类型比较抽象,都是m l f p 这些单个的字母,都表示什么意思呢?
可以执行如下命令查看c++标签类型的含义:
ctags --list-kinds=c++
结果如下:
这时候,我们就可以知道生成tags的命令中“--c++-kinds=”的含义了:就是告诉ctags要生成哪些元素的标签。
现在体验一下标签导航的功能。
vim打开main.cpp,然后:set tags+=你自己的路径/test_tags/tags
光标移到one.printMsg()的printMsy()上,输入g],看效果:
输入需要的标签的序号,比如5,就跳转到了lib/my_class.cpp文件中,并且光标锁定在line4的位置上。
如果想返回到原来的位置,ctrl + o即可。
上述的方法作为替代手工查找已经便捷了很多;但是正如同上面的例子,有时候经常得在同名的标签中选择。
一种替代的方式是,输入某种快捷键,每次遍历一个标签。
现在.vimrc中做如下配置:
" 正向遍历同名标签 nmap <Leader>tn :tnext<CR> " 反向遍历同名标签 nmap <Leader>tp :tprevious<CR>
再用vim打开main.cpp文件;光标停留在printMsg上面:
ctrl + ] (将printMsg设定为要标签导航的目标)
;tn ;tp(向后/前找下一个同名的标签导航的位置)
ctrl + o (回到上一个标签)
ctrl + t (回到调用的位置)
通过这种方式,可以不间断地遍历同名标签,大体上还是优于屏幕上选数字的方法。
(如果是工程化的开发,还可以用indexer这个vim插件来周期性针对工程文件自动生成标签文件,并通知vim引入该标签文件;但目前缺少应用场景,因此先不去讨论。)
tagbar插件。
以上内容,可以做到单个标签的导航。如果要看正在编辑的代码的全部标签内容呢?最好把标签分门别类列出来,类似Eclipse里面那种导航bar的效果?
这里可以引入tagbar的插件。
(1)先安装:https://github.com/majutsushi/tagbar
(2)再在.vimrc中做配置:
" 设置 tagbar 子窗口的位置出现在主编辑区的左边 let tagbar_left=1 " 设置显示/隐藏标签列表子窗口的快捷键。速记:tag list nnoremap <Leader>tl :TagbarToggle<CR> " 设置标签子窗口的宽度 let tagbar_width=32 " tagbar 子窗口中不显示冗余帮助信息 let g:tagbar_compact=1
再用vim打开上面的main.cpp文件,并执行;tl命令,效果如下图:
可以看到,在默认情况下tagbar会列出来当前编辑文件的function和variable两类的标签。
如果要显示更多类别的标签,就需要在.vimrc中加入如下配置:
" 设置 ctags 对哪些代码元素生成标签 let g:tagbar_type_cpp = { 'kinds' : [ 'd:macros:1', 'g:enums', 't:typedefs:0:0', 'e:enumerators:0:0', 'n:namespaces', 'c:classes', 's:structs', 'u:unions', 'f:functions', 'm:members:0:0', 'v:global:0:0', 'x:external:0:0', 'l:local:0:0' ], 'sro' : '::', 'kind2scope' : { 'g' : 'enum', 'n' : 'namespace', 'c' : 'class', 's' : 'struct', 'u' : 'union' }, 'scope2kind' : { 'enum' : 'g', 'namespace' : 'n', 'class' : 'c', 'struct' : 's', 'union' : 'u' } }
看配置项中的名称也能分析出个大概,不具体深究了。再看修改tagbar配置后的效果图:
看到tagbar中的选项丰富了很多(这里只需要记住,以后要是希望tagbar中显示不同的内容,在.vimrc中配置tagbar_type_XXX这个就好了)
标签→源码:选中tagbar中的标签,回车就定位到了源码
源码→标签:光标在源码上停一会儿,对应的tagbar中的标签就高亮了(这算是一个不容易发现的彩蛋)
对tagbar中的标签排序:默认是按照每一类的字母顺序排序;还可以按照标签出现的先后顺序排序;切换的按键是s
(六)内容查找、内容替换、批量注释
1. 内容查找
我遇到的内容查找分以下几种:
(1)在正在编辑的文件中查找。之前自己在vim中查找都用"/"这个方法,效果就是把正在编辑的文件中含有该关键字的都高亮显示。
(2)在文件夹中查找。如果分析工程代码的时候,可能需要查找的关键字在多个文件中,我都是用grep(还有ack可用,但是我没用过)
以上两种情况综合起来,内容查找不仅要求能够标出来关键字,而且还需要关键字所在文件的信息(不同文件含有关键字的语句相同)。
可以用ctrlsf插件(https://github.com/dyng/ctrlsf.vim)来完成这个事情。
(1)ctrlsf插件依赖于ack支持,因此还需要先装一下ack(参照http://www.jianshu.com/p/2f1c140c7eb8)。
(2)在.vimrc中给ctrlsf插件启动设置快捷键
" 使用 ctrlsf.vim 插件在工程内全局查找光标所在关键字,设置快捷键。快捷键速记法:search in project nnoremap <Leader>sp :CtrlSF<CR>
之后再打开main.cpp文件,光标停留在printMsg上,再输入";sp",效果如图:
这个查找功能简直碉堡了,迅速显示出了关键字的上下文信息。
在左边选择需要的关键字上下文,再按p就显示了所在的源码,如下图:
如果想回到原来编辑的代码,按q就可以了。这样查找功能就很好的集成到了vim中,个人感觉可以与标签导航结合使用来查看代码逻辑。
2. 内容替换
这个在开发工作中更常见了:比如需要对某个变量重新命名,尤其需要把所有与这个变量关联的内容(可能在不同文件中)都替换掉。
原教程中并没有提供插件来做这个事情,而是作者自己写了一份vim的脚本函数;由于不是标准化插件,因此先不做配置了。
但是同学在面试中说竟遇到了面试官问vim中如何替换字符的问题,因此掌握一下基本的操作还是必要的。
可以参照这个blog(http://blog.sciencenet.cn/blog-724080-725117.html)学习。
大体来说,vim的内容替换命令的模板伟“:n,ms/string1/string2/g”
(1)n,m:替换发生作用的行(如果是%s就代表所有行,有其他替换要求可以再具体查阅)
(2)string1:目标字符串
(3)string2:要把目标字符串替换成的字符串
(4)g:如果有g就是代表每行所有的匹配字符串;如果没有g就是每行匹配的第一个字符串
3. 批量注释
由于是c/c++的开发,因此引入nerdcommenter这个插件(https://github.com/scrooloose/nerdcommenter)。
还需要在.vimrc中加上一句话“filetype plugin on”。
整行注释。
用vim打开main.cpp。对前四行添加注释。
(1)“V”(注意是大写的V,这样进入的是选择全行的模式,如果是v则不是全行)
(2)“jjj” 选中前四行
(3)";cc" 添加注释
效果如图:
重复(1)(2),将(3)改为“;cu”则消除了注释
部分注释。
比如要注释掉printMsg函数括号中的参数。
光标停留在括号内,执行“vi)”,选中括号中的全部内容,如图:
再执行";cc",效果如图:
这种部分注释的也很常用,如果快捷键使用熟练可以省去不少时间。
另外,对于大中小括号、双引号、单引号等各种号中变量的删除、复制、选择都比较常用。可以参考(http://www.linuxsong.org/2010/09/vim-quick-select-copy-delete/)学习。
(七)代码模板
所谓的代码模板就是常用的 if() while() for()等等固定的套路。
安装UltiSnips插件可以实现这个功能(https://github.com/SirVer/ultisnips)。
有了UltiSnips还不够,还需要告诉UltiSnips按照什么样的模板来补全。
这里就用了原文作者写的一个模板cpp.snippets(由于比较长,就不放上来了)。
把这个模板放在.vim/bundle/ultisnips/mysnippets/cpp.snippets这里。
然后还需要配置下.vimrc文件:
" UltiSnips的tag键与YCM冲突 需要重新设定 let g:UltiSnipsExpandTrigger="<leader><tab>" let g:UltiSnipsJumpForwardTrigger="<leader><tab>" let g:UltiSnipsJumpBackwardTrigger="<leader><s-tab>" " 告诉ultisnips模板文件在哪 let g:UltiSnipsSnippetDirectories=["mysnippets"]
需要注意的是,原文作者提供的cpp.snippets对同一个关键字的不同模板给出了不同的触发关键字。
什么意思呢?我截取cpp.snippets中的一段如下:
# 通过迭代器遍历容器(可读写) snippet for for (auto ${2:iter} = ${1:c}.begin(); ${3:$2} != $1.end(); ${4:++iter}) { ${5:TODO} } endsnippet # 通过迭代器遍历容器(只读) snippet cfor for (auto ${2:citer} = ${1:c}.cbegin(); ${3:$2} != $1.cend(); ${4:++citer}) { ${5:TODO} } endsnippet # 通过下标遍历容器 snippet For for (auto ${2:i} = 0; $2 != ${1}.size(); ${3:++}$2) { ${4:TODO} } endsnippet
for、cfor、For分别触发不同模板,如下图:
以此类推,可以多学习一下原作者的模板快捷键。
也可以设定自己的快捷键。
以vector为例分析,利用代码模板怎么快速定义一个"vector<int> v_int_test"。
先看cpp.snippets中的vector段落如下:
(1)在INSERT模式下输入vec,再输入“;Tab”,默认就出来"vector<char> v;",并且光标停留在"<>"中内容被选中,如图:
(2)此时,由于char都处于选中状态,因此直接输入int就变成如下的状态:
(3)此时,再输入";Tab",则光标跳到了v的后面了,如图:
(4)这时,再输入"_int_test",最后结果如图:
整个过程非常连贯。通过这个例子,了解了自定义snippets的关键三点:
a. 触发。科学定义模板补全的触发前缀(这个例子中触发vector的前缀是vec)
a. 定义骨架。通过${序号}来表示光标跳转的顺序(为了防止与后面自动补全的Tab冲突,在这里设置;Tab为模板补全的快捷键,上面已经说过了)
b. 手动填肉。通过";Tab"完成跳转。
定义的snippets可以最大化发挥代码模板补全的作用,省去了非常多的时间,达到与IDE相同的效果。这算是我的一个痛点,让我决定投身vim族。
#########################
2015.10.14补充:
由于在.vimrc中配置了“let g:UltiSnipsSnippetDirectories=["mysnippets"]”
因此,ultisnippets插件会在.vim/bundle/ultisnips/mysnippets这个路径下搜寻各类文件的代码模板信息。
如图:
如果编辑*.cpp的文件,ultisnippets就加载cpp.snippets中的模板;如果编辑*.c文件,ultisnippets就加载c.snippets文件中的模板。
#########################
(八)代码自动补全
代码补全的方式有两种:基于标签的补全 & 基于语义的补全
1. 基于标签的补全
OmniCppComplete + SuperTab 两个插件:
https://github.com/vim-scripts/OmniCppComplete
https://github.com/vim-scripts/SuperTab
先在/usr/include和/usr/include/c++/4.8.4中执行ctags命令,生成c++和system的标签,并引入.vimrc。
需要在.vimrc中配置如下:
效果还可以:如下图:
有时候需要看的信息比较多,可以修改“set completeopt=”这个参数,获得效果如下:
这种方式显示的信息,都是执行ctags产生的标签信息。有的时候信息多了还杂,所以往往就不单独列出来一个buffer中显示信息了。
上面这种方式主要是我个人补充的,原教程主要希望使用YouCompleteMe这个终极补全神器,因此Omni的补全方式不细说了。
2. 基于语义的补全
这里只需要记住一个终极补全神器YouCompleteMe(YCM)插件(https://github.com/Valloric/YouCompleteMe)。
YCM插件安装。
这个插件需要编译安装,过程比较复杂,主要四个安装步骤:
(1)拉下来YCM的源码包以及相关依赖。
cd ~/.vim/bundle/ git clone https://github.com/Valloric/YouCompleteMe.git cd YouCompleteMe/ git submodule update --init --recursive
效果如图:
(2)下载libclang。(YCM后端调用libclang)
这里下载的是作者推荐的http://llvm.org/releases/download.html中的预编译二进制文件(Pre-built Binaries)。
解压后,将文件夹重命名为"clang+llvm"。存放的路径如下:
(3)编译YCM共享库(用到了上一步下载的clang+llvm中的标准liblang)
执行如下命令:
cd /opt/local sudo mkdir ycm_build cd ycm_build sudo cmake -G "Unix Makefiles" -DPATH_TO_LLVM_ROOT=/opt/local/clang+llvm . ~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp sudo make ycm_support_libs
一个是看终端是否返回全部完成;另一个去看是否生成了三个文件:
(4)配置.ycm_extra_conf.py文件
YCM支持每个工程文件配置不同的conf文件(这样的好处是不用每次新建工程就改.vimrc)。
我的配置文件如下:
1 import os 2 import ycm_core 3 from clang_helpers import PrepareClangFlags 4 5 # Set this to the absolute path to the folder (NOT the file!) containing the 6 # compile_commands.json file to use that instead of 'flags'. See here for 7 # more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html 8 # Most projects will NOT need to set this to anything; you can just change the 9 # 'flags' list of compilation flags. Notice that YCM itself uses that approach. 10 compilation_database_folder = '' 11 12 # These are the compilation flags that will be used in case there's no 13 # compilation database set. 14 flags = [ 15 '-Wall', 16 '-std=c++11', 17 '-stdlib=libc++', 18 '-x', 19 'c++', 20 '-I', 21 '.', 22 '-isystem', 23 '/usr/include/', 24 '-isystem', 25 '/usr/lib/', 26 '-I/opt/local/clang+llvm/include/c++/v1' 27 ] 28 29 if compilation_database_folder: 30 database = ycm_core.CompilationDatabase(compilation_database_folder) 31 else: 32 database = None 33 34 35 def DirectoryOfThisScript(): 36 return os.path.dirname(os.path.abspath(__file__)) 37 38 39 def MakeRelativePathsInFlagsAbsolute(flags, working_directory): 40 if not working_directory: 41 return flags 42 new_flags = [] 43 make_next_absolute = False 44 path_flags = ['-isystem', '-I', '-iquote', '--sysroot='] 45 for flag in flags: 46 new_flag = flag 47 48 if make_next_absolute: 49 make_next_absolute = False 50 if not flag.startswith('/'): 51 new_flag = os.path.join(working_directory, flag) 52 53 for path_flag in path_flags: 54 if flag == path_flag: 55 make_next_absolute = True 56 break 57 58 if flag.startswith(path_flag): 59 path = flag[len(path_flag):] 60 new_flag = path_flag + os.path.join(working_directory, path) 61 break 62 63 if new_flag: 64 new_flags.append(new_flag) 65 return new_flags 66 67 68 def FlagsForFile(filename): 69 if database: 70 # Bear in mind that compilation_info.compiler_flags_ does NOT return a 71 # python list, but a "list-like" StringVec object 72 compilation_info = database.GetCompilationInfoForFile(filename) 73 final_flags = PrepareClangFlags( 74 MakeRelativePathsInFlagsAbsolute( 75 compilation_info.compiler_flags_, 76 compilation_info.compiler_working_dir_), 77 filename) 78 else: 79 relative_to = DirectoryOfThisScript() 80 final_flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to) 81 82 return { 83 'flags': final_flags, 84 'do_cache': True}
其中line15~line26是重点的配置项,需要改成自己的路径。
另请注意"-isystem","/usr/include"是一对配置:"-isystem"是配置项参数名称;"/usr/include"是配置项参数内容(这个不要跳坑了)
插件使用配置。
YCM集成了各种补全引擎:语义补全引擎、标签补全引擎、OmniCppComplete补全引擎、其他补全引擎(如路径补全)
主要集中在.vimrc中,先列出全部可能的配置:
" YCM 补全菜单配色 " 补全功能在注释中同样有效 let g:ycm_complete_in_comments=1 " 允许 vim 加载 .ycm_extra_conf.py 文件,不再提示 let g:ycm_confirm_extra_conf=0 " 开启 YCM 标签补全引擎 let g:ycm_collect_identifiers_from_tags_files=1 " 引入 C++ 标准库tags set tags+=/usr/include/c++/4.8.4/stdcpp.tags " YCM 集成 OmniCppComplete 补全引擎,设置其快捷键 inoremap <leader>; <C-x><C-o> " 补全内容不以分割子窗口形式出现,只显示补全列表 set completeopt-=preview " 从第一个键入字符就开始罗列匹配项 let g:ycm_min_num_of_chars_for_completion=1 " 禁止缓存匹配项,每次都重新生成匹配项 let g:ycm_cache_omnifunc=0 " 语法关键字补全 let g:ycm_seed_identifiers_with_syntax=1
(1)语义补全:这个是默认的补全策略。就是键入字符,补全候选项就列出来了。然后再tab选择。不多说。
(2)标签补全。标签补全已经说过了:先生成标签,再.vimrc中将标签路径加进来,再开启YCM标签引擎。但是标签引擎的开启,会极大的影响自动补全反应速度,有时候你输入的快一些补全根本反应不过来。要注意引入标签路径的数量。(我个人目前是不开标签引擎)
(3)OmniCppComplete补全。这里个也满常用的,比如linux系统开发时最常用的fork()函数,YCM的默认语义补全引擎识别不出来。如下图:
可以看到根本没有fork这个选项,即使有#include <unistd.h>。这是因为YCM默认的随键补全策略是不管系统头文件函数的。
这时候可以告诉YCM:“我发现默认的随键补全不好使了,请给我换成OmniCppComplete补全引擎”。因为,Omni可以做到只要#include中包含了头文件,就可以补全出来。这里用一个比较巧的触发Omni补全的快捷方式,在.vimrc中设置“inoremap <leader>; <C-x><C-o>”,即";;"就可以触发Omni补全引擎。效果如图:
可以看到,卡卡瞬间第一个就是fork。而且,这种补全引擎切换是暂时的,一旦这个fork补全过之后又恢复到最常用的默认语义补全了。
=======================================================
如果非要钻牛角尖,硬要YCM随键补全出来fork,到底能不能行呢?是可以的。
在.vimrc中引入系统标签sys.tags,再开启标签引擎就可以做到,如下图:
为什么原文作者不推荐这种形式呢?我猜原因可能有两点:
一、这需要引入系统标签sys.tag,这个文件不小;引入sys.tags会降低vim的补全反应效率,很可能跟不上键入字符的速度了
二、随键补全一次引入的补全候选项太多了。卡卡列出来一大堆,选的时候可能会影响效率。
=======================================================
(4)路径补全。感觉这个功能稍微弱一些,反应有些慢。不过还是聊胜于无,看图:
(九)工程文件浏览 & 多文档编辑
1. 工程文件浏览
这里用 NERDtree (https://github.com/scrooloose/nerdtree)插件可以查看文件列表。
(1)在bundle下安装插件。
(2)在.vimrc中加配置:
" 使用 NERDTree 插件查看工程文件。设置快捷键,速记:file list nmap <Leader>fl :NERDTreeToggle<CR> " 设置NERDTree子窗口宽度 let NERDTreeWinSize=32 " 设置NERDTree子窗口位置 let NERDTreeWinPos="right" " 显示隐藏文件 let NERDTreeShowHidden=1 " NERDTree 子窗口中不显示冗余帮助信息 let NERDTreeMinimalUI=1 " 删除文件时自动删除文件对应 buffer let NERDTreeAutoDeleteBuffer=1
效果如图所示:
光标停留在tree那个window中:
'r' : 刷新tree
'I' : 切换是否显示隐藏文件
2. 多文档编辑
这里引入MiniBufExploer这个插件(https://github.com/fholgado/minibufexpl.vim)。个人觉得这个插件的默认配置就OK了,结合NERDTree插件可以实现比较好的多文档编辑。
这里注意几个快捷键:
当光标停留在某个buffer上的时候
‘d’ : 关闭这个buffer
‘v’ : 垂直分屏
‘s’ : 水平分屏
当处于Normal模式的时候:
‘:bn’ : 跳到下一个buffer
‘:bp’ : 跳到前一个buffer
‘:bdn’ : 关闭序号为n的buffer
效果如下图:
至此,既有左侧的标签列表(函数、变量、类等),又有右侧的工程文件列表,终于有点儿IDE的样子了,再加上自动补全,终于有些IDE的样子了。
(十)静态语法分析器
这里主要用到Syntastic插件(https://github.com/scrooloose/syntastic)
由于YCM中集成了这个插件,因此基本不用配置了。
这里要注意的事情是语法分析有一定延迟:即错的不能马上显示出来,得过一会儿;改对了也不能马上消除错误提示,得等一下。
效果大概入下图:
在line18写了有错误的语句;':w'写入后,光标下移;卡卡就出现了错误提示:
(1)所在行有特殊标记
(2)所在错误位置也变色
(3)状态栏下面还有错误提示
又多了一项IDE的功能。
(十一)快速移动功能
在Normal模式下:
(1)hjkl自不必说,是常规四个方向移动
(2)w和e是按单词移动
这里介绍一个easymotion插件(https://github.com/easymotion/vim-easymotion)
安装之后,几乎不用配置。(这个教程easymotion不错:http://www.wklken.me/posts/2015/06/07/vim-plugin-easymotion.html)
这个插件的功能用一句话说就是:“把满足条件的位置用 [A~Za~z] 间的标签字符标出来,找到你想去的位置再键入对应标签字符即可快速到达”
(1)向后找 & 跳。
<leader><leader>f :从当前光标位置向后找匹配的单个字符,并用字母标记。
(2)全局找 & 跳。
<leader><leader>s : 从全局找匹配的单个字符,并用字母标记
(3)行跳转
<leader><leader>j/k : 向下/上标出所有行,并用字母标记。输入";;j",效果如图:
输入字母h后,结果如图:
easymotion提供的这种标记跳转方式,可以直接把需要跳到的位置与字母关联上,不用再去输入数字或先搜索在不断的移动去定位。
【写在后面】
这篇随笔完成时,相同的.vim环境大概搭建了两遍:
第一遍是纯初学摸索,很多东西不太清楚;跳坑的时候甚至想放弃算了,反正还有sublime,再不行还有VS大法
第二遍是为了写随笔,索性把第一遍搭建好的vim环境给除去了,step by step再把环境再搭建出来;觉得vim不仅能实现ide的相同功能,更重要的是很多东西可以自己定制,真正做到个性化的编辑环境,大大提升工作效率
完成了这篇随笔的过程,是知识沉淀的过程,是逻辑思路重新整理的过程:
第一遍只是填鸭式照着去做,能做出来就行了,觉得方便就好
第二遍开始思考为什么要有这样的功能,同样的编辑功能用不同的方法去达到各有什么利弊,以及体会各种插件设计者的巧妙思路
一开始真心是不愿意去碰vim,但不愿意碰也得碰,碰不碰最后还是碰了(前一篇日志有说明原因)。我不会去想什么vim哲学之类的事情,只是觉得既然决定要碰vim,就要一点点积累把工具打磨好。
最后希望这篇随笔能对大家也有一点点帮助。