In the GUI version of Vim (gvim), the mapping of keys seem to work for the most part. For instance, it is easy to map Ctrl-Shift-F2 to a keystroke:

"delete all lines in the current buffer
:nmap <C-S-F2> ggdG

For terminal versions of Vim (such as xterm, rxvt, win32's cmd.exe, etc), mapping something like Ctrl-Shift-F2 needs some extra work. It may seem daunting to deal with archaic terminal keycodes at first, but once you understand what is going on in the right context, it is quite simple.

Contents

There are two types of keycodes: terminal keycodes and Vim keycodes. Terminal keycodes look something like

^[[1;2A

A ^[ is ESC key.

To view keycodes type

cat

and press any keys. To exit type Ctrl-d (EOF).

These are actual bytes that are sent to Vim by the terminal when we type Shift-Up, for example (on an xterm).

Vim keycodes look like

<S-Up>

Vim needs its own representation of keycodes because it runs on a variety of platforms. Vim can act upon its own keycodes, and leave the assignment of terminal keycodes to autodetection (or to manual setup by the user, which is what this guide is for).

Vim keycodes look a lot like the keystrokes we would define in a mapping. They are easy to read, quite intuitive, and are almost treated the same by Vim. The only difference is that we can :set internal keycodes. For example, to manually set the keycode for Shift-Down_arrow:

:set <S-Down>=^[[1;2B

where ^[ is a literal ESC special character.

The following is not allowed because the keycode is not a listed Vim keycode:

:set <C-S-Down>=^[[1;6B

Once the correct terminal keycode is assigned to the appropriate Vim keycode, the keystroke should work in terminal Vim. Shift-Down_arrow should now work, but the Ctrl-Shift-Down_arrow keycode can not be set in the same way. For this we need mappings (see section 2b).

To enter a literal character in Vim, first type Ctrl-v, followed by a single keystroke. Hence, typing Ctrl-v then Esc will produce ^[.

To enter a terminal keycode in Vim, first type Ctrl-v, followed by the keystroke we want to obtain the term keycode from. Hence, typing Ctrl-v + Shift-Down_arrow will produce ^[[1;2B.

This means that there are two ways to set the Vim keycode as shown in section 1c:

"literally enter the keycode
:set <S-Down>={C-v}{Esc}[1;2B

or

"let the terminal send its keycode to Vim
:set <S-Down>={C-v}{S-Down}

where curly brackets {..} denote the action of typing the keystroke. For instance, when you see {C-v}, hold down Ctrl and hit v. Do not type it literally as {C-v}.

We shall use the curly brackets as nomenclature from now on.

Also make use of the Normal mode command ga to inspect the resulting keycode. Place the cursor over a special character, and type ga. This will provide you with ascii information of the character under the cursor.

As mentioned in section 1c, the Ctrl-Shift-Down_arrow keycode cannot be set in the same way as the Shift-Down_arrow keycode because it is not a listed Vim keycode. One way to get around that is to do the following:

:map <Esc>[1;6B <C-S-Down>

This maps the literal terminal keycode to a Vim keystroke (remember, not a Vim keycode).

The disadvantage of this is that Vim will "wait" everytime after the Esc key pressed for a potential "[" + "1" + ";" + "6" + "B" keystroke combination, just in case you meant to do <C-S-Down> instead of a simple <Esc> to Normal mode.

If you typed {Esc}[1;6B fast enough, you would do a Ctrl-Shift-Down_arrow. After all, you are mapping a keystroke, not a keycode.

A better way of mapping keycodes is to first assign the terminal keycode to an unused Vim keycode, and then map the newly used Vim keycode. This way, we can set option ttimeoutlen to a small value to ensure that the terminal keycode can only be entered into Vim as fast as the terminal can, but will be humanly-impossible to do it manually.

You only need to employ unused Vim keycodes either when
(a) there are no available Vim keycodes that match the terminal keycode, and
(b) the terminal keycode is longer than a single keystroke
or
(c) setting the Vim keycode that matches the terminal keycode causes weird behavior.

Example where (a) is not true:

:set <F13>=^[[1;2B
:map <F13> <S-Down>

Setting the unused <F13> is redundant, because <S-Down> is already available as a Vim keycode. Do this instead:

:set <S-Down>=^[[1;2B

Example where (b) is not true:

For a Win32 terminal, mapping Ctrl-Backspace is done by

:map <C-{C-v}{BS}> <C-BS>

Since the terminal keycode can be represented by <C-^?>, which is a single keystroke, there is no need to employ an unused Vim keycode.

Example where (c) is true:

For an rxvt terminal, merely doing

:set <A-j>=^[j <A-u>=^[u <A-6>=^[6

will not make Alt-j, Alt-u and Alt-6 work. Reason unknown. Need to assign to unused Vim keycodes.

where ^[ is a literal ESC special character and <S-F1> is literal text.

This means that you may have already set Vim's keycode <S-F1>, and that it was part of the Alt-Shift-F1 terminal keycode that was sent to Vim. Vim processes the entire terminal keycode and noticed that it could replace part of it with its own internal representation.

To fix this and enter the full terminal keycode, backspace up to < and do {C-v}{S-F1}. The full action would be:

:set <xF1>={C-v}{A-S-F1}{BS 6 times}{C-v}{S-F1}

to produce

:set <xF1>=^[^[[23~

To give some perspective, here are the actual terminal keycodes for rxvt: