loro_internal/
undo.rs

1use std::{collections::VecDeque, sync::Arc};
2
3use crate::sync::{AtomicU64, Mutex};
4use either::Either;
5use rustc_hash::{FxHashMap, FxHashSet};
6use loro_common::{
7    ContainerID, Counter, CounterSpan, HasIdSpan, IdSpan, LoroError, LoroResult, LoroValue, PeerID,
8};
9use tracing::{debug_span, info_span, instrument};
10
11use crate::{
12    change::{get_sys_timestamp, Timestamp},
13    cursor::{AbsolutePosition, Cursor},
14    delta::TreeExternalDiff,
15    event::{Diff, EventTriggerKind},
16    version::Frontiers,
17    ContainerDiff, DiffEvent, DocDiff, LoroDoc, Subscription,
18};
19
20/// A batch of diffs.
21///
22/// You can use `loroDoc.apply_diff(diff)` to apply the diff to the document.
23#[derive(Debug, Clone, Default)]
24pub struct DiffBatch {
25    pub cid_to_events: FxHashMap<ContainerID, Diff>,
26    pub order: Vec<ContainerID>,
27}
28
29impl DiffBatch {
30    pub fn new(diff: Vec<DocDiff>) -> Self {
31        let mut map: FxHashMap<ContainerID, Diff> = Default::default();
32        let mut order: Vec<ContainerID> = Vec::with_capacity(diff.len());
33        for d in diff.into_iter() {
34            for item in d.diff.into_iter() {
35                let old = map.insert(item.id.clone(), item.diff);
36                assert!(old.is_none());
37                order.push(item.id.clone());
38            }
39        }
40
41        Self {
42            cid_to_events: map,
43            order,
44        }
45    }
46
47    pub fn compose(&mut self, other: &Self) {
48        if other.cid_to_events.is_empty() {
49            return;
50        }
51
52        for (id, diff) in other.iter() {
53            if let Some(this_diff) = self.cid_to_events.get_mut(id) {
54                this_diff.compose_ref(diff);
55            } else {
56                self.cid_to_events.insert(id.clone(), diff.clone());
57                self.order.push(id.clone());
58            }
59        }
60    }
61
62    pub fn transform(&mut self, other: &Self, left_priority: bool) {
63        if other.cid_to_events.is_empty() || self.cid_to_events.is_empty() {
64            return;
65        }
66
67        for (idx, diff) in self.cid_to_events.iter_mut() {
68            if let Some(b_diff) = other.cid_to_events.get(idx) {
69                diff.transform(b_diff, left_priority);
70            }
71        }
72    }
73
74    pub fn clear(&mut self) {
75        self.cid_to_events.clear();
76        self.order.clear();
77    }
78
79    pub fn iter(&self) -> impl Iterator<Item = (&ContainerID, &Diff)> + '_ {
80        self.order
81            .iter()
82            .map(|cid| (cid, self.cid_to_events.get(cid).unwrap()))
83    }
84
85    #[allow(clippy::should_implement_trait)]
86    pub fn into_iter(self) -> impl Iterator<Item = (ContainerID, Diff)> {
87        let mut cid_to_events = self.cid_to_events;
88        self.order.into_iter().map(move |cid| {
89            let d = cid_to_events.remove(&cid).unwrap();
90            (cid, d)
91        })
92    }
93}
94
95fn transform_cursor(
96    cursor_with_pos: &mut CursorWithPos,
97    remote_diff: &DiffBatch,
98    doc: &LoroDoc,
99    container_remap: &FxHashMap<ContainerID, ContainerID>,
100) {
101    let mut container_changed = false;
102    let mut cid = &cursor_with_pos.cursor.container;
103    while let Some(new_cid) = container_remap.get(cid) {
104        cid = new_cid;
105        container_changed = true;
106    }
107
108    if cursor_with_pos.cursor.id.is_none() {
109        // We don't need to transform a cursor that always points to the leftmost or rightmost position
110        if container_changed {
111            cursor_with_pos.cursor.container = cid.clone();
112        }
113        return;
114    }
115
116    if let Some(diff) = remote_diff.cid_to_events.get(cid) {
117        let new_pos = diff.transform_cursor(cursor_with_pos.pos.pos, false);
118        cursor_with_pos.pos.pos = new_pos;
119    };
120
121    let new_pos = cursor_with_pos.pos.pos;
122    match doc.get_handler(cid.clone()).unwrap() {
123        crate::handler::Handler::Text(h) => {
124            let Some(new_cursor) = h.get_cursor_internal(new_pos, cursor_with_pos.pos.side, false)
125            else {
126                return;
127            };
128
129            cursor_with_pos.cursor = new_cursor;
130        }
131        crate::handler::Handler::List(h) => {
132            let Some(new_cursor) = h.get_cursor(new_pos, cursor_with_pos.pos.side) else {
133                return;
134            };
135
136            cursor_with_pos.cursor = new_cursor;
137        }
138        crate::handler::Handler::MovableList(h) => {
139            let Some(new_cursor) = h.get_cursor(new_pos, cursor_with_pos.pos.side) else {
140                return;
141            };
142
143            cursor_with_pos.cursor = new_cursor;
144        }
145        crate::handler::Handler::Map(_) => {}
146        crate::handler::Handler::Tree(_) => {}
147        crate::handler::Handler::Unknown(_) => {}
148        #[cfg(feature = "counter")]
149        crate::handler::Handler::Counter(_) => {}
150    }
151}
152
153/// UndoManager is responsible for managing undo/redo from the current peer's perspective.
154///
155/// Undo/local is local: it cannot be used to undone the changes made by other peers.
156/// If you want to undo changes made by other peers, you may need to use the time travel feature.
157///
158/// PeerID cannot be changed during the lifetime of the UndoManager
159pub struct UndoManager {
160    peer: Arc<AtomicU64>,
161    container_remap: Arc<Mutex<FxHashMap<ContainerID, ContainerID>>>,
162    inner: Arc<Mutex<UndoManagerInner>>,
163    _peer_id_change_sub: Subscription,
164    _undo_sub: Subscription,
165    doc: LoroDoc,
166}
167
168impl std::fmt::Debug for UndoManager {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        f.debug_struct("UndoManager")
171            .field("peer", &self.peer)
172            .field("container_remap", &self.container_remap)
173            .field("inner", &self.inner)
174            .finish()
175    }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum UndoOrRedo {
180    Undo,
181    Redo,
182}
183
184impl UndoOrRedo {
185    fn opposite(&self) -> UndoOrRedo {
186        match self {
187            Self::Undo => Self::Redo,
188            Self::Redo => Self::Undo,
189        }
190    }
191}
192
193/// When a undo/redo item is pushed, the undo manager will call the on_push callback to get the meta data of the undo item.
194/// The returned cursors will be recorded for a new pushed undo item.
195pub type OnPush = Box<
196    dyn for<'a> Fn(UndoOrRedo, CounterSpan, Option<DiffEvent<'a>>) -> UndoItemMeta + Send + Sync,
197>;
198pub type OnPop = Box<dyn Fn(UndoOrRedo, CounterSpan, UndoItemMeta) + Send + Sync>;
199
200struct UndoManagerInner {
201    next_counter: Option<Counter>,
202    undo_stack: Stack,
203    redo_stack: Stack,
204    processing_undo: bool,
205    last_undo_time: i64,
206    merge_interval_in_ms: i64,
207    max_stack_size: usize,
208    exclude_origin_prefixes: Vec<Box<str>>,
209    last_popped_selection: Option<Vec<CursorWithPos>>,
210    on_push: Option<OnPush>,
211    on_pop: Option<OnPop>,
212    group: Option<UndoGroup>,
213}
214
215impl std::fmt::Debug for UndoManagerInner {
216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        f.debug_struct("UndoManagerInner")
218            .field("latest_counter", &self.next_counter)
219            .field("undo_stack", &self.undo_stack)
220            .field("redo_stack", &self.redo_stack)
221            .field("processing_undo", &self.processing_undo)
222            .field("last_undo_time", &self.last_undo_time)
223            .field("merge_interval", &self.merge_interval_in_ms)
224            .field("max_stack_size", &self.max_stack_size)
225            .field("exclude_origin_prefixes", &self.exclude_origin_prefixes)
226            .field("group", &self.group)
227            .finish()
228    }
229}
230
231#[derive(Debug, Clone, Default)]
232struct UndoGroup {
233    start_counter: Counter,
234    affected_cids: FxHashSet<ContainerID>,
235}
236
237impl UndoGroup {
238    pub fn new(start_counter: Counter) -> Self {
239        Self {
240            start_counter,
241            affected_cids: Default::default(),
242        }
243    }
244}
245
246#[derive(Debug)]
247struct Stack {
248    stack: VecDeque<(VecDeque<StackItem>, Arc<Mutex<DiffBatch>>)>,
249    size: usize,
250}
251
252#[derive(Debug, Clone)]
253struct StackItem {
254    span: CounterSpan,
255    meta: UndoItemMeta,
256}
257
258/// The metadata of an undo item.
259///
260/// The cursors inside the metadata will be transformed by remote operations as well.
261/// So that when the item is popped, users can restore the cursors position correctly.
262#[derive(Debug, Default, Clone)]
263pub struct UndoItemMeta {
264    pub value: LoroValue,
265    pub cursors: Vec<CursorWithPos>,
266}
267
268#[derive(Debug, Clone)]
269pub struct CursorWithPos {
270    pub cursor: Cursor,
271    pub pos: AbsolutePosition,
272}
273
274impl UndoItemMeta {
275    pub fn new() -> Self {
276        Self {
277            value: LoroValue::Null,
278            cursors: Default::default(),
279        }
280    }
281
282    /// It's assumed that the cursor is just acquired before the ops that
283    /// need to be undo/redo.
284    ///
285    /// We need to rely on the validity of the original pos value
286    pub fn add_cursor(&mut self, cursor: &Cursor) {
287        self.cursors.push(CursorWithPos {
288            cursor: cursor.clone(),
289            pos: AbsolutePosition {
290                pos: cursor.origin_pos,
291                side: cursor.side,
292            },
293        });
294    }
295
296    pub fn set_value(&mut self, value: LoroValue) {
297        self.value = value;
298    }
299}
300
301impl Stack {
302    pub fn new() -> Self {
303        let mut stack = VecDeque::new();
304        stack.push_back((VecDeque::new(), Arc::new(Mutex::new(Default::default()))));
305        Stack { stack, size: 0 }
306    }
307
308    /// Peek the top-most StackItem's metadata without modifying the stack.
309    ///
310    /// Returns None if the stack is empty.
311    fn peek_top_meta(&self) -> Option<UndoItemMeta> {
312        if self.is_empty() {
313            return None;
314        }
315
316        for (items, _) in self.stack.iter().rev() {
317            if let Some(item) = items.back() {
318                return Some(item.meta.clone());
319            }
320        }
321
322        None
323    }
324
325    pub fn pop(&mut self) -> Option<(StackItem, Arc<Mutex<DiffBatch>>)> {
326        while self.stack.back().unwrap().0.is_empty() && self.stack.len() > 1 {
327            let (_, diff) = self.stack.pop_back().unwrap();
328            let diff = diff.lock().unwrap();
329            if !diff.cid_to_events.is_empty() {
330                self.stack
331                    .back_mut()
332                    .unwrap()
333                    .1
334                    .lock()
335                    .unwrap()
336                    .compose(&diff);
337            }
338        }
339
340        if self.stack.len() == 1 && self.stack.back().unwrap().0.is_empty() {
341            // If the stack is empty, we need to clear the remote diff
342            self.stack.back_mut().unwrap().1.lock().unwrap().clear();
343            return None;
344        }
345
346        self.size -= 1;
347        let last = self.stack.back_mut().unwrap();
348        last.0.pop_back().map(|x| (x, last.1.clone()))
349        // If this row in stack is empty, we don't pop it right away
350        // Because we still need the remote diff to be available.
351        // Cursor position transformation relies on the remote diff in the same row.
352    }
353
354    pub fn push(&mut self, span: CounterSpan, meta: UndoItemMeta) {
355        self.push_with_merge(span, meta, false, None)
356    }
357
358    pub fn push_with_merge(
359        &mut self,
360        span: CounterSpan,
361        meta: UndoItemMeta,
362        can_merge: bool,
363        group: Option<&UndoGroup>,
364    ) {
365        let last = self.stack.back_mut().unwrap();
366        let last_remote_diff = last.1.lock().unwrap();
367
368        // Check if the remote diff is disjoint with the current undo group
369        let is_disjoint_group = group.is_some_and(|g| {
370            g.affected_cids.iter().all(|cid| {
371                last_remote_diff
372                    .cid_to_events
373                    .get(cid)
374                    .is_none_or(|diff| diff.is_empty())
375            })
376        });
377
378        // Can't merge if remote diffs exist and it's not disjoint with the current undo group
379        let should_create_new_entry =
380            !last_remote_diff.cid_to_events.is_empty() && !is_disjoint_group;
381
382        if should_create_new_entry {
383            // Create a new entry in the stack
384            drop(last_remote_diff);
385            let mut v = VecDeque::new();
386            v.push_back(StackItem { span, meta });
387            self.stack
388                .push_back((v, Arc::new(Mutex::new(DiffBatch::default()))));
389            self.size += 1;
390            return;
391        }
392
393        // Try to merge with the previous entry if allowed
394        if can_merge {
395            if let Some(last_span) = last.0.back_mut() {
396                if last_span.span.end == span.start {
397                    // Merge spans by extending the end of the last span
398                    last_span.span.end = span.end;
399                    return;
400                }
401            }
402        }
403
404        // Add as a new item to the existing entry
405        self.size += 1;
406        last.0.push_back(StackItem { span, meta });
407    }
408
409    pub fn compose_remote_event(&mut self, diff: &[&ContainerDiff]) {
410        if self.is_empty() {
411            return;
412        }
413
414        let remote_diff = &mut self.stack.back_mut().unwrap().1;
415        let mut remote_diff = remote_diff.lock().unwrap();
416        for e in diff {
417            if let Some(d) = remote_diff.cid_to_events.get_mut(&e.id) {
418                d.compose_ref(&e.diff);
419            } else {
420                remote_diff
421                    .cid_to_events
422                    .insert(e.id.clone(), e.diff.clone());
423                remote_diff.order.push(e.id.clone());
424            }
425        }
426    }
427
428    pub fn transform_based_on_this_delta(&mut self, diff: &DiffBatch) {
429        if self.is_empty() {
430            return;
431        }
432        let remote_diff = &mut self.stack.back_mut().unwrap().1;
433        remote_diff.lock().unwrap().transform(diff, false);
434    }
435
436    pub fn clear(&mut self) {
437        self.stack = VecDeque::new();
438        self.stack.push_back((VecDeque::new(), Default::default()));
439        self.size = 0;
440    }
441
442    pub fn is_empty(&self) -> bool {
443        self.size == 0
444    }
445
446    pub fn len(&self) -> usize {
447        self.size
448    }
449
450    fn pop_front(&mut self) {
451        if self.is_empty() {
452            return;
453        }
454
455        self.size -= 1;
456        let first = self.stack.front_mut().unwrap();
457        let f = first.0.pop_front();
458        assert!(f.is_some());
459        if first.0.is_empty() {
460            self.stack.pop_front();
461        }
462    }
463}
464
465impl Default for Stack {
466    fn default() -> Self {
467        Stack::new()
468    }
469}
470
471impl UndoManagerInner {
472    fn new(last_counter: Counter) -> Self {
473        Self {
474            next_counter: Some(last_counter),
475            undo_stack: Default::default(),
476            redo_stack: Default::default(),
477            processing_undo: false,
478            merge_interval_in_ms: 0,
479            last_undo_time: 0,
480            max_stack_size: usize::MAX,
481            exclude_origin_prefixes: vec![],
482            last_popped_selection: None,
483            on_pop: None,
484            on_push: None,
485            group: None,
486        }
487    }
488
489    /// Returns true if a given container diff is disjoint with the current group.
490    /// They are disjoint if they have no overlap in changed container ids.
491    fn is_disjoint_with_group(&self, diff: &[&ContainerDiff]) -> bool {
492        let Some(group) = &self.group else {
493            return false;
494        };
495
496        diff.iter().all(|d| !group.affected_cids.contains(&d.id))
497    }
498
499    fn record_checkpoint(&mut self, latest_counter: Counter, event: Option<DiffEvent>) {
500        let previous_counter = self.next_counter;
501
502        if Some(latest_counter) == self.next_counter {
503            return;
504        }
505
506        if self.next_counter.is_none() {
507            self.next_counter = Some(latest_counter);
508            return;
509        }
510
511        if let Some(group) = &mut self.group {
512            event.iter().for_each(|e| {
513                e.events.iter().for_each(|e| {
514                    group.affected_cids.insert(e.id.clone());
515                })
516            });
517        }
518
519        let now = get_sys_timestamp() as Timestamp;
520        let span = CounterSpan::new(self.next_counter.unwrap(), latest_counter);
521        let meta = self
522            .on_push
523            .as_ref()
524            .map(|x| x(UndoOrRedo::Undo, span, event))
525            .unwrap_or_default();
526
527        // Wether the change is within the accepted merge interval
528        let in_merge_interval = now - self.last_undo_time < self.merge_interval_in_ms;
529
530        // If group is active, but there is nothing in the group, don't merge
531        // If the group is active and it's not the first push in the group, merge
532        let group_should_merge = self.group.is_some()
533            && match (
534                previous_counter,
535                self.group.as_ref().map(|g| g.start_counter),
536            ) {
537                (Some(previous), Some(active)) => previous != active,
538                _ => true,
539            };
540
541        let should_merge = !self.undo_stack.is_empty() && (in_merge_interval || group_should_merge);
542
543        if should_merge {
544            self.undo_stack
545                .push_with_merge(span, meta, true, self.group.as_ref());
546        } else {
547            self.last_undo_time = now;
548            self.undo_stack.push(span, meta);
549        }
550
551        self.next_counter = Some(latest_counter);
552        self.redo_stack.clear();
553        while self.undo_stack.len() > self.max_stack_size {
554            self.undo_stack.pop_front();
555        }
556    }
557}
558
559fn get_counter_end(doc: &LoroDoc, peer: PeerID) -> Counter {
560    doc.oplog()
561        .lock()
562        .unwrap()
563        .vv()
564        .get(&peer)
565        .cloned()
566        .unwrap_or(0)
567}
568
569impl UndoManager {
570    pub fn new(doc: &LoroDoc) -> Self {
571        let peer = Arc::new(AtomicU64::new(doc.peer_id()));
572        let peer_clone = peer.clone();
573        let peer_clone2 = peer.clone();
574        let inner = Arc::new(Mutex::new(UndoManagerInner::new(get_counter_end(
575            doc,
576            doc.peer_id(),
577        ))));
578        let inner_clone = inner.clone();
579        let inner_clone2 = inner.clone();
580        let remap_containers = Arc::new(Mutex::new(FxHashMap::default()));
581        let remap_containers_clone = remap_containers.clone();
582        let undo_sub = doc.subscribe_root(Arc::new(move |event| match event.event_meta.by {
583            EventTriggerKind::Local => {
584                // TODO: PERF undo can be significantly faster if we can get
585                // the DiffBatch for undo here
586                let Ok(mut inner) = inner_clone.lock() else {
587                    return;
588                };
589                if inner.processing_undo {
590                    return;
591                }
592                if let Some(id) = event
593                    .event_meta
594                    .to
595                    .iter()
596                    .find(|x| x.peer == peer_clone.load(std::sync::atomic::Ordering::Relaxed))
597                {
598                    if inner
599                        .exclude_origin_prefixes
600                        .iter()
601                        .any(|x| event.event_meta.origin.starts_with(&**x))
602                    {
603                        // If the event is from the excluded origin, we don't record it
604                        // in the undo stack. But we need to record its effect like it's
605                        // a remote event.
606                        inner.undo_stack.compose_remote_event(event.events);
607                        inner.redo_stack.compose_remote_event(event.events);
608                        inner.next_counter = Some(id.counter + 1);
609                    } else {
610                        inner.record_checkpoint(id.counter + 1, Some(event));
611                    }
612                }
613            }
614            EventTriggerKind::Import => {
615                let mut inner = inner_clone.lock().unwrap();
616
617                for e in event.events {
618                    if let Diff::Tree(tree) = &e.diff {
619                        for item in &tree.diff {
620                            let target = item.target;
621                            if let TreeExternalDiff::Create { .. } = &item.action {
622                                // If the concurrent event is a create event, it may bring the deleted tree node back,
623                                // so we need to remove it from the remap of the container.
624                                remap_containers_clone
625                                    .lock()
626                                    .unwrap()
627                                    .remove(&target.associated_meta_container());
628                            }
629                        }
630                    }
631                }
632
633                let is_import_disjoint = inner.is_disjoint_with_group(event.events);
634
635                inner.undo_stack.compose_remote_event(event.events);
636                inner.redo_stack.compose_remote_event(event.events);
637
638                // If the import is not disjoint, we end the active group
639                // all subsequent changes will be new undo items
640                if !is_import_disjoint {
641                    inner.group = None;
642                }
643            }
644            EventTriggerKind::Checkout => {
645                let mut inner = inner_clone.lock().unwrap();
646                inner.undo_stack.clear();
647                inner.redo_stack.clear();
648                inner.next_counter = None;
649            }
650        }));
651
652        let sub = doc.subscribe_peer_id_change(Box::new(move |id| {
653            let mut inner = inner_clone2.lock().unwrap();
654            inner.undo_stack.clear();
655            inner.redo_stack.clear();
656            inner.next_counter = Some(id.counter);
657            peer_clone2.store(id.peer, std::sync::atomic::Ordering::Relaxed);
658            true
659        }));
660
661        UndoManager {
662            peer,
663            container_remap: remap_containers,
664            inner,
665            _peer_id_change_sub: sub,
666            _undo_sub: undo_sub,
667            doc: doc.clone(),
668        }
669    }
670
671    pub fn group_start(&mut self) -> LoroResult<()> {
672        let mut inner = self.inner.lock().unwrap();
673
674        if inner.group.is_some() {
675            return Err(LoroError::UndoGroupAlreadyStarted);
676        }
677
678        inner.group = Some(UndoGroup::new(inner.next_counter.unwrap()));
679
680        Ok(())
681    }
682
683    pub fn group_end(&mut self) {
684        self.inner.lock().unwrap().group = None;
685    }
686
687    pub fn peer(&self) -> PeerID {
688        self.peer.load(std::sync::atomic::Ordering::Relaxed)
689    }
690
691    pub fn set_merge_interval(&mut self, interval: i64) {
692        self.inner.lock().unwrap().merge_interval_in_ms = interval;
693    }
694
695    pub fn set_max_undo_steps(&mut self, size: usize) {
696        self.inner.lock().unwrap().max_stack_size = size;
697    }
698
699    pub fn add_exclude_origin_prefix(&mut self, prefix: &str) {
700        self.inner
701            .lock()
702            .unwrap()
703            .exclude_origin_prefixes
704            .push(prefix.into());
705    }
706
707    pub fn record_new_checkpoint(&mut self) -> LoroResult<()> {
708        // Use implicit-style barrier to preserve next-commit options across
709        // an empty commit before undo/redo processing.
710        self.doc.with_barrier(|| {});
711        let counter = get_counter_end(&self.doc, self.peer());
712        self.inner.lock().unwrap().record_checkpoint(counter, None);
713        Ok(())
714    }
715
716    #[instrument(skip_all)]
717    pub fn undo(&mut self) -> LoroResult<bool> {
718        self.perform(
719            |x| &mut x.undo_stack,
720            |x| &mut x.redo_stack,
721            UndoOrRedo::Undo,
722        )
723    }
724
725    #[instrument(skip_all)]
726    pub fn redo(&mut self) -> LoroResult<bool> {
727        self.perform(
728            |x| &mut x.redo_stack,
729            |x| &mut x.undo_stack,
730            UndoOrRedo::Redo,
731        )
732    }
733
734    fn perform(
735        &mut self,
736        get_stack: impl Fn(&mut UndoManagerInner) -> &mut Stack,
737        get_opposite: impl Fn(&mut UndoManagerInner) -> &mut Stack,
738        kind: UndoOrRedo,
739    ) -> LoroResult<bool> {
740        let doc = &self.doc.clone();
741        // When in the undo/redo loop, the new undo/redo stack item should restore the selection
742        // to the state it was in before the item that was popped two steps ago from the stack.
743        //
744        //                          ┌────────────┐
745        //                          │Selection 1 │
746        //                          └─────┬──────┘
747        //                                │   Some
748        //                                ▼   ops
749        //                          ┌────────────┐
750        //                          │Selection 2 │
751        //                          └─────┬──────┘
752        //                                │   Some
753        //                                ▼   ops
754        //                          ┌────────────┐
755        //                          │Selection 3 │◁ ─ ─ ─ ─ ─ ─ ─  Restore  ─ ─ ─
756        //                          └─────┬──────┘                               │
757        //                                │
758        //                                │                                      │
759        //                                │                              ┌ ─ ─ ─ ─ ─ ─ ─
760        //           Enter the            │   Undo ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶   Push Redo   │
761        //           undo/redo ─ ─ ─ ▶    ▼                              └ ─ ─ ─ ─ ─ ─ ─
762        //             loop         ┌────────────┐                               │
763        //                          │Selection 2 │◁─ ─ ─  Restore  ─
764        //                          └─────┬──────┘                  │            │
765        //                                │
766        //                                │                         │            │
767        //                                │                 ┌ ─ ─ ─ ─ ─ ─ ─
768        //                                │   Undo ─ ─ ─ ─ ▶   Push Redo   │     │
769        //                                ▼                 └ ─ ─ ─ ─ ─ ─ ─
770        //                          ┌────────────┐                  │            │
771        //                          │Selection 1 │
772        //                          └─────┬──────┘                  │            │
773        //                                │   Redo ◀ ─ ─ ─ ─ ─ ─ ─ ─
774        //                                ▼                                      │
775        //                          ┌────────────┐
776        //         ┌   Restore   ─ ▷│Selection 2 │                               │
777        //                          └─────┬──────┘
778        //         │                      │                                      │
779        // ┌ ─ ─ ─ ─ ─ ─ ─                │
780        //    Push Undo   │◀─ ─ ─ ─ ─ ─ ─ │   Redo ◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
781        // └ ─ ─ ─ ─ ─ ─ ─                ▼
782        //         │                ┌────────────┐
783        //                          │Selection 3 │
784        //         │                └─────┬──────┘
785        //          ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ▶ │   Undo
786        //                                ▼
787        //                          ┌────────────┐
788        //                          │Selection 2 │
789        //                          └────────────┘
790        //
791        // Because users may change the selections during the undo/redo loop, it's
792        // more stable to keep the selection stored in the last stack item
793        // rather than using the current selection directly.
794        self.record_new_checkpoint()?;
795        let end_counter = get_counter_end(doc, self.peer());
796        let mut top = {
797            let mut inner = self.inner.lock().unwrap();
798            inner.processing_undo = true;
799            get_stack(&mut inner).pop()
800        };
801
802        let mut executed = false;
803        while let Some((mut span, remote_diff)) = top {
804            let mut next_push_selection = None;
805            {
806                let inner = self.inner.clone();
807                // We need to clone this because otherwise <transform_delta> will be applied to the same remote diff
808                let remote_change_clone = remote_diff.lock().unwrap().clone();
809                let commit = doc.undo_internal(
810                    IdSpan {
811                        peer: self.peer(),
812                        counter: span.span,
813                    },
814                    &mut self.container_remap.lock().unwrap(),
815                    Some(&remote_change_clone),
816                    &mut |diff| {
817                        info_span!("transform remote diff").in_scope(|| {
818                            let mut inner = inner.lock().unwrap();
819                            // <transform_delta>
820                            get_stack(&mut inner).transform_based_on_this_delta(diff);
821                        });
822                    },
823                )?;
824                drop(commit);
825                let mut inner = self.inner.lock().unwrap();
826                if let Some(x) = inner.on_pop.as_ref() {
827                    for cursor in span.meta.cursors.iter_mut() {
828                        // <cursor_transform> We need to transform cursor here.
829                        // Note that right now <transform_delta> is already done,
830                        // remote_diff is also transformed by it now (that's what we need).
831                        transform_cursor(
832                            cursor,
833                            &remote_diff.lock().unwrap(),
834                            doc,
835                            &self.container_remap.lock().unwrap(),
836                        );
837                    }
838
839                    x(kind, span.span, span.meta.clone());
840                    let take = inner.last_popped_selection.take();
841                    next_push_selection = take;
842                    inner.last_popped_selection = Some(span.meta.cursors);
843                }
844            }
845            let new_counter = get_counter_end(doc, self.peer());
846            if end_counter != new_counter {
847                let mut inner = self.inner.lock().unwrap();
848                let mut meta = inner
849                    .on_push
850                    .as_ref()
851                    .map(|x| {
852                        x(
853                            kind.opposite(),
854                            CounterSpan::new(end_counter, new_counter),
855                            None,
856                        )
857                    })
858                    .unwrap_or_default();
859
860                if matches!(kind, UndoOrRedo::Undo) && get_opposite(&mut inner).is_empty() {
861                    // If it's the first undo, we use the cursors from the users
862                } else if let Some(inner) = next_push_selection.take() {
863                    // Otherwise, we use the cursors from the undo/redo loop
864                    meta.cursors = inner;
865                }
866
867                get_opposite(&mut inner).push(CounterSpan::new(end_counter, new_counter), meta);
868                inner.next_counter = Some(new_counter);
869                executed = true;
870                break;
871            } else {
872                // continue to pop the undo item as this undo is a no-op
873                top = get_stack(&mut self.inner.lock().unwrap()).pop();
874                continue;
875            }
876        }
877
878        self.inner.lock().unwrap().processing_undo = false;
879        Ok(executed)
880    }
881
882    pub fn can_undo(&self) -> bool {
883        !self.inner.lock().unwrap().undo_stack.is_empty()
884    }
885
886    pub fn can_redo(&self) -> bool {
887        !self.inner.lock().unwrap().redo_stack.is_empty()
888    }
889
890    pub fn undo_count(&self) -> usize {
891        self.inner.lock().unwrap().undo_stack.len()
892    }
893
894    pub fn redo_count(&self) -> usize {
895        self.inner.lock().unwrap().redo_stack.len()
896    }
897
898    /// Get the metadata of the top undo stack item, if any.
899    pub fn top_undo_meta(&self) -> Option<UndoItemMeta> {
900        self.inner.lock().unwrap().undo_stack.peek_top_meta()
901    }
902
903    /// Get the metadata of the top redo stack item, if any.
904    pub fn top_redo_meta(&self) -> Option<UndoItemMeta> {
905        self.inner.lock().unwrap().redo_stack.peek_top_meta()
906    }
907
908    /// Get the value associated with the top undo stack item, if any.
909    pub fn top_undo_value(&self) -> Option<LoroValue> {
910        self.top_undo_meta().map(|m| m.value)
911    }
912
913    /// Get the value associated with the top redo stack item, if any.
914    pub fn top_redo_value(&self) -> Option<LoroValue> {
915        self.top_redo_meta().map(|m| m.value)
916    }
917
918    pub fn set_on_push(&self, on_push: Option<OnPush>) {
919        self.inner.lock().unwrap().on_push = on_push;
920    }
921
922    pub fn set_on_pop(&self, on_pop: Option<OnPop>) {
923        self.inner.lock().unwrap().on_pop = on_pop;
924    }
925
926    pub fn clear(&self) {
927        self.inner.lock().unwrap().undo_stack.clear();
928        self.inner.lock().unwrap().redo_stack.clear();
929    }
930}
931
932/// Undo the given spans of operations.
933///
934/// # Parameters
935///
936/// - `spans`: A vector of tuples where each tuple contains an `IdSpan` and its associated `Frontiers`.
937///   - `IdSpan`: Represents a span of operations identified by an ID.
938///   - `Frontiers`: Represents the deps of the given id_span
939/// - `latest_frontiers`: The latest frontiers of the document
940/// - `calc_diff`: A closure that takes two `Frontiers` and calculates the difference between them, returning a `DiffBatch`.
941///
942/// # Returns
943///
944/// - `DiffBatch`: Applying this batch on the `latest_frontiers` will undo the ops in the given spans.
945pub(crate) fn undo(
946    spans: Vec<(IdSpan, Frontiers)>,
947    last_frontiers_or_last_bi: Either<&Frontiers, &DiffBatch>,
948    calc_diff: impl Fn(&Frontiers, &Frontiers) -> DiffBatch,
949    on_last_event_a: &mut dyn FnMut(&DiffBatch),
950) -> DiffBatch {
951    // The process of performing undo is:
952    //
953    // 0. Split the span into a series of continuous spans. There is no external dep within each continuous span.
954    //
955    // For each continuous span_i:
956    //
957    // 1. a. Calculate the event of checkout from id_span.last to id_span.deps, call it Ai. It undo the ops in the current span.
958    //    b. Calculate A'i = Ai + T(Ci-1, Ai) if i > 0, otherwise A'i = Ai.
959    //       NOTE: A'i can undo the ops in the current span and the previous spans, if it's applied on the id_span.last version.
960    // 2. Calculate the event of checkout from id_span.last to [the next span's last id] or [the latest version], call it Bi.
961    // 3. Transform event A'i based on Bi, call it Ci
962    // 4. If span_i is the last span, apply Ci to the current state.
963
964    // -------------------------------------------------------
965    // 0. Split the span into a series of continuous spans
966    // -------------------------------------------------------
967
968    let mut last_ci: Option<DiffBatch> = None;
969    for i in 0..spans.len() {
970        debug_span!("Undo", ?i, "Undo span {:?}", &spans[i]).in_scope(|| {
971            let (this_id_span, this_deps) = &spans[i];
972            // ---------------------------------------
973            // 1.a Calc event A_i
974            // ---------------------------------------
975            let mut event_a_i = debug_span!("1. Calc event A_i").in_scope(|| {
976                // Checkout to the last id of the id_span
977                calc_diff(&this_id_span.id_last().into(), this_deps)
978            });
979
980            // println!("event_a_i: {:?}", event_a_i);
981
982            // ---------------------------------------
983            // 2. Calc event B_i
984            // ---------------------------------------
985            let stack_diff_batch;
986            let event_b_i = 'block: {
987                let next = if i + 1 < spans.len() {
988                    spans[i + 1].0.id_last().into()
989                } else {
990                    match last_frontiers_or_last_bi {
991                        Either::Left(last_frontiers) => last_frontiers.clone(),
992                        Either::Right(right) => break 'block right,
993                    }
994                };
995                stack_diff_batch = Some(calc_diff(&this_id_span.id_last().into(), &next));
996                stack_diff_batch.as_ref().unwrap()
997            };
998
999            // println!("event_b_i: {:?}", event_b_i);
1000
1001            // event_a_prime can undo the ops in the current span and the previous spans
1002            let mut event_a_prime = if let Some(mut last_ci) = last_ci.take() {
1003                // ------------------------------------------------------------------------------
1004                // 1.b Transform and apply Ci-1 based on Ai, call it A'i
1005                // ------------------------------------------------------------------------------
1006                last_ci.transform(&event_a_i, true);
1007
1008                event_a_i.compose(&last_ci);
1009                event_a_i
1010            } else {
1011                event_a_i
1012            };
1013            if i == spans.len() - 1 {
1014                on_last_event_a(&event_a_prime);
1015            }
1016            // --------------------------------------------------
1017            // 3. Transform event A'_i based on B_i, call it C_i
1018            // --------------------------------------------------
1019            event_a_prime.transform(event_b_i, true);
1020
1021            // println!("event_a_prime: {:?}", event_a_prime);
1022
1023            let c_i = event_a_prime;
1024            last_ci = Some(c_i);
1025        });
1026    }
1027
1028    last_ci.unwrap()
1029}