読者です 読者をやめる 読者になる 読者になる

Vim の +timer を使ってみる

そろそろいいかなと思ってまとめてみた。

[+chancel+job+timer]

さて、今まで Vim で外部コマンドの非同期処理と言えば、vimproc.vim を使用してコマンドを実行し、CursorHold でプロセスを監視するのが一般的でした。
しかし、今年の頭に Vim 本体に他のプロセスとソケット通信を行う事ができる +channel という機能が実装されました。
このソケット通信は完全に非同期で処理が行われます。

mattn.kaoriya.net

この機能が実装されたのを皮切りにソケット通信だけではなく、『外部コマンドを非同期で実行する+job』や『タイマー機能を扱う事ができる +timer』などの機能が次々と実装されました。

qiita.com

しかし、実装当初は +channel+job などはあまり安定しておらず、実際に使用するには少し抵抗がありました。
特に +channel は実装直後に関数名に変更が入ったりと破壊的な変更が多かったです。

github.com

そんな +channel+job ですがさすが 4ヶ月も経ち安定してきたかな?と思い始めたのでガッツリと試してみることにしました。 実際触っている時にも(使い方の情報が少ない等の問題はありましたが)不具合らしい不具合はなかっったと思います。 また直接は関係ありませんが、最近では Funcref の扱いがものすごくやりやすくなったのも追い風です。 これのおかげで +job などの使い勝手が向上したと言っても過言ではないレベルです。

[注意点]

当然ですが +job などを使用したい場合は使用する Vim+job に対応している必要があります。 また、安定してきてるとはいえバグが全くない可能性は0ではないのでなるべく最新版の Vim を使用しましょう。 あと日本語のドキュメントなのですが、最新版の仕様に追従していないようなので :help を参照するときは必ず英語版を参照しましょう(see: :help channel@en。 英語がわからなくてもサンプルコードが載ってるのでなんとなくわかると思います。

[+timer を使ってみる]

と、いうことで早速使ってみます。 まずは敷居の低い +tiemr から。 タイマーは『nミリ秒後』に指定した『関数』を呼び出すことができます。 timer_start({time}, {callback} [, {options}]) でタイマーを開始し、 timer_stop({timer}) で任意のタイマーを終了させることが出来ます。

" 引数に timer を受け取る
function! Disp(timer)
    echom "homu"
endfunction

" タイマーを開始する
" 3000ミリ秒後に Disp() を呼び出す
let timer = timer_start(3000, function("Disp"))

" timer_stop() に timer ID を渡すことで任意のタイマーを終了させる
" call timer_stop(timer)

こんな感じで使うことが出来ます。

[呼び出される回数を指定する]

timer_start() の第三引数のオプションで呼び出される回数を指定することも出来ます。

" "repeat" を指定することで呼び出される回数を指定する事ができる
" 1000ミリ秒毎に 5 回呼び出す
let timer = timer_start(1000, function("Disp"), { "repeat" : 5 })

[永続的にタイマーを呼び出す]

この "repeat"-1 を設定することで永続的にタイマーを呼び出すことも出来ます。

" "repeat" を指定することで呼び出される回数を指定する事ができる
" 1000ミリ秒毎に 5 回呼び出す
let timer = timer_start(1000, function("Disp"), { "repeat" : 5 })

この時に timer_stop() を呼び出さない限りは永遠とタイマーが呼び出されてしまうので注意しましょう。

[辞書関数をコールバック関数として使用する]

最近の Vim では辞書関数を call() 経由ではなくて直接呼びだせるようになったので、次のような事が出来ます。

let counter = { "count" : 0 }
function! counter.call(timer)
    echom self.count
    let self.count += 1
endfunction

" 辞書関数をコールバック関数として設定
let timer = timer_start(1000, counter.call, { "repeat" : 10 })

" function() を使用した場合
" let timer = timer_start(1000, function(counter.call, [], counter), { "repeat" : 10 })

辞書関数を使うことでより柔軟にタイマーを使うことが出来ます。

[timer_stop() を使うときの注意点]

timer_stop() ですが timer_start() のコールバック関数内で使用しても耐マーが止まらないので注意しましょう。

secret-garden.hatenablog.com

長くなったので +job は次回へ。