Benedict J. Bentham

Undo in Emacs, 20 May 2020

To "redo" in Emacs, one must undo an undo. This subtlety makes undoing in Emacs both powerful, and a point of confusion. In this post I hope to explain how to gain an intuition for Emacs' default undo mechanism, and demonstrate its power.

As you would expect, the Emacs undo command (bound to C-_1 by default) takes you back a step in your buffer's history. With each successive undo, more changes in the buffer's text are unwound. What is less obvious, is when you stop undoing and do anything else, the undo command will now seemingly take you forward in history.

Buffer state Action
  Insert `A'
A Insert `B'
A B Insert `C'
A B C Insert `D'
A B C D undo
A B C undo
A B C-f
A B undo
A B C undo
A B C D  

So, what is happening here?

As soon as we break the chain of undos by doing something else (in this case simply moving the point forward a character with C-f), the undos are put in the buffer as if they were regular edits, which themselves can be undone.

The truth is, undo will always takes you backwards, through the previous states of the buffer, in the order you saw those states. The most intuitive way to think of this, is to think of an undo as any other edit. Every change you see in the buffer (including those triggered by undos), you can step back through. The beauty of this is it's impossible to lose a change: it's trivial to undo, make some changes, and then get back to the state before the undos.

Undo only

As you may have noticed if we issue a long sequence of undos, make a single change to the buffer, and now want to continue undoing further, we are forced to go back through all previous states of the buffer (i.e. undoing the undos) before we are where we want to be.

Buffer state Action
  Insert `A'
A Insert `B'
A B Insert `C'
A B C Insert `D'
A B C D undo
A B C undo
A B Insert `E'
A B E undo
A B undo
A B C undo
A B C D undo
A B C undo
A B undo
A undo
   

The table above demonstrates this problem. After we undo the `E', we then move to the buffer state prior to this. Notice how the buffer column is mirrored as the undo is taking us backwards through the previous states.

Ideally in this situation we would like a way to continue undoing as if inserting `E' never broke the chain of undos. Something like this:

Buffer Action
  Insert `A'
A Insert `B'
A B Insert `C'
A B C Insert `D'
A B C D undo-only
A B C undo-only
A B Insert `E'
A B E undo-only
A B undo-only
A undo-only
   

Luckily, Emacs lets us do exactly that, with the command undo-only. This command moves us back through previous states like before, but skips over any undos, letting us continue undoing where we left off. The inverse of this is the undo-redo2 command, which will only undo previous undos (i.e. only redo).

Using undo-only and undo-redo gives us a Microsoft Word style undo/redo, while using undo gives us the ability to traverse back through every previous state of the buffer.

Selective undos

By thinking of undos as edits, it opens up the possibility for other powerful undo features, where the state after an undo may not represent a state the buffer has been in before.

Introducing: selective undos.

Selective undos allow you to undo changes within a specific region, while leaving the rest of the buffer untouched. This can substantially reduce the number of undos you need to perform to get where you want.

For example if you make a change to a function in some source code, make a lot of changes to another function, and then realise you want to undo the change in the first function, it's as simple as selecting the region with the function, and hitting undo.

In an editor like Vim, where undo/redo navigates you backwards/forwards through a tree, you would have to undo all the changes across the buffer as a whole until the first function was back to the state you wanted, copy the function, redo everything that was undone, and then paste the copied function over the current one. Notice how the end result of this "undo" in Vim really is an edit.

In conclusion

Emacs comes with a very elegant undo solution: simple, yet powerful. Like many things in Emacs, it can take a little time to get used to, but this pays off in the long run. My recommendation is to bind undo-only and undo-redo to convenient keys and keep undo in your back pocket when you need the raw power.

(global-set-key (kbd "C-/") #'undo-only) 
(global-set-key (kbd "C-_") #'undo-redo) 
(global-set-key (kbd "C-x u") #'undo) 

Footnotes:

1

That's holding down control while pressing underscore, in Emacs-speak. It is also bound to C-x u and C-/ by default.

2

Introduced in Emacs 27.