20

I want to make Control-i perform indent-region (basically since Xcode has built that muscle memory already).

I realise that Control-i and tab are indistinguishable in the Ascii sense, but they are in the keycode sense.

I've tried the obvious:

(global-unset-key (kbd "C-i"))
(global-set-key (kbd "C-i") 'indent-region)

to no avail - pressing Control-i still just does whatever the tab key does in the current context. Is there anything I can do to help Emacs treat the tab button differently to Control-i?

Update: I guess an equivalent outcome would be achieved by being able to remap what a tab/Control-i press does when a region is selected.

2
  • 1
    Is this from a GUI frame or a terminal frame? I don't know if you can override it for a terminal.
    – dgtized
    Commented Sep 25, 2014 at 0:37
  • Good Q, GUI frame usually, but I do remote into servers and use emacs in a terminal sometimes (with a git-shared emacs.d of course :)
    – Mark
    Commented Sep 25, 2014 at 2:12

2 Answers 2

24

I don't think that this can be achieved from a terminal, but in GUI mode you could try this:

(define-key input-decode-map [?\C-i] [C-i])
(global-set-key (kbd "<C-i>") 'indent-region)

I do the same thing with C-m so that it can be distinguished from RET

EDIT:

The following should work whether you are in GUI or TTY mode:

;; Unbind <C-i> from the TAB key and bind it to indent-region.
;; Since TAB and <C-i> cannot be differentiated in TTY emacs,
;; the workaround is to conditionally bind TAB to indent-region
;; when there is an active region selected.
(if (window-system)
    ;; IF we are not in a TTY, unbind C-i from TAB
    (progn
      (define-key input-decode-map [?\C-i] [C-i])
      ;; ... and remap it to indent-region
      (global-set-key (kbd "<C-i>") 'indent-region))
  ;; ELSE IF we are in a TTY, create a replacement for TAB
  (defun my/tab-replacement (&optional START END)
    (interactive "r")
    (if (use-region-p)
        ;; IF active region, use indent-region
        (indent-region START END)
      ;; ELSE IF no active region, use default tab command
      (indent-for-tab-command)))
  ;; Bind our quick-and-dirty TAB replacement to the TAB key
  (global-set-key (kbd "TAB") 'my/tab-replacement))

It is not pretty, but it seems to do the job. I welcome any refinements or edits to this code as necessary.

7
  • 1
    Works perfectly! ++ would buy an emacs stackexchange again :)
    – Mark
    Commented Sep 25, 2014 at 2:14
  • Only minor problem I just thought of is that now we can bring up a terminal emacsclient against an Emacs that was started with a window system (which I do sometimes). If I don't see any delay I'll just use the tab-replacement func in all cases.
    – Mark
    Commented Sep 25, 2014 at 2:18
  • 2
    I just want to add that the <C-i> and [C-i] could have been an arbitrary identifier, like <foobar> and [foobar], and it would still work; just don't call it tab or backspace
    – xdavidliu
    Commented Jul 13, 2019 at 13:04
  • 1
    @alper, Most likely this means that (window-system) returned nil at the time your .emacs was loaded. This could be because you are running a non-graphical instance of Emacs, or because you are running an Emacs daemon.
    – nispio
    Commented Jan 22, 2020 at 16:39
  • 1
    Yes nice catch, I am exactly running Emacs as daemon. Is there any way to maintain your answer by keeping Emacs running as daemon?
    – alper
    Commented Jan 23, 2020 at 21:10
19

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))
4
  • When you say "Emacs 24.24" do you mean "Emacs 24.4"?
    – tarsius
    Commented Oct 11, 2019 at 16:54
  • 1
    @tarsius A comment in the code that's copied from my init file says “24.4”, so I think that's correct, and “24.24” in the text that I wrote for this answer was a typo for “24.4”. Commented Oct 11, 2019 at 16:59
  • I think this is the better answer.
    – ceving
    Commented Jul 6, 2021 at 12:42
  • Just to expand on the topic of terminal emulators and rebinding C-i to another key sequence, both KDE's Konsole (GNU/linux) and Powertoys (windows) can do that in a few clicks in the settings.
    – Charles G
    Commented Sep 15, 2022 at 12:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.