Skip to main content

ashlar_store/
store.rs

1//! [`Store`]: a document as an `Arc` snapshot, edited copy-on-write, with revisions and undo.
2
3use std::sync::Arc;
4
5use crate::history::History;
6use crate::revision::Revision;
7use crate::snapshot::SnapshotCell;
8
9/// Owns a document `D` and its undo history.
10///
11/// - Reads are free: [`Store::doc`] borrows the current snapshot, [`Store::snapshot`] clones the
12///   `Arc` for a job or another thread.
13/// - Edits are copy-on-write: the closure gets a fresh copy (`Arc::make_mut` while the history
14///   still holds the old state), so its cost is `D::clone`. Make `D` cheap to clone by keeping
15///   large parts in `Arc`s (chunks, tracks, pages); an edit then copies only the chunk it touches.
16/// - Every change gets a new [`Revision`]; undo and redo restore the revision of the state they
17///   bring back, so caches keyed by revision stay valid and [`Store::is_dirty`] is exact.
18/// - `X` is extra state recorded with each step and restored by undo/redo (selection, caret). It
19///   can be changed freely with [`Store::extra_mut`] without making a step.
20pub struct Store<D, X = ()> {
21    doc: Arc<D>,
22    extra: X,
23    revision: Revision,
24    saved: Revision,
25    history: History<D, X>,
26    cell: Option<Arc<SnapshotCell<D>>>,
27}
28
29impl<D: std::fmt::Debug, X: std::fmt::Debug> std::fmt::Debug for Store<D, X> {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        f.debug_struct("Store")
32            .field("doc", &self.doc)
33            .field("extra", &self.extra)
34            .field("revision", &self.revision)
35            .field("saved", &self.saved)
36            .field("history", &self.history)
37            .finish()
38    }
39}
40
41impl<D: Clone, X: Clone + Default> Store<D, X> {
42    /// A store holding `doc`, considered saved (not dirty).
43    pub fn new(doc: D) -> Store<D, X> {
44        Store::with_extra(doc, X::default())
45    }
46}
47
48impl<D: Clone, X: Clone> Store<D, X> {
49    /// A store holding `doc` with extra state `extra`, considered saved.
50    pub fn with_extra(doc: D, extra: X) -> Store<D, X> {
51        let revision = Revision::next();
52        Store { doc: Arc::new(doc), extra, revision, saved: revision, history: History::new(), cell: None }
53    }
54
55    // ------------------------------------------------------------------ reading
56
57    /// The current document.
58    pub fn doc(&self) -> &Arc<D> {
59        &self.doc
60    }
61
62    /// The current document, to keep or send to another thread (an `Arc` clone).
63    pub fn snapshot(&self) -> Arc<D> {
64        Arc::clone(&self.doc)
65    }
66
67    pub fn revision(&self) -> Revision {
68        self.revision
69    }
70
71    pub fn extra(&self) -> &X {
72        &self.extra
73    }
74
75    /// Change the extra state without making an undo step.
76    pub fn extra_mut(&mut self) -> &mut X {
77        &mut self.extra
78    }
79
80    pub fn history(&self) -> &History<D, X> {
81        &self.history
82    }
83
84    /// The history, to change its limits or squash steps.
85    pub fn history_mut(&mut self) -> &mut History<D, X> {
86        &mut self.history
87    }
88
89    // ------------------------------------------------------------------ editing
90
91    /// Apply an undoable edit called `label`.
92    pub fn edit<R>(&mut self, label: &str, f: impl FnOnce(&mut D) -> R) -> R {
93        match self.try_edit_with::<R, std::convert::Infallible>(label, None, |d, _| Ok(f(d))) {
94            Ok(r) => r,
95            Err(never) => match never {},
96        }
97    }
98
99    /// Apply an edit that may fail; on error (or a panic in `f`) nothing changes.
100    pub fn try_edit<R, E>(&mut self, label: &str, f: impl FnOnce(&mut D) -> Result<R, E>) -> Result<R, E> {
101        self.try_edit_with(label, None, |d, _| f(d))
102    }
103
104    /// [`Store::edit`] that folds into the previous step when that step has the same label and
105    /// was made with the same `merge_key` with nothing in between (one undo step per drag).
106    pub fn edit_merged<R>(&mut self, label: &str, merge_key: &str, f: impl FnOnce(&mut D) -> R) -> R {
107        match self.try_edit_with::<R, std::convert::Infallible>(label, Some(merge_key), |d, _| Ok(f(d))) {
108            Ok(r) => r,
109            Err(never) => match never {},
110        }
111    }
112
113    /// The general edit: optional merge key, access to the extra state, may fail. On error (or a
114    /// panic in `f`) the document, the extra state, the revision and the history are unchanged.
115    pub fn try_edit_with<R, E>(&mut self, label: &str, merge_key: Option<&str>, f: impl FnOnce(&mut D, &mut X) -> Result<R, E>) -> Result<R, E> {
116        let mut doc = Arc::clone(&self.doc);
117        let mut extra = self.extra.clone();
118        // `self.doc` still holds the old state, so this clones it: the edit works on a copy.
119        let r = f(Arc::make_mut(&mut doc), &mut extra)?;
120        let merge = merge_key.is_some_and(|k| self.history.can_merge(label, k));
121        let old_doc = std::mem::replace(&mut self.doc, doc);
122        let old_extra = std::mem::replace(&mut self.extra, extra);
123        if !merge {
124            self.history.record(label, old_doc, old_extra, self.revision);
125            self.history.set_merge_key(merge_key);
126        }
127        self.revision = Revision::next();
128        self.publish();
129        Ok(r)
130    }
131
132    /// Make `doc`, built elsewhere (a command's working copy, a job's result), the new state as
133    /// one undo step `label`, without copying it. `merge_key` as in [`Store::edit_merged`].
134    /// Committing the current snapshot itself (`Arc::ptr_eq`) changes nothing.
135    pub fn commit(&mut self, label: &str, merge_key: Option<&str>, doc: Arc<D>) {
136        if Arc::ptr_eq(&doc, &self.doc) {
137            return;
138        }
139        let merge = merge_key.is_some_and(|k| self.history.can_merge(label, k));
140        let old_doc = std::mem::replace(&mut self.doc, doc);
141        if !merge {
142            self.history.record(label, old_doc, self.extra.clone(), self.revision);
143            self.history.set_merge_key(merge_key);
144        }
145        self.revision = Revision::next();
146        self.publish();
147    }
148
149    /// Run `f`, which may make any number of edits, as one undo step called `label`.
150    ///
151    /// On `Ok` the edits fold into one step (none when they changed nothing); the steps before
152    /// the transaction are untouched and the redo line is dropped only if something changed. On
153    /// `Err`, or a panic in `f`, the document, extra state, revision, history (redo line and merge
154    /// key included) and saved state are put back as they were. See [`Transaction`].
155    pub fn transaction<T, E>(&mut self, label: &str, f: impl FnOnce(&mut Store<D, X>) -> Result<T, E>) -> Result<T, E> {
156        let mut tx = self.begin_transaction(label);
157        let r = f(&mut tx)?;
158        tx.commit();
159        Ok(r)
160    }
161
162    /// Start a [`Transaction`] called `label`: edit through it, then [`Transaction::commit`];
163    /// dropping it without committing (an early return, a panic) rolls everything back.
164    pub fn begin_transaction(&mut self, label: &str) -> Transaction<'_, D, X> {
165        let scope = self.begin_scope(label);
166        Transaction { store: self, scope: Some(scope) }
167    }
168
169    /// [`Store::transaction`] for a host that owns the store: `f` gets the whole host (to run
170    /// commands that take `&mut H`), `store` finds the store in it. Same semantics: one step on
171    /// `Ok`, everything put back on `Err` or a panic in `f` (the panic then continues), nesting
172    /// as a savepoint, nothing recorded when nothing changed.
173    ///
174    /// ```
175    /// # use ashlar_store::Store;
176    /// struct Host { store: Store<Vec<i32>>, runs: u32 }
177    /// let mut host = Host { store: Store::new(Vec::new()), runs: 0 };
178    /// let r: Result<(), ()> = Store::transaction_on(&mut host, |h| &mut h.store, "Two", |h| {
179    ///     h.runs += 1;
180    ///     h.store.edit("a", |d| d.push(1));
181    ///     h.store.edit("b", |d| d.push(2));
182    ///     Ok(())
183    /// });
184    /// assert_eq!(r, Ok(()));
185    /// assert_eq!(host.store.history().undo_labels().collect::<Vec<_>>(), ["Two"]);
186    /// ```
187    pub fn transaction_on<H, T, E>(
188        host: &mut H,
189        store: impl Fn(&mut H) -> &mut Store<D, X>,
190        label: &str,
191        f: impl FnOnce(&mut H) -> Result<T, E>,
192    ) -> Result<T, E> {
193        let scope = store(host).begin_scope(label);
194        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| f(&mut *host)));
195        let s = store(host);
196        match r {
197            Ok(Ok(v)) => {
198                s.commit_scope(scope);
199                Ok(v)
200            }
201            Ok(Err(e)) => {
202                s.rollback_scope(scope);
203                Err(e)
204            }
205            Err(panic) => {
206                s.rollback_scope(scope);
207                std::panic::resume_unwind(panic)
208            }
209        }
210    }
211
212    /// Set the history aside for a scratch one and remember what a rollback puts back.
213    fn begin_scope(&mut self, label: &str) -> Scope<D, X> {
214        let mut scratch = History::new();
215        scratch.set_max_steps(usize::MAX);
216        let history = std::mem::replace(&mut self.history, scratch);
217        Scope { label: label.to_string(), doc: Arc::clone(&self.doc), extra: self.extra.clone(), revision: self.revision, saved: self.saved, history }
218    }
219
220    /// Put the history back and record one step for the scope if anything changed.
221    fn commit_scope(&mut self, b: Scope<D, X>) {
222        self.history = b.history;
223        if self.revision != b.revision {
224            self.history.record(&b.label, b.doc, b.extra, b.revision);
225        }
226    }
227
228    /// Put back the state at the start of the scope exactly.
229    fn rollback_scope(&mut self, b: Scope<D, X>) {
230        let changed = self.revision != b.revision;
231        self.history = b.history;
232        self.saved = b.saved;
233        self.doc = b.doc;
234        self.extra = b.extra;
235        self.revision = b.revision;
236        if changed {
237            self.publish();
238        }
239    }
240
241    /// Undo the last step; returns its label.
242    pub fn undo(&mut self) -> Option<String> {
243        let e = self.history.undo(Arc::clone(&self.doc), self.extra.clone(), self.revision)?;
244        self.restore(e.doc, e.extra, e.revision);
245        Some(e.label)
246    }
247
248    /// Redo the next undone step; returns its label.
249    pub fn redo(&mut self) -> Option<String> {
250        let e = self.history.redo(Arc::clone(&self.doc), self.extra.clone(), self.revision)?;
251        self.restore(e.doc, e.extra, e.revision);
252        Some(e.label)
253    }
254
255    /// Redo into branch `index` of [`History::branches`] (it must grow from the current state).
256    pub fn redo_branch(&mut self, index: usize) -> Option<String> {
257        if !self.history.switch_branch(index, self.revision) {
258            return None;
259        }
260        self.redo()
261    }
262
263    pub fn can_undo(&self) -> bool {
264        self.history.can_undo()
265    }
266
267    pub fn can_redo(&self) -> bool {
268        self.history.can_redo()
269    }
270
271    /// Replace the whole document (New, Open, Revert): clears the history, counts as saved.
272    pub fn replace(&mut self, doc: D, extra: X) {
273        self.doc = Arc::new(doc);
274        self.extra = extra;
275        self.history.clear();
276        self.revision = Revision::next();
277        self.saved = self.revision;
278        self.publish();
279    }
280
281    fn restore(&mut self, doc: Arc<D>, extra: X, revision: Revision) {
282        self.doc = doc;
283        self.extra = extra;
284        self.revision = revision;
285        self.publish();
286    }
287
288    // ------------------------------------------------------------------ saving
289
290    /// The document differs from the last saved (or loaded) state.
291    pub fn is_dirty(&self) -> bool {
292        self.revision != self.saved
293    }
294
295    /// The current state was saved.
296    pub fn mark_saved(&mut self) {
297        self.saved = self.revision;
298    }
299
300    /// Record that the state with `revision` was saved (a background save of an older snapshot).
301    pub fn mark_saved_at(&mut self, revision: Revision) {
302        self.saved = revision;
303    }
304
305    pub fn saved_revision(&self) -> Revision {
306        self.saved
307    }
308
309    // ------------------------------------------------------------------ other threads
310
311    /// A cell other threads read the latest snapshot from without locking; the store publishes
312    /// every change to it. The same cell is returned on every call.
313    pub fn subscribe_snapshots(&mut self) -> Arc<SnapshotCell<D>> {
314        let (doc, rev) = (Arc::clone(&self.doc), self.revision);
315        Arc::clone(self.cell.get_or_insert_with(|| Arc::new(SnapshotCell::new(doc, rev))))
316    }
317
318    fn publish(&self) {
319        if let Some(c) = &self.cell {
320            c.publish(Arc::clone(&self.doc), self.revision);
321        }
322    }
323}
324
325/// An open transaction: its label, what a rollback puts back, and the history it set aside.
326struct Scope<D, X> {
327    label: String,
328    doc: Arc<D>,
329    extra: X,
330    revision: Revision,
331    saved: Revision,
332    history: History<D, X>,
333}
334
335/// A compound edit in progress ([`Store::begin_transaction`]); derefs to the [`Store`].
336///
337/// While it is open the store's history is a scratch one holding only the transaction's own
338/// steps (so undo inside it goes back no further than its start, and limits never push out an
339/// older step); the real history is set aside untouched. [`Transaction::commit`] puts it back
340/// and records one step `label` from the state at the start to the current one, or nothing if
341/// the revision is the one at the start. Dropping it uncommitted puts back the state at the start
342/// exactly: document, extra state, revision, history, merge key, saved revision.
343///
344/// Transactions nest: an inner one commits as one step into the outer one's scratch history
345/// (folded again by the outer commit) and rolls back only its own edits (a savepoint).
346pub struct Transaction<'a, D: Clone, X: Clone = ()> {
347    store: &'a mut Store<D, X>,
348    scope: Option<Scope<D, X>>,
349}
350
351impl<D: Clone, X: Clone> Transaction<'_, D, X> {
352    /// Fold the transaction's edits into one undo step (none if nothing changed).
353    pub fn commit(mut self) {
354        if let Some(b) = self.scope.take() {
355            self.store.commit_scope(b);
356        }
357    }
358
359    /// Put everything back as it was at the start (the same as dropping it).
360    pub fn rollback(self) {}
361
362    /// Something changed since the start (a commit would record a step).
363    pub fn changed(&self) -> bool {
364        self.scope.as_ref().is_some_and(|b| b.revision != self.store.revision)
365    }
366}
367
368impl<D: Clone, X: Clone> std::ops::Deref for Transaction<'_, D, X> {
369    type Target = Store<D, X>;
370    fn deref(&self) -> &Store<D, X> {
371        self.store
372    }
373}
374
375impl<D: Clone, X: Clone> std::ops::DerefMut for Transaction<'_, D, X> {
376    fn deref_mut(&mut self) -> &mut Store<D, X> {
377        self.store
378    }
379}
380
381impl<D: Clone, X: Clone> Drop for Transaction<'_, D, X> {
382    fn drop(&mut self) {
383        if let Some(b) = self.scope.take() {
384            self.store.rollback_scope(b);
385        }
386    }
387}
388
389impl<D: Clone + std::fmt::Debug, X: Clone + std::fmt::Debug> std::fmt::Debug for Transaction<'_, D, X> {
390    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391        f.debug_struct("Transaction").field("label", &self.scope.as_ref().map(|b| b.label.as_str())).field("store", &self.store).finish()
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398
399    #[derive(Clone, Debug, Default, PartialEq)]
400    struct Doc {
401        items: Vec<i32>,
402    }
403
404    fn store() -> Store<Doc> {
405        Store::new(Doc::default())
406    }
407
408    #[test]
409    fn edit_is_copy_on_write_and_bumps_revision() {
410        let mut s = store();
411        let before = s.snapshot();
412        let r0 = s.revision();
413        assert!(!s.is_dirty());
414        let n = s.edit("Add", |d| {
415            d.items.push(1);
416            d.items.len()
417        });
418        assert_eq!(n, 1);
419        assert!(before.items.is_empty(), "old snapshot untouched");
420        assert_ne!(s.revision(), r0);
421        assert!(s.is_dirty());
422        assert_eq!(s.history().undo_label(), Some("Add"));
423    }
424
425    #[test]
426    fn failed_edit_changes_nothing() {
427        let mut s = store();
428        s.edit("a", |d| d.items.push(1));
429        let rev = s.revision();
430        let r: Result<(), &str> = s.try_edit("b", |d| {
431            d.items.push(2);
432            Err("no")
433        });
434        assert_eq!(r, Err("no"));
435        assert_eq!(s.doc().items, [1]);
436        assert_eq!(s.revision(), rev);
437        assert_eq!(s.history().depth(), 1);
438    }
439
440    #[test]
441    fn panicking_edit_changes_nothing() {
442        let mut s = store();
443        s.edit("a", |d| d.items.push(1));
444        let rev = s.revision();
445        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
446            s.edit("boom", |d| {
447                d.items.push(2);
448                panic!("bug in a command")
449            })
450        }));
451        assert!(r.is_err());
452        assert_eq!(s.doc().items, [1]);
453        assert_eq!(s.revision(), rev);
454        assert_eq!(s.history().undo_labels().collect::<Vec<_>>(), ["a"]);
455    }
456
457    #[test]
458    fn undo_restores_revision_and_dirty_state() {
459        let mut s = store();
460        let r0 = s.revision();
461        s.edit("a", |d| d.items.push(1));
462        let r1 = s.revision();
463        s.mark_saved();
464        s.edit("b", |d| d.items.push(2));
465        assert!(s.is_dirty());
466        assert_eq!(s.undo().as_deref(), Some("b"));
467        assert_eq!(s.revision(), r1);
468        assert!(!s.is_dirty(), "back at the saved state");
469        s.undo();
470        assert_eq!(s.revision(), r0);
471        assert!(s.is_dirty());
472        s.redo();
473        assert!(!s.is_dirty());
474        assert_eq!(s.redo().as_deref(), Some("b"));
475        assert_eq!(s.doc().items, [1, 2]);
476        assert_eq!(s.undo(), Some("b".into()));
477        assert_eq!(s.undo(), Some("a".into()));
478        assert_eq!(s.undo(), None);
479    }
480
481    #[test]
482    fn merged_edits_are_one_step_with_fresh_revisions() {
483        let mut s = store();
484        s.edit_merged("Move", "drag", |d| d.items.push(1));
485        let r1 = s.revision();
486        s.mark_saved();
487        s.edit_merged("Move", "drag", |d| d.items[0] = 2);
488        assert_ne!(s.revision(), r1, "merged content is new content");
489        assert!(s.is_dirty());
490        s.edit_merged("Move", "drag", |d| d.items[0] = 3);
491        assert_eq!(s.history().depth(), 1);
492        s.undo();
493        assert!(s.doc().items.is_empty());
494        s.redo();
495        assert_eq!(s.doc().items, [3]);
496        s.edit_merged("Move", "drag", |d| d.items[0] = 4);
497        assert_eq!(s.history().depth(), 2, "undo/redo ended the gesture");
498        s.edit("Other", |_| ());
499        s.edit_merged("Move", "drag", |d| d.items[0] = 5);
500        assert_eq!(s.history().depth(), 4);
501        // A failed merged edit keeps the gesture going.
502        let _: Result<(), ()> = s.try_edit_with("Move", Some("drag"), |_, _| Err(()));
503        s.edit_merged("Move", "drag", |d| d.items[0] = 6);
504        assert_eq!(s.history().depth(), 4);
505    }
506
507    #[test]
508    fn extra_state_travels_with_steps() {
509        let mut s: Store<Doc, Vec<usize>> = Store::new(Doc::default());
510        s.extra_mut().push(0); // selection before
511        s.try_edit_with::<_, ()>("Add", None, |d, sel| {
512            d.items.push(7);
513            *sel = vec![0];
514            Ok(())
515        })
516        .unwrap();
517        s.extra_mut().clear(); // user deselects (not a step)
518        s.undo();
519        assert_eq!(s.extra(), &vec![0], "selection before the step");
520        s.redo();
521        assert!(s.extra().is_empty(), "selection when undo was pressed");
522    }
523
524    #[test]
525    fn replace_clears_history_and_is_clean() {
526        let mut s = store();
527        s.edit("a", |d| d.items.push(1));
528        s.replace(Doc { items: vec![9] }, ());
529        assert!(!s.can_undo() && !s.can_redo() && !s.is_dirty());
530        assert_eq!(s.doc().items, [9]);
531    }
532
533    #[test]
534    fn snapshots_are_published() {
535        let mut s = store();
536        let cell = s.subscribe_snapshots();
537        assert!(Arc::ptr_eq(&cell, &s.subscribe_snapshots()));
538        s.edit("a", |d| d.items.push(1));
539        assert_eq!(cell.load().doc.items, [1]);
540        assert_eq!(cell.revision(), s.revision());
541        s.undo();
542        assert!(cell.load().doc.items.is_empty());
543        s.redo();
544        s.replace(Doc::default(), ());
545        assert_eq!(cell.revision(), s.revision());
546    }
547
548    #[test]
549    fn branches_through_the_store() {
550        let mut s = store();
551        s.history_mut().set_branching(true);
552        s.edit("a", |d| d.items.push(1));
553        s.undo();
554        s.edit("b", |d| d.items.push(2));
555        s.undo();
556        assert_eq!(s.history().branches().len(), 1);
557        assert_eq!(s.redo_branch(0).as_deref(), Some("a"));
558        assert_eq!(s.doc().items, [1]);
559        assert_eq!(s.redo_branch(0), None, "not at the fork any more");
560    }
561
562    #[test]
563    fn mark_saved_at_an_older_revision() {
564        let mut s = store();
565        s.edit("a", |d| d.items.push(1));
566        let saving = s.revision();
567        s.edit("b", |d| d.items.push(2));
568        s.mark_saved_at(saving);
569        assert!(s.is_dirty());
570        s.undo();
571        assert!(!s.is_dirty());
572        assert_eq!(s.saved_revision(), saving);
573    }
574
575    #[test]
576    fn commit_is_one_step_without_copying() {
577        let mut s = store();
578        let mut work = s.snapshot();
579        Arc::make_mut(&mut work).items.push(1);
580        Arc::make_mut(&mut work).items.push(2);
581        let built = Arc::clone(&work);
582        s.commit("Add Two", None, work);
583        assert!(Arc::ptr_eq(s.doc(), &built), "the committed Arc is kept as is");
584        assert_eq!(s.history().depth(), 1);
585        assert!(s.is_dirty());
586        let same = s.snapshot();
587        let r = s.revision();
588        s.commit("Nothing", None, same);
589        assert_eq!((s.revision(), s.history().depth()), (r, 1), "committing the current state is a no-op");
590        assert_eq!(s.undo().as_deref(), Some("Add Two"));
591        assert!(s.doc().items.is_empty());
592        assert!(!s.is_dirty());
593    }
594
595    fn labels(s: &Store<Doc>) -> (Vec<&str>, Vec<&str>) {
596        (s.history().undo_labels().collect(), s.history().redo_labels().collect())
597    }
598
599    #[test]
600    fn transaction_folds_edits_into_one_step() {
601        let mut s = store();
602        s.edit("a", |d| d.items.push(1));
603        s.edit("b", |d| d.items.push(2));
604        s.undo(); // redo line: b
605        let r: Result<usize, ()> = s.transaction("Assemble", |s| {
606            for i in 10..15 {
607                s.edit("part", |d| d.items.push(i));
608            }
609            s.edit_merged("drag", "k", |d| d.items.push(20));
610            s.edit_merged("drag", "k", |d| d.items.push(21));
611            assert_eq!(s.history().depth(), 6, "the scratch history holds only this transaction's steps");
612            Ok(s.doc().items.len())
613        });
614        assert_eq!(r, Ok(8));
615        assert_eq!(labels(&s), (vec!["a", "Assemble"], vec![]), "one step, redo dropped");
616        assert_eq!(s.history().merge_key(), None);
617        assert_eq!(s.undo().as_deref(), Some("Assemble"));
618        assert_eq!(s.doc().items, [1]);
619        assert_eq!(s.redo().as_deref(), Some("Assemble"));
620        assert_eq!(s.doc().items, [1, 10, 11, 12, 13, 14, 20, 21]);
621    }
622
623    #[test]
624    fn failed_transaction_puts_everything_back() {
625        let mut s: Store<Doc, u8> = Store::new(Doc::default());
626        s.edit("a", |d| d.items.push(1));
627        s.mark_saved();
628        s.edit_merged("Move", "drag", |d| d.items.push(2));
629        s.undo(); // redo: Move
630        s.edit_merged("Move", "drag", |d| d.items.push(3)); // drops redo; gesture "drag" open
631        s.edit("b", |d| d.items.push(4));
632        s.undo(); // redo: b, at [1, 3]
633        s.mark_saved();
634        s.history_mut().set_merge_key(Some("drag"));
635        *s.extra_mut() = 7;
636        let cell = s.subscribe_snapshots();
637        let (doc, rev, hist) = (s.snapshot(), s.revision(), format!("{:?}", s.history()));
638        let r: Result<(), &str> = s.transaction("T", |s| {
639            s.edit("x", |d| d.items.clear());
640            s.mark_saved();
641            *s.extra_mut() = 9;
642            s.undo();
643            s.edit("y", |d| d.items.push(5));
644            Err("no")
645        });
646        assert_eq!(r, Err("no"));
647        assert!(Arc::ptr_eq(s.doc(), &doc));
648        assert_eq!((s.revision(), *s.extra()), (rev, 7));
649        assert!(!s.is_dirty(), "clean before, clean after");
650        assert_eq!(format!("{:?}", s.history()), hist, "history, redo line and merge key as before");
651        assert!(s.history().can_merge("Move", "drag"));
652        assert_eq!(cell.revision(), rev, "the rollback was published");
653        assert_eq!(s.redo().as_deref(), Some("b"));
654
655        // A dirty store stays dirty.
656        s.edit("c", |d| d.items.push(6));
657        let rev = s.revision();
658        let _: Result<(), ()> = s.transaction("T", |s| {
659            s.mark_saved();
660            Err(())
661        });
662        assert!(s.is_dirty());
663        assert_eq!(s.revision(), rev);
664    }
665
666    #[test]
667    fn panicking_transaction_puts_everything_back() {
668        let mut s = store();
669        s.edit("a", |d| d.items.push(1));
670        s.edit("b", |d| d.items.push(2));
671        s.undo();
672        let (rev, hist) = (s.revision(), format!("{:?}", s.history()));
673        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
674            let _: Result<(), ()> = s.transaction("T", |s| {
675                s.edit("x", |d| d.items.push(9));
676                s.edit("y", |d| d.items.push(9));
677                panic!("bug in a command")
678            });
679        }));
680        assert!(r.is_err());
681        assert_eq!((s.doc().items.as_slice(), s.revision()), ([1].as_slice(), rev));
682        assert_eq!(format!("{:?}", s.history()), hist);
683        assert!(s.is_dirty());
684    }
685
686    #[test]
687    fn guard_commits_or_rolls_back_on_drop() {
688        let mut s = store();
689        {
690            let mut tx = s.begin_transaction("Keep");
691            tx.edit("x", |d| d.items.push(1));
692            tx.edit("y", |d| d.items.push(2));
693            assert!(tx.changed());
694            tx.commit();
695        }
696        assert_eq!(labels(&s).0, ["Keep"]);
697        let rev = s.revision();
698        {
699            let mut tx = s.begin_transaction("Drop");
700            tx.edit("x", |d| d.items.clear());
701        }
702        assert_eq!((s.doc().items.as_slice(), s.revision()), ([1, 2].as_slice(), rev));
703        let mut tx = s.begin_transaction("Explicit");
704        tx.edit("x", |d| d.items.clear());
705        tx.rollback();
706        assert_eq!(s.doc().items, [1, 2]);
707        assert_eq!(labels(&s).0, ["Keep"]);
708    }
709
710    #[test]
711    fn empty_transaction_records_nothing() {
712        let mut s = store();
713        s.edit_merged("Move", "drag", |d| d.items.push(1));
714        s.edit("b", |d| d.items.push(2));
715        s.undo();
716        s.history_mut().set_merge_key(Some("drag"));
717        let (rev, hist, dirty) = (s.revision(), format!("{:?}", s.history()), s.is_dirty());
718        assert_eq!(s.transaction::<_, ()>("Nothing", |_| Ok(3)), Ok(3));
719        let _: Result<(), ()> = s.transaction("Failed edit", |s| {
720            let _ = s.try_edit("x", |_| Err::<(), ()>(()));
721            Ok(())
722        });
723        // An edit undone inside the transaction is back at the start too.
724        let _: Result<(), ()> = s.transaction("Edit and undo", |s| {
725            s.edit("x", |d| d.items.push(9));
726            s.undo();
727            Ok(())
728        });
729        assert_eq!(s.revision(), rev);
730        assert_eq!(format!("{:?}", s.history()), hist, "redo line and gesture kept");
731        assert!(s.history().can_merge("Move", "drag"));
732        assert_eq!(s.is_dirty(), dirty);
733    }
734
735    #[test]
736    fn nested_transactions_join_the_outer_one() {
737        let mut s = store();
738        s.edit("a", |d| d.items.push(1));
739        let r: Result<(), ()> = s.transaction("Outer", |s| {
740            s.edit("x", |d| d.items.push(2));
741            let inner: Result<(), ()> = s.transaction("Inner ok", |s| {
742                s.edit("y", |d| d.items.push(3));
743                s.edit("z", |d| d.items.push(4));
744                Ok(())
745            });
746            assert_eq!(inner, Ok(()));
747            assert_eq!(s.history().undo_labels().collect::<Vec<_>>(), ["x", "Inner ok"]);
748            let failed: Result<(), ()> = s.transaction("Inner failed", |s| {
749                s.edit("w", |d| d.items.clear());
750                Err(())
751            });
752            assert_eq!(failed, Err(()));
753            assert_eq!(s.doc().items, [1, 2, 3, 4], "only the inner edits were rolled back");
754            Ok(())
755        });
756        assert_eq!(r, Ok(()));
757        assert_eq!(labels(&s).0, ["a", "Outer"]);
758        s.undo();
759        assert_eq!(s.doc().items, [1]);
760
761        // An outer failure rolls back a committed inner transaction too.
762        let rev = s.revision();
763        let _: Result<(), ()> = s.transaction("Outer", |s| {
764            let _: Result<(), ()> = s.transaction("Inner", |s| {
765                s.edit("y", |d| d.items.push(3));
766                Ok(())
767            });
768            Err(())
769        });
770        assert_eq!((s.doc().items.as_slice(), s.revision()), ([1].as_slice(), rev));
771        assert_eq!(labels(&s), (vec!["a"], vec!["Outer"]));
772    }
773
774    #[test]
775    fn transaction_and_merge_keys() {
776        let mut s = store();
777        s.edit_merged("Move", "drag", |d| d.items.push(1));
778        let _: Result<(), ()> = s.transaction("Move", |s| {
779            // Inside, a gesture merges with itself only, never with a step before the transaction.
780            s.edit_merged("Move", "drag", |d| d.items.push(2));
781            s.edit_merged("Move", "drag", |d| d.items.push(3));
782            assert_eq!(s.history().depth(), 1);
783            Ok(())
784        });
785        assert_eq!(labels(&s).0, ["Move", "Move"]);
786        assert_eq!(s.history().merge_key(), None, "a committed transaction ends the gesture");
787        s.edit_merged("Move", "drag", |d| d.items.push(4));
788        assert_eq!(s.history().depth(), 3);
789    }
790
791    #[test]
792    fn transaction_respects_limits_once() {
793        let mut s = store();
794        s.history_mut().set_max_steps(3);
795        for i in 0..3 {
796            s.edit(&format!("e{i}"), |d| d.items.push(i));
797        }
798        let _: Result<(), ()> = s.transaction("Many", |s| {
799            for i in 0..10 {
800                s.edit("part", |d| d.items.push(i));
801            }
802            Ok(())
803        });
804        assert_eq!(labels(&s).0, ["e1", "e2", "Many"], "only the folded step counted");
805        let _: Result<(), ()> = s.transaction("Fails", |s| {
806            for i in 0..10 {
807                s.edit("part", |d| d.items.push(i));
808            }
809            Err(())
810        });
811        assert_eq!(labels(&s).0, ["e1", "e2", "Many"], "no older step pushed out by a rollback");
812        assert_eq!(s.history().max_steps(), 3);
813        s.history_mut().set_max_steps(0);
814        let _: Result<(), ()> = s.transaction("NoUndo", |s| {
815            s.edit("x", |d| d.items.push(1));
816            Ok(())
817        });
818        assert!(!s.can_undo());
819        assert!(s.is_dirty());
820    }
821
822    /// An app-like owner: commands take the whole host, not just the store.
823    struct Host {
824        store: Store<Doc, u8>,
825        log: Vec<&'static str>,
826    }
827
828    impl Host {
829        fn new() -> Host {
830            Host { store: Store::new(Doc::default()), log: Vec::new() }
831        }
832        /// A command that needs `&mut Host`.
833        fn push(&mut self, label: &'static str, v: i32) {
834            self.log.push(label);
835            self.store.edit(label, |d| d.items.push(v));
836        }
837        fn tx<T, E>(&mut self, label: &str, f: impl FnOnce(&mut Host) -> Result<T, E>) -> Result<T, E> {
838            Store::transaction_on(self, |h| &mut h.store, label, f)
839        }
840    }
841
842    fn host_labels(h: &Host) -> (Vec<&str>, Vec<&str>) {
843        (h.store.history().undo_labels().collect(), h.store.history().redo_labels().collect())
844    }
845
846    #[test]
847    fn host_transaction_folds_commands_into_one_step() {
848        let mut h = Host::new();
849        h.push("a", 1);
850        h.push("b", 2);
851        h.store.undo(); // redo line: b
852        let r: Result<usize, ()> = h.tx("Assemble", |h| {
853            for i in 10..13 {
854                h.push("part", i);
855            }
856            h.store.edit_merged("drag", "k", |d| d.items.push(20));
857            h.store.edit_merged("drag", "k", |d| d.items.push(21));
858            assert_eq!(h.store.history().depth(), 4, "the scratch history holds only this transaction's steps");
859            Ok(h.store.doc().items.len())
860        });
861        assert_eq!(r, Ok(6));
862        assert_eq!(host_labels(&h), (vec!["a", "Assemble"], vec![]), "one step, redo dropped");
863        assert_eq!(h.store.history().merge_key(), None);
864        assert_eq!(h.log, ["a", "b", "part", "part", "part"]);
865        assert_eq!(h.store.undo().as_deref(), Some("Assemble"));
866        assert_eq!(h.store.doc().items, [1]);
867        assert_eq!(h.store.redo().as_deref(), Some("Assemble"));
868        assert_eq!(h.store.doc().items, [1, 10, 11, 12, 20, 21]);
869    }
870
871    #[test]
872    fn failed_host_transaction_puts_everything_back() {
873        let mut h = Host::new();
874        h.push("a", 1);
875        h.store.edit_merged("Move", "drag", |d| d.items.push(2));
876        h.push("b", 3);
877        h.store.undo(); // redo: b
878        h.store.mark_saved();
879        h.store.history_mut().set_merge_key(Some("drag"));
880        *h.store.extra_mut() = 7;
881        let cell = h.store.subscribe_snapshots();
882        let (doc, rev, hist) = (h.store.snapshot(), h.store.revision(), format!("{:?}", h.store.history()));
883        let r: Result<(), &str> = h.tx("T", |h| {
884            h.push("x", 9);
885            h.store.mark_saved();
886            *h.store.extra_mut() = 9;
887            h.store.undo();
888            h.push("y", 5);
889            Err("no")
890        });
891        assert_eq!(r, Err("no"));
892        assert!(Arc::ptr_eq(h.store.doc(), &doc));
893        assert_eq!((h.store.revision(), *h.store.extra()), (rev, 7));
894        assert!(!h.store.is_dirty(), "clean before, clean after");
895        assert_eq!(format!("{:?}", h.store.history()), hist, "history, redo line and merge key as before");
896        assert!(h.store.history().can_merge("Move", "drag"));
897        assert_eq!(cell.revision(), rev, "the rollback was published");
898        assert_eq!(h.log, ["a", "b", "x", "y"], "the rest of the host is the host's business");
899        assert_eq!(h.store.redo().as_deref(), Some("b"));
900    }
901
902    #[test]
903    fn panicking_host_transaction_puts_everything_back() {
904        let mut h = Host::new();
905        h.push("a", 1);
906        h.push("b", 2);
907        h.store.undo();
908        let (rev, hist) = (h.store.revision(), format!("{:?}", h.store.history()));
909        let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
910            let _: Result<(), ()> = h.tx("T", |h| {
911                h.push("x", 9);
912                h.push("y", 9);
913                panic!("bug in a command")
914            });
915        }));
916        assert!(r.is_err(), "the panic continues");
917        assert_eq!((h.store.doc().items.as_slice(), h.store.revision()), ([1].as_slice(), rev));
918        assert_eq!(format!("{:?}", h.store.history()), hist);
919        assert!(h.store.is_dirty());
920    }
921
922    #[test]
923    fn empty_host_transaction_records_nothing() {
924        let mut h = Host::new();
925        h.store.edit_merged("Move", "drag", |d| d.items.push(1));
926        h.push("b", 2);
927        h.store.undo();
928        h.store.history_mut().set_merge_key(Some("drag"));
929        let (rev, hist, dirty) = (h.store.revision(), format!("{:?}", h.store.history()), h.store.is_dirty());
930        assert_eq!(h.tx::<_, ()>("Nothing", |_| Ok(3)), Ok(3));
931        let _: Result<(), ()> = h.tx("Edit and undo", |h| {
932            h.push("x", 9);
933            h.store.undo();
934            Ok(())
935        });
936        assert_eq!(h.store.revision(), rev);
937        assert_eq!(format!("{:?}", h.store.history()), hist, "redo line and gesture kept");
938        assert!(h.store.history().can_merge("Move", "drag"));
939        assert_eq!(h.store.is_dirty(), dirty);
940    }
941
942    #[test]
943    fn host_transactions_nest_with_each_other_and_store_transactions() {
944        let mut h = Host::new();
945        h.push("a", 1);
946        let r: Result<(), ()> = h.tx("Outer", |h| {
947            h.push("x", 2);
948            let inner: Result<(), ()> = h.tx("Inner ok", |h| {
949                h.push("y", 3);
950                h.push("z", 4);
951                Ok(())
952            });
953            assert_eq!(inner, Ok(()));
954            let store_inner: Result<(), ()> = h.store.transaction("Store inner", |s| {
955                s.edit("w", |d| d.items.push(5));
956                Ok(())
957            });
958            assert_eq!(store_inner, Ok(()));
959            assert_eq!(h.store.history().undo_labels().collect::<Vec<_>>(), ["x", "Inner ok", "Store inner"]);
960            let failed: Result<(), ()> = h.tx("Inner failed", |h| {
961                h.store.edit("v", |d| d.items.clear());
962                Err(())
963            });
964            assert_eq!(failed, Err(()));
965            assert_eq!(h.store.doc().items, [1, 2, 3, 4, 5], "only the inner edits were rolled back");
966            Ok(())
967        });
968        assert_eq!(r, Ok(()));
969        assert_eq!(host_labels(&h).0, ["a", "Outer"]);
970        h.store.undo();
971        assert_eq!(h.store.doc().items, [1]);
972
973        // An outer failure rolls back a committed inner transaction too.
974        let rev = h.store.revision();
975        let _: Result<(), ()> = h.tx("Outer", |h| {
976            let _: Result<(), ()> = h.tx("Inner", |h| {
977                h.push("y", 3);
978                Ok(())
979            });
980            Err(())
981        });
982        assert_eq!((h.store.doc().items.as_slice(), h.store.revision()), ([1].as_slice(), rev));
983        assert_eq!(host_labels(&h), (vec!["a"], vec!["Outer"]));
984    }
985
986    #[test]
987    fn host_transaction_respects_limits_once() {
988        let mut h = Host::new();
989        h.store.history_mut().set_max_steps(2);
990        h.push("e0", 0);
991        h.push("e1", 1);
992        let _: Result<(), ()> = h.tx("Many", |h| {
993            for i in 0..10 {
994                h.push("part", i);
995            }
996            Ok(())
997        });
998        assert_eq!(host_labels(&h).0, ["e1", "Many"]);
999        let _: Result<(), ()> = h.tx("Fails", |h| {
1000            for i in 0..10 {
1001                h.push("part", i);
1002            }
1003            Err(())
1004        });
1005        assert_eq!(host_labels(&h).0, ["e1", "Many"], "no older step pushed out by a rollback");
1006        assert_eq!(h.store.history().max_steps(), 2);
1007    }
1008}