この記事はVim3 Advent Calendar 2019 6日目の記事です。
NeoVimに乗り換えるきっかけにもなった、Floating Window。
このFloating Window、めちゃくちゃ遊べる要素があるなと思い、こんなものを作って遊んでいた。
遊んでいるときに色々役立ったVim関数をまとめたいと思う。
- 文字列幅を取得する
- floating windowの位置、大きさを変更する
- floating windowの背景色をランダムな色でセットする
- window_idからwindow_numを取得する
- 指定したfloating windowにコマンドを実行する
- メインウィンドウ以外のウィンドウを全て閉じる
- floatをintに変換
- 終わり
こちらも合わせてどうぞ。
文字列幅を取得する
- 文字数と同じ幅のfloating windowを生成したいときに利用
文字の幅や文字数を取得する関数として
- strwidth()
- strlen()
- strdisplaywidth()
- strlen(substitute('ほげほげ', '.', 'x', 'g'))
など色々あるが、マルチバイトの文字列などを考えなくて済むのはstrdisplaywidth()
だった。
文字列数とウィンドウ幅はイコールではないことを知っておこう。
floating windowの位置、大きさを変更する
- redrawがポイント。while文中などでは再描画が必要になる
floating windowの位置を変更するにはnvim_win_set_config()
で新たにconfigをセットしてあげればよい。幅や高さを変えたいときはconfigをセットする必要はなく、nvim_win_set_width()
やnvim_win_set_height()
を使えば良い。
ただ、移動をアニメーションっぽく見せるためにはconfigをセットした後にredraw
とsleep
が必要になる。
redraw
が無いと、関数を実行した後にvimが再描画されるため、移動後のfloating windowしか表示されない。
また、sleep
がないと一瞬で描画が終わってしまうため、アニメーションっぽくならない。
下記は右下に1ずつ移動していくvimscript。
" floating windowを生成 let config = { \'relative': 'editor', \ 'row': 10, \ 'col': 10, \ 'width': 10, \ 'height': 10, \ 'anchor': 'NW', \ 'style': 'minimal', \} let buf = nvim_create_buf(v:false, v:true) let win_id = nvim_open_win(buf, v:false, config) " floating windowを移動させる let i = 0 while i < 10 let newConfig = { \'relative': config.relative, \'row': config.row + i, \'col': config.col + i \} call nvim_win_set_config(win_id, newConfig) let i += 1 redraw sleep 100ms endwhile
floating windowの背景色をランダムな色でセットする
- 複数のfloating windowを生成するときに区別できるとデバッグがしやすい
これはもうちょっとスマートに書けると思うが、デバッグ用なのでまあいいかなと甘んじてる。
1桁目(%x
)と残り5桁(%05x
)で分けているのは、そうした方が似た色が出にくいから。
" 60000はざっくり計算。00000~fffffの10進数以内だったら何でもいい。 let color = '#' . printf("%x", Random(16)) . printf("%05x", Random(60000)) let hl_name = 'ClipBG' . i execute 'hi' hl_name 'guifg=#ffffff' 'guibg=' . color call nvim_win_set_option(win_id, 'winhighlight', 'Normal:'.hl_name) " 指定した整数内で乱数を生成 function Random(max) abort return str2nr(matchstr(reltimestr(reltime()), '\v\.@<=\d+')[1:]) % a:max endfunction
参考にさせて頂いたサイト
Draw color wheel in NeoVim · GitHub
Native Vim Random number script - Stack Overflow
window_idからwindow_numを取得する
これ単体だとあまり使わないが、後述する「指定のfloating windowにコマンドを実行する」ときに必要となる。
function! s:winid2tabnr(win_id) abort return win_id2tabwin(a:win_id)[1] endfunction
このコードは下記のサイトから頂きました。
ここのサイトめちゃくちゃ綺麗にまとまっていて、かつほぼ全ての相互変換を網羅してるので本当に助かりました、感謝です。
指定したfloating windowにコマンドを実行する
- 指定のfloating windowにexecuteしたいときに必要となる
floating windowでガッツリ遊ぼうと思ったら、特定のfloating windowにコマンドを実行したいってことが結構ある。
そんなときはwindo
を使う。
" :help windo :{window number}windo {cmd}
例えばメインウィンドウにフォーカスを戻したいなら:0windo :
となる。
0番は最初に開いたウィンドウとなるのでおそらくメインウィンドウだろう。cmd
に:
を渡しているのはShellScriptと同じ感覚で何もしないことを示す。
関数として作っておくと何かと便利(引数にwindow numberを渡してもいいと思う)
" メインウィンドウにattach function! s:focus_to_main_window() execute "0windo :" endfunction
また、これは前述のwindow_idからwindow_numberを取得するものと一緒に使うことが多い。 対象のfloating windowに文字列を挿入する、みたいなときはこうする。
" 指定したウィンドウIDのウィンドウの番号を得る function! s:winid2tabnr(win_id) abort return win_id2tabwin(a:win_id)[1] endfunction " 指定したfloating windowの現在行に'hoge'を挿入 let win = s:winid2tabnr(win_id) execute win . 'windo ' . "call setline('.', 'hoge')"
windo
を連発するのが面倒な場合、下記のように一旦指定のfloating windowにフォーカスを移してから実行していくと楽。
let win = s:winid2tabnr(win_id) " 指定のwindowにフォーカスを移す execute win . 'windo :' " フォーカスを移した先のwindowで実行される call setline(".", "hoge")
メインウィンドウ以外のウィンドウを全て閉じる
Ctrl-w Ctrl-o
生成したwindowを全て閉じるときに便利。:ls
を実行すると消えていることがわかる。ただし完全には削除されない。
:ls!
を実行するとvim上には残っていることがわかる。
完全に消すには
:bw[N]
でNにバッファ番号を指定すればOK。
こちらのサイトを参考にさせて頂きました。
floatをintに変換
- floating windowのconfigでcolとrowがfloatだが、計算するときにはint型で欲しい
floating windowを移動させるときなど、ある程度計算を行った後にcolやrowに値をぶち込むことが多い。
その計算途中でfloatだと面倒なので必要になった。
" float型をint型に変換 let col = float2nr(config.col) + 10
終わり
所詮小さいScriptなので、なんとなく頭の中でこうやればできるなと思うことはできるが、いざ実装しようとすると何の関数を使えばいいのか、そもそもあるのかわからなくてめげそうになる。
ただ、Vimは先人様たちが必要だと思ったのは大体入っている。もしない場合は他で代替可能だという無言のメッセージだと捉えれば、楽しくVimScript開発できる。