nvim-lspconfig で Bundler を考慮するプラグインを作った

背景

Neovim のビルトイン LSP クライアントを使っている人は今だと nvim-lspconfig を使っている人が多いです。

LSP サーバーのデフォルトの起動コマンドは、グローバルにインストールされたものを見るようになっています。 例えば ruby-lsp の場合は ruby-lsp というコマンドになります。 これは Bundler を考慮してくれないので ruby-lsp を Bundler 経由で使っているプロジェクトにおいても bundle exec ruby-lsp ではなく ruby-lsp が利用されます。 一応、設定でコマンドを変更することができます。 nvim-lspconfig では ruby-lsp は ruby_ls という名前で設定します。 以下は bundle exec ruby-lsp で起動する設定例です。

1
2
3
4
5
6
local lspconfig = require('lspconfig')
lspconfig.ruby_ls.setup {
...
cmd = { 'bundle', 'exec', 'ruby-lsp' },
...
}

しかしこれだと Bundler を使っていないプロジェクト(ディレクトリ)においても bundle exec ruby-lsp が利用されてしまいます。 無駄にエラーが出るようになって地味にうるさいです。

作った

この問題を解決するために nvim-lspconfig-bundler というプラグインを作りました。

このプラグインを入れて設定を1行追加すると nvim-lspconfig で入れた Ruby の LSP サーバー実行コマンドに必要に応じて bundle exec を付与します。 以下のように lspconfig.hoge.setup よりも前に require('lspconfig-bundler').setup() を一度だけ呼び出しておけば OK です。 require('lspconfig') の前でも大丈夫なはず(プラグイン内部で require しているので変わらないはず )です。

1
2
3
4
5
6
7
local lspconfig = require('lspconfig')

require('lspconfig-bundler').setup()

lspconfig.ruby_ls.setup {
...
}

仕組み

nvim-lspconfig には setup の実行時にフックを仕込む機構が用意されています。 この仕組みについては :h lspconfig-setup-hook に記載があります。具体的な setup が呼び出される前に以下のようにしておくことでフックを仕込むことができます。config はデフォルト値が加味された後の設定を表現するテーブルです。これが変更可能なので実際の読み込みの前にいろいろできるという寸法です。

1
2
3
lspconfig.util.on_setup = lspconfig.util.add_hook_before(lspconfig.util.on_setup, function(config)
...
end)

そのフックの中で既知の Ruby の LSP の場合のみ判定を行ない、必要があれば cmd を書き換えています。 判定はわりとナイーブに Gemfile.lock がある場合にその中を見てコマンドを提供する gem が含まれているかどうかで行なっています。

感想

地味に便利になって満足度が高いです✌️

nvim-lspconfig 側で良い感じにやってほしい機能なので要望はありますが現時点では実装されていなかったので独自にプラグインとして作ってみました。 そしてその issue に宣伝しておきましたw 需要があるようなら本体側に実装するのもありかなと思っています。