Vimでシンプルなsmooth scroll

1 min read読了の目安(約1400字

はじめに

Vimにはいくつかスムーズスクロールを実現するプラグインがあります。
中には物理演算をするようなものもあるのですが、画面の描画が遅いと動作がもっさりしてしまうことがあって、使うのをやめてしまいました。
しかし、私はset nonumberを設定しているので、C-dC-uをしたときに画面に連続性がないと見づらいです。
そこで自分で書いてみたところ、意外と簡単な処理でいい感じに動きました。

コード

これだけです。

let s:stop_time = 10

function! s:down(timer) abort
  execute "normal! 3\<C-e>3j"
endfunction

function! s:up(timer) abort
  execute "normal! 3\<C-y>3k"
endfunction

function! s:smooth_scroll(fn) abort
  let working_timer = get(s:, 'smooth_scroll_timer', 0)
  if !empty(timer_info(working_timer))
    call timer_stop(working_timer)
  endif
  if (a:fn ==# 'down' && line('$') == line('w$')) ||
        \ (a:fn ==# 'up' && line('w0') == 1)
    return
  endif
  let s:smooth_scroll_timer = timer_start(s:stop_time, function('s:' . a:fn), {'repeat' : &scroll/3})
endfunction

nnoremap <silent> <C-u> <cmd>call <SID>smooth_scroll('up')<CR>
nnoremap <silent> <C-d> <cmd>call <SID>smooth_scroll('down')<CR>
vnoremap <silent> <C-u> <cmd>call <SID>smooth_scroll('up')<CR>
vnoremap <silent> <C-d> <cmd>call <SID>smooth_scroll('down')<CR>

s:smooth_scroll()のところは

function! s:smooth_scroll(fn) abort
  let F = function('s:' . a:fn)
  for i in range(&scroll/3)
    call F()
    sleep 10m
    redraw
  endfor
endfunction

のようにしても動くのですが、これだとキーリピートの間隔を短くしているときにキーを押し続けると処理が止まらなくなってしまいます。
画面のスクロール処理をtimerにのせて、smooth_scroll()が呼ばれたときに動いているtimerは止めて新しいtimerを開始することで、キーを離したときにスクロールも終了するようになります。

また一度にスクロールする量を1行ではなく3行にすることで、画面の再描画の回数を減らしています。