How to Group Multiple Emacs Commands Into a Single Undo
If you've got some Emacs Lisp to do a bunch of things, but you call it by mistake and want to be able to undo it all in one go.
It’s surprisingly hard to search for documentation on this concept. Point of view: you’ve written a clever custom command
do-a-million-repetitive-things to save you time and effort by doing a million repetitive things on a simple key press. This works great most of the time, but one day you trigger it accidentally and you’ve ruined your document.
“Never mind”, you say to yourself, “I can just undo it”. So you hit
C-x u and expect your document to be back in working order, but alas…Emacs only undoes one step. And there were 999,999 other ways in which your document was so cleverly mangled.
Was there a way you could have written
do-a-million-repetitive-things to allow you to undo it all at once? Maybe Emacs Lisp has some incantation to let you amalgamate1 all those changes into one change group1?
(defmacro with-single-undo (&rest body) "Execute BODY as a single undo step." `(let ((marker (prepare-change-group))) (unwind-protect ,@body (undo-amalgamate-change-group marker))))
This defines a new macro called
with-single-undo that lets you wrap any arbitrary Emacs Lisp code and have all changes to a buffer be amalgamated into one change group, which tells Emacs’ undo system to treat it as a single undo step.
Hey there! If you’re lucky enough to be using an up-to-date build of Emacs 29, there should be a macro
with-undo-amalgamatethat does this already.4 You should use it if you can!
To continue our example from above, this is how you might put it to use:
(defun do-a-million-repetitive-things () (interactive) (with-single-undo (do-thing-1) (do-thing-2) (do-thing-3) ;; 999,997 more repetitive things ))
Of course, in normal usage nothing will change, but the next time you need to undo
do-a-million-repetitive-things, it’ll be quick and painless.
“Amalgamate” and “change group” are the concept keywords you need to be searching for, in order to read more about this functionality.