You cannot really change the C-[ binding at user-level
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:
and then use
[control-bracketleft] in your keymaps. Pretty simple isn't it?
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
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:
actually the meta prefix (see
meta-prefix-char), and all
bindings that read
M-something will translate to a sequence that starts with
Thus, changing the global map won't be enough: you need first to change
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.
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
input-decode-map modification in such a way
that it activates only in the case a window system is controlling
(let ((frame (framep (selected-frame))))
(or (eq t frame)
(eq 'pc frame)
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?
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:
loadkeys(1) for the
linux console, for xterm
xterm(1) in section Custom Key Bindings, and whatever-i-dont-know for other terminals.
KDE konsole, you can
define custom translations in Settings/Edit Current Profile...
then Keyboard. Here is an excerpt from
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
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
tty-setup-hook (see section 40.1.3):
(let ((term (getenv "TERM")))
(;; xterm-function-map not in doc, but in term/xterm.el
((equal term "linux")
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 [")
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:
ESC O 5 [ to code C-[. This is just
an example: I won't pretend it's a good choice. Only the
part, which means C-, seems to obey some kind of established
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.
- 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.
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["