Tools: Vim commands that handle the detail work without friction

Tools: Vim commands that handle the detail work without friction

Source: Dev.to

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 hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: ]c " jump forward to the next changed hunk [c " jump backward to the previous changed hunk Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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 Enter fullscreen mode Exit fullscreen mode 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] Enter fullscreen mode Exit fullscreen mode 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]