Most of the time Vim does what you expect. But there is a category of editing tasks where the obvious approach quietly fails you: numeric data that sorts wrong, a diff window you can only scroll through, a macro that breaks because of your own mappings, or a regex that swallows half the file instead of a small piece of it. None of these are exotic edge cases — they come up in ordinary work.
These five commands are the ones I reach for when that category of problem shows up.
1) Jumping between diff hunks without scrolling
Why it matters
When you open two files side by side with vimdiff or :diffsplit, the changed sections are highlighted — but you still have to find them. Scrolling through a large file looking for the next colored block is slow and easy to miss. ]c and [c are motions that jump directly to diff hunks, skipping unchanged content entirely.
]c " jump forward to the next changed hunk
[c " jump backward to the previous changed hunk
Real scenario
You are reviewing a pull request locally with vimdiff old.go new.go. The files are 300 lines each and there are seven changes scattered through them. Pressing ]c from the top takes you directly to each hunk in sequence. Pressing [c steps back if you want to re-examine one. No scrolling.
Caveat
These motions require an active diff session — they do nothing in a regular buffer. If you land on a hunk and want to act on it, do (diff obtain) pulls the change from the other window, and dp (diff put) pushes it the other direction. Run :diffupdate if the highlighting goes stale after you make edits.
2) Sorting lines numerically instead of lexicographically
Why it matters
Plain :sort in Vim is alphabetical. That means 10 sorts before 2, 100 sorts before 9, and a list of version numbers or file sizes ends up in nonsensical order. Adding the n flag tells Vim to compare lines by numeric value instead of character code.
:'<,'>sort n " sort selected lines numerically, ascending
:'<,'>sort n! " sort numerically, descending
:%sort n " sort the whole buffer numerically
Real scenario
You have a list of benchmark results:
150ms parse_config
23ms load_cache
1200ms run_query
87ms render_view
Selecting all four lines and running :'<,'>sort n orders them by the leading number: 23ms, 87ms, 150ms, 1200ms. With plain sort, you get 1200ms second because "1" < "8" lexicographically.
Caveat
The n flag extracts the first integer found on each line as the sort key. If a line has no leading number, Vim treats it as zero. Non-numeric lines that happen to mix in will group at the top. For floating-point values or sorting by a middle column, pipe through an external sort instead: :'<,'>!sort -k1 -n.
3) Running normal mode commands without your mappings interfering
Why it matters
:normal {cmd} is a powerful way to replay Normal mode keystrokes over a range of lines — common in macros, autocommands, and shared vimrc snippets. The problem is that it applies your custom mappings. If you have nnoremap w b in your config, then :%normal w moves backward instead of forward. Adding a bang fixes it: :normal! {cmd} always executes the built-in command, regardless of what you have mapped.
:%norm! A; " append semicolon to every line using the real A, not any remapped A
:'<,'>norm! >> " indent selected lines using the real >>, not any remapping
:5,10norm! dw " delete first word on lines 5–10 using the real dw
Real scenario
You are writing a function in a shared vimrc that bulk-indents a range of lines. Without the bang, the function breaks on any machine where > has been remapped. With :norm!, the function works identically everywhere, regardless of the user's mapping configuration.
Caveat
:norm! does not accept special key notation directly in command-line usage. If your command needs <CR>, <Esc>, or <C-a>, you have to wrap it in :execute "norm! \<CR>" (using execute to interpolate the special key). This applies to any key that requires angle-bracket notation.
4) Reverting to the last saved state in one step
Why it matters
Vim's undo history is deep and time-stamped. The :earlier command navigates it using time units instead of individual undo steps — and f is the unit for file writes. So :earlier 1f jumps the buffer back to exactly the state it was in when you last ran :w. It does not matter if you made 50 changes since then; one command reverts all of them.
:earlier 1f " revert to state at last file write
:earlier 2f " revert to two saves ago
:later 1f " re-apply forward to next write checkpoint
Real scenario
You refactored a function, saved, continued editing, and an hour later realized the refactor introduced a subtle bug. You want to see the version just before that last batch of changes without destroying the ability to redo. :earlier 1f takes you back to the previous save in one command. If you change your mind, :later 1f restores the current state.
Caveat
This uses Vim's in-memory undo tree — it does not reload the file from disk. If you want a true disk reload, use :e! instead. Also, undo history is lost when you close Vim unless you have :set undofile enabled. With undofile, the undo tree persists across sessions, which makes :earlier 1f useful even after restarting Vim.
5) Matching the shortest possible string in a regex
Why it matters
By default, .* in a Vim pattern is greedy — it matches as many characters as possible. When your text has repeated delimiters, that means one .* can consume far more than you intended, from the first delimiter all the way to the last one in the file. Vim's non-greedy quantifier is \{-}, and it works as the lazy counterpart to *, matching as few characters as possible.
/start.\{-}end " matches shortest span from 'start' to 'end'
:%s/<b>.\{-}<\/b>//g " removes each <b>...</b> tag pair individually
Real scenario
You have a log file with repeated bracketed fields:
[INFO] Request received [user=alice] [status=200]
[WARN] Timeout reached [user=bob] [status=408]
Running :%s/\[.\{-}\]//g removes every bracket pair individually. Using greedy .* instead would eat from the first [ all the way to the last ] on the line, deleting content you wanted to keep.
Caveat
\{-} is Vim's regex syntax, not PCRE. If you are accustomed to .*? from Python or JavaScript, this is the direct equivalent but the syntax is different. In Vim's very-magic mode (\v), you can write {-} without the backslash. For zero-or-more non-greedy, use .\{-}. For one-or-more, use .\{-1,}.
Wrap-up
None of these are obscure. Diff navigation with ]c/[c replaces scrolling. Numeric sort with sort n prevents the kind of ordering bug that looks like a data problem until you realize it is not. :norm! is essential the moment you start writing any vimrc automation. :earlier 1f is a safety net for the editing session. And non-greedy \{-} is one of those regex tools you reach for constantly once you know it exists.
If you want more practical Vim tricks like these, I publish them regularly at https://vimtricks.wiki.
Which of these do you already use, and which ones sneak up on you?
Top comments (0)