Emacs: Translating C-h to DEL, M-h to M-DEL
Asked Answered
T

2

9

I want my C/M-h keys to be a binding for delete.

The usual solution of global-set-key doesn't work for me, as I want these keys to behave as delete everywhere including the minibuffer and various modes, and be able to work between my various Linux/OS X/Windows emacs installations.

I have the following near solution:

(when (>= emacs-major-version 23)
  (setq help-char (string-to-char "<f1>"))                      ;; Bind ONLY F1 to help, not C-h
  (define-key input-decode-map (kbd "C-h") (kbd "DEL"))         ;; Translate C-h to DEL ;; FIXME:  Should be in emacs 22, doens't work.
  (define-key input-decode-map (kbd "M-h") (kbd "M-<DEL>"))     ;; Translate M-h to M-DEL
  ;; (define-key input-decode-map (kbd "<backspace>") 'version) ;; Stop me using backspace for a while
)

But this obviously only works with Emacs > 22, due to input-decode-map.

Would someone be able to help me find a solution that works in 22, maybe even 21? (Not a priority however). Cheers!

Edit: My Solution:

It's not quite there yet, but this has solved most of my issues:

(setq help-char [f1]) ;; I don't want help when I'm just trying to backspace!

(define-key isearch-mode-map "\C-h" 'isearch-delete-char)
;; (define-key isearch-mode-map "\M-h" 'my-isearch-delete-word)

(defvar my-overriding-binding-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [?\C-h] 'delete-backward-char)
    (define-key map [?\M-h] 'backward-kill-word)
    map))

(define-minor-mode my-overriding-binding-mode
  "Personal global key-bindings."
  :global t)

(my-overriding-binding-mode 1)

There's probably some caveats for some misbehaving modes that need to be written - I'll do that as I come across them. The only real problem now seems to be M-h in isearch, which I'll post as a seperate question.

Thanks again guys, you've been a great help.

Tierza answered 15/12, 2012 at 23:22 Comment(4)
Why? Is it because you like to hit C-h to delete characters, or is it because your backspace key sends a C-h? If the latter, then you'd be better off fixing that (i.e. change the terminal emulator so it sends something else).Catechist
It's the former. Cheers for the answer though.Tierza
You don't need to delay the (define-key isearch-mode-map ...), you can call them at toplevel without using isearch-mode-hook.Catechist
@Tierza did you ever end up making isearch-backward-kill-word?Sloth
Z
4

Here's the first, and most important, part of the answer, with the explanations all given in the elisp comments. This has been tested mostly in X11 environments, but including with a Mac keyboard. It has been tested starting with v19.28, and up to and including v23.3. I think it works with v24, but I have not yet used v24 very much.

You can find my complete (though not always 100% up-to-date) ~/.emacs.el in the following repository of initialization and configuration files:

https://github.com/robohack/dotfiles

and so specifically ~/.emacs.el is at:

https://github.com/robohack/dotfiles/blob/master/.emacs.el

;;; first off, we do some fancy stuff to make C-h work "properly," but still
;;; have good access to the help functions!
;;;
;;; Using C-h for "help" might seem OK to some folks, but since it's also the
;;; ASCII standard value for the "backspace" character, one typically used ever
;;; since the days of the typewriter to move the cursor backwards one position
;;; and in computing normally to erase any character backed over, a vast amount
;;; of stupidity is needed in emacs to continue to (ab)use as the "help"
;;; character.  Instead it is still quite intuitive, and often much easier in
;;; zillions of environments, to use M-? for help.
;;;
;;; So, we can set C-h and C-? and friends to sensible bindings...
;;
;; Remember to call override-local-key-settings in the appropriate hooks to fix
;; up modes which violate global user preferences....
;;
(global-set-key "\C-h" 'delete-backward-char)
(global-set-key "\C-?" 'delete-char)
(global-set-key "\e\C-h" 'backward-kill-word)
(global-set-key "\e\C-?" 'kill-word)

;;; and then we diddle with help to make it work again....
;;
;; Oddly, the help interface in emacs is extremely scatter-brained, with
;; several slightly different ways of doing the same thing.  This is probably
;; due to the fact that several different programmers have implemented various
;; bits and pieces of the help systems.  See help.el and help-macro.el, but try
;; not to tear your hair out when you find out help-event-list in 19.34 is
;; essentially bogus, since it is simply an extension to a "standard" list.
;;
;; Remember to call override-local-key-settings in the appropriate hooks to fix
;; up modes which violate global user preferences....
;;
(global-set-key [f1] 'help-command)     ; first do this for 19.28.
(global-set-key "\e?" 'help-command)    ; this is the first step to set up help
(global-set-key "\e?F" 'view-emacs-FAQ) ; in 19.34 it needs more help...
;; should help-char be just ? instead?
(setq help-char ?\M-?)                  ; this should "fix" the rest.

;; one more handy help-related binding...
;;
(define-key help-map "?" 'describe-key-briefly) ; also C-x? for Jove compat

;;; Now for function key mappings...
;;;
;;; I USUALLY EXPECT THE BACKSPACE KEY TO WORK LIKE AN ASCII BACKSPACE!
;;
;; For some entirely un-fathomable reason the default function bindings make
;; the 'backspace' and 'delete' keys synonymous!
;;
;; NOTE: this *should* work by simply reading termio for current erase char.
;;
;; As of emacs-21.2 a note was added to the NEWS file which says "** On
;; terminals whose erase-char is ^H (Backspace), Emacs now uses
;; normal-erase-is-backspace-mode."  Unfortunately this does EXACTLY the WRONG
;; thing, and in a totally bizzare, disruptive, subversive, and stupid
;; backwards way.  With every major release it's gotten worse and worse and
;; worse; more convoluted, and ugly.
;;
;; So, we must do something to kill that horrible stupid broken poor
;; useless excuse for a feature, normal-erase-is-backspace-mode....
;;
;; seems 23.1 changes function-key-map radically....
;;
;; Unfortunately 23.1 also still has function-key-map so we can't make that
;; (function-key-map) an alias for the new local-function-key-map that we need
;; to use in 23.1 to modify key translations.  Sigh.
;;
;; Instead make a new alias that can be used transparently as the desired map.
;;
(eval-and-compile
  (if (functionp 'defvaralias)          ; since 22.1
      (if (boundp 'local-function-key-map)
          (defvaralias 'my-function-key-map 'local-function-key-map
            "Special variable alias to allow transparent override of
`local-function-key-map' for 23.1 vs 22.3(?).")
        (defvaralias 'my-function-key-map 'function-key-map
          "Special variable alias to allow transparent override
of `function-key-map' for 22.3(?) vs. older,"))
    ;; XXX is this right?  it works (maybe?)
    (defvar my-function-key-map function-key-map)))
;;
;; First undo (local-)function-key-map weirdness.
;;
;; luckily on Mac OS-X X11, at least with the mini-wireless keyboard and on the
;; large USB keyboard, the big "delete" key on the main block is actually
;; sending <backspace> by default, else one would have to first change the X11
;; keyboard map!
;;
(define-key my-function-key-map [delete] [?\C-?])
(define-key my-function-key-map [S-delete] [?\C-h])
(define-key my-function-key-map [M-delete] [?\C-\M-?])
(define-key my-function-key-map [kp-delete] [?\C-?])
(define-key my-function-key-map [backspace] [?\C-h])
(define-key my-function-key-map [S-backspace] [?\C-?])
;;(define-key my-function-key-map [C-backspace] [?\C-h]) ; sometimes *is* DEL....
(define-key my-function-key-map [M-backspace] [?\e?\C-h])
(define-key my-function-key-map [M-S-backspace] [?\e?\C-?])
(define-key my-function-key-map [kp-backspace] [?\C-h])
;;
;; Next, zap the keyboard translate table, set up by
;; normal-erase-is-backspace-mode (in simple.el), which can do nothing
;; but confuse!
;;
(setq keyboard-translate-table nil)
;;
;; Finally, kill, Kill, KILL! the input-decode-map added in 23.x, and set
;; up by normal-erase-is-backspace-mode (in simple.el) which can do
;; nothing but confuse!
;;
;; This is TRULY _E_V_I_L_!!!!  HORRID!!!  MASSIVELY STUPID!!!!
;;
;; input-decode-map is poorly documented, and causes things above and
;; below to fail with the most confusing errors!
;;
;; (This probably only needs to be blown away on window systems, and
;; perhaps only for X, but doing it here now is apparently early enough
;; to allow for terminal mode specific settings to be re-applied to it
;; and so it seems safe to just blow away the asinine stupid attempt to
;; transpose backspace and delete.  RMS is a pedantic idiot on this!)
;;
(if (boundp 'input-decode-map)
    (setq input-decode-map (make-sparse-keymap)))

;; finally here's a little function to help fix up modes which don't honour default
;; bindings in sensible ways.  Use this in any init hooks for modes which cause problems
;;
(defun override-local-key-settings ()
  "User defined function.  Intended to be called within various hooks to
override the value of buffer-local key map settings which may have been
overridden without consideration by the major mode."
  (local-set-key "\C-?" 'delete-char)   ; many modes
  (local-set-key "\C-h" 'delete-backward-char)  ; sh-mode
  ;; the rest are *not* overridden by cc-mode, but are by c-mode
  (local-set-key "\e\C-h" 'backward-kill-word) ; text-mode
  (local-set-key "\e?" 'help-command)   ; nroff-mode
  (local-set-key "\eh" 'mark-c-function)
  (local-set-key "\e\C-?" 'kill-word)
  (local-set-key "\e\C-e" 'compile)
  ;; try this on for size...
  (local-set-key "\C-x\e\C-e" 'recompile)
  )

(add-hook 'isearch-mode-hook
          (function
           (lambda ()
            "Private isearch-mode fix for C-h."
            (define-key isearch-mode-map "\C-h" 'isearch-delete-char))))


;;; OK, that's the end of the stuff to fix GNU Emacs' C-h brain damage.  Phew!
Zarathustra answered 17/12, 2012 at 2:48 Comment(7)
Wow, someone that hates C-h as help more than me. Thanks very much for this, although I'll need some time to pick out the relevant bits for me. I'll do so, post the bits I used, then hopefully close the question.Tierza
Thanks! :-) I think you'll find you need all those bits and perhaps more. I currently have 8 calls to, or rather additions to hooks of, my override-local-key-settings function, for example.Zarathustra
It should perhaps be noted that this C-h/Backspace vs. DEL/Delete battle is as old as the oldest ASCII terminals, or perhaps even older, and the two factions are often described as East-coast vs. West-coast, though the origins are slightly more complex than that.Zarathustra
OK, so a bit of testing shows me this will work brilliantly for C-h, but my M-h requirement is still proving a bit tricky. For instance, 'isearch-delete-word would be handy for the isearch-mode-hook, but the function doesn't exist. I've tried backward-kill-word, which I thought my code above was calling (as M-Del), but no dice.Tierza
Just retested my old code again. Seems that my solution didn't work for isearch anyway - I must have only tested with find-file and the like.Tierza
In my particular case (Emacs 25.3), this broke helm (wouldn't work properly) and eshell (wouldn't start at all).Greenstein
I don't use helm, but eshell seems to work fine for me now with my latest ~/.emacs.el.Zarathustra
C
5

Then I recommend you use a minor-mode:

(defvar my-overriding-binding-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [?\C-h] 'delete-backward-char)
    (define-key map [?\M-h] 'backward-kill-word)
    map))

(define-minor-mode my-overriding-binding-mode
  "Personal global key-bindings."
  :global t)

(my-overriding-binding-mode 1)
Catechist answered 16/12, 2012 at 23:36 Comment(9)
I just tried that code in emacs 22 and 24, both still translate C-h as help-char and M-h as mark-paragraph.Tierza
Well, this is guaranteed 100% untested code, so you need to take it with a grain of salt. I just fixed one obvious error (a missing -map at the end of the name of the keymap), but there might be others.Catechist
No worries on the untested code bit. While my lisp is improving - I actually did pick up on and correct that error - I don't think I could debug that further at this point.Tierza
FWIW, I just tried my above code and "it worked". So, there must be some misunderstanding of what it is you want.Catechist
I tried it again, and it worked. I must have introduced another error, my apologies.Tierza
So, what I'll do - both solutions get to the same place, my keys now work the way I want them, bar for M-h in isearch - but it's become clear that the isearch thing is a different question. I'll put it in one. I'll dogfood the solution while I consider what to do about marking your answers - you've both been very helpful. Cheers.Tierza
I've marked Greg's answer as correct as isearch-mode-hook and setting help-char were essential for some corner cases - Say, C-x C-h, or C-s C-h. I think your use of a minor mode is the correct way to go though, so I've used that in my edited/answered question above.Tierza
So I've belatedly returned here to provide a better and always up-to-date link to my dotfiles in my answer, and I remembered I'm still quite curious as to why one might think a minor mode is somehow better than directly fixing/modifying all the underlying mess so that everything that might interact at the lowest levels is also fixed all in one fell swoop. Stefan or Daniel, perhaps you could add some rational?Zarathustra
@GregA.Woods: On my side, there's no strong reason. My intuition was that the "C-h is help" is likely to be duplicated in several major/minor mode maps, so using an overriding minor mode map makes it possible to override them all at the same time, but it probably has too high a precedence in some cases. There's probably no perfect answer, it's just another instance of the general problem of designing alternate key-bindings.Catechist
Z
4

Here's the first, and most important, part of the answer, with the explanations all given in the elisp comments. This has been tested mostly in X11 environments, but including with a Mac keyboard. It has been tested starting with v19.28, and up to and including v23.3. I think it works with v24, but I have not yet used v24 very much.

You can find my complete (though not always 100% up-to-date) ~/.emacs.el in the following repository of initialization and configuration files:

https://github.com/robohack/dotfiles

and so specifically ~/.emacs.el is at:

https://github.com/robohack/dotfiles/blob/master/.emacs.el

;;; first off, we do some fancy stuff to make C-h work "properly," but still
;;; have good access to the help functions!
;;;
;;; Using C-h for "help" might seem OK to some folks, but since it's also the
;;; ASCII standard value for the "backspace" character, one typically used ever
;;; since the days of the typewriter to move the cursor backwards one position
;;; and in computing normally to erase any character backed over, a vast amount
;;; of stupidity is needed in emacs to continue to (ab)use as the "help"
;;; character.  Instead it is still quite intuitive, and often much easier in
;;; zillions of environments, to use M-? for help.
;;;
;;; So, we can set C-h and C-? and friends to sensible bindings...
;;
;; Remember to call override-local-key-settings in the appropriate hooks to fix
;; up modes which violate global user preferences....
;;
(global-set-key "\C-h" 'delete-backward-char)
(global-set-key "\C-?" 'delete-char)
(global-set-key "\e\C-h" 'backward-kill-word)
(global-set-key "\e\C-?" 'kill-word)

;;; and then we diddle with help to make it work again....
;;
;; Oddly, the help interface in emacs is extremely scatter-brained, with
;; several slightly different ways of doing the same thing.  This is probably
;; due to the fact that several different programmers have implemented various
;; bits and pieces of the help systems.  See help.el and help-macro.el, but try
;; not to tear your hair out when you find out help-event-list in 19.34 is
;; essentially bogus, since it is simply an extension to a "standard" list.
;;
;; Remember to call override-local-key-settings in the appropriate hooks to fix
;; up modes which violate global user preferences....
;;
(global-set-key [f1] 'help-command)     ; first do this for 19.28.
(global-set-key "\e?" 'help-command)    ; this is the first step to set up help
(global-set-key "\e?F" 'view-emacs-FAQ) ; in 19.34 it needs more help...
;; should help-char be just ? instead?
(setq help-char ?\M-?)                  ; this should "fix" the rest.

;; one more handy help-related binding...
;;
(define-key help-map "?" 'describe-key-briefly) ; also C-x? for Jove compat

;;; Now for function key mappings...
;;;
;;; I USUALLY EXPECT THE BACKSPACE KEY TO WORK LIKE AN ASCII BACKSPACE!
;;
;; For some entirely un-fathomable reason the default function bindings make
;; the 'backspace' and 'delete' keys synonymous!
;;
;; NOTE: this *should* work by simply reading termio for current erase char.
;;
;; As of emacs-21.2 a note was added to the NEWS file which says "** On
;; terminals whose erase-char is ^H (Backspace), Emacs now uses
;; normal-erase-is-backspace-mode."  Unfortunately this does EXACTLY the WRONG
;; thing, and in a totally bizzare, disruptive, subversive, and stupid
;; backwards way.  With every major release it's gotten worse and worse and
;; worse; more convoluted, and ugly.
;;
;; So, we must do something to kill that horrible stupid broken poor
;; useless excuse for a feature, normal-erase-is-backspace-mode....
;;
;; seems 23.1 changes function-key-map radically....
;;
;; Unfortunately 23.1 also still has function-key-map so we can't make that
;; (function-key-map) an alias for the new local-function-key-map that we need
;; to use in 23.1 to modify key translations.  Sigh.
;;
;; Instead make a new alias that can be used transparently as the desired map.
;;
(eval-and-compile
  (if (functionp 'defvaralias)          ; since 22.1
      (if (boundp 'local-function-key-map)
          (defvaralias 'my-function-key-map 'local-function-key-map
            "Special variable alias to allow transparent override of
`local-function-key-map' for 23.1 vs 22.3(?).")
        (defvaralias 'my-function-key-map 'function-key-map
          "Special variable alias to allow transparent override
of `function-key-map' for 22.3(?) vs. older,"))
    ;; XXX is this right?  it works (maybe?)
    (defvar my-function-key-map function-key-map)))
;;
;; First undo (local-)function-key-map weirdness.
;;
;; luckily on Mac OS-X X11, at least with the mini-wireless keyboard and on the
;; large USB keyboard, the big "delete" key on the main block is actually
;; sending <backspace> by default, else one would have to first change the X11
;; keyboard map!
;;
(define-key my-function-key-map [delete] [?\C-?])
(define-key my-function-key-map [S-delete] [?\C-h])
(define-key my-function-key-map [M-delete] [?\C-\M-?])
(define-key my-function-key-map [kp-delete] [?\C-?])
(define-key my-function-key-map [backspace] [?\C-h])
(define-key my-function-key-map [S-backspace] [?\C-?])
;;(define-key my-function-key-map [C-backspace] [?\C-h]) ; sometimes *is* DEL....
(define-key my-function-key-map [M-backspace] [?\e?\C-h])
(define-key my-function-key-map [M-S-backspace] [?\e?\C-?])
(define-key my-function-key-map [kp-backspace] [?\C-h])
;;
;; Next, zap the keyboard translate table, set up by
;; normal-erase-is-backspace-mode (in simple.el), which can do nothing
;; but confuse!
;;
(setq keyboard-translate-table nil)
;;
;; Finally, kill, Kill, KILL! the input-decode-map added in 23.x, and set
;; up by normal-erase-is-backspace-mode (in simple.el) which can do
;; nothing but confuse!
;;
;; This is TRULY _E_V_I_L_!!!!  HORRID!!!  MASSIVELY STUPID!!!!
;;
;; input-decode-map is poorly documented, and causes things above and
;; below to fail with the most confusing errors!
;;
;; (This probably only needs to be blown away on window systems, and
;; perhaps only for X, but doing it here now is apparently early enough
;; to allow for terminal mode specific settings to be re-applied to it
;; and so it seems safe to just blow away the asinine stupid attempt to
;; transpose backspace and delete.  RMS is a pedantic idiot on this!)
;;
(if (boundp 'input-decode-map)
    (setq input-decode-map (make-sparse-keymap)))

;; finally here's a little function to help fix up modes which don't honour default
;; bindings in sensible ways.  Use this in any init hooks for modes which cause problems
;;
(defun override-local-key-settings ()
  "User defined function.  Intended to be called within various hooks to
override the value of buffer-local key map settings which may have been
overridden without consideration by the major mode."
  (local-set-key "\C-?" 'delete-char)   ; many modes
  (local-set-key "\C-h" 'delete-backward-char)  ; sh-mode
  ;; the rest are *not* overridden by cc-mode, but are by c-mode
  (local-set-key "\e\C-h" 'backward-kill-word) ; text-mode
  (local-set-key "\e?" 'help-command)   ; nroff-mode
  (local-set-key "\eh" 'mark-c-function)
  (local-set-key "\e\C-?" 'kill-word)
  (local-set-key "\e\C-e" 'compile)
  ;; try this on for size...
  (local-set-key "\C-x\e\C-e" 'recompile)
  )

(add-hook 'isearch-mode-hook
          (function
           (lambda ()
            "Private isearch-mode fix for C-h."
            (define-key isearch-mode-map "\C-h" 'isearch-delete-char))))


;;; OK, that's the end of the stuff to fix GNU Emacs' C-h brain damage.  Phew!
Zarathustra answered 17/12, 2012 at 2:48 Comment(7)
Wow, someone that hates C-h as help more than me. Thanks very much for this, although I'll need some time to pick out the relevant bits for me. I'll do so, post the bits I used, then hopefully close the question.Tierza
Thanks! :-) I think you'll find you need all those bits and perhaps more. I currently have 8 calls to, or rather additions to hooks of, my override-local-key-settings function, for example.Zarathustra
It should perhaps be noted that this C-h/Backspace vs. DEL/Delete battle is as old as the oldest ASCII terminals, or perhaps even older, and the two factions are often described as East-coast vs. West-coast, though the origins are slightly more complex than that.Zarathustra
OK, so a bit of testing shows me this will work brilliantly for C-h, but my M-h requirement is still proving a bit tricky. For instance, 'isearch-delete-word would be handy for the isearch-mode-hook, but the function doesn't exist. I've tried backward-kill-word, which I thought my code above was calling (as M-Del), but no dice.Tierza
Just retested my old code again. Seems that my solution didn't work for isearch anyway - I must have only tested with find-file and the like.Tierza
In my particular case (Emacs 25.3), this broke helm (wouldn't work properly) and eshell (wouldn't start at all).Greenstein
I don't use helm, but eshell seems to work fine for me now with my latest ~/.emacs.el.Zarathustra

© 2022 - 2024 — McMap. All rights reserved.