File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 1+ ---
2+ " loro-crdt " : patch
3+ ---
4+
5+ Fix panic in ` UndoManager ` when ` maxUndoSteps ` trimming encounters an empty front stack row left by a prior undo with remote diffs.
Original file line number Diff line number Diff line change @@ -442,18 +442,40 @@ impl Stack {
442442 self . size
443443 }
444444
445+ fn discard_empty_front_rows ( & mut self ) {
446+ // Undo pop can leave an empty front row that only carries remote diffs.
447+ // There is no older stack item for that diff to transform during trimming.
448+ while self
449+ . stack
450+ . front ( )
451+ . is_some_and ( |( items, _) | items. is_empty ( ) )
452+ {
453+ self . stack . pop_front ( ) ;
454+ }
455+ }
456+
457+ fn ensure_trailing_empty_row ( & mut self ) {
458+ if self . stack . is_empty ( ) {
459+ self . stack
460+ . push_back ( ( VecDeque :: new ( ) , Arc :: new ( Mutex :: new ( Default :: default ( ) ) ) ) ) ;
461+ }
462+ }
463+
445464 fn pop_front ( & mut self ) {
446465 if self . is_empty ( ) {
447466 return ;
448467 }
449468
469+ self . discard_empty_front_rows ( ) ;
450470 self . size -= 1 ;
451471 let first = self . stack . front_mut ( ) . unwrap ( ) ;
452472 let f = first. 0 . pop_front ( ) ;
453473 assert ! ( f. is_some( ) ) ;
454474 if first. 0 . is_empty ( ) {
455475 self . stack . pop_front ( ) ;
456476 }
477+
478+ self . ensure_trailing_empty_row ( ) ;
457479 }
458480
459481 fn set_top_meta ( & mut self , meta : UndoItemMeta ) {
Original file line number Diff line number Diff line change @@ -80,6 +80,35 @@ describe("undo", () => {
8080 expect ( doc . getText ( "text" ) . length ) . toBe ( 100 ) ;
8181 } ) ;
8282
83+ test ( "max undo steps after remote update and undo" , ( ) => {
84+ const doc = new LoroDoc ( ) ;
85+ doc . setPeerId ( 1 ) ;
86+ const text = doc . getText ( "text" ) ;
87+ const undo = new UndoManager ( doc , { maxUndoSteps : 3 , mergeInterval : 0 } ) ;
88+
89+ text . insert ( 0 , "A" ) ;
90+ doc . commit ( ) ;
91+
92+ const remote = new LoroDoc ( ) ;
93+ remote . setPeerId ( 2 ) ;
94+ remote . import ( doc . export ( { mode : "snapshot" } ) ) ;
95+ remote . getText ( "text" ) . insert ( 0 , "R" ) ;
96+ remote . commit ( ) ;
97+
98+ doc . import ( remote . export ( { mode : "update" } ) ) ;
99+ expect ( undo . undo ( ) ) . toBeTruthy ( ) ;
100+ expect ( text . toString ( ) ) . toBe ( "R" ) ;
101+
102+ for ( let i = 0 ; i < 4 ; i ++ ) {
103+ text . insert ( text . length , i . toString ( ) ) ;
104+ doc . commit ( ) ;
105+ }
106+
107+ expect ( doc . toJSON ( ) ) . toStrictEqual ( {
108+ text : "R0123" ,
109+ } ) ;
110+ } ) ;
111+
83112 test ( "Skip chosen events" , ( ) => {
84113 const doc = new LoroDoc ( ) ;
85114 const undo = new UndoManager ( doc , {
Original file line number Diff line number Diff line change @@ -256,6 +256,37 @@ fn test_undo_counter_after_remote_update_issue_905() {
256256 assert_eq ! ( counter_a. get_value( ) , 2.0 ) ;
257257}
258258
259+ #[ test]
260+ fn undo_max_steps_trim_after_remote_undo_should_not_panic ( ) -> Result < ( ) , LoroError > {
261+ let doc_a = LoroDoc :: new ( ) ;
262+ doc_a. set_peer_id ( 1 ) ?;
263+ let text_a = doc_a. get_text ( "text" ) ;
264+ let mut undo_manager = UndoManager :: new ( & doc_a) ;
265+ undo_manager. set_merge_interval ( 0 ) ;
266+ undo_manager. set_max_undo_steps ( 3 ) ;
267+
268+ text_a. insert ( 0 , "A" ) ?;
269+ doc_a. commit ( ) ;
270+
271+ let doc_b = LoroDoc :: from_snapshot ( & doc_a. export ( ExportMode :: Snapshot ) ?) ?;
272+ doc_b. set_peer_id ( 2 ) ?;
273+ let text_b = doc_b. get_text ( "text" ) ;
274+ text_b. insert ( 0 , "R" ) ?;
275+ doc_b. commit ( ) ;
276+
277+ doc_a. import ( & doc_b. export ( ExportMode :: all_updates ( ) ) ?) ?;
278+ assert ! ( undo_manager. undo( ) ?) ;
279+ assert_eq ! ( text_a. to_string( ) , "R" ) ;
280+
281+ for i in 0 ..4 {
282+ text_a. insert ( text_a. len_unicode ( ) , & i. to_string ( ) ) ?;
283+ doc_a. commit ( ) ;
284+ }
285+
286+ assert_eq ! ( undo_manager. undo_count( ) , 3 ) ;
287+ Ok ( ( ) )
288+ }
289+
259290#[ test]
260291fn import_twice ( ) {
261292 let doc = LoroDoc :: new ( ) ;
You can’t perform that action at this time.
0 commit comments