Emacs keybinding changes between Emacs 23 and Emacs 24
Asked Answered
T

2

8

I recently upgraded to Emacs24, and a number of my custom keybindings broke as a result of it.

According to the fine manual, it is possible to make Emacs stop conflating function keys with their ASCII control codes (eg, it is possible to have C-m and RET bound to different things, or C-i and TAB, and so on). This has always been a large pet peeve of mine with Emacs, that such valuable "first level" keyboard shortcuts are wasted on things for which I already have dedicated keys on my keyboard. I want to bind them to different things, in my case, to 'modernize' the keybindings by mimicking gedit. In Emacs23, this was working beautifully:

(global-set-key (kbd "C-i") 'goto-line)
(global-set-key (kbd "C-m") 'comment-or-uncomment-region)
(global-set-key (kbd "C-d") 'kill-whole-line)

;; Fix some stuff broken by the above
(global-set-key [delete] 'delete-char)
(global-set-key (kbd "TAB") 'indent-for-tab-command)
(global-set-key (kbd "RET") 'newline)

Then, I upgraded to Emacs24 and it broke, kinda. It still "works" in the sense that C-m certainly does one thing, and RET does another, but the problem is that the return key no longer behaves properly in terminal mode or in the minibuffer. Instead of activating the command I've just typed, in both cases, the return key simply moves the cursor down to the next line and I'm left with no way of activating the commands I type in to either the minibuffer or the terminal.

Ironically, Emacs24 introduced a lot of changes to the behavior of deleting, and in the process they decoupled C-d from DEL so that it's actually now safe to bind C-d to something without needing to bind DEL back to the expected behavior, so it would be great if I could achieve similar "it just works" behavior for my return key, while C-m is bound to something else.

So, I can envision two possible solutions to this problem. One might look like this:

(global-set-key (kbd "C-m") 'comment-or-uncomment-region)
(global-set-key (kbd "RET") 'do-what-i-expect-the-return-key-to-do-in-any-mode)

OR, something like this would be even nicer:

(setq decouple-ascii-control-codes-from-function-keys t)

But I'm not aware of any such variable or function that would help me out in this scenario.

I've made several unsuccessful attempts at using mode-hooks to restore the correct bindings in terminal and minibuffer modes, but I just can't seem to get anything to work. Help!

Thanks.

Telethermometer answered 11/9, 2012 at 2:42 Comment(0)
G
3

The way these "sister keys" are handled by default in Emacs is to redirect (via function-key-map) the special keys (like tab, and return) to their ASCII equivalent, and then only add key-bindings to the ASCII version. So you can easily add new meanings to the non-ASCII version with something like

(global-set-key [return] 'my-new-command)

but in your case you want to do the reverse which is to let return behave as before while changing C-m. The most reliable way I can think of to do that (reliable in the sense that it should work with most major/minor modes bindings) is to remap C-m early and unconditionally to some new event, as in:

(define-key input-decode-map [?\C-m] [C-m])
(define-key input-decode-map [?\C-i] [C-i])

this will not affect the handling of return and tab since input-decode-map is applied before function-key-map, i.e. before those keys are turned into ASCII control keys. So you can then do:

(global-set-key [C-m] 'my-new-command)
(global-set-key [C-i] 'my-newer-command)

A downside is that this will not only apply to bindings for C-i but also to bindings for C-c C-i which will now only work as C-c TAB (which will sometimes be just fine, but might occasionally be less mnemonic).

Another downside is that if there is a binding for tab, then tab won't be useable to reach a C-i binding. But we can fix those two problems by adding the following:

(define-key function-key-map [C-i] [?\C-i])
(define-key function-key-map [C-m] [?\C-m])

which will turn the new C-i event back into a normal C-i in case where there is no binding that uses the new event.

Goliath answered 12/9, 2012 at 17:42 Comment(2)
I copy&pasted your solution verbatim, but it only seems to work for C-m and not for C-i. Eg, C-m and RET are successfully doing different actions, but then when I hit the TAB key it does the action that I bound to C-i instead. Any thoughts on why? I accepted your answer anyway though just because C-m was the one I was struggling with; I already had C-i and TAB working just with global-set-key.Telethermometer
Nevermind, it looks like something else in my .emacs was interfering. I deleted it and your solution is working, in emacs24, verbatim. Thanks!Telethermometer
T
5

This seems to work:

(add-hook 'find-file-hook
          (lambda ()
            (local-set-key (kbd "C-m") 'comment-or-uncomment-region)
            (local-set-key (kbd "<return>") 'newline-and-indent)))

The idea here is that instead of tinkering with the return key globally (which is what breaks the terminal and minibuffer buffers), we only set these keybindings on a per-buffer basis, except that we do it unconditionally for all buffers that represent files on disk.

It's a little bit inefficient, having to run every time I open a file, but it's nice insofar as I don't have to think of every possible mode to "fix", it simply doesn't break terminal/minibuffer/etc modes in the first place.

Telethermometer answered 11/9, 2012 at 19:26 Comment(3)
You don't need to worry about the inefficiency. If you take a look at the amount of work find-file normally does (e.g. with M-x find-function RET find-file-noselect RET), your two calls to local-set-key are obviously negligible.Words
They may be negligible, but "it's already slow, can't hurt to make it worse!" is a sloppy attitude. Stefan's answer is excellent and solves the problem without any hooks.Telethermometer
Stefan's answer is fine, I merely pointed out that your answer, too, has merit, and had upvoted it. I didn't say it was "already slow", nor is there anything sloppy in general about assessing how a change affects an execution path.Words
G
3

The way these "sister keys" are handled by default in Emacs is to redirect (via function-key-map) the special keys (like tab, and return) to their ASCII equivalent, and then only add key-bindings to the ASCII version. So you can easily add new meanings to the non-ASCII version with something like

(global-set-key [return] 'my-new-command)

but in your case you want to do the reverse which is to let return behave as before while changing C-m. The most reliable way I can think of to do that (reliable in the sense that it should work with most major/minor modes bindings) is to remap C-m early and unconditionally to some new event, as in:

(define-key input-decode-map [?\C-m] [C-m])
(define-key input-decode-map [?\C-i] [C-i])

this will not affect the handling of return and tab since input-decode-map is applied before function-key-map, i.e. before those keys are turned into ASCII control keys. So you can then do:

(global-set-key [C-m] 'my-new-command)
(global-set-key [C-i] 'my-newer-command)

A downside is that this will not only apply to bindings for C-i but also to bindings for C-c C-i which will now only work as C-c TAB (which will sometimes be just fine, but might occasionally be less mnemonic).

Another downside is that if there is a binding for tab, then tab won't be useable to reach a C-i binding. But we can fix those two problems by adding the following:

(define-key function-key-map [C-i] [?\C-i])
(define-key function-key-map [C-m] [?\C-m])

which will turn the new C-i event back into a normal C-i in case where there is no binding that uses the new event.

Goliath answered 12/9, 2012 at 17:42 Comment(2)
I copy&pasted your solution verbatim, but it only seems to work for C-m and not for C-i. Eg, C-m and RET are successfully doing different actions, but then when I hit the TAB key it does the action that I bound to C-i instead. Any thoughts on why? I accepted your answer anyway though just because C-m was the one I was struggling with; I already had C-i and TAB working just with global-set-key.Telethermometer
Nevermind, it looks like something else in my .emacs was interfering. I deleted it and your solution is working, in emacs24, verbatim. Thanks!Telethermometer

© 2022 - 2024 — McMap. All rights reserved.