undo-propose-el
undo-propose-el copied to clipboard
Navigate the emacs undo history by staging undo's in a temporary buffer
- undo-propose ** New maintainer wanted
Please let me know if you'd be interested in maintaining this package. I'm no longer using it, as I found the built-in =revert-buffer= suffices for my need of checkpointing undo's.
** Introduction
~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.
** Screenshot
Example of searching the undo history (right) for an old paragraph, and pasting it back into the original buffer (left):
[[./assets/undo-propose.gif]]
** Installation
~undo-propose~ is available on [[https://melpa.org/#/][MELPA]].
** Usage
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 ~C-c C-d~.
** Configuration *** Adding commands (e.g. redo)
The ~undo-propose~ buffer is read-only, so most commands won't work. However, ~undo~ and ~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 [[https://www.emacswiki.org/emacs/RedoMode][redo]], call ~(undo-propose-wrap redo)~.
*** Hooks
- ~undo-propose-entry-hook~ is run after staring undo-propose
- ~undo-propose-done-hook~ is run after committing or squash committing an undo-propose
*** Window
By default, ~undo-propose~ opens the temporary buffer in a new window.
To configure window behavior, you can set ~display-buffer-alist~ ([[https://www.gnu.org/software/emacs/manual/html_node/elisp/Choosing-Window.html#Choosing-Window][Emacs manual]]).
For example, to revert to the previous default behavior of opening the buffer in the current window:
#+begin_src emacs-lisp (add-to-list 'display-buffer-alist '("\*Undo Propose" (display-buffer-same-window))) #+end_src
*** Markers
Currently =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.
*** Example configurations **** Simple configuration
#+begin_src emacs-lisp (require 'undo-propose) (global-set-key (kbd "C-c u") 'undo-propose) #+end_src
**** Opinionated evil configuration
#+begin_src emacs-lisp (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)) #+end_src
Note that ~undo-propose~ (~C-r~ in this configuration) just calls ~undo~ if you're already in an ~undo-propose-mode~ buffer.
** Related packages
undo-propose is inspired by [[https://www.emacswiki.org/emacs/UndoTree][undo-tree]] and [[https://melpa.org/#/git-timemachine][git-timemachine]].
See [[https://www.emacswiki.org/emacs/UndoTree][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.
** References
- [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Undo.html][GNU Emacs Manual - Undo]]
- [[https://www.reddit.com/r/emacs/comments/6yzwic/how_emacs_undo_works/][reddit.com/r/emacs: How Emacs undo works]]
** Changes *** 4.0.0
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 ~t~.
*** 3.0.0
~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-s~
*** 2.0.0
~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 ~undo-propose-commit-buffer-only~ instead.