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
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
(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
(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
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.
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.24. 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.24, 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)))
(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;6;%d~" control shift)
("\e\[27;7;%d~" control meta)
("\e\[27;8;%d~" control meta shift)
;; with ?.VT100.formatOtherKeys: 1
("\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 "")
(eval-after-load "xterm" '(my-eval-after-load-xterm))