[原创] [YCM] YouCompleteMe安装完全指南
2016-02-01 13:40 HGtz2222 阅读(11190) 评论(3) 编辑 收藏 举报因为实在实在受不鸟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目录的内容. 最终搞定了.