あずんひの日

あずんひの色々を書き留めるブログ

Neovim用にLSPのコードアクションを差分表示するプラグインを作った

Neovimには組み込みのLSPがあります。 しかし機能が貧弱もいいところなのでtelescope.nvimdressing.nvimなどを入れるわけですが、 依然としてコードアクション(修正を提案してくれる機能)周りは不満が残ります。

例えば、Neovim標準のvim.lsp.buf.code_actionやtelescope.nvim・dressing.nvimを使うとこうなります。

Neovim標準のコードアクション
Neovim標準のコードアクション
telescope.nvimとdressing.nvimを使ったコードアクション
telescope.nvimとdressing.nvimを使ったコードアクション

分かりますか?そう、

コードアクションがどう適用されるか分からん!!!

と言うわけでこの不満を解消するプラグインを作りました。

使い方

このプラグインはコードアクションの候補をtelescope.nvimnui.nvimを使って綺麗に表示し、 選択中のアクションの適用結果をプレビューエリアに差分として表示します。 複数ファイルや新規ファイル作成も差分が出ますし、ビジュアルモードでもちゃんと動作します。

導入は適当にpacker.nvimを使ってこんな感じでやります。 nvim-lightbulbを入れるとカーソル位置でコードアクションが使える場合に💡が出るので一緒に入れておくと良いです。

packer用luaスクリプト

require("packer").startup(function(use)
  -- ...

  -- telescope.nvimを使わないならいらない
  use {
    "nvim-telescope/telescope.nvim",
    requires = "nvim-lua/plenary.nvim",
    config = function()
      require("telescope").setup {
        -- 必要なら設定
      }
    end
  }

  use {
    "kosayoda/nvim-lightbulb",
    config = function()
      require("nvim-lightbulb").setup {
        autocmd = { enabled = true },
        -- 行番号ではなく行末に出す
        sign = { enabled = false },
        virtual_text = { enabled = true },
      }
    end,
  }
  use {
    "aznhe21/actions-preview.nvim",
    -- nuiを使う場合はコメントを外す
    -- requires = "MunifTanjim/nui.nvim",
    config = function()
      require("actions-preview").setup {
        -- バックエンドに使うプラグインの優先順位。デフォルトではtelescopeを優先的に使う
        -- backend = { "nui", "telescope" },
        -- telescopeで表示する場合の設定。ウィンドウ小さめでもいい感じに出す
        telescope = {
          sorting_strategy = "ascending",
          layout_strategy = "vertical",
          layout_config = {
            width = 0.8,
            height = 0.9,
            prompt_position = "top",
            preview_cutoff = 20,
            preview_height = function(_, _, max_lines)
              return max_lines - 15
            end,
          },
        },
        -- nuiで表示する場合の設定。枠線をカラースキーマの色で出す
        nui = {
          preview = {
            win_options = {
              winhighlight = "Normal:Normal,FloatBorder:FloatBorder",
            },
          },
          select = {
            win_options = {
              winhighlight = "Normal:Normal,FloatBorder:FloatBorder",
            },
          },
        },
      }
    end,
  }
end)

あとはLSPのon_attachやautocmdのLspAttach(Neovim 0.8以降限定)などを使ってキーマップを設定し、 コードアクションが使用できる箇所(nvim-lightbulbで💡が表示される場所)でキーを押します。

vim.keymap.set({ "v", "n" }, "gf", require("actions-preview").code_actions)

どう動作するのか見てみましょう。

actions-preview.nvimを使ったコードアクション
actions-preview.nvimを使ったコードアクション
複数ファイルの差分
複数ファイルの差分

めっちゃ便利!!!!!(自画自賛

差分のカスタマイズ

差分の表示にはvim.diff()という今回の用途におあつらえ向きのAPIを使用しています(本体のLua部分で使われていないのでなぜあるのか謎)。

このAPIはgitと同じ差分生成ライブラリが使われており、カスタマイズが色々と可能なのでオプションを説明しておきます。

require("actions-preview").setup {
  diff = {
    -- 差分生成のアルゴリズム
    algorithm = "myers",     -- デフォルト
    algorithm = "minimal",   -- 時間が掛かるができるだけ小さい差分を生成する
    algorithm = "patience",  -- 賢めなアルゴリズムのpatienceを使用する
    algorithm = "histogram", -- patienceとほぼ同じ結果だが速いらしい

    -- 差分がある部分の前後に表示する行数。git diff --unified=<n>相当
    ctxlen = 3,
    -- 同一ファイルの差分塊間の行数がこれ以下なら全部表示する。git diff --inter-hunk-context=<lines>相当
    interhunkctxlen = 0,

    -- あらゆるスペースの変更を無視する。trueならgit diff --ignore-all-space相当
    ignore_whitespace = false,
    -- 行頭や連続するスペースの変更を無視する。trueならgit diff --ignore-space-change相当
    ignore_whitespace_change = false,
    -- 行末スペースの変更を無視する。trueならgit diff --ignore-space-at-eol相当
    ignore_whitespace_change_at_eol = false,
    -- 改行前のCR(\r)を無視する。trueならgit diff --ignore-cr-at-eol相当
    ignore_cr_at_eol = false,
    -- 連続した空行の変更を無視する。trueならgit diff --ignore-blank-lines相当
    ignore_blank_lines = false,
    -- 差分のズレを抑制する。trueならgit diff --indent-heuristic相当。actions-preview.nvimではデフォルト無効
    indent_heuristic = false,
  },
  -- ... telescopeやnuiの設定
}

元ネタ

nvim-code-action-menuというプラグインがあってほぼ同じ事をしてくれるんですが、 見た目がダサいのと差分がちゃんと出ないパターンがあったので自作することにしました。