Vim のプラグインを書くときに、後でこの処理を実行したい、ということがしばしばあります。この場合二つの選択肢があり、自動コマンド (:help :autocmd
) かタイマー (:help timer
) が使えます。ところがこの二つは全く異なるインターフェースをもっており、似たような目的であっても全く違った記述をしなければなりません。また組み合わせて使う場合にはコードが複雑になりがちです。
また、Vim script あるあるの一つだと思うのですが、一回だけ自動コマンドで処理したい、ということがよくあります。この場合自動コマンドイベントに関数をセットし、その関数自身が自動コマンドを解除することで実現します。
function! s:main() abort
" set the autocmd event for s:run_once()
augroup my-group
autocmd!
autocmd WinLeave * call s:run_once()
augroup END
endfunction
function! s:run_once() abort
call s:do_something()
" unset the autocmd events
augroup my-group
autocmd! WinLeave
augroup END
endfunction
が、正直毎回これを書くのは面倒くさいです。そもそも実行したいのは s:do_something()
なのに、s:run_once()
のような関数でラップする手間がかかります。このような定型作業は繰り返したくありません。
vital-Schedule をつかう
vital-Schedule
は上のような不満を解決するためのモジュールです。上の例を vital-Schedule
を使って書き直してみます。
let s:Schedule = vital#{pluginname}#new().import('Schedule')
function! s:main() abort
" set task
let task = s:Schedule.Task()
call task.call(function('s:do_something'), [])
call task.waitfor(['WinLeave'])
endfunction
task.call()
メソッドは組み込みの call()
関数と同じインターフェースを持っています。このメソッドに渡された関数が後で実行されます。これに直接 s:do_something()
を渡すことにより、 s:run_once()
が必要なくなりました。自動コマンド周りの諸々の手続きは vital-Schedule
が代替します。
もっと具体的な例を考えてみましょう。例えば何らかの編集を行い、その行を一定時間ハイライトしたい場合を考えてみましょう。ハイライトはタイマーをつかい時間経過で消すのですが、ユーザーがそれ以前に次の編集を開始した場合にも消したいとします。つまり、タイマーと自動コマンドの組み合わせです。
let s:Schedule = vital#{pluginname}#new().import('Schedule')
function! s:highlight(lnum, duration) abort
let id = matchaddpos('MyPluginHigroup', [a:lnum])
" set task
let task = s:Schedule.Task()
call task.call('matchdelete', [id])
call task.waitfor([a:duration, 'TextChanged', 'TextChangedI'])
endfunction
s:highlight()
関数に行番号とハイライトの持続時間を渡すと一時ハイライトをしてくれます。(実際にはバッファやウィンドウを移動した場合なども考える必要があるかもしれません)
タイマーと自動コマンドを組み合わせて、「どれか最初に発火したもの」という条件で登録した関数を実行しています。
コンストラクタ (のようなもの) に文字列を渡すことで augroup を指定することができます。これは省略可能で、省略した場合は vital-Schedule
が使われます。 augroup なしではありません。人類は augroup なしの自動コマンドを定義すべきではありません。
let Schedule = vital#{pluginname}#new().import('Schedule')
let task = Schedule.Task('my-group')
あるいは augroup()
関数を使うと augroup の固定されたモジュールのコピーが使えます。下の例は上の例と同じ意味になります。
let Schedule = vital#{pluginname}#new().import('Schedule').augroup('my-group')
let task = Schedule.Task()
あまり vital.vim のことをよくわからずに書いているので、おかしいところがあったら教えていただけるとありがたいです。 :help vital.vim
すると、 How to make a vital module と External vital modules へのリンクがありましたが前者の情報は古いっぽい? :help
から読めるとうれしいんだけど翻訳するのは大変そう。
あと今回、辞書関数を積極的に使ってみたらスタックトレースに関数名が出ず、デバッグ作業が地獄と化しました…なにかいい方法はないものか…