Hack #173: Vimでタイマー機能を模倣する
Posted at 2010/09/12ツイート
VimにはEmacsとは違ってタイマー機能がありませんが、タイマーが欲しくなることがままあります。ここではタイマー機能について議論を行い、Vimで模倣する方法について述べます。
タイマー機能が必要なとき
まず、どのようなときにタイマー機能が必要なのでしょうか。例えば処理のポーリングです。Vimと外部プロセスが通信をした場合、Vimがプロセスの終了を待っていると、その間はVimの動作がストップしてしまいます。これでは他の作業ができなくなるので、大きなストレスとなります。そして残念ながらVimでは割り込みが使えません(#1)。そこでタイマーを使って時々通信をしながら処理の完了を待つという方法を通常取ります。
1: vim-server
という機能を使えばできないこともありませんが、ここでは触れません。
Emacsのタイマー機能
VimのライバルであるEmacsにはきちんとタイマー機能があります。これは外部プロセスとの通信のために実装されたと考えられます。巨大なデスクトップ環境であるEmacsでは、インタプリタとの通信を実現するためにタイマー機能が必要不可欠だからです。ただし、タイマーのチェックはアイドル時間にのみ動作します。これは考えてみればあたりまえの話です。もし、処理の途中に割り込まれるとすると、プログラムの状態がどうなるか予想ができないからです。Emacsのタイマーでは一応呼び出す時間を設定できますが、アイドル時にしか呼びだせないので、正確な時間で処理が行われるとは限りません。
タイマー機能の模倣
タイマー機能の模倣はInsert modeとNormal modeで微妙に方法が異なります。
基本的にはCursorHold, CursorHoldIのautocmdに関数の呼び出しのイベントを設定します。
CursorHoldI, CursorHoldは新たにキーが押されない限り連続して発生することがないので、処理の後にダミーのfeedkeys()
を呼び出し、関数が連続して呼ばれるようにします。
任意の時間でイベントを発生させるためには、’updatetime’を保存・復元する必要があります。
autocmd CursorHold <buffer> call s:foo()
function s:foo()
" 何か処理をする
call feedkeys("g\<ESC>", 'n')
endfunction
autocmd CursorHoldI <buffer> call s:bar()
function s:bar()
" 何か処理をする
call feedkeys("\<C-r>\<ESC>", 'n')
endfunction
疑似タイマーの欠点
残念ながら、疑似タイマーでは次の問題があります。
コマンドごとにタイムアウトを指定できない
‘updatetime’は一つしか設定できないため、複数のプラグインが同時に書きかえようとすると干渉します。 しかもグローバルなオプションなので、値の復元を忘れると影響が大きくなります。 ‘updatetime’は本来スワップファイルの書き出しのための時間です。todoには’holdtime’を新設し、この変数を分離しようという記述がありますが、まだ実現される見こみはありません。Vim 7.3でも実装されていません。
CursorHoldIはポップアップメニューが出てくると動作しない
これは補完処理にタイマーを使おうとした場合に問題となります。CursorMovedIはポップアップメニューの候補がなくなったらイベントが発生するのでまだマシですが、CursorHoldIはそうではありません。よって、neocomplcacheではCursorHoldIとCursorMovedIを無理矢理組み合わせています。
CursorHoldIを連続して発生させるのが難しい
call feedkeys("\<C-r>", 'n')
ではキー操作が発生するので副作用があります。feedkeys()
を呼んですぐにキーが押される訳ではないので、タイミングによっては動作しないこともあるようです。せめて、call feedkeys("\<NOP>")
が使えるといいのですが、これではCursorHoldIは発生しません。
マルチスレッドを使っても問題は解決しない
Vimは外部インタフェースを用いることで、マルチスレッド化したプラグインを書くことができます。Pythonの外部インタフェースの仕様については、Hack #132: Pythonインタフェースを使う(1)を参照してください。しかしマルチスレッドを用いたとしても、マルチスレッド側でVimのバッファをいじると高確率でクラッシュします。Vimはマルチスレッドセーフに作られていないからです。よって通信スレッド側では処理をキューに格納し、メインスレッド側でバッファをいじることになります。よってタイマー機能は必須です。
理想的なタイマー機能
理想的なタイマーとは次のようなものです。
autocmd CursorHoldI <timer=1000> call foo()
<timer=xxx>
というのがイベントの実行時間を表します。単位はms。CursorHoldI, CursorHoldでのみ有効なオプションです。
省略されると今までと同様にholdtimeかupdatetimeを使います。当然、ポップアップメニューが消えても動作します。
Real Vim Hacksプロジェクトでどうにか実現したいものです。
もどる
blog comments powered by Disqus