deoplete.nvimとLSP補完での変数名prefix重複解消

年始休み中に古いままだったVim環境を更新して各種補完系をLSP(Language Server Protocol)のプラグインに差し替えた。しばらく快適なエディタ生活を送っていたつもりだったが、時折発生する入力誤りが補完起因であることに気がついた。

違和感があったのは、Rubyインスタンス変数を入力する際に @@value のように @ が二重入力されてしまうこと。そういえば、PHPなどを書いていたときにも $$ のようにprefixが重複する。

補完文字列重複の原因

これには複数の原因があったが、ひとつには補完の仕組みをLSPに移行したことで補完文字列が変化していた。

従来の動作はneocompleteによる補完で、変数名の補完などにはprefix部分は含まれていなかった。ところが、LSP経由で提示される補完候補は基本的に @value のようにprefixを含むものが返ってくるため従来の動作との相違がある。

2点目として、自分の設定ではdeoplete.nvimの設定を公式ドキュメント同様の記述としていた。これは一般的なキーワード文字列にマッチするパターンではあるが、prefixを含まない文字列部分の表現となっている。

call deoplete#custom#option({
    ...
    \ 'keyword_patterns': {
    \    '_': '[a-zA-Z_]\k*',
    \    'ruby': '[a-zA-Z_]\w*[!?]?',
    \ },

deoplete.nvimでは補完開始位置(complete_position)の計算に、特に指定がなければkeyword_patternsを用いる実装となっている(rplugin/python3/deoplete/base/source.py)。このため、入力位置から判定された補完候補はprefixを含む文字列であるのに対して、上記のようなkeyword_patternsの場合はprefixを含まない位置が補完開始位置と判断されるため、prefixmの重複が発生してしまうのであった。

つまり、以下のような流れが発生することになる。

  1. @val まで入力した時点で入力補完を呼び出す
  2. 補完候補として @value が返される
  3. 補完開始位置は keyword_patterns にマッチする val の先頭とされるため、@ の後に @value が補完される
  4. @@value の重複文字列となる

対処

原因が分かれば対処は容易なので keyword_patterns を調整すれば良い。

変数名にprefixがつく言語は、メジャーなものでは Ruby, Perl , PHP くらいだろうか。よく利用するシーンを想定して、一旦以下のような定義に書き換えた。

    \ 'keyword_patterns': {
    \    '_': '\$?[a-zA-Z_]\k*',
    \    'ruby': '@?[a-zA-Z_]\w*[!?]?',
    \ },

Rubyのクラス変数を考えると @* のほうが望ましいか? Perlだと[\$@%] あたりを定義しておいてもよいかもしれないと思ったが、もうPerlは10年以上書いていないので必要になったら考えることにする。

ともあれ、これで変数補完時の重複は解消された。

根本対処は可能か

問題は解決したものの、適切な設定値を指定しないと意図した動作にならないのはエディタの補完機能として頼りない。利用者が意識せずとも快適に動作する環境は作れるだろうか。

補完機能がエディタなどでの利用を想定している場合、補完ロジックを提供する側から置換位置の情報が返されてもよい気がするが、今のところLSPの補完仕様にそのような情報は含まれない(Language Server Protocol Specification - Completion Request)。

deoplete.nvimはsourceのget_complete_positionをオーバーライドすることで補完位置を調整することができるが、LSPの場合は通常LSPクライアント自体がsourceとなるため接続先サーバ毎に異なる挙動を提供することも難しいように思われる。

現時点では最適解を思いついていないので、せめてもの情報源としてこの記事を残した。