%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" ")):::current classDef current stroke: #f96, stroke-width: 2px
Undoing and redoing
Undoing and redoing are operations so common while editing files that we don’t think about them much. Most software however have a poor undo/redo system in which edits get lost all the time.
Emacs’ undos never loses edits and undo-tree brings a wonderful undo/redo system to it.
Undo systems
Linear systems: classic undo/redo
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" "))---4((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" ")):::current---4((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" ")):::current---3((" "))---4((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" ")):::current---4((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" "))-.-4((" ")):::lost 3((" "))---5((" ")):::current classDef current stroke: #f96, stroke-width: 2px classDef lost stroke-dasharray: 3 4
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" ")):::current-.-4((" ")):::lost 3((" "))---5((" ")) classDef current stroke: #f96, stroke-width: 2px classDef lost stroke-dasharray: 3 4
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" "))-.-4((" ")):::lost 3((" "))---5((" ")):::current classDef current stroke: #f96, stroke-width: 2px classDef lost stroke-dasharray: 3 4
Linear systems: Emacs
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3))---4((4)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3))---4((4))---5((3)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3))---4((4))---5((3))---6((2)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3))---4((4))---5((3))---6((2))---7((3)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3))---4((4))---5((3))---6((2))---7((3))---8((5)):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3))---4((4))---5((3))---6((2))---7((3))---8((5))---9((3))---10((2))---11((3))---12((4))---13((3))---14((2))---15((1)):::current classDef current stroke: #f96, stroke-width: 2px
Non linear system: undo-tree
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" "))---4((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" ")):::current---4((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" ")):::current---3((" "))---4((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" ")):::current---4((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" "))---4((" ")) 3((" "))---5((" ")):::current classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" "))---4((" ")) 3((" ")):::current---5((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" "))---2((" "))---3((" "))---4((" ")):::current 3((" "))---5((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((" ")):::current---2((" "))---3((" "))---4((" ")) 3((" "))---5((" ")) classDef current stroke: #f96, stroke-width: 2px
%%{init: { 'flowchart': {'rankSpacing':20} } }%% flowchart TD 1((1))---2((2))---3((3))---4((4))---5((3))---6((2))---7((3))---8((5))---9((3))---10((2))---11((3))---12((4))---13((3))---14((2))---15((1)):::current classDef current stroke: #f96, stroke-width: 2px
And this is an exceedingly simple example only involving 5 different file states. I let you imagine how it quickly explodes in complexity in real life situations 🙂
Now, the default Emacs system has the huge benefit to never lose any edit. It is already a huge improvement over the default system on most software! The thing is that when we undo and redo changes, linear systems are not ideal. A tree structure that can be fully navigated is just a more sensible solution.
Undo-tree was initially developed for Vim, so Vim can also use an ideal undo/redo system.
Installing and customizing undo-tree
This is a personal affair.
The minimal configuration when using straight (to download the package) and use-package to load it and customize it, looks like this:
use-package undo-tree
(t) :straight
My personal configuration looks like this:
use-package undo-tree
(t
:straight
:init1)
(global-undo-tree-mode "C-l" . undo-tree-undo)
:bind (("C-r" . undo-tree-redo)
("s-t" . undo-tree-visualize)
(
:map undo-tree-visualizer-mode-map;; go to selected undo state
"<return>" . undo-tree-visualizer-quit)
(;; cancel (return to state before calling undo-tree-visualize)
"q" . undo-tree-visualizer-abort))) (