You cannot really change the C-[ binding at user-level
maps, as
you would do with global-set-key
. However, you can change it as
a keyboard event before it reaches those keymaps. You can say for example:
(define-key input-decode-map
(kbd "C-[")
[control-bracketleft])
and then use [control-bracketleft]
in your keymaps. Pretty simple isn't it?
Director's Cut
Unfortunately, it is not that simple, and this solution requires
some adjustments, which will appear to be very painful. You've been warned.
But let's see first why user-level maps cannot answer the question.
In the following, I refer to the Emacs Lisp manual for emacs 26.1 when I
say "see something"
without more precision.
C-[ is interpreted at a
very early stage as the ASCII control character ESC
(see
21.7.1 - Keyboard Events).
This code is spread out all other the place as the prefix
for longer sequences. There is a reason for that: ESC
is
actually the meta prefix (see meta-prefix-char
), and all
bindings that read
M-something will translate to a sequence that starts with ESC
.
Thus, changing the global map won't be enough: you need first to change meta-prefix-char
, then
to remap ESC
to your new meta-prefix-char
in each and every map that uses M- before you can safely map C-[.
OK then, of course: let's use input-decode-map
. There is
a couple of similar maps we may get tempted to use (see sections 21.8.3
and 22.14), but let's stick to this one.
And
well... this works! You're done, aren't you?
Actually, no, the story does not end here. This works... as long as you're
using a window system. If due to bad luck you're jailed
in the linux console in an emergency state, you realize how
dramatic the situation has become: arrow keys, Home, and
of course M- bindings, are all garbage. Why? Because when the terminal says ESC
(which it does when you type C-[), it really means ESC
, and starts
a sequence of the same kind it uses to transmit non-ASCII characters.
Observing the disaster, you may consider it wise to protect
the above input-decode-map
modification in such a way
that it activates only in the case a window system is controlling
the keyboard:
(let ((frame (framep (selected-frame))))
(or (eq t frame)
(eq 'pc frame)
(define-key input-decode-map
(kbd "C-[")
[control-bracketleft])
)))
Terminals then work as they used to.
Now, can we deal with C-[ on terminals?
Actually, yes we can, on the linux console as well as on the other terminal
emulators I can play with. But that makes the story a
pretty long one, as new characters enter the scene. For it is no more
emacs alone: the terminal has now the central role.
Let's lend an ear on what the linux console has to say. Type C-v before
some key to hear it plain. C-[ is ESC
; so is Esc.
The up arrow sounds like ESC [ A
, while M-a is
ESC A
. Hmm... Looks like this meta key circumvolutions in
emacs isn't it?
Anyway.
Unless we're ready to play some tricks based on the time
elapsed between the character events (which by the way won't distinguish
Esc from C-[), it seems we've got no choice but to tell the
console what we actually don't mean ESC
when we type C-[. Moreover, it appears pretty soon
that C-[ is not the only
problem with stock terminal codes: modifiers are most of time
erased out the transmitted information. We need to customize the
terminal for the very same reason we customize emacs: it would be
so much more practical if we do.
At this point, you shall dare a deep look into your terminal's documentation eyes:
man pages loadkeys(1)
for the
linux console, for xterm
xterm(1)
in section Custom Key Bindings, and whatever-i-dont-know for other terminals.
In KDE konsole
, you can
define custom translations in Settings/Edit Current Profile...
then Keyboard. Here is an excerpt from ~/.local/share/konsole/Test.keytab
after playing with this latter dialog:
key [+Ctrl+AnyModifier : "\EO*["
Once you have the terminal send ESC O 5 [
for
C-[ (as in the above configuration), you can go back to emacs.
For of course,
you're not done yet.
To instruct emacs what dialect a given terminal uses, you can
adjust input-decode-map
. Yes, this is fortuitously the very one we've modified
at the beginning of this story, and this is the very one
term/xterm.el
touches when xterm is involved. A good place for the adjustment
is tty-setup-hook
(see section 40.1.3):
(add-hook 'tty-setup-hook
(lambda ()
(let ((term (getenv "TERM")))
(cond
(;; xterm-function-map not in doc, but in term/xterm.el
(boundp 'xterm-function-map)
(map-my-term-codes xterm-function-map))
((equal term "linux")
(map-my-term-codes input-decode-map))
)
)))
Be aware this hook only runs if you're in a terminal. Thus,
you cannot insert here the code for the window system initialization.
Here's the translation function by itself:
(defun map-my-term-codes (map)
(define-key map (kbd "M-O 5 [")
[control-bracketleft])
)
And then you can take some rest: it's the end of the journey. Of course, if you don't care about
terminals, it's quick as you'll skip all the painful part. But you'll admit
it's also rather incomplete.
Two final notes:
I choose ESC O 5 [
to code C-[. This is just
an example: I won't pretend it's a good choice. Only the 5
part, which means C-, seems to obey some kind of established
convention
configuring the linux console leaves a bad taste: it does
not seem possible to do the binding without using an
intermediate existing symbol, and those I would need
do not exist. I use symbols in
the F21-F246 range as in most internet examples, but
it is not very satisfying. It's OK for a few unrelated bindings,
but it won't serve a systematic schema.
Edit
- I've completed this with the Esc case - which has its own personality - in another post: How to remove bindings to the ESC prefix key
here is a fragment of a configuration to feed loadkeys
with. I put this in /root/custom.kmap, and load it when I need to (which is rare). My actual config also maps arrows and different combinations of modifier, but it's rather long, choice of symbols and sequences is questionable, and I'm not sure the keycodes for my keyboard will match yours. So let's keep it at its due level: this is nothing but an illustration.
keymaps 0-127
# http://tldp.org/HOWTO/Keyboard-and-Console-HOWTO-15.html
# web+man:keymaps
# web+man:loadkeys
# escape
keycode 1 = F100
alt keycode 1 = Escape # keep the Escape behavior somewhere
# keycode 26 = bracketleft
control keycode 26 = F115 # Control_bracketleft does not exist
string F100 = "\033OO" # map this to [escape] in map-my-term-codes
string F115 = "\033O5["