2022-09-26

Neovimプラグイン rspec.nvim を作った

rspec.nvim という、RSpec を Neovim からシームレスに実行できるプラグインを作った。

この記事ではプラグインの機能の紹介をしつつ、開発した背景や実装時に考えたことを書いていく。

モチベーション

最近、Neovim の設定ファイルを init.vim から init.lua に書き換えたのだけど、その際に Lua に触れ、言語仕様を調べたり簡単なコードを書いてみて、なんとなく好印象を持ったのがきっかけだった。

Neovim 0.5 から Lua を内蔵しており、プラグインも Lua で書けるようになっている。もともとエディタのプラグインを書いてみたいと思うことはあり、Lua で書けるならいっちょやってみるかという気持ちになった。

開発のネタとして、これまで Neovim からテストを実行するために test.vim という様々な言語の様々なテストフレームワークに対応したプラグインを使っていたのだけど、実際に使うのはほぼ RSpec のみという現状だったので、これを置き換えるプラグインを作ることにした。

rspec.nvim の機能

5つの機能を紹介する。

1. テスト対象の柔軟な選択

rspec.nvim では以下の4パターンのテスト実行をサポートしている。

  • specファイル全体を実行する
  • 現在のカーソル位置に最も近いテストのみを実行する
  • 最後に失敗したテストだけ実行する (--only-failures オプション)
  • 最後のテストコマンドを再実行する
specファイル全体を実行 現在のカーソル位置に最も近いテストを実行
specファイル全体の実行 現在のカーソル位置に最も近いテストを実行

これらは test.vim の機能にかなり影響を受けている。 個人的には、実行も速い 現在のカーソル位置に最も近いテストのみを実行する 機能はかなりよく使う。

なお、rspec /path/to/spec のようにアプリケーションの全specを実行するコマンドは対応しなかった。全specの実行はエディタ上でやるよりも、コマンドラインから実行する方が何かと都合が良いと感じているからだ。

2. 非同期でのテスト実行

rspec.nvim では rspec を非同期で実行するため、現在の編集作業をブロックしない。

以下は rspec を実行中でも自由にカーソル移動できていることを示す gif 動画。 非同期でのテスト実行

さらに、誤って複数回実行してしまわないように、重複実行を防ぐ仕組みも実装している。 重複実行を防ぐ

test.vim では Vim の Terminal 等を使い小さめのウィンドウを生成してそこでテストを実行するということをしていたが、現在のウィンドウの幅が狭まってしまうこととフォーカスが奪われるのが微妙だなと思ったので踏襲しなかった。

3. 実行コマンドと実行パスの自動選択

rspec の実行コマンドとして bin/rspec を使いたい場合や bundle exec rspec を使いたい場合がある。

rspec.nvim では、カレントディレクトリから親ディレクトリを辿り、 bin/rspec もしくは Gemfile の存在を確認して、以下の優先順位で実行コマンドを自動で決定するようにした。

  1. bin/rspec
  2. bundle exec rspec
  3. rspec (global)

多くの場合はこの優先順位で問題ないだろうと思っている。

また、コマンド実行時のカレントディレクトリもいい感じに自動移動するようにしており、 bin/rspec を選択した場合は bin/ があるディレクトリと同じ階層、bundle exec rspec を選択した場合は Gemfile があるディレクトリと同じ階層に移動する。

これにより、例えば Rails アプリの spec ディレクトリ配下の深い位置にいる状態であっても、rspec を実行できるようになっている (通常はRailsのルートに移動しないとうまく実行できない)。

4. 失敗したテストを quickfix に自動で追加

失敗したテストが存在する場合、それらを quickfix に自動で登録するようにした。
quickfix なので、リストから選択して失敗したテスト位置へジャンプできる。

失敗したテストをquickfixに追加

失敗したテストがある場合はすぐに修正することが多いと考え、デフォルトでは quickfix window を自動でオープンするようにしているが、もしかすると邪魔になるかもしれないと思い、設定 open_quickfix_when_spec_failed で挙動を変更できるようにしている。

5. テスト結果の詳細を素早く確認できる

まず、テストを実行するとその結果のサマリーを Neovim のコマンドライン行に1行で出力するようにしている。

成功時の出力
成功時の出力
失敗時の出力
失敗時の出力

これは、成功失敗に限らず必要最小限の情報をすぐに確認できるようにしたい意図がある。

また、テスト結果の詳細(= rspecの出力結果)については Neovim の floating window で確認できる。

テスト結果の詳細を確認

内容を確認したら window をサッと閉じることができるように Enter, q, Esc キーで閉じるようにしてある。

キーマップ

rspec.nvim の機能はすべてコマンドの実行が起点になっているが、開発作業中に都度コマンドを呼び出すのは面倒なのでキーマップの設定をおすすめする。

以下は今自分が設定している比較的覚えやすいと思うキーマップ。

vim.keymap.set("n", "<leader>rn", ":RSpecNearest<CR>", { noremap = true, silent = true })
vim.keymap.set("n", "<leader>rf", ":RSpecCurrentFile<CR>", { noremap = true, silent = true })
vim.keymap.set("n", "<leader>rr", ":RSpecRerun<CR>", { noremap = true, silent = true })
vim.keymap.set("n", "<leader>rF", ":RSpecOnlyFailures<CR>", { noremap = true, silent = true })
vim.keymap.set("n", "<leader>rs", ":RSpecShowLastResult<CR>", { noremap = true, silent = true })

終わりに

初めて作った Neovim プラグイン、 rspec.nvim の紹介でした。

興味を持ってくれた Neovim 使いの Rubyist の方は使ってみてくれると嬉しい。