GUI frames
In GUI frames (whether X11, Windows, OSX, …), Emacs reads the Tab key as the tab
function key. However, because the Tab key on terminals traditionally sends the ^I
(Control+I) character, Emacs translates the tab
function key into the Control+I character (character 9), which is displayed as TAB
. This translation is done via function-key-map
.
A similar translation happens with some other function keys. (Backspace and Delete are a thorny case which I will not discuss in detail here.)
Function key Translated to character Notes
Number Name Decomposition
backspace 127 DEL Ctrl+? May be translated to C-h instead
tab 9 TAB Ctrl+I
linefeed 10 LFD Ctrl+J Few keyboards have this key
return 13 RET Ctrl+M
escape 27 ESC Ctrl+[
If you want to separate Tab from Ctrl+I altogether, remove the binding from function-key-map
:
(define-key function-key-map [tab] nil)
However this is not very useful, because entries in function-key-map
are overridden by bindings in mode-specific keymaps or in the global map. So if you want to define a different binding for tab
, just do it (in Elisp, not interactively, because the key reading prompt applies the function-key-map
translation so you'd end up rebinding TAB
and not tab
):
(global-set-key [tab] '…)
(define-key some-mode-map [tab] '…)
All standard modes that modify the action of the Tab key do it by modifying the TAB
key, which is a nickname for the C-i
character generated by the key combination Ctrl+I. If you want standard bindings to apply to tab
rather than C-i
, leave function-key-map
and mode keymaps alone, and instead redirect Ctrl+I to a different key.
(define-key input-decode-map [(control ?i)] [control-i])
(define-key input-decode-map [(control ?I)] [(shift control-i)])
(define-key some-mode-map [control-i] '…)
Now Emacs will report Ctrl+I as “<control-i>
(translated from TAB
)”. This isn't pretty, but it's unavoidable: the pretty-printing of character 9 as TAB
is built into the Emacs source code.
Terminal frames
In terminal frames, the problem is harder and often impossible. Terminals don't transmit keys, they transmit characters (more precisely, in fact, they transmit bytes). The Tab key is transmitted as the tab character — which is Control+I, same as what the key combination Ctrl+I generates. Function keys that have no corresponding character (such as cursor keys) are transmitted as escape sequences, i.e. sequences of characters beginning with ESC
= Control+[ (which is why Emacs defines escape as a prefix key — ESC
has to be a prefix). See How do keyboard input and text output work? for more background.
There are a few terminals that can be configured to send different key sequences for function keys, but not many. Both LeoNerd's libtermkey/libtickit and Thomas Dickey's xterm (since version 216) support this. In Xterm, the feature is optional and activated through the modifyOtherKeys
resource. However, I don't know of any popular terminal emulator other than xterm that supports this, in particular the many emulators built on libvte. Some terminal emulators let you do this manually through a user-defined correspondence from keychords to escape sequences.
This mechanism allows many key combinations to be distinguished, not just tab/C-i, return/C-m and escape/C-[. See Problems with keybindings when using terminal for a more detailed description.
The basic xterm feature is supported since Emacs 24.4. However the fundamentals (in particular Tab, Return, Escape, Backspace) still send the traditional control characters, because that's what applications expect. There is a mode where Ctrl+letter sends an escape sequence instead of the control character. So to distinguish the function keys from the Ctrl combinations on Emacs 24.4, modify its support for modifyOtherKeys
to use this mode by setting the resource to 2 instead of 1.
;; xterm with the resource ?.VT100.modifyOtherKeys: 2
;; GNU Emacs >=24.4 sets xterm in this mode and define
;; some of the escape sequences but not all of them.
(defun character-apply-modifiers (c &rest modifiers)
"Apply modifiers to the character C.
MODIFIERS must be a list of symbols amongst (meta control shift).
Return an event vector."
(if (memq 'control modifiers) (setq c (if (or (and (<= ?@ c) (<= c ?_))
(and (<= ?a c) (<= c ?z)))
(logand c ?\x1f)
(logior (lsh 1 26) c))))
(if (memq 'meta modifiers) (setq c (logior (lsh 1 27) c)))
(if (memq 'shift modifiers) (setq c (logior (lsh 1 25) c)))
(vector c))
(defun my-eval-after-load-xterm ()
(when (and (boundp 'xterm-extra-capabilities) (boundp 'xterm-function-map))
;; Override the standard definition to set modifyOtherKeys to 2 instead of 1
(defun xterm-turn-on-modify-other-keys ()
"Turn the modifyOtherKeys feature of xterm back on."
(let ((terminal (frame-terminal)))
(when (and (terminal-live-p terminal)
(memq terminal xterm-modify-other-keys-terminal-list))
(send-string-to-terminal "\e[>4;2m" terminal))))
(let ((c 32))
(while (<= c 126)
(mapc (lambda (x)
(define-key xterm-function-map (format (car x) c)
(apply 'character-apply-modifiers c (cdr x))))
'(;; with ?.VT100.formatOtherKeys: 0
("\e\[27;3;%d~" meta)
("\e\[27;5;%d~" control)
("\e\[27;6;%d~" control shift)
("\e\[27;7;%d~" control meta)
("\e\[27;8;%d~" control meta shift)
;; with ?.VT100.formatOtherKeys: 1
("\e\[%d;3~" meta)
("\e\[%d;5~" control)
("\e\[%d;6~" control shift)
("\e\[%d;7~" control meta)
("\e\[%d;8~" control meta shift)))
(setq c (1+ c)))))
(define-key xterm-function-map "")
t)
(eval-after-load "xterm" '(my-eval-after-load-xterm))