🦕

NeovimでDenoを開発するときにめっちゃ効率上げるコマンドを作った

2021/10/02に公開

追記ここから
本記事の内容はNeovim専用です。
以下の記事で紹介しているものは、エディタを問わず、便利な機能が追加されています。
https://zenn.dev/kawarimidoll/articles/d371abbd46b65b
今後はこちらをご利用ください。
追記ここまで


最近、いろいろとDenoで開発しています。
以下のような制作物をZennで紹介しています。

開発を続けていくうち、サンプルやテストを書いて、確認のために手動で何度も実行するのが億劫になってきました。

そんな中、こちらの記事にインスパイアされ、変更を自動検出して確認できる環境があると良いなと認識しました。
https://zenn.dev/yutaro_elk/articles/ce343b13506364

ということで、NeovimでDenoを自動実行するコマンドを作ったので紹介します。

NeovimでDenoを自動実行するコマンド

こちらがDenoRunコマンドです。

上記をinit.vimに保存してください。

旧バージョン

最初はDenoRunコマンドからDnoTerm関数を呼び出す形で作っていました。

~/.config/nvim/init.vim
function! s:DenoTerm() abort
  let l:filename = expand('%:p')
  let l:cmd = l:filename =~ '\(\.\|_\)\?test\.\(ts\|tsx\|js\|mjs\|jsx\)$'
   \ ? 'test' : 'run'
  only | echo '' | split | wincmd j | resize 12 |
   \ execute 'terminal deno ' . l:cmd .
   \ ' -A --no-check --unstable --watch ' . l:filename |
   \ stopinsert | execute 'normal! G' |
   \ set bufhidden=wipe | wincmd k
endfun
command! -bang DenoRun call s:DenoTerm()

現行版とは以下の点が異なります。

  • onlyの出力を消すためにecho ''を使用していた
  • 正規表現の判断がDenoと異なるものがあった
    • Denoはatest.tsのようにtestの前にアルファベットが来るものをテストとみなさないが、この正規表現はテストと判断してしまう
    • こういうファイルを使う場合があるかは不明
  • ウィンドウ分割にsplit | wincmd j | resize 12と3コマンド使っていた

動作

:DenoRunを実行すると、画面が上下に2分割され、下側の画面がターミナルとなります。このターミナル内で現在のファイルに対しdeno runが実行されます。
--watchオプションにより、ファイルの保存で自動的にdeno runが再実行されるため、開発に集中できます。
また、テストファイルで実行すると、deno runではなくdeno testが走ります。

解説

以下は既にNeovimスクリプトに詳しい方、Denoの実行に関することだけ知りたい方には不要な内容だと思いますが、関数の中身を知りたい方は一読していただくと調整などしやすくなるかと思います。

複数のコマンドを順次実行しているだけなので、各々について解説します。

silent only

複数のウィンドウが開いている場合に、現在フォーカスのあたっているウィンドウ以外を全て閉じます。
これがなくても動作するのですが、:DenoRunを複数回実行したときにターミナルがいくつも開いてしまうのを防ぐため、前処理として呼び出しています。

また、silentは本質に関わらないのですが、ウィンドウが一つしかないときに単にonlyを実行すると「既にウィンドウは1つしかありません」という警告メッセージがされて煩わしいので、silentを使うことでこれを除去しています。

botright 12 split

現在のウィンドウの下側に、高さ12行のウィンドウを新しく開きます[1]。「日」みたいな状態になりますね。

適切な行数は画面解像度などによって変わると思うので、好みによって調整してください。

execute 'terminal deno ' . (expand('%:t') =~ '^(.*[._])?test.(ts|tsx|js|mjs|jsx)$' ? 'test' : 'run') . ' -A --no-check --unstable --watch ' . expand('%:p')

メインの処理です。下側のウィンドウでターミナルを開き、そこでdeno [run or test] -A --no-check --unstable --watch [現在のファイル名]を実行します。

(expand('%:t') =~ '^\(.*[._]\)\?test\.\(ts\|tsx\|js\|mjs\|jsx\)$' ? 'test' : 'run')は現在のファイル名がDenoのテストファイル形式に該当するかを正規表現で調べ、テストファイルならtestに、そうでなければrunになります。エスケープだらけなのはVimの正規表現の仕様です。
最後のexpand('%:p')は現在のファイルのフルパスになります。

execute 'terminal ...'ではなくtermopen()でも動作可能です。

開発で使うことを想定しているため全ての権限を許可した状態でdeno runしていますが、外部のスクリプトを開いて実行する場合はご注意ください。

stopinsert

ターミナルモードから抜けます。
公式ヘルプにある以下の設定がされていると、ターミナルに入った時点でターミナルモードになっているため、stopinsertしてモードを切り替えます。

~/.config/nvim/init.vim
autocmd TermOpen * startinsert

初めからノーマルモードの場合はそのままです。

execute 'normal! G'

Gでターミナルバッファの末尾に移動します。
これをしないと結果が更新されても画面が追従スクロールしていかないためです。

一つ前のstopinsertでノーマルモードになっているので、execute 'normal! G'ではなく$でも動作可能です。

set bufhidden=wipe

ターミナルバッファのbufhidden設定を変更します。
デフォルトはhideなのですが、このままだと前述のonlyなどでバッファを非表示にした際にターミナルがバックグラウンドで残り続けてしまうため、wipeを設定して非表示とともに削除するようにします。

独立したコマンドとして実行するのではなく、split+cmdオプションに渡してもOKです。

execute 'autocmd BufEnter <buffer> if winnr("$") == 1 | quit! | endif'

バッファローカルな自動コマンドを定義します。
意味は、「このバッファに入ったときにウィンドウ数が1なら終了する」です。
Vimを終了しようとしてエディタ側を閉じたときに、ターミナルだけが残りつづけてしまうのを防ぐための設定です。
Vista.vimのissueに出ていたhookを参考にしました。

https://github.com/liuchengxu/vista.vim/issues/358

wincmd k

フォーカスを上側のウィンドウに移動します。つまり、元のエディタ側に戻ります。

結果として、エディタ側はそのままで、下画面でファイル保存監視中のターミナルが立ち上がっている状態が出来上がります。

おわりに

動作確認が非常に楽になりました。今まで手動で再実行していたのが信じられません…。
これを使ってDenoの開発を加速させていければと思っています。
Neovimを使っている方はお試しください[2]
また、もっと良いやり方がありましたらコメントいただけると幸いです。

脚注
  1. 「分割線が水平に入るから水平分割」なのか「上下に分割されるから垂直分割」なのかどっちなんでしょうか ↩︎

  2. Vimでも一部を変更すれば動作しそうだと思いますが試していません ↩︎

Discussion