Easily repeat Emacs functions: a repeat post

September 2023

"Winning takes talent, to repeat takes character."

– John Wooden

Remember Emacs's transient maps?

I posted about them all the way back in 2016. Transient maps temporarily add a keymap that is active only for a single keypress. It enables things like repeating commands, or making a set of similar commands easier to call one after another.

Well, times have changed.

In 2022, Emacs 28.1 was released. This release added repeat mode, a piece of functionality that's similar to transient maps. Like transient maps, repeat mode allows for different actions to be triggered by keypresses after a function is called. Unlike transient maps, repeat mode works without modifying the source of the function being acted on.

But repeat mode is confusing.

Like "repeat mode" suggests, it lets the user repeat commands. If that were all it does, repeat mode would be useful, but it would duplicate C-x z (#'repeat). Luckily, repeat mode has more functionality than just repeating.

Maybe even the name is confusing.

Using repeat mode

Enable it with (repeat-mode 1).

Now, every time a command is called, Emacs will look at the 'repeat-map property on that specific command. If it finds a keymap there, Emacs will bind it as a transient map after running the command.

To use it, add a keymap to the function with (put #'original-function-name 'repeat-map 'keymap-for-the-function)

If you look carefully, you'll notice that I lied before.

There isn't a keymap as the property; there's a symbol. That is, the keymap name is quoted. It can't directly be a function, like a make-keymap call. Instead, make a variable and set its value to the desired keymap, like (defvar my-example-repeat-map (define-keymap "h" #'function-to-repeat)).

Once we do all that, after calling #'original-function-name, the user can call #'function-to-repeat by pressing h.

I suspect repeat mode doesn't look directly for a keymap to encourage variable use. That way, users can add and remove bindings from the keymap after it's added to the function. But that's just a guess.

Not just for repeating

Remember how I said repeat mode was confusing? Well, it sure is. Unintuitively, repeat mode is not just for repeating. It can be used to call a whole menu of commands after the original function.

If you're writing your own functions, I'm not sure whether to use repeat maps vs transient maps. As far as I can tell, the result is basically the same, except for two things:

  1. Repeat-maps can be listed by #'describe-repeat-maps. I don't think this is especially useful.

  2. Repeat-maps automatically message the user the keys that are bound in that map, although they do not tell the user what the keys do. Transient maps don't provide any guidance to the user.

On the other hand, repeat mode means you can change any given function, not just ones you're coding yourself.

An example!

Let's say you want to capitalize, lowercase, and upcase words easily. We want to choose between pressing c to call #'capitalize-dwim, d to call #'downcase-dwim, and u for #'upcase-dwim. First, let's create a keymap that binds those three keys.

(defvar change-of-case-keymap
    "c" #'capitalize-dwim
    "d" #'downcase-dwim
    "u" #'upcase-dwim))

Now, we set that map as the 'repeat-map' property on the three functions.

(put #'capitalize-dwim 'repeat-map 'change-of-case-keymap)
(put #'downcase-dwim 'repeat-map 'change-of-case-keymap)
(put #'upcase-dwim 'repeat-map 'change-of-case-keymap)

Now, after running any of those three functions, the keymap is activated, and it's possible to run any of the three functions again with a keypress.

Go and repeat!

That's all I have to say. Go and repeat.

Go and repeat.

If you want to hear when I publish more, sign up for my mailing list!


    < Announcing caser.el, a package to camelCase, dash-case, and snake_case text
    tag: emacs