👋

Neovimで非同期でシェルコマンドを実行して完了後に自動で閉じる技術

2022/08/22に公開

Neovimのターミナル設定の個人的まとめです。

https://twitter.com/kawarimidoll/status/1558716438004658176

Neovimで:terminal echo hello worldのようにシェルコマンドを実行すると、[Process exited 0]のようにステータスコードを表示して実行を停止します。
このあと、何らかのキーを入力することでターミナルが終了します。


コマンドの終了ステータスが出る

echo hello worldなどであれば表示を見たいので実行が停止してくれたほうが良いのですが、:terminalで開始したインタラクティブシェルでexitを実行したときなど、そのまま終了したほうが都合の良い場合もあるでしょう。


exitしたんだからそのまま終了してほしい

この場合、:terminalコマンドではなく、termopen()関数を使用すると即終了することができます。

TerminalAutocloseコマンド

以下の設定を使います。

function! s:terminal_autoclose(cmd) abort
  let bn = bufnr()
  let cmd = get(a:, 'cmd', '')
  if cmd == ''
    let cmd = &shell
  endif

  let opts = { 'on_exit': { -> { execute(bn .. 'bwipeout', 'silent!') } } }
  call termopen(cmd, opts)
  normal! G
endfunction
command! -nargs=* TerminalAutoclose call s:terminal_autoclose(<q-args>)

TerminalAutocloseコマンドは、無引数で実行するとインタラクティブシェルを開始し、引数を渡すとそれをシェルコマンドとして実行します。

ここでのポイントは、termopen()関数の第二引数のオプションにわたすon_exitフックです。これは、termopen()関数の第一引数のコマンドが終了したときに呼ばれる関数です。
ここでbwipeoutを実行することで、コマンド終了時に[Process exited 0]を出さずにバッファを消去(つまり、自動で終了)できます。

https://neovim.io/doc/user/builtin.html#termopen()

利用例

上記のTerminalAutocloseの利用例を紹介します。

command! RunCommand botright split +enew
  \ | execute 'TerminalAutoclose some_command'
  \ | set bufhidden=wipe | wincmd p
  1. botright split +enewで現在のウィンドウの下に新しくターミナルウィンドウを作る
  2. TerminalAutocloseでコマンドを実行
  3. バッファが閉じたときに自動で破棄するよう設定
  4. wincmd pで元のウィンドウにフォーカスを戻す

これにより、シェルコマンドを実行しつつ、カレントバッファで編集を続けることができます。
:!を使う場合と異なり、時間がかかるコマンドでも編集作業がブロックされません。

ターミナルウィンドウでコマンドを直接停止した場合にはon_exitbwipeoutが、:onlyなどでウィンドウを閉じた場合にはbufhidden=wipeが発動し、バッファが破棄されます。

一定時間で自動終了するコマンドの例

一定時間で実行終了する例です。
gh graphを実行し、3秒後に自動終了します。
即終了すると結果を見ることができないので、&& sleep 3を入れています。

command! GhGraph botright 10 split +enew
  \ | execute 'TerminalAutoclose gh graph && sleep 3'
  \ | set bufhidden=wipe | wincmd p

https://zenn.dev/kawarimidoll/articles/75430b40622e7c

なにか情報をちらっと確認したい場合などに便利です。
ここではsleepで3秒待機させていますが、元から一定時間後に終了するコマンドに対しても使えます。


GhGraph

実行し続けるコマンドの例

終了するまで実行し続ける例です。
nyancatを実行します。

command! Nyancat botright split +enew
  \ | execute 'TerminalAutoclose nyancat'
  \ | set bufhidden=wipe | wincmd p

https://github.com/klange/nyancat

エディタの下でなにかのコマンドを動作させておきたい場合に使えると思います。
watch系のコマンドとは相性が良さそうです。
開発サーバーを実行する場合などはbufhidden=hideのほうが良いかもしれません。


Nyancat

splitではなくフローティングウィンドウで使う例

splitではなくフローティングウィンドウで実行することも可能です。別記事に記載します。

comming soon!

参考

こちらの実装を一部参考にしました。

https://github.com/kassio/neoterm

Discussion