使用Deoplete作為Vim的代碼補(bǔ)全
YouCompleteMe
從開始學(xué)習(xí)用Vim寫代碼起,我一直都是用的YouCompleteMe來進(jìn)行補(bǔ)全。因?yàn)榫W(wǎng)上一搜,至少中文資源里面關(guān)于Python和C的Vim補(bǔ)全插件就全是說YCM。
我對(duì)YCM的感覺,談不上壞,但也不是很好。
首先YCM用起來,至少在Mac上其實(shí)是很方便的:Vim-plug下載,進(jìn)目錄./install.py --clang-completer安裝玩,基本上就可以開始用了。頂天在修改一下ycm_extra_conf.py讓它自己去找include目錄和compile_commands.json就可以了。
但不滿的地方在于,這個(gè)插件有點(diǎn)大。這可能和它的架構(gòu)有關(guān)系:它作為一個(gè)Vim插件試圖去整合那些后端做靜態(tài)分析的庫(kù)。這導(dǎo)致它不管是橫向還是縱向發(fā)展都很緩慢:拓展語(yǔ)言和拓展功能的工作量都很大。
再有就是在Arch上總有點(diǎn)奇奇怪怪的問題。完全不是過去開箱即用的感覺。因?yàn)椴寮?,看文檔看的頭痛。雖然最后花了半小時(shí)是用上了,但一直問自己為什么不去用VScode。
Deoplete
Deoplete其實(shí)從功能上說,和YCM是一樣的。但它的實(shí)現(xiàn)方式就很討喜:就是一個(gè)前端的補(bǔ)全引擎而已,后端的東西能分離的都盡量分離出去。就能夠認(rèn)清自己只是一個(gè)前端插件來說,我就很喜歡。
因?yàn)檫@樣的分離,Deoplete從理論上來說,就沒有什么語(yǔ)言不支持的。因?yàn)樗梢宰远xcomplete source。在Completion Sources上列出專門支持的語(yǔ)句就不止20種。而且最重要的是它支持LanguageClient這個(gè)插件。
LanguageClient
幾周前,在Vim Subreddit上看到關(guān)于LSP(Language Server Protocol)的貼子后,就覺得這個(gè)點(diǎn)子好。因?yàn)楦鱾€(gè)語(yǔ)言的代碼分析庫(kù)紛繁復(fù)雜,只要LSP這個(gè)協(xié)議推廣開來,各語(yǔ)言各自實(shí)現(xiàn)一個(gè)支持LSP的language server統(tǒng)一調(diào)用接口,那么市面上所有的編輯器都可以享用到不妥協(xié)的語(yǔ)言服務(wù)了(重構(gòu)對(duì)于純編輯器就簡(jiǎn)單許多)。
于是我這兩天搜了一搜,果不其然Vim就已經(jīng)有了LanguageClient這個(gè)插件了。
LanguageClient其實(shí)就是一個(gè)中間件,存在的原因就是因?yàn)镹eovim/vim自帶的和第三方的補(bǔ)全引擎不支持LSP。LanuageClient在中間當(dāng)胖翻譯就是了。
Language Servers
因?yàn)楝F(xiàn)在主要寫C++,現(xiàn)在C/C++主要的LS就兩個(gè)一個(gè)是clangd一個(gè)是cquery。
clangd:
- 好處是它是clang自帶的。如果僅需要補(bǔ)全用它就夠了。
- 壞處是功能實(shí)現(xiàn)的比較少,而且它沒辦法找到在
./build目錄下的compile_commands.json,你必須軟鏈接到項(xiàng)目根目錄才行。
而cquery就幾乎是個(gè)全功能的Language Server了。它支持:
- 代碼格式化; [clangd也可以]
- 跨文件重命名symbol;
- 定義跳轉(zhuǎn);
- 引用跳轉(zhuǎn);
- 枚舉引用列表;
- 文檔內(nèi)Symbol搜索;
- 工程內(nèi)Symbol搜索(包括各種頭文件);
- 快速定位Diagnostic位置;
我剛剛配置完進(jìn)去實(shí)際工程里面溜一圈,確實(shí)爽的多。越來越像IDE了。只有一個(gè)功能貌似用不了:implementation是沒法從頭文件跳去cpp文件的。但是呢,卻可以從reference列表跳轉(zhuǎn)(列表第一項(xiàng)),所以也不是什么問題了。
配置
其實(shí)Deoplete + LanguageClient + cquery,不知道是不是心里作用,雖然配置起來行數(shù)多了。但是比YCM好懂得多。
首先,安裝好neovim python client和cquery。如果是Arch,cquery就在AUR上,全自動(dòng)編譯。實(shí)在懶得編譯也可以上國(guó)內(nèi)的archlinuxcn源,里面之前是有cquery二進(jìn)制的。
然后下載那兩個(gè)插件,再加一個(gè)fzf。因?yàn)?code>languageClient的列表什么的都是用fzf做交互的。再者說,現(xiàn)在用linux誰(shuí)還不用fzf?下面是vim-plug部分:
Plug 'autozimu/LanguageClient-neovim', {
\ 'branch': 'next',
\ 'do': 'bash install.sh',
\ }
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }
下面配置Deoplete:
" 自啟動(dòng)
let g:deoplete#enable_at_startup = 1
" smart case不解釋
let g:deoplete#enable_smart_case = 1
" 用戶輸入至少兩個(gè)字符時(shí)再開始提示補(bǔ)全
call deoplete#custom#source('LanguageClient',
\ 'min_pattern_length',
\ 2)
" 字符串中不補(bǔ)全
call deoplete#custom#source('_',
\ 'disabled_syntaxes', ['String']
\ )
" 補(bǔ)全結(jié)束或離開插入模式時(shí),關(guān)閉預(yù)覽窗口
autocmd InsertLeave,CompleteDone * if pumvisible() == 0 | pclose | endif
" 為每個(gè)語(yǔ)言定義completion source
" 是的vim script和zsh script都有,這就是deoplete
call deoplete#custom#option('sources', {
\ 'cpp': ['LanguageClient'],
\ 'c': ['LanguageClient'],
\ 'vim': ['vim'],
\ 'zsh': ['zsh']
\})
" 忽略一些沒意思的completion source。
let g:deoplete#ignore_sources = {}
let g:deoplete#ignore_sources._ = ['buffer', 'around']
再來配置LanguageClient:
" abandoned的Buffer隱藏起來,這是vim的設(shè)置。
" 如果沒有這個(gè)設(shè)置,修改過的文件需要保存了才能換buffer
" 這會(huì)影響全局重命名,因?yàn)閂im提示保存因此打斷下一個(gè)文件的重命名。
set hidden
" 告訴LS那個(gè)文件夾才是project root,同時(shí)也告訴它c(diǎn)ompile_commands在哪里
let g:LanguageClient_rootMarkers = {
\ 'cpp': ['compile_commands.json', 'build'],
\ 'c': ['compile_commands.json', 'build']
\ }
" 為語(yǔ)言指定Language server和server的參數(shù)
let g:LanguageClient_serverCommands = {
\ 'cpp': ['cquery', '--log-file=/tmp/cq.log'],
\ 'c': ['cquery', '--log-file=/tmp/cq.log'],
\ }
" Server加一個(gè)參數(shù)就放在下列文件中
let g:LanguageClient_loadSettings = 1
let g:LanguageClient_settingsPath = '愛放哪里放哪里'
" 把Server的補(bǔ)全API提交給Vim
" 一般有deoplete就可以用了,加上一條以防萬(wàn)一。
set completefunc=LanguageClient#complete
" 把Server的格式化API提交給Vim
set formatexpr=LanguageClient_textDocument_rangeFormatting()
因?yàn)槟J(rèn)Deoplete的補(bǔ)全是ctrl-n下翻和ctrl-p上翻,如果喜歡tab還可以加上兩行:
inoremap <expr><tab> pumvisible() ? "\<c-n>" : "\<tab>"
inoremap <expr><S-tab> pumvisible() ? "\<c-p>" : "\<tab>"
最后在settings.json 里面寫下你的server的別的參數(shù):
{
"initializationOptions": {
"cacheDirectory": "/tmp/cquery"
}
}
記得把路徑寫到上面的配置里面去。
記得裝fzf。
使用
寫的太多了,困了。自己看文檔吧。
Happy Vimming!