本稿は 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.
この顔,かなり煽リティが高い気がします……
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
あれれ……これでは Vim が終了できないじゃないか!
おわりに
本当は今日までにすごいプラグインを作って自慢する記事を書きたかったのですが,進捗がなかったためこのような非常にくだらない記事になってしまいました……すみません.
とはいえ,なんだかんだ言いつつも vimrc に書ける程度の手軽さで,種々のイベントを検知して望みの処理をフックする方法については私の知る限りを網羅する内容になっています.何かの際に参考にしてもらえれば幸いです.
Happy Vimming!
-
私の知る限り,この語の初出は第3回 vimrc 読書会です. ↩︎
-
もっとも私は undo 履歴を半永久化する undo-persistence を有効にしているため,この設定を行っても大して恐怖感はありませんが. ↩︎
-
Vim 8 のタイマー機能については Vim 8.0 Advent Calender 5日目の記事に thinca さんによる詳しい説明があります. ↩︎
-
インサートモード中で
:smile
コマンドを実行する場合,その出力行数を陽に指定する方法はないようです(もしご存知の方がいらしたら教えてください).そのため,実際にこの設定を行っても,頭の数行しか表示されない場合があります(筆者の環境では実行の度に表示行数が変わりました). ↩︎