🕴️

Neovimのフローティングウィンドウでシェルコマンドを実行する方法の個人的まとめ

2022/08/27に公開

Neovimのフローティングウィンドウでシェルコマンドを実行する方法について調べていたのでまとめます。

https://neovim.io/doc/user/api.html#api-floatwin

概要

nvim_open_win()の解説

フローティングウィンドウを開くために使用するのはnvim_open_win(buf, enter, config)関数です。3つの引数をとります。

https://neovim.io/doc/user/api.html#nvim_open_win()

第一引数はフローティングウィンドウに表示するバッファ番号です。今回はシェルコマンドを実行してすぐに破棄されるため、nvim_create_buf(v:false, v:true)で一時バッファを作るのが良いと思います。
第二引数はウィンドウに移動するかどうかの真偽値です。今回は作ったウィンドウでシェルコマンドを実行したいのでv:trueを指定します。
第三引数はウィンドウの設定で、辞書を渡します。今回の記事で使用するキーのみ紹介します。

  • row: 縦位置の指定です。小数も使えます。
  • col: 横位置の指定です。小数も使えます。
  • width: ウィンドウの幅です。最低値は1。
  • height: ウィンドウの高さです。最低値は1。
  • relative: row/colの基準の指定です。この記事では"editor"を指定して、エディタ画面全体を基準にします。
  • anchor: 四隅のどれをrow/colの位置にするか指定します。東西南北に対応しています。 これ南半球だと通じないのでは…?
    • "NW": 北西=左上 (デフォルト)
    • "NE": 北東=右上
    • "SW": 南西=左下
    • "SE": 南東=右下
  • focusable: :wincmdなどでウィンドウにフォーカスできるかの真偽値です。デフォルトは真。なお、ここを偽にしてもnvim_set_current_win()を使えばフォーカスできます。
  • style: "minimal"を指定すると、numberrelativenumbercursorlineといったオプションがオフになります。一つ一つsetしなくて良いので楽です。

ウィンドウを開いてシェルコマンドを実行する関数

上記のnvim_open_win()および以下の記事で作成したs:terminal_autoclose()を利用すると、以下のテンプレートができます。

https://zenn.dev/kawarimidoll/articles/04ffb0d2270328

function! s:shell_on_floatwin() abort
  " create empty buffer
  let buf = nvim_create_buf(v:false, v:true)

  " create float window
  let winconf = {
    \   'style': 'minimal', 'relative': 'editor',
    \   'width': nvim_get_option('columns'),
    \   'height': nvim_get_option('lines'),
    \   'row': 1, 'col': 1,
    \   'focusable': v:false
    \ }
  call nvim_open_win(buf, v:true, winconf)

  " run command
  call s:terminal_autoclose('some_command')

  " set local options
  set bufhidden=wipe

  " back to the previous window
  stopinsert
  wincmd p
endfunction
  1. 一時バッファを作る
  2. フローティングウィンドウを画面いっぱいに展開
  3. コマンドを実行
  4. オプションをセット
  5. 元のウィンドウに復帰

…という処理を行っています。

ここで作成するウィンドウの設定、実行するシェルコマンド、セットするオプション等を書き換えることで、いろいろなコマンドを好みの形で実行できます。

利用例

ここからは利用例を示します。

画面全体でシェルコマンド実行

以下は、画面全域に透明なフローティングウィンドウを展開し、slを走らせるサンプルです。

function! s:start_sl() abort
  let buf = nvim_create_buf(v:false, v:true)
  let winconf = {
    \   'style': 'minimal', 'relative': 'editor',
    \   'width': nvim_get_option('columns'),
    \   'height': nvim_get_option('lines'),
    \   'row': 1, 'col': 1,
    \   'focusable': v:false
    \ }
  call nvim_open_win(buf, v:true, winconf)
  call s:terminal_autoclose('sl')
  set winblend=100 bufhidden=wipe
  stopinsert
  wincmd p
endfunction
command! SL call s:start_sl()

https://ja.wikipedia.org/wiki/Sl_(UNIX)

winblend=100にしているので、SLのAA部分だけが画面に表示されます。
フローティングウィンドウは編集中のウィンドウとは独立しているので、SLを眺めながら編集を継続できます。

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

同じノリでcmatrixを動かす例も作ってみたのですが…slと違い、画面全体に雨が降り続いてしまうので、これを実行しながら編集するのは難しいですね。
https://github.com/abishekvashok/cmatrix

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

なんとか見られる例

逆に、通常のバッファでcmatrixを実行し、編集対象ファイルを半透明のフローティングウィンドウに表示させたところ、割とそれっぽい表示を作ることができました↓

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

ただし、ウィンドウ操作(分割とか)が通常のようにできなくなるので、これで編集するのはやっぱり難しいかもしれません。

実行したシェルコマンドを対話的に操作

実践的な利用例です。lazygitを実行します。
これは表示を眺めるのではなく操作に入りたいので、'focusable': v:trueを設定し、関数の最後でstartinsertを実行しています。
winblendの設定は無し、または10%透過くらいで設定するときれいになりそうです。

function! s:start_lazygit() abort
  let buf = nvim_create_buf(v:false, v:true)
  let winconf = {
    \   'style': 'minimal', 'relative': 'editor',
    \   'width': nvim_get_option('columns'),
    \   'height': nvim_get_option('lines'),
    \   'row': 1, 'col': 1,
    \   'focusable': v:true
    \ }
  call nvim_open_win(buf, v:true, winconf)
  call s:terminal_autoclose('lazygit')
  set bufhidden=wipe
  startinsert
endfunction
command! LazyGit call s:start_lazygit()

https://github.com/jesseduffield/lazygit

なお、lazygitの実行だけであればプラグインが存在します。

https://github.com/kdheepak/lazygit.nvim

画面の一部でシェルコマンドを実行

サイズを指定したフローティングウィンドウの例です。
bad-apple-node-jsを実行します。
'anchor': 'NE'を指定し、フローティングウィンドウの右上を基準に位置を指定しています。最上行が見られなくなってしまうのを防ぐために'row': 2にしています。

function! s:start_bad_apple() abort
  let buf = nvim_create_buf(v:false, v:true)
  let winconf = {
    \   'style': 'minimal', 'relative': 'editor',
    \   'width': 48, 'height': 20, 'anchor': 'NE',
    \   'row': 2,
    \ '  col': nvim_get_option('columns'),
    \   'focusable': v:false, 'noautocmd': v:true
    \ }
  call nvim_open_win(buf, v:true, winconf)
  let cmd = 'cd /Users/kawarimidoll/ghq/github.com/Reyansh-Khobragade/bad-apple-nodejs && yarn start'
  call s:terminal_autoclose(cmd)
  set winblend=100 bufhidden=wipe
  stopinsert
  normal! G
  wincmd p
endfunction
command! StartBadApple call s:start_bad_apple()

https://github.com/Reyansh-Khobragade/bad-apple-nodejs

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

(このツイートの時点では'row': 1にしています)

シングルトン化

nvim_create_buf()の返り値をグローバル変数に設定するとシングルトン化できます。また、その変数を使ってbwipeoutすることで実行停止が可能です。

function! s:start_bad_apple() abort
  if get(g:, 'bad_apple_buf')
    lua vim.notify('BadApple is running. Run :StopBadApple to clear.')
    return
  endif
  let g:bad_apple_buf = nvim_create_buf(v:false, v:true)
  let winconf = {
    \   'style': 'minimal', 'relative': 'editor',
    \   'width': 48, 'height': 20, 'anchor': 'NE',
    \   'row': 2,
    \   'col': nvim_get_option('columns'),
    \   'focusable': v:false
    \ }
  call nvim_open_win(g:bad_apple_buf, v:true, winconf)
  let cmd = 'cd /Users/kawarimidoll/ghq/github.com/Reyansh-Khobragade/bad-apple-nodejs && yarn start'
  call termopen(cmd, {
    \ 'on_exit': { _ ->
    \    get(g:, 'bad_apple_buf') &&
    \    execute(g:bad_apple_buf .. 'bwipeout! | unlet! g:bad_apple_buf', 'silent!')
    \ }})
  set winblend=100 bufhidden=wipe
  stopinsert
  normal! G
  wincmd p
endfunction
function! s:stop_bad_apple(...) abort
  if get(g:, 'bad_apple_buf')
    silent! execute g:bad_apple_buf .. 'bwipeout!'
    unlet! g:bad_apple_buf
  endif
endfunction
command! StartBadApple call s:start_bad_apple()
command! StopBadApple call s:stop_bad_apple()

参考

この記事は、こちらの自由研究でいろいろ調査した結果をまとめたものです。

https://zenn.dev/kawarimidoll/scraps/e2bbbb46847e00

「フローティングウィンドウでコマンドを実行する」というプラグインであれば既にvim-floatermがあります。
SLを走らせるような使い方でなければ、このプラグインを使ったほうが早いかもしれません。

https://github.com/voldikss/vim-floaterm

APIの引数などについてはこちらの記事も参考になります。
https://qiita.com/slin/items/874dbc3ca34ea83e90b7

Discussion