'cursorline' を必要な時にだけ有効にする

問題

Vim には 'cursorline' というオプションがある。これを有効にすると、現在カーソルがある行がハイライト表示される。カーソルの位置を見失いにくくなるので便利なオプションだ。
しかし、デメリットがないわけではない。

  • 微妙に重い。
  • 背景色や一部の色が上書きされるので、カーソルのある行の背景色が見えなくなる。例えば、コメント内の TODO や 'list' で表示される文字など。

そもそも 'cursorline' は常に必要だろうか。編集中にカーソルを見失うことはそうそうないはず。見失うのはちょっと席を外したり別の作業に移って、戻ってきた時だ。必要な時だけ有効にすることはできないだろうか。

解決方法

以下のような設定を行うと、しばらく手を離した時だけ 'cursorline' が有効になる。

augroup vimrc-auto-cursorline
  autocmd!
  autocmd CursorMoved,CursorMovedI,WinLeave * setlocal nocursorline
  autocmd CursorHold,CursorHoldI * setlocal cursorline
augroup END

解説

しばらく操作をしないでいると CursorHold が発動して 'cursorline' が有効になる。作業を再開してカーソルを動かすと CursorMoved などが発動して 'cursorline' が無効になる。
好みにより 'cursorcolumn' を設定してもいい。
CursorHold イベントは 'updatetime' オプションで発生するまでの時間を調節できる。ただし、'updatetime' は本来スワップファイルが更新されるまでの時間なので、設定する際は注意。

発展

ここまで来るとウィンドウ間を移動した直後もハイライトして欲しい。ウィンドウの移動直後もカーソルを見失いがちだからだ。
単に有効にする条件に WinEnter を入れればよさそうだが、これではうまくいかない。

augroup vimrc-auto-cursorline
  autocmd!
  autocmd CursorMoved,CursorMovedI,WinLeave * setlocal nocursorline
  "                              ↓思惑通りに動かない!
  autocmd CursorHold,CursorHoldI,WinEnter * setlocal cursorline
augroup END

どうも、ウィンドウに入った直後に CursorMoved イベントが発生しているらしい。しかし、移動したウィンドウ間でのカーソルの位置(行、桁)が同じだった場合には発生しない。Vim のバグのような気がするが、とにかくこのままでは動かない。
この問題を回避するには、少々強引な方法だが、移動直後の 1 回だけ CursorMoved を無視するようにしてしまえば良い。

augroup vimrc-auto-cursorline
  autocmd!
  autocmd CursorMoved,CursorMovedI * call s:auto_cursorline('CursorMoved')
  autocmd CursorHold,CursorHoldI * call s:auto_cursorline('CursorHold')
  autocmd WinEnter * call s:auto_cursorline('WinEnter')
  autocmd WinLeave * call s:auto_cursorline('WinLeave')

  let s:cursorline_lock = 0
  function! s:auto_cursorline(event)
    if a:event ==# 'WinEnter'
      setlocal cursorline
      let s:cursorline_lock = 2
    elseif a:event ==# 'WinLeave'
      setlocal nocursorline
    elseif a:event ==# 'CursorMoved'
      if s:cursorline_lock
        if 1 < s:cursorline_lock
          let s:cursorline_lock = 1
        else
          setlocal nocursorline
          let s:cursorline_lock = 0
        endif
      endif
    elseif a:event ==# 'CursorHold'
      setlocal cursorline
      let s:cursorline_lock = 1
    endif
  endfunction
augroup END

この方法であるとまったく同じ場所同士で移動した場合、最初の 1 回の移動は 'cursorline' が有効になったままになるが、元々レアケースであるし特に問題はない。
また、ついでに変数の値を調節することで :set を呼ぶ回数を減らしてある。これで多少早くなるはずだ。