Taming Vim — 3. Mappings & Macros
This is the third part in the series Taming Vim, a series focused on improving the intermediate Vim user’s understanding and operation of the text editor.
Understanding mappings
Mapping, or key mapping, is used to change the meaning of keys.
We can, for example, map the “F1” key to do something silly like delete from
the first a to the next a:
:map <F1> 0fadfa
Now we can go to the beginning of the line (0), go to the next a (fa),
delete to the following a (dfa) just by pressing “F1” in normal mode. Vim
has other modes (see what happens if you press “F1” in another mode) but we
only intended this to work in normal mode. Consulting the map
overview shows that :map defines the mapping for most modes
when what we actually want is :nmap. However, it is still possible to make
our command work in insert mode (visual mode is left as an exercise):
:imap <F1> <Esc><F1>i
Exit insert mode (<Esc>), perform our “F1” mapping, enter insert mode again.
We can also map commands that need to be executed on the Vim command line:
:nnoremap <F2> :%s/teh/the/g<CR>
Pressing “F2” will replace all instances of “teh” with “the”, notice that
<CR> appears at the of the command to emulate pressing the return key,
without this we’d just be left in the command line without executing anything
(which can be useful in some circumstances.) Other special
keys (like the up arrow, “F1”, etc.) and special
characters (like “escape”, “return”, etc.) are well
documented. More often than not mappings should be declared with the “noremap”
variants to avoid accidental recursive or nested mappings.
A special key often used by Vim users (and plugin authors) for their own
purposes is <Leader>. The map leader defaults to backslash (\)
but can be changed (I use the more accessible comma (,) key) by setting the
mapleader variable; there are some caveats about setting this, be sure to
consult the help.
Vim mappings are not limited to just a single special key or even a single
character, they can be a sequence of just about any keystrokes! Following on
from this, Vim allows ambiguous mappings, meaning that it is
possible to have a mapping for aa and ab and Vim will wait 1 after the
first a to see which one you meant.
Anything you can type into Vim with your hands can be mapped!
Mappings
The make command can be a useful for things other than running GNU make. I use it to run Pyflakes and JSHint, opening the quickfix window if there are any errors, simply by pressing “F5”:
nnoremap <F5> :silent make %<CR>:cwindow<CR>
" Quickfix navigation.
nnoremap ]q :cnext<CR>
nnoremap [q :cprevious<CR>
(Some people prefer to run their unit tests with make, I haven’t found a good
way to get trial results into the quickfix window yet.)
Or to move the current line up and down (from normal, visual or insert mode) one line at a time with “Ctrl-Shift-Up” and “Ctrl-Shift-Down”:
nnoremap <silent> <C-S-Up> :move .-2<CR>|
nnoremap <silent> <C-S-Down> :move .+1<CR>|
vnoremap <silent> <C-S-Up> :move '<-2<CR>gv|
vnoremap <silent> <C-S-Down> :move '>+1<CR>gv|
inoremap <silent> <C-S-Up> <C-o>:move .-2<CR>|
inoremap <silent> <C-S-Down> <C-o>:move .+1<CR>|
Or ack (with ack.vim) the word under the cursor (or the current
visual selection), and put the results in the location list, with
<Leader>gw or <Leader>gW:
" :LAck the word under the cursor in the current file and open the location list.
nnoremap <Leader>gw :silent LAck \\b<C-r><C-w>\\b %:p<Bar>:lwindow<CR>
vnoremap <Leader>gw "zy:silent LAck <C-r>z %:p<Bar>:lwindow<CR>
" :LAck the word under the cursor recursively and open the location list.
nnoremap <Leader>gW :silent LAck \\b<C-r><C-w>\\b<Bar>:lwindow<CR>
vnoremap <Leader>gW "zy:silent LAck <C-r>z<Bar>:lwindow<CR>
" Location list navigation.
nnoremap ]w :lnext<CR>
nnoremap [w :lprevious<CR>
(It is possible to change :LAck to :lgrep with no or few modifications.)
Macros
Macros, or complex repeats, are closely related to mappings but seem to be geared more for short-term use. The Vim wiki page for macros talks about saving macros but I would turn it into a mapping for longer-term use.
Begin recording a macro by pressing q followed by the name of
a register, the text “recording” will appear in the status line to confirm,
commands are recorded into the selected register until q is pressed again. To
execute (read: play back) the contents of a register use the @ command
followed by the name of the register to play back. Usefully, @@ will execute
the previously executed macro again.
True story
Recently I ran JSHint over a codebase I work on and it suggested, among other
things, I rewrite foo["bar"] as foo.bar. Now this may seem like a job for
a substitution but consider these points:
This only applies to indexing objects with static strings, which could potentially be delimited by
'or", and contain escaped delimiters.These statements often share a line with other code that I don’t want to unwittingly break.
I already know Vim commands and I can build my macro as I edit with powerful text-editing commands.
Using my JSHint compiler and ]q the cursor is placed on the first character
after the [, armed with this I arrived at the following sequence of commands
after only a few attempts:
di'"_ca[.<Ctrl-R>"<Escape>
(<Ctrl-R> and <Escape> are literal keystrokes pushed during the recording.)
Delete the text inside, but excluding, the single quotes (di'). Delete the
text inside, and including, the square brackets into the blackhole register and
start insert mode ("_ca[). In insert mode, insert a period and the
contents of the unnamed register, containing the text from the first delete,
and return to normal mode (.<Ctrl-R>"<Escape>).
Pressing ]q to move between errors and @@ to run my macro over them
(occasionally making other edits) really sped the process up while also giving
me a warm fuzzy feeling inside.
X marks the spot.
Mappings and macros are powerful ways to improve productivity with tasks you perform frequently, the difficult part is recognizing those time-consuming (or RSI-inducing) bad habits that would benefit from being converted to mappings (or macros.)
I’m slowly building up my collection of mappings, most of which were inspired by the work of others, but I’d love to hear about some mappings you are proud of, rely on day to day or just think are awesome!
What’s all this then?
I don’t have a topic planned for my next post, if you have something you’d like to know more about or something interesting you think other people should know more about, let me know!
-
There is a timeout for mapped key sequences, see timeout, ttimeout, timeoutlen and ttimeoutlen for more information. ↩