DEV Community

Vimtricks.wiki
Vimtricks.wiki

Posted on • Originally published at vimtricks.wiki

Vim tricks I use when I need operations to land exactly right

There's a category of Vim friction that isn't about speed — it's about landing in the right place. You recorded a macro that's almost right but has one wrong keystroke. You want to delete from here to a line you marked ten minutes ago. You're trying to insert newlines in a substitution and getting garbage characters instead. These aren't beginner problems, but they trip up experienced users too because the solutions aren't obvious.

Here are five tricks that consistently reduce that friction.

1) Edit a macro as plain text instead of re-recording it

Why it matters

Re-recording a long macro because of one wrong keystroke is painful. There's a faster way: paste the macro's keystrokes as editable text, fix the mistake, and yank it back.

"qp         " paste register q as text onto a new line
{edits}     " fix what's wrong using normal editing commands
0"qy$       " yank the corrected line back into register q
dd          " delete the helper line
Enter fullscreen mode Exit fullscreen mode

Real scenario

You recorded a macro in q that processes log lines, but it uses w where it should use W. Type "qp to paste the raw keystrokes on a new line. The sequence appears as readable (and editable) text, including control characters rendered as ^[ for Escape and ^M for Enter. Fix the w, then 0"qy$ to yank the whole corrected line back into register q, then dd to clean up. Your macro is now corrected without re-recording a single keystroke.

Caveat

Non-printable keys appear as control characters. To insert them while editing, use <C-v> followed by the key — <C-v><Esc> inserts a literal escape byte. Typing a raw caret and bracket (^[) won't produce the right byte sequence and will break your macro in subtle ways.


2) Use a named mark as a motion target for operators

Why it matters

Named marks aren't just jump destinations — any operator (d, y, c, >, gq) accepts a mark as its motion target, giving you precise linewise ranges without entering visual mode.

ma          " set mark 'a' on the current line
{navigate}  " move somewhere else in the file
d'a         " delete from current line to the line containing mark a
Enter fullscreen mode Exit fullscreen mode

Real scenario

You're cleaning up a config file. You set mark a at the start of a section you want to remove, scroll down to where it ends, and type d'a. The entire range — inclusive of both lines — is gone in one motion. The same trick works for yanking (y'a), re-indenting (>'a), or reformatting (gq'a). No visual mode, no line counting.

Caveat

The apostrophe form 'a is linewise — it operates on complete lines from the current line to mark a's line. If you need characterwise precision, use backtick: d`a deletes from the exact cursor position to the exact character where the mark was set. The two behave differently and it's easy to grab the wrong one, especially when the mark falls mid-line.


3) Anchor the second address of a range to the first match, not the cursor

Why it matters

In Vim range notation, a comma between two addresses makes the second address relative to the cursor. Replace it with a semicolon and the second address becomes relative to whatever line the first address matched — which is almost always what you actually want in scripts and macros.

:/error/;+2 yank a
Enter fullscreen mode Exit fullscreen mode

Real scenario

You want to yank the next error log line plus the two lines after it, regardless of where your cursor is sitting. With /error/,+2 yank a, the +2 is relative to the cursor — if you're on line 1 and the error is on line 50, you capture lines 1–52 instead of 50–52. With /error/;+2 yank a, the +2 is anchored to the match at line 50, so you always get the right three-line block.

The semicolon form chains cleanly too: :/start/;/middle/;/end/ delete — each search anchors to the previous result rather than drifting back to the cursor.

Caveat

This distinction is easy to overlook in interactive use, but it matters a lot inside macros where cursor position is unpredictable. If range-based operations occasionally capture the wrong lines, a misused , vs ; is often the quiet culprit.


4) Use \r, not \n, to insert a newline in a substitution replacement

Why it matters

In a search pattern, \n correctly matches a newline. But in the replacement string, \n inserts a NUL byte (displayed as ^@) — not a line break. To insert an actual newline in the replacement, use \r.

:%s/,/\r/g
Enter fullscreen mode Exit fullscreen mode

Real scenario

You have a comma-separated list on one line and want each item on its own line:

apple,banana,cherry
Enter fullscreen mode Exit fullscreen mode

Running :%s/,/\n/g produces garbage characters. Running :%s/,/\r/g correctly splits it into three lines. Same deal when adding a blank line before every function declaration: :%s/^function/\r&/ works; :%s/^function/\n&/ silently inserts NULs.

Caveat

The asymmetry is consistent: \n in the search side matches a newline; \r in the replacement side inserts one. This is intentional in Vim's design, but it trips up nearly everyone at least once. In some Neovim builds \n in the replacement may happen to work, but \r is the portable and reliable choice across all Vim and Neovim versions.


5) Reuse the last substitution's replacement text with ~

Why it matters

After a substitution, putting ~ in a new replacement string expands to whatever the last replacement was. This lets you apply the same transformation to a different pattern without retyping.

:%s/password/[REDACTED]/g
:s/api_secret/~/
Enter fullscreen mode Exit fullscreen mode

Real scenario

You're sanitizing a config dump for a bug report. First you replace all occurrences of password with [REDACTED]. Then you spot api_secret a few lines down — same replacement needed. Instead of retyping the bracket-wrapped word, :s/api_secret/~/ expands ~ to [REDACTED] from the previous substitution. If you're normalizing a handful of different identifier patterns to the same replacement, you do the first one verbosely and all subsequent ones with ~/.

Caveat

~ expands to the most recent replacement text globally — if you run any other substitution in between, even on a different buffer, ~ picks up that replacement instead. Keep the sequence tight or verify the current ~ value before relying on it in a longer editing session.


Wrap-up

Five tricks that reduce the "close but not quite" friction in Vim work: editing a macro without re-recording it, using marks as operator motion targets, anchoring range endpoints with ;, inserting real newlines with \r in substitutions, and recycling replacement text with ~. None of these are obscure — they're just behaviors that pay off every few days once you know they exist.

If you want more practical Vim tricks, I publish them at https://vimtricks.wiki.
What Vim operation still feels more awkward than it should be?

Top comments (0)