Skip to Content

At least one Vim trick you might not know

I’ve been using Vim for eight years and am still discovering new things. This is usually seen as a Good Thing About Vim. In my head, though, it’s a failing of discoverability: I keep discovering new things because Vim makes it so hard to know what’s available.

While people often talk about the beauty of modal editing or text objects, I don’t think that gets at the essence of Vim. Vim is a patchwork of subsystems crammed together with every crag of space stuffed with extra special-purpose tooling. There’s over a hundred different keystroke commands in Normal mode alone. That density is a big part of what makes Vim so useful. When “show all matching tags for keyword” is just g] then you’re much more likely to actually use it.

In nondiscoverable systems we have to rely on guides to find useful stuff. There aren’t that many for Vim, though. You have beginning articles, like ciw and such.1 And you have expert articles that dive into subsystems. But nobody really talks about those special purpose tricks, leading people to stumble into stuff they needed for the past six years.

This article is about some of the little tricks that I use in Vim. None of them are deep dives, and I encourage you to learn more about whatever’s interesting. They also aren’t connected to each other. But that’s fine. In total, they’re more than enough to help a lot.

Organization

There are- very roughly- two categories of Vim users. Purists value Vim’s small size and ubiquitousness. They tend to keep configuration to a minimum in case they need to use it on an unfamiliar computer (such as during ssh). Exobrains, on the other hand, stuff Vim full of plugins, functions, and homebrew mappings in a vain attempt to pretend they’re using Emacs. If you took away an exobrain’s vimrc they’d be completely helpless.

As you can probably tell, I’m much more exobrain than purist. I’ve divided the tricks into two sections based on whether or not it involves adding mappings or settings to base Vim.

Purist Vim

I used the standard Vim help representations for modal commands, ie <cr> means pressing the enter key. In cases where you have to :h a specific string to get help, like :h E676, I put the help string in parenthesis.

Misc Normal Commands

":, @:
": is the register storing the last executed command. You can write ":p to print it to the buffer. @: reruns the last command.
"=
The “expression” register. You can input any vimL expression here and paste it, use with ctrl-R, etc. So you can paste in the local timestamp by typing "=strftime("%c")<cr>p.
mA, 'A
m{letter} sets a mark at your cursor. Then '{letter} will jump to that line. For lowercase letters this is per buffer, so you can use it for navigation. For uppercase letters, it’s global: if you’re not in the file with the A mark, 'A will jump to that file. You can see all of your set marks with :marks:.
ctrl-A and ctrl-X
Increments and decrements the next number in the line, from the cursor. This jumps to the number, so you can use it from anywhere. 10c-A is a lot easier than wwwwwciw20.
q:
Opens a history of your previous commands. You can operate on them like any Vim text, but changes aren’t saved. But you can run a modified command with <CR>. This makes it really easy to quickly modify and rerun commands, or search for an old one to reuse.
q/, q?
Same as q:, except for searches.
ctrl-I, ctrl-O
Moves you to the next or previous location in the jumplist. Useful for checking a quick thing and then backing right out. Really nice for reading help files.

Macros

See this post for a deep dive into using macros.

Visual Mode

gv
Selects the previous visual.
v_o
Goes to the other end of the visual block. Useful if you started one line too low or something. In block mode, it goes to the opposite diagonal corner: use v_O to go to the opposite horizontal corner.

g ctrl-A / ctrl-X

In visual mode, ctrl-A just increments the first number on every line. g ctrl-A, on the other hand, will bump the increment by one for each matching line. This is much easier to explain with a table:

selected ctrl-A g ctrl-A 2 g ctrl-A
a 0
b 0
c
d 0 
a 1
b 1
c
d 1 
a 1
b 2
c
d 3 
a 2
b 4
c
d 6 

operators: v, V, c-v (:h o_v)

You probably know about visual mode: v is character-wise, V is line-wise, ctrl-V is blockwise. But the three can also be used as motion operators, making the motion the corresponding -wise. For example, if you have

abc
abc
abc

If you place your cursor on the top b and press d2j, it will delete all three lines. That’s because j is a linewise motion. If you instead pressed d<c-V>2j, it would convert the motion to blockwise and delete just the middle column of bs.

One way I used to use this was with deleting to a search. Normally d/ is an exclusive character motion. So I’ll use dV/ to make it linewise and include the search-line in the deletion. There’s another way to do that, though:

/regex/{n}
Makes the motion go to the nth line below the match, or above if n is negative. It also has the side effect of making the motion linewise. So you want to delete to the first line matching regex and include that line, you can do d/regex//0.

Ex Commands

Ex commands are the stuff you write from the command mode, such as :s. Beyond substitution, there are a lot of other useful ways to use ex. All of these examples need a range, such as %.

:g/regex/ex

Runs the Ex command only on the lines that match regex. So for example you can use g/regex/d to delete all the lines matching the regex. v is like g except it runs ex on all of the lines that don’t match regex.

This becomes more powerful with norm and friends.

:norm {Vim}

Acts as if you ran {Vim} on every single line in the range. For example, g/regex/norm f dw will delete the first word after the first space on every line matching regex. This is often much easier than using a macro.

norm obeys all of your mappings. For example, if you mapped jk to <esc> in insert mode, norm I jk$diw will prepend a space to the beginning of the line, leave insert-mode, and then delete the last word on the line. I like this functionality a lot, but if you’d prefer it not to use your mappings, you can use norm! instead.

:co .

Copies the range to the current line. You can also do arbitrary points instead of ., such as +3 or 'a. mv moves it instead.

:y {reg}

Copies the range to the register {reg}. If {reg} is a capital register, this appends to the existing register. ie if we do

let @a = '' | %g/regex/y A

It will copy all lines matching regex in the entire file to a. This can help with extracting broken-up text from a file and copying it to the system clipboard (with let @+ = @a.)

:windo {ex}

Runs ex on all windows. :windo $ will scroll all windows to the bottom. There’s also bufdo, cdo, tabdo, etc.

This works really well with g and s. If you want to replace every instance of AA with BB but want to check each substitution first, you can use vimgrep AA to load all matches into a quickfix, then cdo s/AA/BB/cge to find/replace all the matches.

Exobrain Vim

These are the stuff that requires persistent storage or you modifying your Vim session. Hypothetically you could use them as a purist by typing them in, but some of these are significant enough changes that go against the purist spirit.

I’m only including uncommon things here. Like a lot of people map H to ^, so I don’t need to talk about that too. I also don’t need to talk about vim-sensible or vim-surround, and included only the more obscure plugins.

If you’re constantly tweaking your vimrc, do yourself a favor and add a command for that:

command! Vimrc :vs $MYVIMRC

Settings

I put all my settings, maps, and functions into a single vimrc file. Breaking it up into many files makes it harder to find what I’m looking for.

Most of the settings aren’t really Vim “tricks”. Best bet is to look at vim-sensible: almost everything there is good for your vimrc.

set lazyredraw
Do not redraw screen in the middle of a macro. Makes them complete faster.
set smartcase/ignorecase
With both on, searches with no capitals are case insensitive, while searches with a capital characters are case sensitive.
set undofile
Persistent undo, even if you close and reopen Vim. Super great when combined with the undotree plugin.
set foldcolumn={n}
Makes folds visible in the sidebar. The higher n is, the more folds are represented visually and the fewer are represented by a number.
set suffixesadd={str}
gf normally is “goto file under cursor”, but requires you also have the file suffix in the string. suffixesadd also checks for files with that suffix. If you have suffixesadd=.md, then pressing gf on the string “foo” will look for files foo and foo.md.
set inccommand=nosplit

This is Neovim only. inccommand shows you in realtime what changes your ex command should make. Right now it only supports s, but even that is incredibly useful. If you type :s/regex, it will highlight what matches regex. If you then add /change, it will show all matches replaced with change. This works with all of the regex properties, include backreferences and groups.

set statusline (:h statusline)

Specifies what appears in the bar at the bottom of each window. The formatting here is a lot more complicated and finicky than other settings, and explaining it would take a post of its own. In terms of simple tricks, there’s a couple of things we can do. First, Vim’s default statusline is

:set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P

The easiest thing to replace here is the %P, which shows the percentage you’re through the file. The statusline format reads %{exp()} as writing the result of exp(). So for markdown files, we can do things like

:set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %{wordcount()[\"words\"]}

To replace the percentage with the document wordcount.

You can also set tabline. If you don’t use tabs you can probably hack this to be a “global statusline”. Like you could do

set tabline=%{strftime('%c')}

To always show the date on top.

Maps

I have a lot of maps.

A lot of Vim’s prime real-estate is taken up by cruft. s saves one whole keystroke over doing just cl. U is the same as u except it does the undo as a new change, which is functionally useless. Q is identical to gQ and is a colossal trap, anyway. Z is only used for ZZ and ZQ. Heck, the Vim manual recommends rebinding things like _ and , to custom maps as “you probably never use them.” I’d much rather add completely new tasks to my keyboard vs saving a couple keystrokes. Some of the maps I have:

nnoremap Q @@
Instead of stumbling into ex mode, repeat the last macro used.
nnoremap s "_d
Makes s (along with corresponding maps for ss and S) act like d, except it doesn’t save the cut text to a register. Helps when I want to delete something without clobbering my unnamed register.
nnoremap <c-j> <c-w>j
Move to window below. Corresponding maps for h, k, l. Makes using windows much easier.
nnoremap <leader>e :exe getline(line('.'))<cr>
Run the current line as if it were a command. Often more convenient than q: when experimenting.

Special Arguments (:h map-arguments)

Writing map <buffer> lhs rhs makes the mapping only for that buffer. This is really handle when combined with autocommands, as a short-term shortcut, or when defining maps through a function. Buffer maps have priority over global maps, meaning you can override a general command with a more specifically-useful one.

On every use map <expr> {lhs} {expr} evaluates {expr} and uses the return value as the end mapping. One simple use-case is conditional maps. I have

nnoremap <expr> k (v:count == 0 ? 'gk' : 'k')
nnoremap <expr> j (v:count == 0 ? 'gj' : 'j')

Which makes j and k move by wrapped line unless I had a count, in which case it behaves normally. So I can navigate long paragraphs of prose without breaking things like 10j.

<silent> is good if you are having mappings launch ex commands.

inoremaps

You can make maps in insert-mode with inoremap. Maps start in insert-mode, so inoremap ;a aaaa will type in ‘aaaa’ instead of ‘;a’. If you want to do something in normal mode, use <c-O>. IE if we have

inoremap ;1 <c-o>ma

then ;1 will set the 'a mark at the point you are typing.

I tend to use semicolons as my leader key for imaps, because I almost never need to follow a ; with anything other than a space or a newline.

autocmd

Autocommands are great for configuration. You usually configure them in the form

augroup {name}
  autocmd! " Prevents duplicate autocommands
  au {events} {file regex} {command}
augroup END

Then whenever any of {events} happen in a file matching {file regex}, {command} fires. Events are listed under :h event. For example, if you do

augroup every
  autocmd!
  au InsertEnter * set norelativenumber
  au InsertLeave * set relativenumber
augroup END

Then Vim will turn off relativenumber only for insert mode.

Writing au {event} <buffer> {ex} only applies the autocommand to the buffer you are in. I sometimes use this for adding short-term event handlers to a specific file.

BufNewFile,BufRead

BufNewFile triggers when you create a new file, BufRead when you open the buffer for the first time. These are commonly used to add filetype-specific settings and mappings. One I have is

augroup md
  autocmd!
  au BufNewFile,BufRead *.md syntax keyword todo TODO
  au BufNewFile,BufRead *.md inoremap <buffer> ;` ```<cr><cr>```<Up><Up>
augroup END

This means for markdown files only, the string TODO is highlighted and typing ;` in insert mode will add code fences.

You can do much more complex things with autocommands. For example, adding an au for BufWriteCmd will override the standard save, letting you put in custom logic. That’s moving beyond the realm of “Vim tricks” and into the realm of “dark magic”.

Plugins

Most people know about popular plugins like vim-surround and NERDtree. This is a list of some of the more obscure ones that I think are very useful.

Undotree

Most text editors have linear undo. If you make change A, undo it, and then make change B, then A is lost forever. Vim, on the other hand, stores the entire undo tree. u only undoes to a previous state in your current branch. g- moves to the previous chronological version of the code. You can see the list of undo leafs with :undolist.

That format is hard to read, though, and we’re better off seeing the actual tree. That’s what Undotree does: it laets you pop open a nice ascii representation of the undo tree for easy navigation.

vim.swap

Gives you commands to swap arguments around, so you can replace (a, f(b, c)) with (f(b, c), a) in a couple keystrokes. I regularly have to do edits like that, so this is a huge quality of life improvement for me.

Neoterm

Puts a higher-level API around the neo/vim embedded terminal. Like :T {text} will send {text} to the terminal. Makes for nice REPLs.

" TODO {{{

There’s a lot that isn’t covered here because they’re too dense or long, like writing functions or the syntax system. There’s also a lot I don’t know yet. Here are some things that I plan on learning next:

Preview, Quickfix, and List Windows

I’ll occasionally use tools that use these, but I don’t know how to manipulate them myself. I want to add quickfix errors to my TLA+ plugin. I also like the idea of putting auxilary information and callback commands into a preview window. I see some possibilities that would be hard to replicate in an IDE.

The Neovim API

Neovim has an extensive API for composing Vim with external programs. So you can have a Python script send commands to a Neovim instance, or control it through a server. I’ve seen some cool concept demos with this, where people autocomplete based on what’s in their browser. It just seems like a lot of fun!

Text Objects

Never defined my own.


Anyway, that’s a brief tour of some slightly-more-obscure Vim features. Hope you learned something useful!


  1. This used to say cia instead of ciw. That was a typo, not a reference to the CIA Vim tipsheet. [return]