editor/history.rs
1//! Undo and redo, as coalesced snapshots.
2//!
3//! A whole [`Doc`] per step rather than a diff. That is what [`ui::input::TextField`]
4//! settled on for a string, and the argument carries: a document held in memory
5//! is small next to the machinery a transaction log needs, and a snapshot cannot
6//! be wrong about what it restores. Steps, not keystrokes — a run of typing
7//! coalesces into one, so the limit is deeper than it looks.
8//!
9//! Coalescing is by **adjacency rather than by a pause**, so there is no timing
10//! threshold to invent: the next edit joins the last group when it is the same
11//! kind and picks up where that one left off. Anything else — a motion, a
12//! structural change, a click — starts a new group.
13
14use ui::history::SnapshotHistory;
15
16use markdown::{Cursor, Doc, Selection};
17
18use crate::{anchor::Anchor, editor::Mode};
19
20/// How many steps a document keeps.
21///
22/// Deeper than a text field's, because a document is the thing people actually
23/// walk backwards through, and bounded because an unbounded history of a
24/// growing document is a slow leak nothing reclaims.
25pub const DEFAULT_UNDO_LIMIT: usize = 100;
26
27/// What an edit did, so a run of the same kind can coalesce into one step
28/// instead of giving the document back a character at a time.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum EditKind {
31 Insert,
32 Delete,
33 /// Anything structural — a split, an indent, a block turned into another.
34 /// Never coalesces: these are the steps a reader wants to land on.
35 Structure,
36}
37
38#[derive(Clone)]
39struct Snapshot {
40 doc: Doc,
41 /// Which form the document was being edited in, so a step back across a
42 /// switch to the source lands in the form it was taken in.
43 mode: Mode,
44 selection: Selection,
45 /// Carried with the document because an undo replaces it wholesale: there
46 /// is no delta to map an anchor through, so the anchors of that moment have
47 /// to be the ones that come back.
48 anchors: Vec<Anchor>,
49}
50
51/// A document's undo and redo stacks, shared by every editor on it.
52pub struct History {
53 snapshots: SnapshotHistory<Snapshot>,
54}
55
56impl Default for History {
57 fn default() -> Self {
58 Self::with_limit(DEFAULT_UNDO_LIMIT)
59 }
60}
61
62impl History {
63 pub fn with_limit(limit: usize) -> Self {
64 Self {
65 snapshots: SnapshotHistory::new(limit),
66 }
67 }
68
69 /// Drop every step, both ways.
70 pub fn clear(&mut self) {
71 self.snapshots.clear();
72 }
73
74 /// Whether there is a step to take back.
75 pub fn can_undo(&self) -> bool {
76 self.snapshots.can_undo()
77 }
78
79 /// Record the state *before* an edit. `joins` leaves it to the step the
80 /// last edit opened — see [`Run::joins`]. Either way the redo stack goes.
81 pub fn record(
82 &mut self,
83 joins: bool,
84 mode: Mode,
85 doc: &Doc,
86 selection: Selection,
87 anchors: &[Anchor],
88 ) {
89 self.snapshots.record((!joins).then(|| Snapshot {
90 doc: doc.clone(),
91 mode,
92 selection,
93 anchors: anchors.to_vec(),
94 }));
95 }
96
97 /// Step back, handing the caller the state to restore. Pushes what it was
98 /// given onto the redo stack.
99 pub fn undo(
100 &mut self,
101 mode: Mode,
102 doc: &Doc,
103 selection: Selection,
104 anchors: &[Anchor],
105 ) -> Option<Step> {
106 let previous = self.snapshots.undo(|| Snapshot {
107 doc: doc.clone(),
108 mode,
109 selection,
110 anchors: anchors.to_vec(),
111 })?;
112 Some(previous.into())
113 }
114
115 pub fn redo(
116 &mut self,
117 mode: Mode,
118 doc: &Doc,
119 selection: Selection,
120 anchors: &[Anchor],
121 ) -> Option<Step> {
122 let next = self.snapshots.redo(|| Snapshot {
123 doc: doc.clone(),
124 mode,
125 selection,
126 anchors: anchors.to_vec(),
127 })?;
128 Some(next.into())
129 }
130}
131
132/// Where one editor's run of edits left off: the kind of the last edit and
133/// where it *left* the caret, which decides whether the next edit joins that
134/// step or opens one of its own.
135#[derive(Default)]
136pub struct Run {
137 last: Option<(EditKind, Cursor)>,
138}
139
140impl Run {
141 /// Whether an edit of `kind` from `selection` continues the run — same
142 /// kind, same text, and picking up exactly where the last one stopped.
143 /// Structural edits never do.
144 pub fn joins(&self, kind: EditKind, selection: Selection) -> bool {
145 kind != EditKind::Structure
146 && self.last == Some((kind, selection.head))
147 && selection.is_collapsed()
148 }
149
150 /// Close the edit, noting where it left the caret. The next edit joins this
151 /// run only if it starts from exactly here.
152 pub fn landed(&mut self, kind: EditKind, selection: Selection) {
153 self.last = (kind != EditKind::Structure).then_some((kind, selection.head));
154 }
155
156 /// Anything that is not an edit ends the run — a motion, a click, a focus
157 /// change. Without this, typing a word, clicking elsewhere and typing again
158 /// would undo as one step across two places.
159 pub fn interrupt(&mut self) {
160 self.last = None;
161 }
162}
163
164/// The state a step restores, which is a whole moment rather than a diff.
165pub struct Step {
166 pub doc: Doc,
167 pub mode: Mode,
168 pub selection: Selection,
169 pub anchors: Vec<Anchor>,
170}
171
172impl From<Snapshot> for Step {
173 fn from(snapshot: Snapshot) -> Self {
174 Self {
175 doc: snapshot.doc,
176 mode: snapshot.mode,
177 selection: snapshot.selection,
178 anchors: snapshot.anchors,
179 }
180 }
181}