koturnの日記

普通の人です.ブログ上のコードはコピペ自由です.

Vimのrange指定した関数を呼び出したときにハマッたこと

.vimrcに昔記述した,カーソルを移動せず,ファイルの全ての行末のホワイトスペースを消去するコマンドを,範囲指定できるように書き直したときにハマった話をする. 以下のコマンドを,

function! s:delete_trailing_whitespace() abort
  let cursor = getcurpos()
  silent keepjumps %s/\s\+$//ge
  call histdel('search', -1)
  call setpos('.', cursor)
endfunction
command! -bar DeleteTrailingWhitespace  call s:delete_trailing_whitespace()

関数のrange識別を用いて,次のように書き直した.

function! s:delete_trailing_whitespace() abort range
  let cursor = getcurpos()
  execute 'silent keepjumps' a:firstline ',' a:lastline 's/\s\+$//ge'
  call histdel('search', -1)
  call setpos('.', cursor)
endfunction
command! -bar -range=% DeleteTrailingWhitespace  <line1>,<line2>call s:delete_trailing_whitespace()

しかし,このコマンドを :DeleteTrailingWhitespace のように実行する(範囲は1行目から最終行)と,カーソルがファイルの1行目に移動してしまう. (-range=% は,範囲指定をしないときは,1行目から最終行が指定されたことになる) どうやら,range識別をつけた関数内における getcurpos() で得られるのは,指定された範囲の最初の位置であるらしく,関数を呼び出した時点でのカーソル位置が得られるわけではない.

そのため,次のように,関数の引数として範囲を渡すように書き換えた. (引数名に firstlinelastline を用いることはできない)

function! s:delete_trailing_whitespace(line1, line2) abort
  let cursor = getcurpos()
  execute 'silent keepjumps' a:line1 ',' a:line2 's/\s\+$//ge'
  call histdel('search', -1)
  call setpos('.', cursor)
endfunction
command! -bar -range=% DeleteTrailingWhitespace  call s:delete_trailing_whitespace(<line1>, <line2>)

これならば,関数内で呼び出された getcurpos() で得られるのは,関数を呼び出した時点でのカーソル位置である.

今までrange識別を用いた関数を書いたことがなかったので,このような挙動をすることがわかっていなかった. もっとスマートな解決方法があるのかもしれない.