15

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.

  • 1
    Is this from a GUI frame or a terminal frame? I don't know if you can override it for a terminal. – dgtized Sep 25 '14 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 Aufflick Sep 25 '14 at 2:12
14

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.

  • 1
    Works perfectly! ++ would buy an emacs stackexchange again :) – Mark Aufflick Sep 25 '14 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 Aufflick Sep 25 '14 at 2:18
13

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.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)))
  (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))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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