drshapeless


Legacy define-key and global-set-key in Emacs

Tags: emacs

Create: 2022-07-12, Update: 2022-07-12

I ate my words, I cannot publish a blog post per week. I am having multiple interesting long posts in the process, but none of them have reached my standard of publishing.

This is a short post.

Background

I was again messing with my Emacs config. Somehow I have the idea of getting rid of use-package, (in my case, leaf). They did a great job of abstracting and packing related configs into a single block. But too much abstractions make me hard to understand what is going on under the hood.

I live in the HEAD of Emacs. I regularly compile it from source about every two weeks.

I accidentally discovered that, define-key and global-set-key are now legacy functions.

Replacement

It is shocking that such common functions are now legacy. The documentation suggests we should instead use keymap-set.

For define-key.

This is a legacy function; see keymap-set for the recommended function to use instead.

For global-set-key.

This is a legacy function; see keymap-global-set for the recommended function to use instead.

The new functions add a checking to the KEY string parameter.

Originally, if I use global-set-key, I would use kbd. I am not a fan of using backslash.

(global-set-key (kbd "C-c k") #'recompile)

Now we only have to do this. It is a bit easier.

(keymap-global-set "C-c k" #'recompile)

When using the new functions, we have to pay attention to the key-valid-p checker. I broke my config with this.

;; Original
(define-key yas-minor-mode-map (kbd "<TAB>") nil)
;; My new attempt, this gives an error.
(keymap-set yas-minor-mode-map "<TAB>" nil)

;; Correct approach.
(keymap-set yas-minor-mode-map "TAB" nil)
(keymap-set yas-minor-mode-map "<tab>" nil)

The new functions have a stricter requirement for the key string.

Here's some example key sequences.

  "f"           (the key f)
  "S o m"       (a three key sequence of the keys S, o and m)
  "C-c o"       (a two key sequence of the keys c with the control modifier
                 and then the key o)
  "H-<left>"    (the key named "left" with the hyper modifier)
  "M-RET"       (the "return" key with a meta modifier)
  "C-M-<space>" (the "space" key with both the control and meta modifiers)

  These are the characters that have shorthand syntax:
  NUL, RET, TAB, LFD, ESC, SPC, DEL.

Modifiers have to be specified in this order:

   A-C-H-M-S-s

which is

   Alt-Control-Hyper-Meta-Shift-super

Compatible layer

For Emacs users with a single machine, you can update all your config into using the newer functions.

But if you are like me, running Emacs on multiple machines with different versions. You should beware of the compatibility issues, at least be compatible with the lastest stable.

My solution is to write a simple wrapper.

(defconst *is-older-emacs* (version< emacs-version "29.0.50"))

(if *is-older-emacs*
    (progn
      (defun drsl/keymap-set (keymap key definition)
        "Workaround of keymap-set in older Emacs.

This does not check the validity of key string."
        (define-key keymap (kbd key) definition))

      (defun drsl/keymap-unset (keymap key &optional remove)
        "Workaround of keymap-unset in older Emacs.

This does not check the validity of key string."
        ;; Older `define-key' only accept 3 arguments.
        (define-key keymap (kbd key) nil))

      (defun drsl/keymap-global-set (key command)
        "Workaround of keymap-global-set in older Emacs.

This does not check the validity of key string."
        (drsl/keymap-set (current-global-map) key command))

      (defun drsl/keymap-global-unset (key &optional remove)
        "Workaround of keymap-global-unset in older Emacs.

This does not check the validity of key string."
        (drsl/keymap-unset (current-global-map) key remove))

      (defalias 'keymap-set #'drsl/keymap-set)
      (defalias 'keymap-unset #'drsl/keymap-unset)
      (defalias 'keymap-global-set #'drsl/keymap-global-set)
      (defalias 'keymap-global-unset #'drsl/keymap-global-unset))
  )

Last note

The keymap-set and keymap-global-set are only available in the last HEAD. Emacs 28.1 currently do not have these functions. Be careful if you want to try.

There are some keymap related update in the latest Emacs, like the replacement of local-set-key. I am not going into the details of them. If you are interested in the function documentations or definitions, use C-h f to find the function inside Emacs.