undo-propose.el is a package that allows you to stage undo’s in a temporary buffer before committing them.
Emacs’ undo system is powerful, but difficult to navigate, due to the fact that Emacs treats previous undo’s as ordinary changes that can themselves be undone. One can get lost when moving through a chain of undo’s, undo’s of undo’s, and so forth, as the same edit will be traversed multiple times backwards and forwards. On top of that, trying to find an old edit can add many undo’s to the edit history, making the undo ring longer and more difficult to navigate later on.
undo-propose addresses this by letting you stage undo’s inside a temporary buffer. This has a few benefits:
- If you get lost, you can cancel the whole series of undo’s, without modifying the original buffer or undo history.
- You can search through your undo history for old snippets, copy and paste them back in manually, then discard the rest of the undo’s.
- When finished undo’ing, you can choose to squash the undo’s and add them as a single edit event. This makes the undo history shorter; to go back, you only have to undo 1 step, rather than redo’ing each undo individually.
Example of searching the undo history (right) for an old paragraph, and pasting it back into the original buffer (left):
undo-propose is available on MELPA.
To use undo-propose, call
M-x undo-propose in the buffer you are editing. This will send you to a new temporary buffer, which is read-only except for allowing
undo commands. In this buffer, call
undo as you normally would, until you have reached your desired place in the undo history. When you are finished, type
C-c C-c to commit the changes (both in the buffer and undo-ring) back to the parent. Alternatively, type
C-c C-s to copy the buffer but not the individual undo events (squashing them into a single edit event in the undo history). To cancel, type
C-c C-k. You can also ediff the proposed chain of undo’s by typing
Adding commands (e.g. redo)
undo-propose buffer is read-only, so most commands won’t work. However,
undo-only are specially wrapped so that they will work in the buffer. You can use the macro
undo-propose-wrap to make additional commands useable in
undo-propose. For example, to add redo, call
undo-propose-entry-hookis run after staring undo-propose
undo-propose-done-hookis run after committing or squash committing an undo-propose
undo-propose opens the temporary buffer in a new window.
To configure window behavior, you can set
display-buffer-alist (Emacs manual).
For example, to revert to the previous default behavior of opening the buffer in the current window:
(add-to-list 'display-buffer-alist '("\\*Undo Propose" (display-buffer-same-window)))
undo-propose does not correctly update markers in the parent buffer after undo’ing. As a workaround, you can add markers to
undo-propose-marker-list to ensure they are updated after undo’ing.
(require 'undo-propose) (global-set-key (kbd "C-c u") 'undo-propose)
Opinionated evil configuration
(with-eval-after-load 'evil (global-undo-tree-mode -1) (evil-define-key 'normal 'global "u" 'undo-only)) (use-package undo-propose :commands undo-propose :init (evil-define-key 'normal 'global (kbd "C-r") 'undo-propose))
C-r in this configuration) just calls
undo if you’re already in an
See undo-tree for a more powerful undo navigation system. Unfortunately, many users experience corruption issues, leading to lost work (for example, see https://github.com/emacs-evil/evil/issues/1074 and http://ergoemacs.org/emacs/emacs_best_redo_mode.html).
In contrast, undo-propose is much smaller, and meant to complement native emacs’ undo rather than replace it. It tries to minimize direct interaction with undo internals, in order to reduce the likelihood of bugs that corrupt the undo history.
Switched to using the
display-buffer framework to configure window display. This obsoletes the option
undo-propose-pop-to-buffer. The new default window behavior has also changed to pop up a new window, as if the obsolete option
undo-propose-pop-to-buffer were set to
undo-propose-commit-buffer-only was renamed to
undo-propose-squash-commit, and its keybinding
C-c C-b was moved to
C-c C-c in the undo-propose buffer now commits to the undo-history (
undo-propose-commit). To copy the buffer but not the undo-history (squashing the undo’s), use
C-c C-b (
undo-propose-commit-buffer-only). The old function
undo-propose-finish is now obsolete; use