空行を挿入する+α

Vim Advent Calendar 2012の125日目の記事です。
124日目はcohamaさんによるはじめてプラグインを作ってみた。それとhelpの書き方など - 反省はしても後悔はしない。でした。
もう、1年の1/3越えちゃったんですね。一人で10記事くらい書いている方もいらっしゃるし、いったいVimmerはどこへ向かっているのか、僕も最後まで見守りたいと思います。

前置き

今日は、空行を挿入する方法をご紹介したいと思います。空行を挿入する方法と聞いて、あれ?どこかで聞いたことがあると思われた方も多いと思います。
そうですね。Vim HacksのVim-users.jp - Hack #57: 空行を挿入するで紹介されていますね。ujihisa++

上記記事の問題を要約すると、ノーマルモードで挿入モードに入らず副作用なく(コメントやインデント等の自動挿入なしに)空行を挿入したい。ということでした。
上記問題は記事で解決していますが、今回はちょっと気になった点があったのでその解決策をご紹介したいと思います。

気になったのは以下の2点です。

  • カウント指定(何行挿入する指定)したい
  • .で繰り返したい

本記事では上記を解決する話をします。時間がない方はまとめだけ見ればコードが載っているのでそちらをご覧ください。

カウント指定したい

行挿入はノーマルモードでのoappend()関数で可能です。
oを使えば、5oなどと入力すれば5行挿入することができますが、自動インデント挿入やコメント挿入があるので今回は使いません。

じゃあappend()関数を使って以下のようにすればどうでしょうか?

nnoremap <silent> <Space>o   :call append(line('.'),   '')<CR>

これだと、5oとした時に以下のようになります。

:.,.+4call append(line('.'),   '')<CR>

範囲指定でのコマンド実行になるので、現在行から下4行目まで順にappend()がコールされていくことになり、一応カウント対応可能です。
ただ、この場合、カーソルが挿入した行の最終行から1つ上の行に移動してしまいます。
現在行の次行以降に5行挿入したいだけで、移動はして欲しくありません。

そこで、v:count1を使います。

カウント系の変数はv:countv:count1があります。違いはカウントを指定しなかった時にv:countは0、v:count1は1になります。以下の様なイメージです。

指定したカウント 1 2 3 4 5
v:count 0 2 3 4 5
v:count1 1 2 3 4 5

次行にカウント指定で挿入したい場合、v:count1を使って以下の様に書けます。

for i in range(1, v:count1)
  call append(line('.'),   '')
endfor

ループのインデックスのiは捨ててます。
同じように前行の場合は以下のように書けますね。

for i in range(1, v:count1)
  call append(line('.') - 1,   '')
endfor

それぞれ一行で書いてマッピング書くと以下になります。

nnoremap <silent> <Space>o   :<C-u>for i in range(1, v:count1) \| call append(line('.'),   '') \| endfor<CR>
nnoremap <silent> <Space>O   :<C-u>for i in range(1, v:count1) \| call append(line('.')-1, '') \| endfor<CR>

.で繰り返したい

最初は上記で満足していたんですが、そのうち上記操作後の.の動作が気に入らなくなりました。上記のマッピング後に.を入力すると、以前カウントが指定されていても、行が1行だけしか入力されません。.は最後の変更を繰り返す。ということなので、この最後の変更が、ループの最後のappend()を繰り返すと解釈されます。
やりたいのは、5行挿入したなら、.で5行挿入したいのです。
繰り返し対応のvim pluginもあることだし(repeat.vim)、何かしら繰り返しの動作を設定・変更する方法があるのだろうなぁと思って調べてみましたが、全く見当たりませんでした。はて、じゃあrepeat.vimはどうやって繰り返しを実現しているのだろうと思って中身を見てみたところ、以下のマッピングをしていました。

nnoremap <silent> .     :<C-U>call repeat#run(v:count)<CR>
nnoremap <silent> u     :<C-U>call repeat#wrap('u',v:count)<CR>

おおう、key mappingで対応していたんですね。。。しかもご丁寧にuまでマッピングしている。。。で、繰り返し操作を変更したい場合はどうするかというとrepeat.vimの関数をコールして直前の操作を教えてあげてると、repeat.vimプラグイン側で管理してくれます。以下はsurround.vimのソースから抜粋したものです。。

silent! call repeat#set("\<Plug>Dsurround".char,scount)

第一引数に繰り返し時(.入力時)にタイプしたい文字列、第二引数にカウント数を指定します。

で、空行挿入の話に戻ると、先ほどの空行挿入はoにマッピングしているので以下のように関数をコールしてあげれば、次に.を入力した時にこれを入力する動きになります。

call repeat#set("<Space>o", v:count1)

で、最終的にどうなったかというと以下のkey mappingです。

nnoremap <silent> <Space>o   :<C-u>for i in range(1, v:count1) \| call append(line('.'),   '') \| endfor \| silent! call repeat#set("<Space>o", v:count1)<CR>
nnoremap <silent> <Space>O   :<C-u>for i in range(1, v:count1) \| call append(line('.')-1, '') \| endfor \| silent! call repeat#set("<Space>O", v:count1)<CR>

まとめ

以下コードをvimrcに貼り付け、repeat.vim プラグインを入れれば、oで現在行の次行に挿入、Oで現在行の前行に挿入でき、カウント指定、.での繰り返しができるようになりました。

nnoremap <silent> <Space>o   :<C-u>for i in range(1, v:count1) \| call append(line('.'),   '') \| endfor \| silent! call repeat#set("<Space>o", v:count1)<CR>
nnoremap <silent> <Space>O   :<C-u>for i in range(1, v:count1) \| call append(line('.')-1, '') \| endfor \| silent! call repeat#set("<Space>O", v:count1)<CR>

できれば、vim pluginなしで.の繰り返しの操作を変更したかったので、どなたかご存知のかたいらっしゃいましたらコメントなどいただけるとありがたいです。Vim側で対応できないものなんですかね?

明日のVim Advent Calendar 2012はShougoさんです。