僕は頻繁にVimを終了しているわけではない. ある程度Vimに閉じこもり,必要に応じてシェルに移動したりする. 簡易な処理ならば,vimshell.vimで済ませることも多い. また,Vimを起動したままPCをスリープさせることもよくある. tmuxの1つのペインでVimを立ち上げたまま放置することはしょっちゅうある.
このように,比較的長時間Vimを立ち上げていることが多い. そこで気になるのが,「Vimを起動してからどのくらいの時間が経過したのか」だ. これを確認できれば,作業量を実感でき,「仕事したな~」という満足感を得られるだろう. そこで,Vimを起動している時間を計測するために,以下を.vimrcに記述した.
" Timer {{{ " .vimrcの再読み込みに備えてunlockvarする " 変数が定義されていなくても,エラーは出ない unlockvar s:Timer let s:Timer = { \ 'elapsed_time': 0.0, \ 'is_stopped': 1 \} function! s:Timer.new(name) abort dict let timer = copy(self) let timer.name = a:name call timer.start() return timer endfunction function! s:Timer.start() abort dict if self.is_stopped let self.is_stopped = 0 let self.start_time = reltime() endif endfunction function! s:Timer.stop() abort dict if !self.is_stopped let self.elapsed_time += str2float(reltimestr(reltime(self.start_time))) let self.is_stopped = 1 endif endfunction function! s:Timer.get_elapsed_time() abort dict return (self.is_stopped ? 0.0 : str2float(reltimestr(reltime(self.start_time)))) + self.elapsed_time endfunction function! s:Timer.show() abort dict let t = s:convert_time(self.get_elapsed_time()) echomsg printf('%16s: %02d:%02d:%02d.%1d', self.name, t.hour, t.minute, t.second, t.msec) endfunction lockvar s:Timer " }}} function! s:convert_time(time) abort let integer_part = float2nr(a:time) let decimal_part = a:time - integer_part let t = { \ 'hour': integer_part / 3600, \ 'msec': float2nr((decimal_part + 0.000001) * 10) \} let integer_part = integer_part % 3600 let t.minute = integer_part / 60 let t.second = integer_part % 60 return t endfunction " .vimrcの再読み込み時にタイマーがリセットされるのを防ぐ if has('vim_starting') let s:startupdate = strftime('%c') let s:timer_launched = s:Timer.new('Launched Time') let s:timer_active = s:Timer.new('Active Time') let s:timer_used = s:Timer.new('Used Time') endif augroup Timer autocmd! autocmd CursorMoved,CursorMovedI * call s:timer_used.start() autocmd CursorHold,CursorHoldI,FocusLost * call s:timer_used.stop() autocmd FocusGained,WinEnter * call s:timer_active.start() autocmd FocusLost * call s:timer_active.stop() augroup END function! s:show_time_info() abort echo 'Launched at:' s:startupdate call s:timer_launched.show() call s:timer_active.show() call s:timer_used.show() endfunction command! -bar ShowTimeInfo call s:show_time_info()
オブジェクト指向っぽく作ってみた. Vim scriptでもオブジェクト指向はできるのだ.
:ShowTimeInfo
とコマンドを実行することで,
を表示できる.
以下,コードの解説をしよう.
s:Timer
はタイマーオブジェクトの雛型である.
必要最低限のタイマーの開始(再開),停止,経過時間の表示のみを実装している.
雛型であるため,一応lockvar s:Timer
をして,後から変更が加えられないようにしている.
この雛型はs:Timer.new()
というメソッドで自身のクローンを返却する.
s:Timer.new()
は,要はコンストラクタである.
このs:Timer.new()
を用いて,
- Vimを起動してからの単純な経過時間(PCがスリープの時間を含む)を計測する
s:timer_launched
- Vimがアクティブであった時間を計測する
s:timer_active
- Vimを使用した時間を計測する
s:timer_used
という3つのタイマーオブジェクトを生成する.
また,let s:startupdate = strftime('%c')
とすることで,Vimを起動した日時の文字列をs:startupdate
に保存しておく.
ただし,strftime('%c')
で得られる日付の文字列は,時間のロケールに依存するので,決まったフォーマットで取得したいならば,予めlanguage time C
のようにコマンドを実行して,時間のロケールを変更したり,strftime()
に与える文字列を変更すべきである.
例えば,時間のロケールに依存せず,2015-07-23 01:00:00
のようなフォーマットで時間を取得したいなら,strftime('%Y-%m-%d %H:%M:%S')
と記述するのがよい.
Vimがアクティブである時間は以下のように計測する.
Vimがフォーカスを得るとFocusGained
,フォーカスが外れるとFocusLost
というイベントが発生する.
このタイミングでタイマーを開始(再開),停止すればいいだけである.
さて,このFocusGained
とFocusLost
であるが,GUI版といくつかのコンソール版でしか利用できないらしい.
KaoriYa版のCUIのVimではFocusGained
とFocusLost
利用できたが,手元のCygwin版のVimでは利用できなかった.
そのような環境では,「Vimを起動してからの単純な経過時間 = Vimがアクティブであった時間」となるだろう.
なお,稀にウィンドウがアクティブになってもFocusGained
を検知できないことがある.
確実にウィンドウがアクティブになったのを検知するために,WinEnter
のタイミングでもタイマーを開始(再開)するようにした.
前述の通り,コンソール版のVimでは,FocusGained
とFocusLost
が利用できないものがある.
そこで,Vimがアクティブである時間の代替として,Vimを使用している時間を計測しようと考えた.
しかし,これまたいいイベントが無く,CursorHold[I]
とCursorMoved[I]
を利用するしかなかった.
CursorHold[I]
は,オプション'updatetime'の間,ユーザがキーを押さなかった場合に発生する,つまり,手を止めると発生するイベントである.
反対に,CursorMoved[I]
はカーソルが移動した際に発生するイベントである.
これらのイベントを利用して,「Vimを使用している時間」とするものを計測するわけだ.
しかし,この「Vimを使用している時間」は,手を動かしてない間は全てVimを使用していないということになってしまう. 実際のコーディングでは,ずっと手を動かしているわけではなく,考え込む時間もある(むしろ,この時間の方が長いはずだ)ため,あまり現実に即した計測方法とはいえないだろう.
実は,定期的にCursorHold[I]
を発火させることもでき,「xx秒経過した後,タイマーを停止」ということを実現できるのだが,たかだかVimを使用している時間を計測するためにVimに負荷をかけるのは好きではない.
あえて,そうしたいという人がいるならば,上記コード中の
augroup Timer autocmd! autocmd CursorMoved,CursorMovedI * call s:timer_used.start() autocmd CursorHold,CursorHoldI,FocusLost * call s:timer_used.stop() " ...
の部分を以下のように変更するとよいだろう.
let s:timer_used.clock = 0 " 30秒以上経過したらタイマーを停止する let s:timer_used.time_to_stop = 30000 function! s:timer_used.update() abort dict if self.clock < self.time_to_stop && !self.is_stopped call feedkeys(mode() ==# 'i' ? "\<C-g>\<ESC>" : "g\<ESC>", 'n') let self.clock += &updatetime else call self.stop() let self.clock = 0 endif endfunction function! s:timer_used.wakeup() abort dict let self.clock = 0 call self.start() endfunction augroup Timer autocmd! autocmd CursorMoved,CursorMovedI * call s:timer_used.wakeup() autocmd CursorHold,CursorHoldI * call s:timer_used.update() autocmd FocusLost * call s:timer_used.stop() " ...
この例であれば,30秒程度Vimを放置すると,Vimの使用時間の計測を停止するようになる.
30秒「程度」としたのは,updatetime
に設定した値によっては,キッチリ30秒を計測できないからだ.
例えば,updatetime
の値が14000であれば(通常,こんな値を設定することはないと思うが),Vimを放置してから42秒後に使用時間のタイマーが停止する.
だからといって,updatetime
の値を10のように小さな値にしてしまうと,10ミリ秒毎にCursorHold[I]
が発火するため,PCに非常に負荷がかかる.
上記の設定をするのであれば,Vimを使用している間,updatetime
毎に,常にタイマーの処理がされているということを踏まえておこう.