直前の検索キーワードをそのまま変更するcgn/cgN
/urlで検索してから、やっぱりその「url」を別の単語に変えたくなりました。カーソルはもう一致の上に乗っているので、ciwで単語を選び直すのは一手多い操作です。直前の検索パターンをそのまま変更の対象にするcgnを使うと、検索から書き換えまでが1本につながります。
検証環境:macOS 26.4.1・Vim 9.1.1752 と Neovim 0.12.4。この記事の実測はすべて、設定を読まない状態(Vimは-Nu NONE、Neovimは-u NONE)で起動して確かめたものです。'wrapscan'など断りのないオプションは既定値のままです。
目次(8項目)
| キー | これだけ覚える |
|---|---|
| cgn | 次の検索マッチを変更 |
| cgN | 前の検索マッチを変更 |
| n . | 次のマッチへ移動して同じ変更を繰り返す |
| dgn / ygn | 一致を削除 / ヤンク |
| vgn | 一致をビジュアル選択 |
基本の使い方
cgn " 次の検索マッチを変更
cgN " 前の検索マッチを変更
cgnは変更のオペレータcと、範囲を指すgnの組み合わせです。gnは直前の検索パターンに一致する範囲をビジュアル選択するモーションで、使い心地はテキストオブジェクトに近くなっています。iwが単語の範囲を指すのと同じ位置に、検索の一致範囲を差し込めると考えると分かりやすいはずです。挙動の核になるのは、どの一致を選ぶかがカーソルの位置で決まる点です。ヘルプのgnの項には、次のように振る舞いが並べて書かれています。
Search forward for the last used search pattern, like with `n`, and start Visual mode to select the match. If the cursor is on the match, visually selects it. If an operator is pending, operates on the match. E.g., "dgn" deletes the text of the next match. If Visual mode is active, extends the selection until the end of the next match. 'wrapscan' applies. Note: Unlike `n` the search direction does not depend on the previous search command.
2文目の「カーソルが一致の上にあればその一致を選ぶ」が、cgnを使うときにいちばん効いてきます。4行すべてにoldがあるバッファの1行目先頭でcgnを押すと、変わったのは1行目でした。1行目だけをzzz lineに差し替えて一致から外すと、変わったのは2行目です。つまり無条件に次へ飛ぶのではなく、乗っている一致があればそれを優先します。
直前に/を押していなくても、検索レジスタ@/に値が入っていればcgnは動きます。:let @/ = "two"と打ってからcgnを押したところ、手元ではtwoの1件目が変更に入りました。カーソル下の単語を検索する*とcgnの組み合わせが定番なのも、*が@/を書き換えるコマンドだからです。
gnは他のオペレータとも組み合わせられる
gnはオペレータの後ろに置ける範囲指定なので、c専用ではありません。oldやbetaを検索した状態から、オペレータを変えて実際に押した結果が次の4つです。
| 操作 | 実測の結果 |
|---|---|
| dgn | カーソルが乗っていたoldだけが消えた |
| ygn | 一致したbetaが無名レジスタに入った |
| vgn | 一致したoldだけがビジュアル選択された |
| d2gn | 1行目は残り、2行目のoldが消えた |
最後のd2gnに注目してください。gnの前に置いた数字は「その回数だけ削除する」ではなく「何件目の一致か」を指します。1行目にカーソルを置いた状態で押すと1行目のoldはそのまま残り、2件目にあたる2行目だけが消えました。飛ばしたい一致が1つだけと分かっているなら、この形で一気に狙えます。
すでにビジュアルモードに入っているときのgnは、選択を次の一致の末尾まで伸ばす働きに変わります。old one old two old threeという1行の先頭でvを押してからgnを2回押すと、選択は2件目のoldの末尾まで届き、続けてdを押すとold one oldがまとめて消えました。範囲の端が検索パターンで決まるので、行数や文字数を数えずに選択を伸ばせます。
置換コマンドとの違い
:%s/old/new/gによる全置換とcgnは、どちらも検索して書き換える点では同じです。分かれ目は、書き換えるかどうかを1件ずつ決めたいかどうかにあります。全部を機械的に変えてよいなら:%sが速く、残す箇所が混ざるならcgnのほうが手が止まりません。
件数もひとつの目安になります。数件から十数件で、周りのコードを見ながら進めたい規模ならcgnが合います。数十件を超えて全部同じ扱いでよいなら、:%sで一気に片づけたほうが速く終わります。押せるキーと取り消しの単位はcフラグ付きの置換とundoの単位にまとめてあるので、比べてから選んでください。
もうひとつ、置換後の文字列の扱いが違います。:sの置換後には&や\1のような特別な意味を持つ記号があり、手元で:%s/old/a&b/gを実行するとold 1がaoldb 1になりました。cgnはインサートモードでそのまま打つので、同じa&bが文字どおり入ります。記号の多い文字列に差し替えるときは、エスケープを気にせず打てるぶんcgnが楽です。
.コマンドと組み合わせて連続変更する
/old<Enter> " 検索
cgnnew<Esc> " 最初の1件を変更
. " 次のマッチへ進んで同じ変更
. " さらに次へ
ここがcgnの一番おいしいところです。.を押すだけで、次のマッチまで移動して同じ変更が入ります。nで送ってから.を押す必要はありません。これは.が「1行目を書き換えたこと」ではなく「cgnという操作」を繰り返しているからです。繰り返しのたびにgnが一致を探し直すので、結果として次の箇所へ進みます。.そのものが何を記憶しているのかはドットコマンドで直前の編集を繰り返す仕組みのほうで扱っています。
4行すべてにoldがあるバッファで手元のVim 9.1を使い、cgnのあと.を2回押した結果です。
| 操作 | バッファの状態 |
|---|---|
| cgnで1件目を変更 | NEW one / old two / old three / old four |
| . | NEW one / NEW two / old three / old four |
| . | NEW one / NEW two / NEW three / old four |
飛ばしたいときだけnを使う
ではnはいつ使うのかというと、変更したくないマッチを飛ばすときです。nを押すとカーソルが次のマッチへ移り、その状態で.を押すとそこが変更対象になります。つまり1件飛ばしたいならnを2回押してから.です。
NEW one
old two
old three
NEW one
old two
NEW three
手元でも2行目はoldのまま、3行目だけが変わりました。「全部変える」なら.の連打、「ここは残す」ならnを1回多く押す、という覚え方になります。
要点:検索した単語をそのまま変更したいならcgnを使います。繰り返しは.だけで進み、飛ばしたいときにだけnを挟みます。
置換後の文字列が同じパターンに当たると止まる
.が次へ進めるのは、変更を終えた時点でカーソルが一致の外に出ているからです。逆に、打ち込んだ文字列が同じパターンに当たり、しかもカーソルがその上に残ると、.は同じ場所を書き直し続けます。x = 100とy = 250の2行で/\dと検索し、cgnで9に変えてから.を2回押したところ、結果はx = 900のまま動きませんでした。
空振りしているのではありません。同じ操作のあとに:echo undotree().seq_curを実行すると3が返り、変更そのものは3回記録されていました。数字1文字を数字1文字に書き換える操作を、カーソルが同じ位置で3回繰り返していたことになります。抜けるにはnを1回挟んで次の一致へ送ります。実際にnのあと.を押すとx = 990になり、2桁目へ進みました。
*で検索したときは1件目に注意
カーソル下の単語を検索する*から始める場合、1つ落とし穴があります。*はカーソルを次のマッチへ移動させるので、そのままcgnすると最初の1件が変更されずに残ります。手元で3か所にfooがあるバッファの1行目で*を押すと、カーソルは2行目に移り、続けてcgnしたときに変わったのも2行目でした。
1件目から変更したいなら、*のあとにNを押して元の位置へ戻してからcgnを始めます。同じバッファで*のあとにNを挟むと、変わったのは1行目になりました。
最初は:%sがあれば足りると思っていて、キーを1つ増やす気になれませんでした。気が変わったのは、設定ファイルの中の古いパス名を書き換えたときです。コメントに残した経緯のメモだけは当時の名前のまま置いておきたくて、範囲の指定を考え直すところで手が止まりました。cgnに切り替えてからは画面を上から順に追いながら手だけ動かせばよくなり、どこを残したかも頭に残るようになりました。
gnは検索の向きに引きずられない
nとgnは名前が似ていますが、進む向きの決まり方が違います。nの向きは直前の検索が前方だったか後方だったかで変わるのに対して、gnはいつでも前方を探します。先ほど引用したヘルプの最後の1文が、その注意書きにあたります。実際に確かめました。old one・zzz mid・old threeの3行を用意し、2行目にカーソルを置いて?oldで後方検索します。このとき検索そのものがカーソルを1行目へ運ぶので、2Gで2行目へ戻してから試します。そこで押すキーによって、変わる行が入れ替わりました。
| 2行目から押したキー | 変わった行 |
|---|---|
| nのあとcgn | 1行目のold one |
| cgnだけ | 3行目のold three |
後方へ向かうために用意されているのがcgNです。ただしカーソルが一致の上にあるときは、cgnとcgNはどちらも同じ一致を選びます。4行すべてにoldがあるバッファの最終行で試すと、両方とも4行目が変わりました。この場面では2つを区別する意味がありません。
差が出るのは.で繰り返したときです。最終行でcgNを実行してから.を2回押すと、4行目・3行目・2行目の順にさかのぼって変わりました。追加したコードをファイルの下から順に直していくような場面では、上へ戻るcgNのほうが移動が少なくて済みます。
もうひとつ、gnは'wrapscan'に従います。既定はオンなので、最後の一致より後ろにカーソルがあってもファイルの先頭へ回り込みます。old twoが2行目だけにあるバッファの4行目でcgnを押すと、2行目が変更に入りました。
:set nowrapscanにしている場合は、同じ場面でcgnが何もしません。厄介なのはエラーが表示されないことです。gnが一致を見つけられないとcごと取り消され、続けて打った置換後の文字列がノーマルモードのコマンドとして流れます。4行目がxyzのバッファでcgnxと打ったところ、xが1文字削除として働いて4行目がyzになりました。ここまでの実測はNeovim 0.12.4でもすべて同じ結果です。
マクロと組み合わせるとさらに強力
.で足りるのに@aを持ち出す理由は、回数を指定できることにあります。.の前に数字を置いても「その回数だけ繰り返す」にはなりません。数字はgnへ渡り、何件目の一致を選ぶかの指定として働きます。
oldが5行にあるバッファで確かめました。1行目をcgnで変えたあと3.を押すと、変わったのは4行目だけで、2行目と3行目はoldのまま残ります。同じ変更をレジスタaに記録して3@aを押すと、2行目から4行目までの3件が変わりました。
| 1件目を変えたあとの操作 | 変わった行(実測) |
|---|---|
| 3. | 4行目だけ |
| 3@a | 2行目・3行目・4行目 |
3@a " 記録した操作を3回繰り返す(3件変わる)
3. " 3件先の1件だけを変える
件数が数えられているときは3@aのようにまとめて流し、読めないときは.を押しながら画面を見るほうが安全です。途中で判断が必要な箇所に来たらマクロを止め、nで送ってからcgnに戻れます。複数行にまたがる手順まで記録したくなったら、マクロで繰り返し作業を自動化する方法のほうが本題に近いはずです。
まとめ
cgnは、検索で見つけた範囲をそのまま編集の対象にできるという一点で、:%sともドットコマンド単体とも役割が違います。同じ「オペレータに検索を渡す」発想ではオペレータと検索モーションの組み合わせがあり、一致そのものではなく手前や後ろを狙いたいときは検索オフセットの指定が効きます。どれも直前の検索パターンを起点にするので、/を押したあとの選択肢として並べて覚えておくと迷いません。