(2021/12/25追記) この記事で話題にした問題は最新のddc-nvim-lspで修正されている。こちらのissue及びこちらのcommitを参照。もっとも、この記事を書いてから大分経ったため、ddc_nvim_lsp.luaのソースコードも今では大分変わっている。
以下の文章のまとめ
バージョン違いには注意する
ddc-nvim-lspは2021/10/1時点では、neovim 0.5.0を想定して作られているプラグインである。しかし自分はneovim 0.5.1を使ってしまっていた。neovim 0.5.1からlsp handlerの引数に破壊的変更があったため、LSPの補完が効かなかった。
究明に当たってDockerを触ったり、Luaを触ったり、ドキュメントを漁ったりして色々糧にはなったので、記録しておく。
何が起きたのか
まず、プラグインの管理にはShougo/dein.vimを使った。
neovimのbuildin LSPを使ってLSPが使える環境を構築した。設定に当たって以下のプラグインを導入した。
入力補完はShougo/ddc.vimを使った。それにあたって以下のプラグインを導入した。
- vim-denops/denops.vim: ddc.vimがDenoの機能を使うため必要。
- Shougo/ddc-matcher_head
- Shougo/ddc-sorter_rank
- Shougo/ddc-around
- Shougo/ddc-nvim-lsp
最後のddc-nvim-lspがうまく動かなかった.
Language Serverとしてpyrightを導入したのだが、実際にPythonのファイルで入力補完を試したところ,ddc-aroundの補完は反応するが,ddc-nvim-lspの補完候補が現れなかった。
Dockerを使って再現性を検証する
まず、
- 何か他のプラグインが邪魔しているのではないか
- Macという環境だから問題なのだろうか
という仮説を立てた。そのためには、何も無い素のneovimの環境を作る必要があると考えた。そこで、環境をDockerで構築しようと考えた。
Docker環境の構築
適当なディレクトリを作って、そこにDockerfileとdocker-compose.ymlを作成する。
Dockerfileを以下のようにする。ベースイメージはanatolelucet/neovimにした。この時点でdeinを導入する。コマンドはdeinのQuick startを参照した。deinのインストールにあたってcurl、gitコマンドが必要なので、ここで導入する。
| |
neovimの設定ファイルはコンテナ外で編集できるようにしておく。同ディレクトリにディレクトリ.config/nvim/を作成し、その上で、docker-compose.ymlを以下のようにする。
| |
.config/nvim/の中にinit.vim、dein.toml'、'dein_lazy.tomlを作成。init.vimは以下の通り。
| |
dein.toml、dein_lazy.tomlについては長くなるため省略するが、必要なプラグインを導入する。
この時点でdocker-compose buildをすればコンテナがビルドされ、その後docker-compose run nvimでコンテナを実行する。
コンテナ内での作業
Language Serverであるpyrightを導入する。pyrightの導入に当たってpython3、npm、nodeが必要。aptコマンドだと古いバージョンしか入らないようだったので、
NodeSourceのInstallation instructionsに従って最新のバージョンを入れる。
# curl -fsSL https://deb.nodesource.com/setup_16.x | bash - # apt-get install -y nodejs
pyrightのCommand-lineに従ってpyrightを導入する。
# npm install -g pyright
続いてDenoのInstallationに従ってDenoを導入する。
# curl -fsSL https://deno.land/x/install/install.sh | sh
denoのパスを設定するため、.bashrcに以下の内容を追記する。その後、変更を反映させるためにsource .bashrcを実行。
| |
動いた
これでnvim main.pyを実行する。なんとこれでLanguage Serverの補完が効いた。
コンテナの設定ファイルをホストに移すが、うまくいかない
うまくいったinit.vim、dein.toml、dein_lazy.tomlをホスト側に移して、うまくいくか試してみる。勿論バックアップは取っておく。
ところが、ホスト側では補完が効かなかった。
同じ設定ファイルなのにもかかわらず、コンテナとホストで違いが現れている。「それでは、Mac固有の問題なのだろうか」と考えた(実はそうでないことは後々分かることになる)。
「Mac固有の問題だとしても、どこでうまくいっていないのかを知りたい」と考えた。うまくいかない状況を以下の2つに分割して、まず前者について調査した。
- LSPサーバーとの通信がうまくいっていないのか
- LSPサーバーとの通信後の処理がうまくいっていないのか
以降、Dockerからは離れ、ホスト側で調査をする。
Luaを触る
自前でLSPサーバーとの通信を行ってみて、正しい結果が帰ってくるかどうかをみることにした。neovimではこれをLuaのAPIとして提供している。Luaなんてほとんど書いたことは無いが、ddc-nvim-lspのソースコードから何をやっているのかを類推して、補完を取得する処理を読み出した。
Luaのスクリプトを実行する方法は:h lua-commandsに書かれている。ここではLuaファイルに書いたものを実行したいので、luafileコマンドを使う。
Luaファイルの内容
適当なLuaファイルを作成し、内容を以下のようにする。
| |
buf_request
LSPにリクエストを送るにはvim.lsp.buf_requestという関数を使えば良いらしい。ドキュメントから抜粋すると、引数の説明は以下の通り。
| |
現在のバッファに対して補完情報のリクエストを送りたいので、{bufnr}には0を入れる。補完のリクエストなので、LSPの仕様より{method}にはtextDocument/completionを入れる。{params}には現在のカーソル位置の情報を入れたいが、これはvim.lsp.util.make_position_params関数でできる。
lsp-handler
handlerにはいわゆるコールバック関数を指定する。関数の引数は:h lsp-handlerで確認できる。以下、neovim 0.5.1のドキュメントから一部抜粋する。
| |
dump
handlerでの処理は、単にLSPから受け取った結果を出力することにした。table関数はそのままでは出力できないため、dump関数を書いた。これはtableをJSONっぽい形式で出力する関数。
Luaファイルの実行
適当なPythonのファイルを作り、以下のように書く。
| |
補完の処理が正常に動作しているなら、ピリオドの後に文字列系の関数(joinなど)が補完候補として出てくるはずである。
そこで、ピリオドの部分にカーソルを持っていって、luafile ファイル名でLuaファイルを実行する。
すると、neovimのウインドウ下に以下の文が表示された。
| |
これはLSPサーバーから受けとった情報である。ということは、LSPとの通信はうまくいっているようだ。LSPとの通信で失敗しているわけではない。
原因の場所を見つける
「LSPサーバーから情報が受け取れているとしたら、その後の処理に問題があるかもしれない」と考え、ddc-nvim-lspのコードを読み始めた。コードが置いてある場所は設定によるが、自分の環境では~/.cache/dein/repos/github.com/Shougo/ddc-vim-lsp/ある。
とりあえずvim.lsp.buf_requestに近いところから読み始めた。そこで、{handler}として指定したget_candidates関数に目をつけた。
| |
ソースコードのコメントによると、neovimのバージョンによって、lsp-handlerの引数が変わるらしい。代入文にて短絡評価を使っている。そこではneovimのバージョンが0.6でなかった時点で、2番目の引数arg2がresultであると確定している。しかし、よく考えるとそれはおかしい。neovim 0.5.1のドキュメントを参照した時は、引数の順番はfunction(err, result, ctx, config)であり、1番目の引数がresultのはず。
もしや、と思い、のソースコードを以下のように変更した。
| |
なんとこれでLSPの補完が効くようになった。その場しのぎの変更ではあるが、使えるのでそのままにしておく。
lsp-handler変更についてのPull requestをみると、どうやらneovim 0.5.1のバージョンにこれが取り込まれたようである。実際、0.5.1 Changelogにも記載されている。この変更のせいで、補完の処理がうまく動かなくなっていたようである。
Dockerコンテナのneovimでは動いたのは、Dockerのベースイメージとして入っていたneovimのバージョンが0.5.0だったからである。
プラグインのリポジトリをよく見る
2021/10/1時点でddc-nvim-lspのRequiredを読むと、そこにneovim 0.5.0+ with LSP configurationと書いてあった。なので、neovim 0.5.0じゃないと動作が保証されない。0.5.0と0.5.1のバージョン違いなんて対して無いだろうと思っていたが、そういうところまでちゃんと疑うべきだった。
学んだこと
- Docker及びDocker Composeの使い方が少し分かった。
- neovimでLuaをどう実行するかが分かった。
- neovimでLSPサーバーにどうリクエストすればよいのかが分かった。
- バージョン違いで大きな変化が起こることがあるということが分かった。