Make Vimmer Happy Again

2016-12-12 (updated: 2018-08-26)  #Vim 

本稿は Vim Advent Calendar 2016 の12日目の記事です.

今年の9月に Vim 8.0 がリリースされました.これに伴って追加された Vim の新機能は数多くありますが,その中に:smileコマンドというものがあることをご存知でしょうか.私は Yokohama.vim #8 に参加した際に知りました.このコマンドは,実行すると次のような「スマイル」が表示されます.

スマイル

:smileのヘルプを見ると “make the user happy(ユーザを幸せにする)” と解説されています.確かに,これは大変愉快で素晴らしい新機能だと思いました.

しかし,惜しいことにこの機能はユーザ(Vimmer)に対する考察がほんのわずかに足りていないようにも思われます.すなわち「ある Vimmer が unhappy なとき,その Vimmer に:smileコマンドを叩く余裕があるだろうか」ということに関する考察が不十分な気がするのです.本当に unhappy な Vimmer には:smileコマンドを叩く余裕などないのではないでしょうか.

そこで,本稿では Vimmer が unhappy なときはどんなときかについての深い考察を行い,そうしたケースで先ほどの「スマイル」が自動的に表示されるような vimrc の設定を考えてみたいと思います.

不浄キーを押してしまうとき

よく知られているように Vim でのカーソル移動は(QWERTY 配列において)ホームポジションから動かずに押すことのできるhjklで行うのが基本です.デフォルトではいわゆる「矢印キー」でも同様の移動を行うことが可能ですが,実際にこれらが使用されることは非常に稀で,一部の Vimmer の間では「不浄キー」などと呼ばれているぐらいです1

不浄キーは人によって(<Nop>にマップすることによって)無効化していたり,もっと過激な例では「開いているバッファの内容を全消去の上,それを保存して Vim を終了する」というような設定になっていたり2します(いずれも vimrc 読書会で見かけたことがあります).

おそらく Vimmer が不浄キーを押してしまうなどというのはよほど疲れているときに違いありません.つまり,不浄キーは Vimmer が unhappy なときに押されるキーと言えます.

ということで,上下左右4種の不浄キーを押すと:smileコマンドが実行されるようなキーマップを設定してみましょう.noremapを用いるとノーマルモードとヴィジュアルモードのキーマップ,inoremapでインサートモードでのキーマップを設定できるので,例えば.vimrcに以下のように記述すれば,上記3モードすべてにおいて不浄キーを押すと例のスマイルが表示されるようになります.

noremap  <Left>  :<C-u>smile<CR>
noremap  <Right> :<C-u>smile<CR>
noremap  <Up>    :<C-u>smile<CR>
noremap  <Down>  :<C-u>smile<CR>
inoremap <Left>  <Esc>:smile<CR>
inoremap <Right> <Esc>:smile<CR>
inoremap <Up>    <Esc>:smile<CR>
inoremap <Down>  <Esc>:smile<CR>

動作確認のためにインサートモードで不浄キーを押してみます.

不浄キーを押すとスマイルが表示される

なんというか,すごくバカにされている感じがしますね……

一定時間入力がないとき

Vim は十分に習熟すれば思考のスピードで編集が行える強力なエディタです.熟練の Vimmer が Vim を用いてコーディングしているとき,その手が止まるなどということは基本的にあり得ません.とはいえ,Vimmer も人間ですから,時にはよいアルゴリズムが思い浮かばず,詰まってしまうこともあるかもしれません.これは間違いなく unhappy なことと言えるでしょう.

そこでコーディングのリズムが止まったとき,すなわちインサートモードに入った状態でユーザが一定時間何のキーも入力もしなかった場合に:smileコマンドが実行されるような設定を考えます.

Vim において,コマンドを自動的に行いたい場合には autocommand という機能が使えます.そのトリガに使えるイベントには「新しいバッファの編集を始めたとき」(BufRead)や「バッファ全体をファイルに書き込むとき」(BufWrite)など数多くの種類があります.

今回の「インサートモードに入った状態でユーザが一定時間何のキーも押さないとき」は,イベントCursorHoldIによってとらえることもできそうですが,このイベントにおける “一定時間” を変更するためにはupdatetimeという「スワップファイルがディスクに書き込まれる時間」を制御するためのオプション値を変更しなければなりません.これは何となくバッドノウハウのような気がするので,今回は Vim 8 で新たに導入されたタイマー機能3を用いて実装してみることにします.

" “一定時間” の指定(単位はミリ秒)
let s:smile_time = 10000

" 文字が入力された場合にタイマーをリセットする関数
function! UpdateSmileTimer(timer)
  call timer_stop(a:timer)
  let s:smile_timer = timer_start(s:smile_time, 'ShowSmile')
endfunction

" スマイルコマンドを実行する関数
function! ShowSmile(timer)
  smile
endfunction

augroup smile
  autocmd!
  " インサートモードに入って s:smile_time が経過したら ShowSmile() を実行
  autocmd InsertEnter * let s:smile_timer = timer_start(s:smile_time, 'ShowSmile')
  " 入力があったら UpdateSmileTimer() を実行
  autocmd InsertCharPre * call UpdateSmileTimer(s:smile_timer)
augroup END

上の設定ではインサートモード中で10秒間文字の入力をしなかった場合:smileコマンドが実行されるようになっています.実用上は10秒でもかなりシビアだと思いますが,デモにはそれでも長すぎるのでs:smile_timeをもっと短くしたときの動作例を以下に示します4

5秒間 Vim を操作しないとスマイルが表示される

この顔,かなり煽リティが高い気がします……

Vim を終了するとき

Vim に別れを告げるとき,それは言うまでもなく Vimmer にとっては不幸なときです.それでもせめて “Good Bye” の一言は笑顔で言いたいものです.

というわけで,最後は Vim 終了時にスマイルを表示させてみましょう.上で紹介したautocmdのトリガに使えるイベントの中には Vim 終了時をとらえるものもありますが,ここでは:quitコマンドの上書きによって実現することにします.

デフォルトの Vim では,ユーザは小文字から始まるコマンドを定義することができないため(必ず大文字で始める必要があります):quitコマンドを上書きすることができません.しかし vim-altercmd というプラグインを導入すると小文字から始まるコマンドを定義できるようになります.

プラグインのインストールは各自お好きな方法でなさると良いでしょう.私は dein.vim ユーザなので次のようにしました.

call dein#add('tyru/vim-altercmd')

さて,準備は整ったので:quitの上書きをしていきます.vim-altercmd を用いてコマンドを定義するためにはAlterCommandを使用します.このコマンドを.vimrcで使用する場合,それよりも前にcall altercmd#load()によってプラグインを有効化する必要があるようです.AlterCommandによるコマンド定義において[]表記を利用すると省略可能なコマンドを定義することもできます.

call altercmd#load()
AlterCommand q[uit] smile

:quit するとスマイルが表示される

あれれ……これでは Vim が終了できないじゃないか!

おわりに

本当は今日までにすごいプラグインを作って自慢する記事を書きたかったのですが,進捗がなかったためこのような非常にくだらない記事になってしまいました……すみません.

とはいえ,なんだかんだ言いつつも vimrc に書ける程度の手軽さで,種々のイベントを検知して望みの処理をフックする方法については私の知る限りを網羅する内容になっています.何かの際に参考にしてもらえれば幸いです.

Happy Vimming!


  1. 私の知る限り,この語の初出は第3回 vimrc 読書会です. ↩︎

  2. もっとも私は undo 履歴を半永久化する undo-persistence を有効にしているため,この設定を行っても大して恐怖感はありませんが. ↩︎

  3. Vim 8 のタイマー機能については Vim 8.0 Advent Calender 5日目の記事に thinca さんによる詳しい説明があります. ↩︎

  4. インサートモード中で:smileコマンドを実行する場合,その出力行数を陽に指定する方法はないようです(もしご存知の方がいらしたら教えてください).そのため,実際にこの設定を行っても,頭の数行しか表示されない場合があります(筆者の環境では実行の度に表示行数が変わりました). ↩︎