Neovimのフローティングウィンドウでシェルコマンドを実行する方法の個人的まとめ
Neovimのフローティングウィンドウでシェルコマンドを実行する方法について調べていたのでまとめます。
概要
nvim_open_win()
の解説
フローティングウィンドウを開くために使用するのはnvim_open_win(buf, enter, config)
関数です。3つの引数をとります。
第一引数はフローティングウィンドウに表示するバッファ番号です。今回はシェルコマンドを実行してすぐに破棄されるため、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"
を指定すると、number
、relativenumber
、cursorline
といったオプションがオフになります。一つ一つset
しなくて良いので楽です。
ウィンドウを開いてシェルコマンドを実行する関数
上記のnvim_open_win()
および以下の記事で作成したs:terminal_autoclose()
を利用すると、以下のテンプレートができます。
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
- 一時バッファを作る
- フローティングウィンドウを画面いっぱいに展開
- コマンドを実行
- オプションをセット
- 元のウィンドウに復帰
…という処理を行っています。
ここで作成するウィンドウの設定、実行するシェルコマンド、セットするオプション等を書き換えることで、いろいろなコマンドを好みの形で実行できます。
利用例
ここからは利用例を示します。
画面全体でシェルコマンド実行
以下は、画面全域に透明なフローティングウィンドウを展開し、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()
winblend=100
にしているので、SLのAA部分だけが画面に表示されます。
フローティングウィンドウは編集中のウィンドウとは独立しているので、SLを眺めながら編集を継続できます。
同じノリでcmatrix
を動かす例も作ってみたのですが…sl
と違い、画面全体に雨が降り続いてしまうので、これを実行しながら編集するのは難しいですね。
なんとか見られる例
逆に、通常のバッファでcmatrix
を実行し、編集対象ファイルを半透明のフローティングウィンドウに表示させたところ、割とそれっぽい表示を作ることができました↓
ただし、ウィンドウ操作(分割とか)が通常のようにできなくなるので、これで編集するのはやっぱり難しいかもしれません。
実行したシェルコマンドを対話的に操作
実践的な利用例です。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()
なお、lazygitの実行だけであればプラグインが存在します。
画面の一部でシェルコマンドを実行
サイズを指定したフローティングウィンドウの例です。
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()
(このツイートの時点では'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()
参考
この記事は、こちらの自由研究でいろいろ調査した結果をまとめたものです。
「フローティングウィンドウでコマンドを実行する」というプラグインであれば既にvim-floatermがあります。
SLを走らせるような使い方でなければ、このプラグインを使ったほうが早いかもしれません。
APIの引数などについてはこちらの記事も参考になります。
Discussion