🏃

中級 Vim 操作

2024/06/05に公開

はじめに

本記事は以下の記事のオマージュです。

https://zenn.dev/eloy/articles/4017913277f629

Vim の基本操作のうち、比較的マイナーながら汎用的に使える機能や小技を集めました。プラグインや複雑な設定が必要なものは含まれておらず、いずれも Vim と Neovim の両方で使うことができます。気になったものがあれば使ってみてください。

ノーマルモード編

検索結果を次々と置き換える

Vim で文字列置換を行う最も有名な方法は :substitute コマンド (短縮形: :s) ですが、ノーマルモードの cgn というイディオムも便利です。これは c オペレータと gn テキストオブジェクト (:h gn) を組み合わせたもので、検索結果を任意の文字列に置き換えることができます。


cgn を用いて単語を次々と置換する例

上の動画では、以下の手順で VimL という文字列を Vim script に置き換えています。

  1. /VimL<CR>VimL という文字列を検索
  2. cgn で検索結果にマッチする範囲 (gn) を削除しつつ挿入モードに入る (c)
  3. Vim script と入力して挿入モードを抜ける
  4. . で直前の置換を繰り返す

cgn による置換操作は単一の編集と扱われるため、ドットリピートで同じ置換をリズミカルに繰り返せる点がポイントです。もっと複雑な編集をしたければマクロを使う手もあります。

:s コマンドによる置換と cgn による置換、双方に長所と短所があります。

  • :s コマンドのメリット
    • 置換対象の個数に限らず、コマンドを一度実行するだけで一括置換できる
    • コマンド実行履歴が残るため、過去に行った編集を再現するのが楽
    • 上級者向けではあるものの、様々な置換パターンを覚えれば複雑な置換も可能 (:h sub-replace-special)
    • 多数の対象を一括で置換したい、時間を空けて同じ置換を再現したいなら :s が便利
  • cgn のメリット
    • 文字入力時に補完が効く(組み込みの <C-x> 補完や coc.nvim などのプラグインによる自動補完)
    • マッチ箇所ごとに、インタラクティブに置換するかどうかの対応を変えられる [1]
    • c 以外のオペレータなどを用いることで多彩なテキスト編集ができる
    • 少数の対象を臨機応変に置換したい、補完の恩恵を受けたいなら cgn が便利

用途に応じて適切な方を選択したいところですね。私は基本的に cgn を使い、置換対象がおおよそ 10 を超えていそうであれば :s の使用を検討します。

引用符で囲まれた箇所全体を選択する

i"a" は引用符 " で囲まれた範囲を選択するテキストオブジェクトです。i""..." の中身を、 a""..." 全体を選択できます。ここまでは有名ですね。

ci" は癖がなく便利ですが、a" は少々厄介です。"..." 全体を選択する際、外側にある周辺の空白も巻き添えにしてしまうからです。たとえば以下のようなコードのうち ".vim" という文字列リテラルを extension という変数に置き換えたいとします。このとき ca" とすると、 + の直後のにある空白も一緒に消えてしまいます。

def add_suffix(fname: str, extension: str) -> str:
    return fname + ".vim"

このようなときは a" より 2i" が便利です。2i"a" と似ているものの、周囲の空白を巻き込みまない点が異なります (:h i")。わずかな違いですが、2i" のほうが範囲が直感的ではないでしょうか。


a"2i" の挙動の比較

設定ファイルをいじることに抵抗がなければ、以下のコマンドを設定ファイルに追加することをおすすめします。以下の6行を書くと、a" を指定したときにも周囲の空白を巻き込まなくなります。a"2i" をスワップするのも良いでしょう。

.vimrc
xnoremap a" 2i"
xnoremap a' 2i'
xnoremap a` 2i`
onoremap a" 2i"
onoremap a' 2i'
onoremap a` 2i`

Neovim の場合は Lua を用いてよりロジカルに書くこともできます。

init.lua
for _, quote in ipairs({'"', "'", "`"}) do
    vim.keymap.set({"x", "o"}, "a" .. quote, "2i" .. quote)
end

連番を簡単に作成する

ノーマルモード・ビジュアルモードでの <C-a><C-x> はカーソル下の数値などを増減できる非常に便利なコマンドですが、その派生である g<C-a> は少しマイナーかもしれません。1 2 3 4 5 ... という数字の並び、「連番」を簡単に作成するためのコマンドです (:h v_g_CTRL-A)。


g<C-a> を用いて連番を作成する例

上の動画では、わずか3ステップで番号付き箇条書きのリストを作成しています。

  1. 0. Something. と書かれたテキストを10行並べる
  2. <C-v> で矩形ビジュアルモードに入り、数字を選択
  3. g<C-a> を押す

このように、 g<C-a> を使うことで楽に連番が作成できます。箇条書きだけでなくダミーデータの作成などにも使えます。

閉じ括弧の直前まで削除する

特定の文字の直前まで削除したいとき、t モーションがよく用いられます。たとえば以下の関数呼び出しのうち、第2引数以下を書き換えたいとします。

some_func(arg1, arg2, arg3)

このような場合は arg2 の先頭までカーソルを移して ct) とすれば、カーソル位置から関数呼び出しの末尾の ) の直前までを書き換えられます。

では以下のケースではどうでしょう。

some_func(arg1, len(arg2), len(arg3))

この場合は ) が1行に複数あるため、t) を用いて狙った ) まで一度に削除するのは大変です。c3t) のようにカウントを使う手もありますが、もっと良い方法があります。

実は t) の代わりに ]) モーションを使うと、カーソルの属する括弧の範囲を自動で検出し、その末尾に移動することができます (:h ]))。

some_func(arg1, |child_func1(arg2), child_func2(arg3))
                *---------------------------------->|   c]) はここまで削除する

実際のデモ動画を見てみるとより分かりやすいでしょう。


]) モーションで狙いをつけてテキストを置換する例

]} も同様の機能を波括弧 { } に対して行うモーションです。なお、]] は全く異なる機能を持つモーションなので気をつけてください (:h ]])。

挿入モード編

挿入モード内でインデントを増減する

インデント操作といえば > / < オペレータですが、挿入モードでもカーソル行のインデントを増減できます。

キー 意味 ヘルプ
<C-t> カーソル行のインデントを1増やす :h i_CTRL-T
<C-d> カーソル行のインデントを1減らす :h i_CTRL-D

カーソルが行頭になくても動作する点で <Tab> よりも便利です。インデントは <C-t> / <C-d> で行う癖をつけるとよいでしょう。


様々なインデント操作

インデントの対象範囲や現在のモードに応じて、> / < オペレータと <C-t> / <C-d> を使い分けましょう。

レジスタの中身を挿入する

挿入モードで <C-r>" と打てば、無名レジスタ(最後にヤンクや削除を実行した文字列が格納されるレジスタ)の中身を貼り付けることができます。より一般には、挿入モードで <C-r>{register} と打つことで {register} レジスタの中身を貼り付けることができます(無名レジスタ = " レジスタ)。
レジスタには様々なものがあります (:h registers) が、以下のイディオムだけでも覚えておくと役立つかもしれません。

コマンド 効果
<C-r>+ クリップボードの中身を挿入する
<C-r>% 現在のバッファのファイルパスを挿入する
<C-r>/ 最後に検索した検索パターンを挿入する
<C-r>={expr}<CR> 任意の Vim script の式の評価結果を挿入する(後述)

なお、これらは挿入モードだけでなくコマンドラインモードでも使えます。

Vim を電卓のように使う

先ほど述べた <C-r>{register} を応用すれば、 Vim を電卓代わりに用いることもできます。

たとえば、以下のように書かれている数字の総和を計算したいとします。

15284
24232
53280
3087
43493
1259

Vim を使って求めてみましょう。まずは以下の手順で、有効な「Vim script の式」に変換します。

  1. 各行の冒頭に + を挿入
    • 1行目に + を付ける必要はないため、2行目から最後までを矩形選択し行頭に + を挿入
  2. 全体を選択し、 J コマンドで行を結合

これだけで以下のような式が得られます。これで有効な Vim script の式ができました。

15284 + 24232 + 53280 + 3087 + 43493 + 1259

あとは、この計算結果を Vim に計算してもらいましょう。結果をバッファに吐き出したいので、以下のようにします。

  1. 行全体をヤンク
  2. i<C-r>= と押下し、コマンドラインモードに入る
    • : で起動したときと異なり、プロンプトが = となっている特殊なコマンドラインモードです。
    • プロンプトに入力した Vim script の式を評価し、結果をバッファに挿入することができます。
  3. <C-r>" でヤンクした式を貼り付ける
    • 先程述べた無名レジスタの貼り付けをコマンドラインモードで使っています。
  4. <CR> を押し、評価結果をバッファに挿入

以上の流れを動画にすると以下のようになります。


expression register を電卓代わりに用いる例

Vim script の構文や組み込み関数に詳しくなれば、もっと実用的な計算(日付操作など)もできます。Vim を極めるうえで Vim script の習得は不可欠なのです。

コマンドラインモード編

外部コマンドを用いてバッファの内容を整形する

デフォルトの Vim の機能は万能ではありません。たとえば特定の言語の高度なフォーマットや機械翻訳などを行うにはプラグインが必要です。しかし、もしその機能を提供する外部コマンドが実行環境に存在するなら、プラグインが無くとも用が足りるかもしれません。Vim は外部コマンドとの連携が非常に得意だからです。

特定の範囲に対して外部コマンドを適用する :{range}! を例に挙げましょう (:h :range!)。
以下は外部コマンドの jq を Vim の中で実行し、Markdown ファイルのコードブロックに記述されている JSON を整形した例です(jq は何らかの方法でインストール済とします)。


jq を用いてコードブロックの JSON を整形する例

手順はきわめて単純です。

  1. 変換したい行を選択
  2. :! と打ち、その後に実行したいコマンドを入力(今回は jq .
    • デモ動画ではコマンドラインの表示が :'<,'>! となっていますが、途中にある '<,'> はビジュアルモードの範囲を表す range であり、ビジュアルモードからコマンドラインモードに遷移するタイミングで自動挿入されます。そのためユーザ視点では :! と打つだけでよいのです。
  3. 選択範囲が標準入力として jq に渡され、標準出力の内容で置き換えられた結果、 JSON が整形される

Vim の中で外部コマンドを実行する方法は他にもあります。

コマンド 機能 ヘルプ
:!{cmd} 外部コマンド {cmd} を単に実行する :h :!
:{range}!{cmd} {range} で示されたバッファの範囲を標準出力とし、外部コマンド {cmd} を実行した結果で指定範囲を置き換える :h :range!
:r !{cmd} 外部コマンド {cmd} の実行結果をバッファに挿入する :h r!
:w !{cmd} バッファの中身を外部コマンド {cmd} の標準入力に流し込んで実行する :h w_c

バッファ行をコマンドラインにコピーする

コマンドラインモードで<C-r><C-l> と打つと、コマンドラインに入る直前にいたバッファのカーソル行の内容をコマンドラインモードに書き写すことができます (:h c_CTRL-R_CTRL-L)。バッファ上に書かれている Vim script などを雑に実行したいときに便利です。


<C-r><C-l> でバッファ上の Vim script を実行する例

ヘルプを素早く引く

ヘルプとは :help(短縮形: :h)コマンドで参照できる Vim の公式ドキュメントです。この記事に何度も登場した :h {subject} も、{subject} に関連するヘルプを表示するコマンドです。Vim の機能はきわめて多く、全てのコマンドやオプションの仕様を正しく覚えるのは現実的ではありません。しかしヘルプを十分に使いこなせば、コマンドの細かい使い方を正確に把握し、正しく使うことができます。
ここでは、ヘルプを素早く引くためのコツを2つ紹介しましょう。

種類ごとのパターンを意識する

Vim ヘルプのタグは以下のような命名規則にしたがって付けられています。

種別 法則
オプション :h 'number' シングルクォート ' で囲まれる
Ex コマンド :h :insert 先頭に : が付く
関数 :h insert() 末尾に () が付く
Vim 正規表現 :h /\V 先頭に / が付く
置換後の特殊文字 :h s/\u 先頭に s/ が付く
レジスタ :h "= 先頭に " が付く
マーク :h '> 先頭に ' が付く
起動オプション :h -s 先頭に - が付く
filename-modifiers :h %:p 先頭に %: が付く

これらの命名規則によって、オプションと関数、コマンドなどがきちんと区別して調べられるようになっています。たとえば 'number' オプションについて知りたければ、 :h 'number' と検索しましょう。単に :h number とすると :number コマンドの説明が表示されてしまうからです。

キーマップを素早く引く裏技

CTRL-A などのキーマップを引くとき、:h CTRL-A<CR> とそのまま打つのは大変です。本記事で紹介した :h c_CTRL-R_CTRL-L などは、そのまま打つとかなり大変です。

実は、:h ^a<CR> と打っても :h CTRL-A<CR> と打ったときと同じ結果になります[2]

検索例 等価な引き方
:h CTRL-A :h ^a
:h i_CTRL-V :h i_^v
:h c_CTRL-R_CTRL-L :h c_^r^l

地味にけっこうな手数の短縮になります。CTRL の付くキーバインドはマイナーなものが非常に多く、意外と活躍の機会の多い小技です。

おわりに

Vim の操作に関する小技をいくつか紹介しました。この記事によって、Vimmer の皆さんが少しでも楽しくテキスト編集できるようになれば幸いです。

こういった Vim の Tips や裏技に興味がある方は、過去3年間にわたり実施された Vim short tips advent calendar に目を通してみてください。

https://qiita.com/advent-calendar/2019/vim-short-tips

https://qiita.com/advent-calendar/2020/vim-short-tips

https://qiita.com/advent-calendar/2021/vim-short-tips

他にもなにか Vim に関する Tips を思いついた方は、ぜひ Vim 駅伝に投稿してみてください。

https://vim-jp.org/ekiden/

余談

本記事のデモには vhs という CLI と 4513ECHO/nvim-keycastr というプラグインを使用しました。

脚注
  1. :s コマンドでも c フラグを使うことでインタラクティブに置換を実行できる (:h :s_c) ものの、途中の変換を一気にすっ飛ばしたりしたいときは cgn のほうが便利です。 cgn は通常のノーマルモードの延長で編集が進むため、モーションが自由に使えるからです。 ↩︎

  2. ^a は制御文字ではなく、キャレット ^ のあとに小文字の a を入力するということ。 ↩︎

Discussion