Skip to main content

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}