給(Neo-)VIM裝配Language Server Protocol(配合deoplete)

renyuneyun 2021年05月23日(週日) 1 mins

作爲一個長期(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)

可以看到,和示例相比,我主要進行了兩處改動:

  1. 對應的Language Server實現不用絕對路徑,而用相對路徑。我的考慮主要是萬一需要在virtual environment中工作,使用絕對路徑大概會出錯。不過我還沒有仔細測試過,不清楚到底怎樣。
  2. 我新增了一個<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配置在這裏,可以隨意參考使用。


Related posts:

您可以在Hypothesis上的該群組內進行評論,或使用下面的Disqus評論。