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