🤖

Partedit で Neovim LSP が起動しない

に公開

問題の現象

Markdown やドキュメント類など、部分的に他の言語が混じる文書を Vim で編集する際は thinca/vim-partedit が便利です。使い方は簡単で、範囲選択した上で :Partedit コマンドを実行するとその内容が別バッファとして切り出されます。そうすることで、部分的に別のファイルタイプで編集できるというものです。保存操作をすると BufWriteCmd イベントが発火し、元ファイルに反映されるという仕組みになっています。

Neovim には組み込みの LSP 機能がありますが、上述の Partedit によるバッファ内にて起動してほしい LSP サーバーが起動しないという問題が発生しました。これでは補完などが全く効かず本末転倒です。

環境

以下のリポジトリに最小構成を作成しています。Nix がインストールされていれば、クローンしてリポジトリで nix develop . --no-update-lock-file すれば該当環境に入れます。

https://github.com/gw31415/zenn-example-partedit-nvim-lsp

再現手順

以下の3ケースに対して該当バッファにアタッチされた LSP サーバーをチェックします。

  1. 実在する Lua ファイルの編集
  2. 実在する Lua ファイルを Partedit で開いたもの
  3. 初期起動時の空バッファのファイルタイプを lua にしたもの

アタッチされた LSP サーバーをチェックするコマンドは以下です。

:lua= vim.lsp.get_clients { bufnr = 0 }

Case 1. 実在ファイルの編集

lua-language-server をインストール・セットアップしているので、適当な Lua ファイルを開くと LSP サーバーがアタッチされる筈です。丁度設定ファイルが Lua ファイルなので開いてみましょう。

nvim nvim/init.lua

そして :lua= vim.lsp.get_clients { bufnr = 0 } でチェック。

Luaの出力(1)

きちんと起動していますね。

Case 2. Partedit で起動したバッファ

それではそのまま以下のコマンドでバッファ全体を Partedit にぶち込みましょう。

:%Partedit -filetype lua

そして :lua= vim.lsp.get_clients { bufnr = 0 } でチェック。

Luaの出力(2)

何もアタッチされません。ちなみに、手動で :lua vim.lsp.enable 'lua_ls' を打ちこんだ後も同様の結果でした。

Case 3. ft=lua の空バッファ

気を取り直して、初期起動のバッファに対して :set ft=lua してチェックしてみましょう。

" 引数なしで nvim した後
:set ft=lua
:lua= vim.lsp.get_clients { bufnr = 0 }

Luaの出力(3)

アタッチされていました!

結局原因は何なのか

Case 3. で実在しないバッファに対してもアタッチされていたので、対応するファイルの実在は関係ないようです。

先に結論を言うと、原因は buftype の値にあります。 Case 2.に対して :set bt='' をした後に LSP サーバーを再度有効化すると適切にアタッチされます。

" Parteditのバッファでも大丈夫なパターン
:%Partedit -filetype lua
:set bt=''
:lua vim.lsp.enable 'lua_ls'
:lua= vim.lsp.get_clients { bufnr = 0 }

現在の Neovim LSP の実装では以下のように、何故か buftype が空でないとアタッチされない ようにガードする処理が記載されています。

https://github.com/neovim/neovim/blob/db1542af3d03d304738fa22c1c848c2f02c37156/runtime/lua/vim/lsp.lua#L533-L538

Only ever attach to buffers that represent an actual file.

とあるように、自動起動処理が各種プラグインの作成したバッファに対して誤爆することを回避するために挿入された処理であるようです。

実際、今回検証した3ケースに対しての buftype はそれぞれ以下のようになっています。

Case buftype
Case1. 実在Luaファイル (空)
Case2. Parteditバッファ acwrite
Case3. 初期空バッファ (空)

回避法

暫定的に、自身は thinca/vim-partedit をフォークさせていただき、バッファに acwrite を付加しないようにすることで対処しています。

https://github.com/gw31415/vim-partedit/commit/dbb7f08999088a60961af51b0ef607975ccc9172

--- a/autoload/partedit.vim
+++ b/autoload/partedit.vim
@@ -77,7 +77,9 @@ function! partedit#start(startline, endline, ...)
   let b:partedit__contents = original_contents
   let b:partedit__prefix = prefix
   let b:partedit__bufhidden = bufhidden
-  setlocal buftype=acwrite nomodified bufhidden=wipe noswapfile
+  " NOTE(gw31415): Delete acwrite: because Neovim LSP does not start
+  " Reference: https://github.com/neovim/neovim/blob/d4c8e8df1c80cf195dc1d1b98c1c8429dd3f43ed/runtime/lua/vim/lsp.lua#L535-L537
+  setlocal nomodified bufhidden=wipe noswapfile
 
   command! -buffer -bar ParteditEnd execute b:partedit__bufnr 'buffer'

プラグイン本体に手を加える点でかなり無理矢理な気もしますが、こちらで正常に :Partedit できるようになりました。

とはいえ、意味論的に acwrite は外したくありません。恐らく手動で vim.lsp.start() するのが正攻法ですが、 vim.lsp.enable() と同様の起動条件を書く羽目になりそうだったので諦めて中断しました。良い対処法があれば教えてください。

GitHubで編集を提案

Discussion