First, a bit of history. In the old days, when you typed some text on a terminal, each key sent a character to the application. As the machines that the terminal was connected to became more powerful, terminals standardized on a control key, which would send some special characters, which were not printed but had some kind of function. The eventually triumphant character set was ASCII, with 128 characters out of which 32 are control characters. Pressing Ctrl together with a letter or other symbol written 10xxxxx₂ (binary notation) sends the control character whose code is 00xxxxx₂, e.g. Ctrl+[ sends character number 27₁₀ = 0011011₂ because
[ is 91₁₀ = 1011011₂.
A few function keys on terminals sent control characters:
- Backspace =
Ctrl-H (BS = BackSpace)¹
- Tab =
Ctrl-I (HT = Horizontal Tab)
- Linefeed =
Ctrl-J (LF = Line Feed) (few terminals ever had this key)
- Return or Enter =
Ctrl-M (CR = Carriage Return)
- Escape =
Ctrl-[ (ESC = Escape)
When terminals had more function keys, there weren't enough control characters to represent them all. So they sent sequences of character, and the universal convention is that these character sequences begin with the escape character,
As time went by, hardware terminals became rarer and rarer; nowadays there are many levels of translation between the keyboard and the application. The limitation in numbers of available characters and the hard-coded correspondences between certain key combinations and certain control characters is no longer relevant. However, applications have remained compatible with existing terminals, and terminals have remained compatible with existing applications, which made it difficult to change anything.
So even today, on Unix-like systems, applications running in a terminal emulator receive the character
Ctrl-I when the user presses the Tab key, the character
Ctrl-[ when the user presses Esc, etc. If Vim is running in a Unix terminal, it can't distinguish between
<Ctrl-[> because the terminal sends the same information.
Vim running in other environments doesn't have this limitation, so in principle it would be possible for them to send different information. Given that Vim uses the
Ctrl-[ character to represent
<Esc> in a lot of places, changing that would be impractical; instead Ctrl+[ could be made to send a different input event.
I'm not aware of any version of Vim itself that does differentiate between Esc and Ctrl+[ (but it isn't like I'd ever used any). Gvim, for one, seems to emulate a garden variety terminal.
Ironically, your best bet may be Vim running in a terminal emulator. Some terminal emulators allow you to customize escape sequences sent by keys and keychords. So you might arrange for Ctrl+[ to send not the ␛ (
Ctrl-[) character, but some escape sequence. Xterm, the “reference” terminal emulator for Unix-like systems, supports two such schemes. For example:
- Esc sends
␛ and Tab sends
␉, in all configurations.
?.VT100.modifyOtherKeys: 0 (default) or
?.VT100.modifyOtherKeys: 1, Ctrl+[ sends
␛ and Ctrl+I sends
?.VT100.modifyOtherKeys: 1 only affects meta combinations and control combinations that lack an ASCII character.
?.VT100.modifyOtherKeys: 2 and
?.VT100.formatOtherKeys: 0 (default), Ctrl+[ sends
␛[27;5;91~ and Ctrl+I sends
?.VT100.modifyOtherKeys: 2 and
?.VT100.formatOtherKeys: 1, Ctrl+[ sends
␛[91;5u and Ctrl+I sends
Support can be turned on by setting the
modifyOtherKeys resource when starting xterm (and
formatOtherKeys to select between the two schemes), or it can be turned on and off dynamically by the application.
Vim has no built-in support that I know of. You can write your own bindings for these escape sequences. It may be possible to get Vim to automatically set the terminal in the
modifyOtherKeys mode by feeding it a modified terminal definition, but doing so is beyond the scope of this answer.
Neovim has built-in support for one of the variants. As far as I understand, it doesn't switch support on if the terminal supports it, that's up to the user to do.
¹ Increasingly and most commonly nowadays, Backspace sends character 127.