From accfb929dd5b82fc2ad90d6dea42c62bc942e37c Mon Sep 17 00:00:00 2001 From: andreim27 Date: Fri, 6 Mar 2026 14:00:28 -0500 Subject: [PATCH] feat(undo): add clearRedo and clearUndo methods Add methods to selectively clear only the redo or undo stack, preserving the other stack. This is useful when coordinating undo/redo across multiple participants (e.g., multiple editors) where a new edit in one participant should invalidate redo in all other participants without affecting their undo history. --- crates/loro-internal/src/undo.rs | 10 ++++++ crates/loro-internal/tests/undo.rs | 56 ++++++++++++++++++++++++++++++ crates/loro-wasm/src/lib.rs | 28 +++++++++++++++ 3 files changed, 94 insertions(+) diff --git a/crates/loro-internal/src/undo.rs b/crates/loro-internal/src/undo.rs index 52a1aaf31..88867dfc9 100644 --- a/crates/loro-internal/src/undo.rs +++ b/crates/loro-internal/src/undo.rs @@ -955,6 +955,16 @@ impl UndoManager { self.inner.lock().borrow_mut().redo_stack.clear(); } + /// Clear only the redo stack, preserving the undo stack. + pub fn clear_redo(&self) { + self.inner.lock().borrow_mut().redo_stack.clear(); + } + + /// Clear only the undo stack, preserving the redo stack. + pub fn clear_undo(&self) { + self.inner.lock().borrow_mut().undo_stack.clear(); + } + pub fn set_top_undo_meta(&self, meta: UndoItemMeta) { self.inner.lock().borrow_mut().undo_stack.set_top_meta(meta); } diff --git a/crates/loro-internal/tests/undo.rs b/crates/loro-internal/tests/undo.rs index 0641e5993..23e2103b9 100644 --- a/crates/loro-internal/tests/undo.rs +++ b/crates/loro-internal/tests/undo.rs @@ -165,3 +165,59 @@ fn test_undo_group_start_with_remote_ops() { undo_manager.undo().unwrap(); assert_eq!(doc.get_text("text").to_string(), "test"); } + +#[test] +fn test_clear_redo() { + let doc = LoroDoc::new(); + let undo_manager = UndoManager::new(&doc); + let text = doc.get_text("text"); + + // Make some edits + text.update("hello", UpdateOptions::default()).unwrap(); + doc.commit_then_renew(); + text.update("hello world", UpdateOptions::default()).unwrap(); + doc.commit_then_renew(); + + // Undo to create redo stack + undo_manager.undo().unwrap(); + assert_eq!(text.to_string(), "hello"); + assert!(undo_manager.can_redo(), "should be able to redo"); + assert!(undo_manager.can_undo(), "should be able to undo"); + + // Clear only redo stack + undo_manager.clear_redo(); + assert!(!undo_manager.can_redo(), "redo stack should be empty"); + assert!(undo_manager.can_undo(), "undo stack should still have items"); + + // Verify undo still works + undo_manager.undo().unwrap(); + assert_eq!(text.to_string(), ""); +} + +#[test] +fn test_clear_undo() { + let doc = LoroDoc::new(); + let undo_manager = UndoManager::new(&doc); + let text = doc.get_text("text"); + + // Make some edits + text.update("hello", UpdateOptions::default()).unwrap(); + doc.commit_then_renew(); + text.update("hello world", UpdateOptions::default()).unwrap(); + doc.commit_then_renew(); + + // Undo to create redo stack + undo_manager.undo().unwrap(); + assert_eq!(text.to_string(), "hello"); + assert!(undo_manager.can_redo(), "should be able to redo"); + assert!(undo_manager.can_undo(), "should be able to undo"); + + // Clear only undo stack + undo_manager.clear_undo(); + assert!(undo_manager.can_redo(), "redo stack should still have items"); + assert!(!undo_manager.can_undo(), "undo stack should be empty"); + + // Verify redo still works + undo_manager.redo().unwrap(); + assert_eq!(text.to_string(), "hello world"); +} diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index c0421cc93..f0c107c33 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -5332,6 +5332,20 @@ impl UndoManager { pub fn clear(&self) { self.undo.lock().clear(); } + + /// Clear only the redo stack, preserving the undo stack. + /// + /// This is useful when coordinating undo/redo across multiple participants + /// (e.g., multiple editors) where a new edit in one participant should + /// invalidate redo in all other participants. + pub fn clearRedo(&self) { + self.undo.lock().clear_redo(); + } + + /// Clear only the undo stack, preserving the redo stack. + pub fn clearUndo(&self) { + self.undo.lock().clear_undo(); + } } /// Use this function to throw an error after the micro task. @@ -6558,6 +6572,20 @@ interface UndoManager { * Ends the current grouping of undo operations. */ groupEnd(): void; + + /** + * Clear only the redo stack, preserving the undo stack. + * + * This is useful when coordinating undo/redo across multiple participants + * (e.g., multiple editors) where a new edit in one participant should + * invalidate redo in all other participants. + */ + clearRedo(): void; + + /** + * Clear only the undo stack, preserving the redo stack. + */ + clearUndo(): void; } interface LoroDoc = Record> { /**