DEV Community

Vimtricks.wiki
Vimtricks.wiki

Posted on • Originally published at vimtricks.wiki

How I do safer multi-file edits in Vim

Bulk edits are where even experienced Vim users can accidentally create a mess: too-wide matches, noisy jump history, or a replacement that looked right three files ago but is wrong in this one. Over time, I ended up with a short workflow that keeps speed high without giving up control.

This is the sequence I use when I need to change code across a project but still keep context and sanity.

1) Build a literal quickfix list from the current word

Why it matters

Before changing anything, I want a tight target list. If the first search is sloppy, every step after that becomes cleanup work. :vimgrep with \V gives me a literal search and sends matches straight into quickfix so I can inspect what I am about to touch.

:vimgrep /\V<C-r><C-w>/gj **/*
Enter fullscreen mode Exit fullscreen mode

Real scenario

You are renaming a function like process_order across a service and want an editable hit list first. Run the command on the symbol, open quickfix with :copen, and skim entries before applying any change. That 15-second pass catches the obvious false positives early.

Caveat

<C-r><C-w> inserts the current word under cursor, not arbitrary punctuation-heavy text. If you need broader text insertion, switch approach (for example <C-r><C-a> for WORD-style text) and still keep the literal \V behavior.

2) Apply quickfix-wide substitution without polluting state

Why it matters

Once quickfix is curated, I want batch changes that do not wreck my navigation tools. This command applies the replace, writes only changed files, and avoids clobbering jump and search state while it runs.

:cdo keepjumps keeppatterns %s/\<OldSymbol\>/NewSymbol/ge | update
Enter fullscreen mode Exit fullscreen mode

Real scenario

You searched for a deprecated API name and got 60 entries in 18 files. After confirming the list, run this once to replace exact word matches and persist edits. Then use quickfix navigation again to spot-check updated call sites.

Caveat

cdo executes per quickfix entry, so this can repeat work inside the same file. If your operation should run once per file instead of once per match, use :cfdo.

3) Use selective replacement when global replace is too risky

Why it matters

Not every rename is safe to automate globally. Some matches need changing, others must stay. The * + cgn flow gives me speed with per-match judgment, and . keeps it fast after the first replacement.

*
cgnnew_name<Esc>
.
Enter fullscreen mode Exit fullscreen mode

Real scenario

You are changing count to item_count, but count_total and discount should stay untouched. Start on the first exact count, run the sequence, and press n to skip contexts you do not want before using . again.

Caveat

This flow depends on the active search pattern. If your last search is stale, hit * again on the exact token you intend to change before continuing.

4) Walk older edits with context restored

Why it matters

After a multi-file pass, review speed matters. Jumping by changelist alone is useful, but hidden folds and poor viewport placement slow verification. Chaining these motions gives me a dependable “show me the last real edit here” move.

g;zvzz
Enter fullscreen mode Exit fullscreen mode

Real scenario

You made several edits in a long file and want a final sweep before commit. Repeating this sequence walks backward through changes, opens folds when needed, and centers each location so you can validate intent quickly.

Caveat

g; walks changelist entries, which are not a semantic diff. For project-wide review, I still pair this with quickfix, :changes, or git diff depending on scope.

5) Add a temporary line prefix across a matched set

Why it matters

During debugging, I often need to disable or mark lines quickly without visual-block gymnastics. :global with :normal scales that action from one line to many using a clear match rule.

:g/^/normal I// 
Enter fullscreen mode Exit fullscreen mode

Real scenario

You want to comment a copied block during an experiment, run tests, then revert cleanly afterward. One command prepends // at the first non-blank position across matching lines, keeping indentation readable.

Caveat

As written, this targets every line matched by ^, including empty lines. Narrow the pattern when needed (for example :g/^\s*\k/normal I//) to avoid touching blank separators.

Wrap-up

This workflow is simple on purpose: build a precise list, batch safely, switch to selective edits where needed, review quickly, and use repeatable linewise transforms for temporary changes. The commands are small, but together they reduce the two expensive problems in refactors: accidental changes and slow verification.

If you want more practical Vim tricks, I publish them at https://vimtricks.wiki.
What part of multi-file editing in Vim still feels fragile in your workflow?

Top comments (0)