Tools
Tools: Vim commands that handle the detail work without friction
2026-03-02
0 views
admin
1) Jumping between diff hunks without scrolling ## Why it matters ## Real scenario ## Caveat ## 2) Sorting lines numerically instead of lexicographically ## Why it matters ## Real scenario ## Caveat ## 3) Running normal mode commands without your mappings interfering ## Why it matters ## Real scenario ## Caveat ## 4) Reverting to the last saved state in one step ## Why it matters ## Real scenario ## Caveat ## 5) Matching the shortest possible string in a regex ## Why it matters ## Real scenario ## Caveat ## Wrap-up 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. 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. 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. 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. 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. You have a list of benchmark results: 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. 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. :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. 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. :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. 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. 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. 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. 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. You have a log file with repeated bracketed fields: 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. \{-} 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,}. 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? Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to ? It will become hidden in your post, but will still be visible via the comment's permalink. as well , this person and/or CODE_BLOCK: ]c " jump forward to the next changed hunk [c " jump backward to the previous changed hunk CODE_BLOCK: ]c " jump forward to the next changed hunk [c " jump backward to the previous changed hunk CODE_BLOCK: ]c " jump forward to the next changed hunk [c " jump backward to the previous changed hunk CODE_BLOCK: :'<,'>sort n " sort selected lines numerically, ascending :'<,'>sort n! " sort numerically, descending :%sort n " sort the whole buffer numerically CODE_BLOCK: :'<,'>sort n " sort selected lines numerically, ascending :'<,'>sort n! " sort numerically, descending :%sort n " sort the whole buffer numerically CODE_BLOCK: :'<,'>sort n " sort selected lines numerically, ascending :'<,'>sort n! " sort numerically, descending :%sort n " sort the whole buffer numerically CODE_BLOCK: 150ms parse_config 23ms load_cache 1200ms run_query 87ms render_view CODE_BLOCK: 150ms parse_config 23ms load_cache 1200ms run_query 87ms render_view CODE_BLOCK: 150ms parse_config 23ms load_cache 1200ms run_query 87ms render_view COMMAND_BLOCK: :%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 COMMAND_BLOCK: :%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 COMMAND_BLOCK: :%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 CODE_BLOCK: :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 CODE_BLOCK: :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 CODE_BLOCK: :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 COMMAND_BLOCK: /start.\{-}end " matches shortest span from 'start' to 'end' :%s/<b>.\{-}<\/b>//g " removes each <b>...</b> tag pair individually COMMAND_BLOCK: /start.\{-}end " matches shortest span from 'start' to 'end' :%s/<b>.\{-}<\/b>//g " removes each <b>...</b> tag pair individually COMMAND_BLOCK: /start.\{-}end " matches shortest span from 'start' to 'end' :%s/<b>.\{-}<\/b>//g " removes each <b>...</b> tag pair individually CODE_BLOCK: [INFO] Request received [user=alice] [status=200] [WARN] Timeout reached [user=bob] [status=408] CODE_BLOCK: [INFO] Request received [user=alice] [status=200] [WARN] Timeout reached [user=bob] [status=408] CODE_BLOCK: [INFO] Request received [user=alice] [status=200] [WARN] Timeout reached [user=bob] [status=408]
toolsutilitiessecurity toolscommandshandledetailwithoutfriction