2024年コーディングでお世話になったNeovimプラグインたちの紹介

こんにちは！今年一年で頻繁に利用したプラグインを振り返ろうと思い、それぞれのプラグインをこんな風に使っていますよーっていうのをまとめてみました！

# 記事中に出てくるプラグインたち
- [overseer.nvim](https://github.com/stevearc/overseer.nvim)
- [nvim-notify](https://github.com/rcarriga/nvim-notify)
- [vim-test](https://github.com/vim-test/vim-test)
- [neotest](https://github.com/nvim-neotest/neotest)
- [nvim-coverage](https://github.com/andythigpen/nvim-coverage)
- [Hydra.nvim](https://github.com/nvimtools/hydra.nvim)
- [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim)

# [overseer.nvim](https://github.com/stevearc/overseer.nvim)
始めのプラグインは一推しの、汎用的なタスクランナーの紹介です。Makefileのコマンド一覧や独自タスクなどを出してくれてNeovim上から実行できるので、私の場合はgit pushやテスト実行時に利用することが多いです。
デフォルトで[nvim-notify](https://github.com/rcarriga/nvim-notify)と連携対応しているので、いい感じに実行完了などの通知表示してくれます。さらに、テスト失敗時にはquickfixやDiagnosticsに送ることもできるので、エラー対象箇所にジャンプすることもさくっとできます！

このようにリストが表示され、選択することで実行できます。
![Overseer Task List](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/56ea2f2d-df3e-79a8-da59-a5a5d492aa5b.png)

実行ごとに履歴が保存されており、
![Overseer Task History](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/a80a94b4-9e41-18f7-6435-005f96c45c3f.png)

それぞれの結果も確認することができます。そのため、前回どんなエラーで落ちてたんだっけ？とかを容易に確認できます！
![Overseer Task History Detail](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/f60607b9-630e-718f-3c2f-b53ecda21db3.png)

コーディング中は、git pushやテスト実行をよく実行しますので、その設定を紹介しようかと思います。

## カスタムタスクの追加
私は、モジュールとしてカスタムタスクの追加を行なっています。
詳細は、公式Documentに詳細は記載されています。[as a module](https://github.com/stevearc/overseer.nvim/blob/master/doc/guides.md#custom-tasks)

例えば、下記のようにgit pushタスクを設定しています。
```lua:overseer/template/user/git_push_head.lua
return {
  name = "git push HEAD",
  builder = function()
    return {
      cmd = { "git", "push", "-v", "origin", "HEAD" },
      components = { "default" },
    }
  end,
}
```

```lua:overseer/template/user/init.lua
return {
  "user.git_push_head",
}
```

setup時に、下記のようにカスタムタスクを格納しているモジュール名を指定することで、利用できるようになります。
```
require('overseer').setup({
  templates = {
    "builtin",
    "user",
  },
  ...
}
```

Neovim上では、下記コマンドを実行することで、git pushされるようになります。
```lua
:lua require("overseer").run_template({ name = "git push HEAD" })
```

nvim-notifyを導入しているので、git push処理が完了したら下記画像のように右下付近に通知が来るようになっています。
![git push完了後に通知が来ている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/2ef47abe-75e3-e8ec-0d64-64e192fc6c6a.png)


## test実行

テスト実行には、[vim-test](https://github.com/vim-test/vim-test)を利用しています。
vim-testのstrategyをカスタムしてoverseerと連携しています。
カスタムしたstrategyの流れのイメージとしては、実行予定のコマンド文字列を元に、いい感じにoverseerで実行させているだけです！

```lua
function OverseerStrategy(cmd)
  local args = {}
  for match in string.gmatch(cmd, "[^ ]+") do
    table.insert(args, string.match(match, "[^'].*[^']"))
  end

  local path = string.match(cmd, "[^ ]+$")
  path = string.gsub(path, "/%.%.%.", "")

  local workspace_path, _ = require("project_nvim.project").get_project_root()

  local task = require('overseer').new_task({
    cmd = args,
    cwd = workspace_path,
    components = { { 'on_output_quickfix', open = false, relative_file_root = path }, 'default' }
  })
  task:start()
end

-- カスタムstrategyを設定
vim.api.nvim_set_var("test#custom_strategies", {
  overseer = OverseerStrategy,
})
vim.api.nvim_set_var("test#strategy", "overseer")
```

この状態で、vim-testコマンドのTestNearestなどを実行することによって、overseer側でtestのタスクが実行されます。

TestNearestを実行したら、下記画像の右下通知のようにtestコマンドが実行されて成功されていることが分かります！
![TestNearest 実行](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/36723d10-51f7-87cc-75b6-5f4e2bd8ea7c.png)


使い慣れていたvim-testの方が調整しやすくて使い続けていますが、[neotest](https://github.com/nvim-neotest/neotest)も気になっています。今度触ってみようかと思います！

### coverage
テスト実行のついでに、コードのカバレッジも確認したい場面があるかと思います。
十分なテスト書けているかな？って確認したいときには、[nvim-coverage](https://github.com/andythigpen/nvim-coverage)を利用しています。
私の場合は、vim-testでテスト実行する時に、coverprofile指定して作成するようにしており、nvim-coverageでそのファイルを確認するようにしています。

```lua
vim.api.nvim_set_var("test#go#gotest#options", "-v -coverprofile=cover.out")
```

```lua
require("coverage").setup({
  lang = {
    go = {
      coverage_file = "cover.out",
    },
  },
  ...
})
```

下記画像のように赤は通過していない箇所になっていますので、ToPluralメソッドのテストが存在していないことが分かりましたね！
![nvim-coverageの表示状態](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/f93a043a-6e60-5b62-1e42-a95758a32b28.png)

# [Hydra.nvim](https://github.com/nvimtools/hydra.nvim)
皆さんこういう経験はないですか？キーバインド設定したけど、どうだったけ？
設定したてのものなどの手に馴染んでいないキーバインドは忘れがちですよね！キーバインドが多くなってくるとこういうことが増えるなって思うので、それを解決してくれるプラグインの紹介です！
イメージとしては、設定したHydraを実行すると、それに紐づいたサブコマンドなどのキーバインド一覧を表示してくれるって感じです！
例えば、下記画像のようにテスト関連コマンドを設定しているHydraを実行すると、右下にキーバインド一覧を表示してくれます！そして、表示されているキーバインドを入力することで、設定した操作を行なってくれます！
これによって、滅多に使わないけどあの処理どうやるんだったけ？がメニューとして表示されるので迷子にならないですし、いつも使っているものはノールックで実行することが出来るので重宝しています！

![Hydra Menu](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218282/58a010a8-0d1b-09b8-68f1-007f92d998cd.png)

[公式のサンプル](https://github.com/nvimtools/hydra.nvim/wiki/Git)見ていただくと、凝ったこともできるようになっています。ただ、設定が面倒な部分もあったりするので、シンプルで十分なものは、楽に設定できるwrapした処理を用意して利用しています。適当な実装ですが、サクッと設定出来るようになったので満足しています！（参考程度に貼り付けておきます）

```my/hydra.lua
local M = {}

local SEPARATOR_LENGTH = 10

local setted_hydra_list = {}

local function make_hint(name, heads)
   local separator_space = string.rep(" ", SEPARATOR_LENGTH)
   local title = string.format("[ %s ]", name)
   local result = {string.format("%s%s%s", separator_space, title, separator_space)}
   for _, head in ipairs(heads) do
      local line = ""
      if head[3].sep then
         line = string.format("%s\n", head[3].sep)
      end

      line = string.format("%s_%s_: %s", line, head[1], head[3].desc)
      table.insert(result, line)
   end

   return table.concat(result, "\n")
end

local function set_hydra(name, heads, config)
   config = vim.tbl_deep_extend('keep', config or {}, {
      color = 'pink',
      invoke_on_body = false,
      hint = {
         position = 'bottom-right',
         border = 'rounded',
      },
   })

   local hydra = require('hydra')({
      name = name,
      hint = make_hint(name, heads),
      config = config,
      heads = heads,
   })
   setted_hydra_list[name] = hydra
   return hydra
end

function M.set_hydra(name, heads, config)
   setted_hydra_list[name] = {
      name = name,
      heads = heads,
      config = config,
      lazy = true,
   }
   return function()
      M.open(name)
   end
end

function M.get_setted_hydra_names()
  local keys = {}
  for k, _ in pairs(setted_hydra_list) do
    table.insert(keys, k)
  end

  return keys
end

function M.open(name)
   local hydra = setted_hydra_list[name]

   -- 遅延読み込み処理のため、open時にhydraを作成する
   if hydra.lazy then
      hydra = set_hydra(hydra.name, hydra.heads, hydra.config)
   end

   hydra:activate()
end

return M
```

## 設定例
下記のように設定しておくことで、上記の画像のようなリストが表示されるようになります！
```example.lua
local test = require('my.test')
vim.keymap.set('n', '<Space>hdt', require('my.hydra').set_hydra('Test', {
   { 'n', test.run_nearest,   { desc = 'nearest', exit = true } },
   { 'f', test.run_file,      { desc = 'file', exit = true } },
   { 'l', test.run_last,      { desc = 'last', exit = true } },
   { 's', test.suite,         { desc = 'suite', exit = true } },
   { 'D', test.debug_mode,    { desc = 'debug mode' } },
   { 'N', test.normal_mode,   { desc = 'normal mode' } },
   { 'O', test.overseer_mode, { desc = 'overseer mode' } },
   { 'q', nil,                { exit = true, nowait = true, desc = 'exit' } },
}), { silent = true, noremap = true })
```

hydraに設定したキーバインド自体を忘れるっていうこともあるかと思い、[telescope.nvim](https://github.com/nvim-telescope/telescope.nvim)で設定したhydraのリストを表示させて、実行してくれるピッカーを作っていて、たまに利用しています！このリスト表示のキーバインドだけど覚えていれば、設定したHydra一覧が確認できるので安心です！

```picker.lua
local pickers = require "telescope.pickers"
local finders = require "telescope.finders"
local conf = require("telescope.config").values

local function list_hydra()
  pickers.new({}, {
    prompt_title = "Hydra List",
    finder = finders.new_table {
      results = require('my.hydra').get_setted_hydra_names(),
    },
    sorter = conf.generic_sorter(opts),
    attach_mappings = function(prompt_bufnr, map)
      actions.select_default:replace(function()
        actions.close(prompt_bufnr)
        local selection = action_state.get_selected_entry()
        require('my.hydra').open(selection.value)
      end)
      return true
    end,
  }):find()
end

vim.keymap.set('n', '<Space>hdl', list_hydra, { silent = true, noremap = true })
```

# まとめ
他にも多数のプラグインを利用していますが、特に頻繁に利用しているプラグインに絞って紹介を行いました。そういう使い方してるんだーなど参考になる部分があれば幸いです！