Hillel Wayne, May 31 2019 | link
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 is interesting. They also aren’t connected to each other. But that’s fine. In total, they’re more than enough to help a lot.
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.
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.
vim think but you can rid yourself of
multiple blank lines using cat! Using cat -s
will do the job. To use this with vi m type the following
command: %!cat -s and reap the rewards.":, @:": 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.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.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 | a 1 | a 1 | a 2 |
| b 0 | b 1 | b 2 | b 4 |
| c | c | c | c |
| d 0 | d 1 | d 3 | d 6 |
: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 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/exRuns 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.
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.
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.
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.: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.
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.
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,BufReadBufNewFile 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”.
cia instead of ciw. That
was a typo, not a reference to the CIA Vim
tipsheet.