Skip to main content

loro_internal/
undo.rs

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