1. 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:

    1. This only applies to indexing objects with static strings, which could potentially be delimited by ' or ", and contain escaped delimiters.

    2. These statements often share a line with other code that I don’t want to unwittingly break.

    3. 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!


    1. There is a timeout for mapped key sequences, see timeout, ttimeout, timeoutlen and ttimeoutlen for more information. 

Notes

  1. preciselythat posted this