【Vim】必要な時だけカーソル行をハイライトする


Vim には 'cursorline' という機能があります。これは名前の通り、カーソルのある行を強調し、目立たせてくれるものです。

便利な機能なのですが、常時有効にしていると以下のような問題がありました。

  • 有効のまま画面をスクロールすると動作が重い。
  • (カラースキームによっては)文字が背景と同化して見づらい。

そもそもこれはカーソルの位置を見失わないようにするための機能ですから、必要な時だけ有効になってくれると便利です。具体的には、以下のように切り替わって欲しいです。

  • カーソルを動かすと無効にする。
  • ウィンドウを開いたり、移動したりした時に有効にする。
  • しばらくカーソルが止まっている時も有効にする。

……と、ここまでは以下のエントリーに書いてあることの要約です。

エントリー内のスクリプトでまさにこれが実現できるのですが、後述の理由により一つのプラグインとしてまとめました。

インストール方法などは README を読んでみてください。以下は、どうしてこのプラグインを作ったのかについて書いています。

CursorHold イベントの功罪

従来の Vim では、他の言語にはよくある「タイマー機能」というものがありませんでした。例えば JavaScript の setTimeout() のような、「一定時間が経ったら〇〇を実行する」ということができなかったのです。

その不便をある程度解消するために使われていたのが CursorHold イベントです。

これはカーソルを移動させた後、'updatetime' に指定した時間が過ぎた時に発動するイベントです。おそらくユーザーはカーソルを留めて何か考え中のはずですから、多少重たい動作をさせても構わないだろう、というわけです。このため、なるべくユーザーの操作をブロックしたくないプラグインは、みんなこの CursorHold イベントで関数を呼び出していました。

ただこの 'updatetime' の値は、Vim 全体で一つしか設定できません。例えば vim-gitgutter のようなプラグインではこの値を小さく(100ms)設定することが推奨されています

しかし今回の要件のためには 100ms という時間は短すぎます。上述の thinca 氏のエントリー内のスクリプトでは CursorHold を使っているのですが、vim-gitgutter のために、頻繁に cursorline が on/off されるのが悩みのタネでした。

CursorHold の代わりにタイマー機能を使う

Vim8.0 ではまさにこのために、タイマー機能が実装されました。

使い方は簡単で、timer_start() の引数に時間と関数を指定するだけです。

function! EchoHoge(timer_id) abort
  echo 'hoge'
endfunction

" 1秒後に hoge と表示する
let timer_id = timer_start(1000, 'EchoHoge')
" 関数リファレンスも使える
let timer_id = timer_start(1000, function('EchoHoge'))
" Lambda も OK
let timer_id = timer_start(1000, {-> execute('echo "hoge"', '')})

今回はこれを使って、以下のような処理を実装しています。

autocmd CursorMoved,CursorMovedI * call s:cursor_moved()

function! s:cursor_moved() abort
  " cursorline を消す
  setlocal nocursorline
  " すでに起動しているタイマーを止める
  call timer_stop(s:timer_id)
  " 新しいタイマーを起動
  let s:timer_id = timer_start(1000, function('s:enable_cursorline'))
endfunction

function! s:enable_cursorline(timer_id) abort
  setlocal cursorline
endfunction

基本的にはこれだけです。ただ、このままだとカーソルを動かすたびに setlocal nocursorline していて無駄ですし、他にもいろんなエッジケースがあったのでもう少し複雑なことをやっています。詳しくはソースを読んでみてください。

まとめ

これからは CursorHold じゃなくてタイマー機能を使おうぜ!という話でした。