Control, Escape, and Meta Tricks
Terminal Tricks
Open a Unix or Linux terminal emulator. If you have Terminal.app on
macOS, ensure that the "Use Option as Meta Key" option is enabled in
its "Preferences" section. Now type foo bar baz
followed by meta+b
(i.e., alt+b or option+b
on modern keyboards). In a typical desktop environment with a
typical and modern terminal emulator running a modern shell like
Bash, Zsh, etc., the cursor should move backward by one word. Now
type esc b. The cursor should move back again
by one word. Finally type ctrl+[ b
and the same thing should happen again. How are we able to perform
the same operation in three different ways?
Note that if the desktop environment or the terminal emulator or the
set of shell key bindings is configured differently, the results may
vary. But we will assume that the typical defaults are in effect in
the remainder of this post. To understand why these three key
sequences yield the same result, it might be a good exercise to run
the command cat
and type the three key sequences again.
The three key sequences we are talking about are:
- meta+b (i.e., alt+b or option+b on modern keyboards)
- esc b
- ctrl+[ b
When we run cat
and type the three key sequences
mentioned above, the following output may appear:
$ cat ^[b^[b^[b
The output shows that the terminal sends the same input
to cat
each time: the escape character that
appears as ^[
in the output and the
character b
. This becomes more apparent if instead of
running cat
, we run od -t d1
and type the
three key sequences followed
by ctrl+d enter:
$ od -t d1 ^[b^[b^[b 0000000 27 98 27 98 27 98 10 0000007
Indeed decimal 27 is the code of the escape character.
Similarly decimal 98 is the code of the character b
.
Control Codes
Let us first discuss why typing ctrl+[
produces the escape character. The
character [
has code 91 (binary 1011011) and holding
the ctrl key while typing it results in a control code
obtained by taking 91 (binary 1011011), keeping its five least
significant bits, and discarding the rest. We get the control code
27 (binary 11011) as the result. This is the code of
the escape character. This explains
why ctrl+[ produces the escape
character and why the escape character is represented
as ^[
while typing it into the standard input. The
caret sign (^
) here is a notation for
the ctrl
modifier.
The following table provides some more examples of control codes that can be obtained by typing the ctrl key along with some other key.
Key | Modified Character | Control Character | ||||
---|---|---|---|---|---|---|
Binary | Decimal | Character | Binary | Decimal | Character | |
ctrl+@ | 1000000 | 64 | @ | 00000 | 0 | Null |
ctrl+g | 1000111 | 71 | G | 00111 | 7 | Bell |
ctrl+h | 1001000 | 72 | H | 01000 | 8 | Backspace |
ctrl+i | 1001001 | 73 | I | 01001 | 9 | Horizontal Tab |
ctrl+j | 1001010 | 74 | I | 01010 | 10 | Line Feed |
ctrl+m | 1001101 | 77 | M | 01101 | 13 | Carriage Return |
ctrl+[ | 1011011 | 91 | [ | 11011 | 27 | Escape |
This explains why typing ctrl+g in a modern terminal emulator produces an audible beep or a visual flash, why ctrl+h erases a character, and so on. This also explains why we can type ctrl+[ in Vim to escape from insert mode to normal mode. While we can type escape with the esc key on the keyboard, we can do so with the ctrl+[ key too within the terminal.
The keen eyed may notice that the table above has lowercase letters in the first column but the second and third columns use the code of the corresponding uppercase letters. The lowercase letters in the first column is merely a notation I am using in this post to identify the keys on a keyboard. They don't actually mean lowercase characters. In fact, the very early keyboards only had uppercase letters and they simply toggled the 7th least significant bit of the modified character to obtain the control code.
By the way, an interesting thing worth noting here is that even if
we do consider the code of the lowercase character and pick only its
five least significant bits, we get the same control code as we
would get if we started with the corresponding uppercase character.
For example, consider the key sequence
ctrl+g. The uppercase character G
has the code 71 (decimal 1000111) and the lowercase
character g
has the code 103 (decimal 1100111). The
five least significant bits are equal in both. So when we pick the
five least significant bits and discard the rest, we get the same
result, i.e., 7 (binary 111) which is the code of the bell
character. This is due to the fact that the five least significant
bits of the code of a lowercase character is exactly the same as
that of the corresponding uppercase character. They differ only in
their sixth least significant bit. This is only an interesting
observation. It is of little significance though because like I
mentioned earlier, the early keyboards only flipped a bit in the
code of the uppercase characters when the ctrl modifier
was applied.
In addition to what we have discussed so far, some terminal emulators also implement a few special rules such as the following:
Key | Modified Character | Resulting Character | ||||
---|---|---|---|---|---|---|
Binary | Decimal | Character | Binary | Decimal | Character | |
ctrl+space | 0100000 | 32 | Space | 0 | 0 | Null |
ctrl+? | 0111111 | 63 | ? | 1111111 | 127 | Delete |
One could argue that the first row shows a straightforward example of picking the least significiant five bits to arrive at the control code, so it is not really a special case. That is a fair point. However, in the history of computing, different systems have implemented slightly different methods to compute the resulting control codes from our input. Flipping the 7th least significant bit was one of the early methods. Turning off only the the 6th and the 7th least significant bits has been another method. Subtracting 64 from the character code has been yet another one. These different methods produce identical results for the first table but not so for the second table. For example, while turning off the 6th and 7th least significnat bits of 32 (the code of the space character) does give us 0 but merely flipping its 7th bit does not. Further, note that allowing ctrl+space to produce the null character is a bit redundant because ctrl+@ already did that right from the days of very early keyboards. The second entry above is also a special rule because we neither turn off bits nor subtract 64. Instead, we flip the 7th least significant bit which amounts to adding 64 to the code of the modified character. It is also the only control code that has its 6th and 7th least significant bits turned on.
Meta Key Sequences
The meta key no longer exists on modern keyboards. On
modern keyboards, we use the alt or option key
instead of meta. For example, when a shell's manual says
that we need to type meta+b to move the cursor
back by one word, what we really type on a modern keyboard is
either alt+b
or option+b. In fact, the cat
and od
experiments mentioned earlier show that when we
type the modern alternative for the meta key along with
another key, what most terminals really send to the underlying
program is an escape control code (27) followed by the code
of the modified character.
Meta Key Sequence | Escape Key Sequence | Control Key Sequence |
---|---|---|
meta+b | esc b | ctrl+[ b |
meta+f | esc f | ctrl+[ f |
meta+/ | esc / | ctrl+[ / |
meta+: | esc : | ctrl+[ : |
There are several layers of software involved between the keyboard input and the application running in the terminal and the exact behaviour of the alt or option key may vary depending on the configuration of each of these layers. Terminal configuration alone is a complex topic that can be discussed extensively. However, the behaviour described here is one of the popular defaults. Alternative behaviours exist but they generally produce similar effects for the user.
Awkward Vim Tricks
Most Vim users know that we can go from insert mode to command-line mode and search for patterns by typing either esc / or C-[ /. But what some people may find surprising is that we can also go from insert mode to searching patterns simply by typing meta+/. Yes, this can be verified by running Vim in a terminal emulator. While insert mode is active, type alt+/ or option+/ and the current mode should instantly switch to the command-line mode with the forward-slash (/) prompt waiting for our search pattern. This works only in a terminal emulator. It may not work in the graphical version of Vim. The table above illustrates why this works in a terminal emulator.
Similarly, in a Vim instance running within a terminal emulator, we can type meta+: to go directly from insert mode to command-line mode and enter Ex commands. We can type meta+0 to go directly from insert mode to the first character of a line, or type meta+$ to go to the end of the line, and so on. These are equivalent to typing esc 0, esc $, etc.
More interestingly, we can type meta+O to open a line above, meta+A to append text at the end of the line, meta+I to append text at the beginning of the line, or meta+S to delete the current line while staying in insert mode! Since the Vim commands O, A, I, and S leave us back in insert mode, we are able to perform an editing operation that involves leaving the insert mode, doing something interesting, and returning to insert mode instantly using the the meta key combination. The following table summarises these observations:
Initial Mode | Meta Key Sequence | Equivalent To | Operation | Final State |
---|---|---|---|---|
Insert | meta+/ | esc / | Enter command-line mode to search a pattern | Command-line |
Insert | meta+: | esc : | Enter command-line mode to enter Ex command | Command-line |
Insert | meta+0 | esc 0 | Move to the first character of the line | Normal |
Insert | meta+$ | esc $ | Move to the end of the line | Normal |
Insert | meta+O | esc O | Begin a new line above and insert text | Insert |
Insert | meta+A | esc A | Append text to the end of line | Insert |
Insert | meta+I | esc I | Insert text before the first non-blank in line | Insert |
Insert | meta+S | esc S | Delete line and insert text | Insert |
There is no good reason to use Vim like this but it works, thanks to the quirky history of Unix terminals and keyboards!