かなりすごいブログ

Vimで離れた複数の行に対する一括操作を行う

Vim Advent Calendar 2013、165日目の記事になります。

離れた複数の行に一括操作を行いたい

と言ってもなんのことか全く伝わらないと思うので例を挙げたいと思います。

vim-rengbangというプラグインが存在します。これはVimで連番を扱うためのプラグインで、機能の一つとして、選択した範囲の数字を連番として書きなおしてくれるコマンドがあります。

<ul>
  <li><span data-sugoi-id="0">(かなり)</span></li>
  <li><span data-sugoi-id="0">(かなり)</span></li>
  <li><span data-sugoi-id="0">(かなり)</span></li>
  <li><span data-sugoi-id="0">(かなり)</span></li>
  <li><span data-sugoi-id="0">(かなり)</span></li>
  <li><span data-sugoi-id="0">(かなり)</span></li>
</ul>

こういったhtmlがあったとして、data-sugoi-idの値が全て0になっている部分を0,1,2...という連番数字に書き換えたいとします。

この時、data-sugoi-idの数字を含む行を選択し:'<,'>RengBangを実行すると、プラグインが意図通りに書き換えを行ってくれます。大変便利ですね。

しかし、次のような例はどうでしょうか。

<ul>
  <li>
    <span data-sugoi-id="0">(かなり)</span>
    <span data-kanari-id="0">(とても)</span>
  </li>
  <li>
    <span data-sugoi-id="0">(かなり)</span>
    <span data-kanari-id="0">(とても)</span>
  </li>
  <li>
    <span data-sugoi-id="0">(かなり)</span>
    <span data-kanari-id="0">(とても)</span>
  </li>
</ul>

このhtmlを、先ほどと同じようにdata-sugoi-idの数字を連番に書き換えたい、ただしdata-kanari-idの値はそのままにしておきたい、という場合はどうでしょうか。

先ほどとおなじように<ul>から</ul>までの行を選択して:'<,'>RengBangした場合は、意図しないdata-kanari-idの部分まで書き換えられてしまいます。実はvim-rengbangの標準機能でこういった例にも対応はできるのですが、ここはvim-rengbangに限らず、「連続していない複数の行に対して一度でまとめて処理を行いたい」といった場面で汎用的に使えるテクニックがあると便利そうです。

qfreplace便利

そこでvim-qfreplaceの出番です。これは:grepの結果を用いて置換用のUIを提供してくれる便利プラグインです。

qfreplaceを導入することで、上記の様な場面での汎用的な解決方法が得られます。

まず、:vimgrep /data-sugoi-id/ %として、現在開いているhtmlのバッファを対象に、data-sugoi-idという文字列を含む行のみを対象とした検索を行います。その後:copenを実行しQuickFixバッファを開きましょう、QuickFixバッファに移動したら、:Qfreplaceコマンドを実行します。すると、検索にヒットした行のみがリストアップされたバッファが作成されますので、行を全て選択し:'<,'>RengBangを実行しましょう。その後:wqを実行すると、元のhtmlバッファに変更が反映されます。

まとめ

単純な置換を行いたい場合は標準の置換コマンドやマクロなどを利用するのが簡単ではありますが、vim-rengbangの様に、「複数の行に対して処理を行う」かつ「処理の内容が他の行に行った処理の内容に依存する」といった処理を行いたい場合、このテクニックが有効です。

ちなみにこのtipsは私がおしょーさんに相談しておしょーさんが考えてくれたものです。おしょーさんすごい。

おわり

おわり