作为一个长期(Neo-)VIM用户,我对代码补全等活计还是有很大需求的。之前在试用各种插件的时候就在想,为什么不搞个通用的,非要每个插件自己搞一套,然后还需要互相配套。最近两年听说微软搞了个Language Server Protocol,而且还和RedHat等合作最终将其标准化并开放出来,我对此还是十分好奇的。
然而我之前刚切换到deoplete不久,配合any-jump还是挺好用的,于是就没有动力去继续切换——毕竟我看到许多个LSP插件,但每个的文档都语焉不详的,而且也考虑LSP可能会改,它们的完成度也需要时间来提升。而且貌似实现得最好的是CoC——嗯,NodeJS的,不想用。
直到到了昨天,我发现了一个大问题(见后文),导致我不得不去修改我的流程。于是我又去试了LSP支持插件,最终用了LanguageClient-neovim这一插件,和deoplete配合很好,而且迁移过程十分简单。于是就有了此文,叙述一下我之前的方案问题在哪,以及切换的过程,以帮助潜在的类似需求者。
之前的方案和问题
我最早用的是YouCompleteMe,但因为种种因素(慢,每次更新薛定谔地需要重新编译且耗时很久,并且出问题),我于是去寻找其他方案。恰好当时我切换到NeoVim不久,于是就顺手找了一下使用相关异步接口的插件,就用起来了deoplete。
但deoplete只提供自动补全的机制,还需要相应的语言插件。但好在它的仓库里有维护得不错的列表,于是我就在其中寻找自己需要的语言,安装即可。对于相当多语言,只要用最常见的就好,比如Python用jedi,go直接用go等等。
另外,为了可以进行跳转(deoplete不支持该功能),我又找到了any-jump这个插件。虽然并没有语义探测,会有一些多余的结果,但整体来说使用还是比较顺利的,而且架不住它快啊。于是我就这么用了很久。
然而在我将我的某一个Python程序(包)改用绝对导入后,any-jump再也找不到函数定义了,而且也不会在其他文件中搜索引用了!这可就要了命了,毕竟跳转是很重要的功能。更令我郁闷的是,在另一个Python程序(包)里,它明明工作得十分正常。无论直接搜索还是去any-jump的仓库issue中找,都没有相关信息,于是我只能怀疑是它的机制在这里有问题。
寻找新解决方案
为了减少麻烦,我期待新的解决方案可以让我尽量少地需要配置,并且最好可以和我之前的配置共同工作。毕竟我目前的最大问题是Python的部分状况下any-jump不工作,而不是完全没有可用的工作环境。
我之前也找过LSP方案,而且上次还看到说NeoVim打算在0.5版正式引入对LSP的支持。于是CoC这种「完整」的方案似乎意义不大,我更倾向于模块化的方案。
于是我在一个回答中找到了指引,发现了LanguageClient-neovim这个插件。他们宣称这个插件和deoplete可以很好地联合工作,并且我试用后发现的确如此。
打开仓库发现上次更新是去年12月。但翻了一下分支列表,就发现dev分支还在维护,于是就放下心来。我切换的部分目的还是临时使用,等待NeoVim 0.5版基础上的支持。
安装与配置
那么理所当然地,照着说明,我直接在插件部分新增了它,并且进行了非常简单的配置。
首先是安装并启用该插件。我正好也在用vim-plug来管理插件,于是直接复制:
Plug 'autozimu/LanguageClient-neovim', { \ 'branch': 'next', \ 'do': 'bash install.sh', \ } " (Optional) Multi-entry selection UI. Plug 'junegunn/fzf'
然后还需要进行简单配置。文档中有例子,于是我就拿来稍作修改:
" Required for operations modifying multiple buffers like rename. set hidden let g:LanguageClient_serverCommands = { \ 'rust': ['rustup', 'run', 'stable', 'rls'], \ 'python': ['pyls'], \ } " note that if you are using Plug mapping you should not use `noremap` mappings. nmap <F5> <Plug>(lcn-menu) nmap <leader>l <Plug>(lcn-menu) " Or map each action separately nmap <silent>K <Plug>(lcn-hover) nmap <silent> gd <Plug>(lcn-definition) nmap <silent> <F2> <Plug>(lcn-rename)
可以看到,和示例相比,我主要进行了两处改动:
- 对应的Language Server实现不用绝对路径,而用相对路径。我的考虑主要是万一需要在virtual environment中工作,使用绝对路径大概会出错。不过我还没有仔细测试过,不清楚到底怎样。
- 我新增了一个
<leader>l
到它的调用菜单的映射。这是因为之前用any-jump时我就映射<leader>j
为调用any-jump的搜索,这里想用类似的方法。
当然,配置完后,另外执行nvim +PlugInstall +UpdateRemotePlugins +qa
(或者打开nvim直接执行对应的命令)来真正安装它。另外,我选择了使用系统的包管理器来安装对应的Language Server实现,这也是Arch Linux下的通常倾向。
注意,(在Arch上)pyls
对应的包其实叫python-language-server
,但提供的命令倒确实是pyls
。
到这里,其实安装和配置已经完成了。之后就只需要和以前一样调用补全即可,来自Language Server的结果会自动加到deoplete给出的列表中。各项快捷键也都很好理解,尤其是F5(和<leader>l
)会叫出主选单。
其他
在此之外,我还进行了一些微调。其中最主要的就是在哪里存留显示函数签名。我用echodoc来提示函数签名。本来的设置是显示在底栏中的,为了避免遮挡。但现在我将其调整到显示在旁边,使用floating text/window机制:
let g:echodoc#enable_at_startup = 1 let g:echodoc#type = 'floating'
我不知道这是得益于deoplete还是LanguageClient-neovim的机制,但至少它和我目前的配置配合良好。
另外简单解释一下我为什么还是没用CoC:CoC很骄傲地宣称自己基于NodeJS,这让我有点不喜欢。而除此之外,CoC的安装说明也不怎么清晰,我还是看了前面提到的那个回答才大概明白怎么装。然后看到CoC文档里说它又是一个平台,我就彻底放弃了。
当然这不是说CoC不好——我没用过,也没深入了解过,所以没法做这种评论。但在我已经知道NeoVim打算在下一个版本中原生支持LSP的前提下,我自然是不会想要去再搞一个很有可能之后会被大规模改组/重构的平台上的平台的。
最后,我完整的vim配置在这里,可以随意参考使用。
您可以在Hypothesis上的該群組內進行評論,或使用下面的Disqus評論。