因为实在实在受不鸟ctags了: 代码中有很多类具有相同名字的变量, 比如 "id". 当我想看下当前的这个 "id" 到底是哪个id的时候, 可怕的事情粗线了, , , 一口气出来了10几个备选. 而且, 不能跳转到局部变量, 补全也不准确 , , , , , , 好吧, 我终于下定决心来折腾一下YouCompleteMe(YCM).

  先简要介绍下楼主的开发环境: 一台能连外网的windows pc, 一台内网服务器开发机(64位的redhat6, 这个服务器并不能连外网). 平时搬砖都是用pc ssh 到服务器上, 直接在服务器上用vim码砖.

  网上介绍YCM安装的攻略也有不少, 但是他们并不适合我当前的处境. 于是决定啃一啃官网文档. 毕竟这个总是最准确和详细的. 官网参见: https://github.com/Valloric/YouCompleteMe#commands

  虽然官网文档有一节说的是 "快速安装" , 但是作者说 "并不一定适用于你的环境" . 我并不认为我的环境上可以执行成功, 干脆就按照Full Installation Guide操作了.

  第一步, 升级vim.

  YCM要求vim版本为7.3以上, 而我的开发机的版本才是7.0. orz~~. 另外, 也不要指望0疼的内网yum源. 另外, 我也不想从源码编译vim(虽然YCM的作者说了一句"Don't worry, it's easy"~~). 于是开始在goooogle上找vim的rpm包. 那么问题来了, 一共需要几个rpm包呢? 在开发机上用这个命令: rpm -qa | grep vim, 发现有四个包:  vim-common, vim-enhanced, vim-minimal, vim-filesystem.

  幸好公司里面可以直接上goooogle, 直接google "vim  rpm" 找到了这么一个网站: https://www.rpmfind.net/linux/rpm2html/search.php. 然后在这里面挨个去搜这几个包, 这个网站会把所有发行版的rpm包都列出来, 对于我的64位redhat6, 要选择el6_x86_64后缀的包. 将他们下载到pc上, 再rz命令传到开发机上, 安装之. 安装之前要先卸载掉之前的vim包. 然后就ok了, 的到了一个7.4版本的vim. (过程并不是轻描淡写的两句话就搞定了, 至少我把所谓的 "Unix哲学" fuck了一百遍, , , ,).

  

  第二步, 下载YCM.

  网上所有的攻略都说, 通过Vundle来下载YCM. 然而我这是内网机器啊啊啊啊, 并没法使用Vundle. 我也尝试通过在pc上搭了个http代理, 然后让开发机通过pc的代理来访问外网. 然而github上的链接都是https~~~~~Orz, 日了狗了. 仔细观察了下YCM的目录结构, 发现也就是和一个普通的vim插件差不多嘛~~于是在pc上git clone, 然后打个zip包, 再rz到开发机上. 等等, 文档上有一句这样的话: If you don't install YCM with Vundle, make sure you have run git submodule update --init --recursive after checking out the YCM repository (Vundle will do this for you) to fetch YCM's dependencies. 好吧, 先执行下git submodule update --init --recursive, 然后再打包传过去. 发现这个命令执行下去后, 多出来好多东西. 这些东西都是后面用的上的.

  将zip包解压到~/.vim/bundle/YouCompleteMe目录.

  

  第三步, 安装clang.

  YCM也是基于clang的补全. 安装clang肯定是必要的. 用类似于安装vim的方法, 在google上找clang的rpm包. 主要是clang-3.4.2-4.el6.x86_64.rpm, llvm-3.4.2-4.el6.x86_64.rpm, llvm-libs-3.4.2-4.el6.x86_64.rpm这三个包. 这里有一点很重要, 就是64位系统, 一定要装x86_64的包. 开始的时候装错了, clang装了i686的包(i686其实是32位的), 虽然clang也能正常用, 但是YCM编译C++引擎时就会杯具啦.

  

  第四步, 安装CMake.

  这次内网的yum源终于有了点作用了. 直接yum install cmake就安装成功了.

  

  第五步, 编译C++引擎(ycm_support_lib).

  如果不是用来补全C++, 那这一步就可以略过啦. 首先建立一个ycm_build目录(随便建在哪里都行. 这个只是一个临时目录而已). 然后cd ycm_build, 再执行

  cmake -G "Unix Makefiles" -DUSE_SYSTEM_LIBCLANG=ON  .   ~/.vim/bundle/YouCompleteMe/third_party/ycmd/cpp

  由于我使用的是系统的clang, 所以要用-DUSE_SYSTEM_LIBCLANG=ON.

  这时候可以看到ycm_build目录下多出来一堆东西.

  然后再执行 cmake --build . --target ycm_support_libs. 

  这一步如果顺利, 那再好不过了.

  第一次尝试的时候没有加-DUSE_SYSTEM_LIBCLANG=ON, 编译编译着就失败了.

  第二次, 加了-DUSE_SYSTEM_LIBCLANG=ON之后, 执行到91%, 还是报错, 后来发现是clang安装了32位的版本, , , ,

  终于编译通过啦, 这时候, 可以看到~/.vim/bundle/YouCompleteMe/third_party/ycmd目录下多了一个ycm_client_support.so和ycm_core.so, 这应该就是编译出来的C++引擎了.

  

  第六步, 使YCM生效

  YCM需要一个叫做.ycm_extra_conf.py 的文件作为YCM "入口" . 启动vim的时候会去寻找这个文件, 然后加载它(从当前目录逐层往上找). 这个文件主要的意义在于, 让clang能把当前的源码文件 "编译 " 通过. 因为YCM是基于语义补全的, 会对.cpp进行语法分析和语义分析. 于是就得告诉clang一些具体的编译参数(比较重要的是-I, 得让clang知道去哪些目录下找头文件). 如果clang不能正确的编译.cpp, 那么很多补全的功能就失效了. 通过 :YcmDiags 命令可以查看当前的文件有哪些编译错误. 这个文件有两个主要的部分: 一个是flags数组, 在这里面是填编译选项的. 一个是compilation_database_folder. 这个compilation_database_folder是依赖于clang的一个特性. clang可以在编译的时候导出一个数据库, 然后用这个compilation_database_folder加载这个clang的数据库就可以得到一个最精确最完整的补全和跳转. 由于我们的代码是gcc编译的(相信用clang编译的项目还是少数), 所以并没有数据库可以导出, 也只能用flags了. (这俩东西是二选一的. 如果有数据库, 优先读数据库, 没有的话就读flags).

  参照模板修改了一下flags(主要就是加上几个 -I, 因为我们的代码中引用了很多公司内部的库的头文件, 得让clang找到这些头文件的位置).

  官网上的指南, 到了上一步就没了. 然而这个时候YCM并没有生效呢~~~. 尝试把YouComplete目录下的autoload/youcompleteme.vim和plugin目录下的youcompleteme.vim均拷贝到~/.vim的autoload和plugin下, 这回终于有反应了, 但是报错说找不到ycm_core.so. 打开youcompleteme.vim, 发现里面在尝试从../third_party/ycmd和../python下找内容. 于是把~/.vim/bundle/YouCompleteMe/下的python和third_party目录都拷贝到~/.vim目录下.

  这个时候, YCM终于可以用了. 写了一个test.cpp, 简单试了下, 不管是补全和跳转, 都很给力, 完全符合自己的预期. 另外YCM还有语法检查的功能, 但是我觉得错误标记太丑, 就给关了(let g:ycm_enable_diagnostic_signs = 0).

  然后非常开心的用公司项目的源码试了下, , , 顿时脸一黑, , , UnicodeDecodeError: 'utf8' codec can't decode byte 0xcd in position 0: invalid continuation byte. 反复的报这个错误, YCM的各种功能完全都木有了. T_T顿时感觉辛辛苦苦二十年, 一夜回到解放前. 仔细分析了下, 难道是源码中的中文是gbk的格式导致的? 于是将一个cpp转成utf8格式, 再试下, 果然YCM又能用了. NM, 这怎么办, 难道要把公司的源码都搞成utf8的么? 这不科学. 果断先去goooogle一下, 发现已经有人遇到了这个问题: https://github.com/Valloric/YouCompleteMe/issues/1458 YCM的作者在另外一个帖子(https://github.com/Valloric/YouCompleteMe/issues/1378)中是这么回复的 "this might be related to whatever is set as your current file encoding in Vim". 看来猜测的不错, 毕竟作者是歪果仁, 凭啥让人家支持gbk嘞?

  不过毕竟是开源项目, 最大的好处是可以看到源码, 并且可以去修改~~于是决定改改源码让YCM支持gbk. 首先利用这个命令 :YcmDebugInfo , 可以看到报错的详细信息:

Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/root/.vim/autoload/../python/ycm/youcompleteme.py", line 526, in DebugInfo
'debug_info' )
File "/root/.vim/autoload/../python/ycm/client/base_request.py", line 71, in PostDataToHandler
timeout ) )
File "/root/.vim/autoload/../python/ycm/client/base_request.py", line 166, in JsonFromFuture
raise MakeServerException( response.json() )
ycmd.responses.ServerError: UnicodeDecodeError: 'utf8' codec can't decode byte 0xc1 in position 0: invalid start byte
E858: Eval did not return a valid python object

  挨个文件打开看了看, 最后定位到问题可能出现在这里:

  /root/.vim/third_party/ycmd/ycmd/utils.py, 中有一个RecursiveEncodeUnicodeToUtf8函数. 这个函数大概的意思是把源代码文件变成utf8格式, 然后后面再按utf8格式decode. 如果这一步有问题的话, 那么decode肯定会出错. 于是修改了这个函数成如下的样子:

def RecursiveEncodeUnicodeToUtf8( value ):
if isinstance( value, unicode ):
return value.encode( 'utf8' )
if isinstance( value, str ):
try:
value = value.decode('GBK').encode('utf8')
except UnicodeDecodeError,e:
pass
return value
elif isinstance( value, collections.Mapping ):
return dict( map( RecursiveEncodeUnicodeToUtf8, value.iteritems() ) )
elif isinstance( value, collections.Iterable ):
return type( value )( map( RecursiveEncodeUnicodeToUtf8, value ) )
else:
return value

  主要是针对if isinstance( value, str ):分支的修改. 之前是直接return value了. 这就意味着返回了一个gbk格式的字符串, 放到后面按utf8解析肯定会错. 于是将这个值先按gbk decode, 再encode成utf8. 然后, 问题就解决啦!!

======================2016.03.15补充======================

  发现有些头文件中, 使用命名空间 :: 不能补全. 使用YcmToggleLogs命令观察到日志中有一个错误: (仅截取部分出错的调用栈)

  File "~/.vim/third_party/ycmd/third_party/bottle/bottle.py", line 861, in _handle
return route.call(**args)
File "~/.vim/third_party/ycmd/third_party/bottle/bottle.py", line 1734, in wrapper
rv = callback(*a, **ka)
File "~/.vim/third_party/ycmd/ycmd/../ycmd/watchdog_plugin.py", line 100, in wrapper
return callback( *args, **kwargs )
File "~/.vim/third_party/ycmd/ycmd/../ycmd/hmac_plugin.py", line 62, in wrapper
body = callback( *args, **kwargs )
File "~/.vim/third_party/ycmd/ycmd/../ycmd/handlers.py", line 126, in GetCompletions
errors = errors ) )
File "~/.vim/third_party/ycmd/ycmd/../ycmd/handlers.py", line 226, in _JsonResponse
return json.dumps( data, default = _UniversalSerialize )
File "/usr/lib64/python2.6/json/__init__.py", line 237, in dumps
**kw).encode(obj)
File "/usr/lib64/python2.6/json/encoder.py", line 367, in encode
chunks = list(self.iterencode(o))

  问题仍然出在处理gbk编码上. 使用如下方法修改:  ~/.vim/third_party/ycmd/ycmd/../ycmd/handlers.py文件, _JsonResponse函数中:

from utils import RecursiveEncodeUnicodeToUtf8

def _JsonResponse( data ):
response.set_header( 'Content-Type', 'application/json' )
#return json.dumps( data, default = _UniversalSerialize )
return json.dumps( RecursiveEncodeUnicodeToUtf8(data), default = _UniversalSerialize )

  用处理编码的函数处理一下传入的data参数, 问题解决.

  

  至此, YCM真的可以用啦!

  效果确实完爆ctags几条街, 不枉我这么费力的折腾.

  最后放上我的YCM配置:

let g:ycm_global_ycm_extra_conf = '/search/odin/code/.ycm_extra_conf.py'
let g:ycm_confirm_extra_conf = 0
let g:ycm_key_invoke_completion='<C-i>'
set completeopt=longest,menu
autocmd InsertLeave * if pumvisible() == 0|pclose|endif
inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<CR>"
let g:ycm_enable_diagnostic_signs = 0
let g:ycm_enable_diagnostic_highlighting = 1
let g:ycm_collect_identifiers_from_comments_and_strings = 0
let g:ycm_complete_in_comments = 0
let g:ycm_complete_in_strings = 0
let g:ycm_min_num_of_chars_for_completion = 2

  更多的选项可以参见官网上的 option 部分.

  总结

  google是在比baidu牛逼太多了. 没有google绝对玩不转.

  仔细啃文档, 遇到的很多问题, 文档中都有提及.

====================2016.06.22更新=======================

  到了新公司, 要在新的开发机上装ycm. 新公司的开发机虽然可以连外网(也就是可以用vundle来装ycm了), 但是没有root权限, 于是并没有办法通过yum或者rpm装clang. 而且也不想手工从源码编译clang.

  不过幸好新公司的开发机也是redhat6, 将原来的clang相关的.so拷贝过来, 然后在.bashrc中增加一行export LD_LIBRARY_PATH="LD_LIBRARY_PATH:~/.vim/third_party/ycmd/ycmd/"

  这个时候加载ycm_core.so时就可以正确的找到libclang.so, 从而可以正确的调用clang了.

====================2016.09.14更新=======================

  公司换开发机了. 新开发机的vim是7.2版本的, 而且没有sudo权限没法更版本, , , YCM又用不了了. 考虑从源码编译安装vim, 但是依赖的编译工具也需要安装, 还是绕不开sudo权限. 后来脑子灵光一闪, 直接把旧开发机上的vim的可执行文件拷过来就可以嘞. 先 whereis vim, 找到vim安装目录, 在/usr/bin/vim /usr/share/vim中, 然后把对应目录里的内容拷贝到自己的home目录下, 在 bashrc 里面把 vim alias 成home目录下的新版vim. 这时候报错, vim会尝试在/usr/share/vim/vim74中尝试寻找系统配置, 这个路径是编译的时候就确定了的, 没法运行时去改. 只好去找管理员同学, 让帮忙建了一个软连接指向自己home目录的内容. 最终搞定了.

05-08 15:38