diff --git a/base/src/test/user_model/test_batch_row_column_diff.rs b/base/src/test/user_model/test_batch_row_column_diff.rs index 829e6d8..b8adbca 100644 --- a/base/src/test/user_model/test_batch_row_column_diff.rs +++ b/base/src/test/user_model/test_batch_row_column_diff.rs @@ -25,15 +25,14 @@ fn diff_invariant_insert_rows() { assert!(model.insert_rows(0, 5, 3).is_ok()); let list = last_diff_list(&mut model); - assert_eq!(list.len(), 3); - for diff in list { - match diff { - Diff::InsertRow { sheet, row } => { - assert_eq!(sheet, 0); - assert_eq!(row, 5); - } - _ => panic!("Unexpected diff variant"), + assert_eq!(list.len(), 1); + match &list[0] { + Diff::InsertRows { sheet, row, count } => { + assert_eq!(*sheet, 0); + assert_eq!(*row, 5); + assert_eq!(*count, 3); } + _ => panic!("Unexpected diff variant"), } } @@ -44,15 +43,18 @@ fn diff_invariant_insert_columns() { assert!(model.insert_columns(0, 2, 4).is_ok()); let list = last_diff_list(&mut model); - assert_eq!(list.len(), 4); - for diff in list { - match diff { - Diff::InsertColumn { sheet, column } => { - assert_eq!(sheet, 0); - assert_eq!(column, 2); - } - _ => panic!("Unexpected diff variant"), + assert_eq!(list.len(), 1); + match &list[0] { + Diff::InsertColumns { + sheet, + column, + count, + } => { + assert_eq!(*sheet, 0); + assert_eq!(*column, 2); + assert_eq!(*count, 4); } + _ => panic!("Unexpected diff variant"), } } @@ -93,7 +95,7 @@ fn undo_redo_after_batch_delete() { #[test] fn diff_order_delete_rows() { - // Verifies that delete diffs are generated bottom-to-top + // Verifies that delete diffs are generated with all data preserved let base = new_empty_model(); let mut model = UserModel::from_model(base); @@ -105,18 +107,27 @@ fn diff_order_delete_rows() { assert!(model.delete_rows(0, 5, 5).is_ok()); let list = last_diff_list(&mut model); - assert_eq!(list.len(), 5); + assert_eq!(list.len(), 1); - // Diffs should be in reverse order: 9, 8, 7, 6, 5 - let mut expected_row = 9; - for diff in list { - match diff { - Diff::DeleteRow { row, .. } => { - assert_eq!(row, expected_row); - expected_row -= 1; + // Should have one bulk diff with all the row data + match &list[0] { + Diff::DeleteRows { + sheet, + row, + count, + old_data, + } => { + assert_eq!(*sheet, 0); + assert_eq!(*row, 5); + assert_eq!(*count, 5); + assert_eq!(old_data.len(), 5); + // Verify the data was collected for each row + for (i, row_data) in old_data.iter().enumerate() { + let _expected_value = (5 + i).to_string(); + assert!(row_data.data.contains_key(&1)); } - _ => panic!("Unexpected diff variant"), } + _ => panic!("Unexpected diff variant"), } } @@ -157,6 +168,7 @@ fn edge_case_single_operation() { #[test] fn delete_empty_rows() { + // Delete multiple empty rows and verify behavior let base = new_empty_model(); let mut model = UserModel::from_model(base); @@ -171,19 +183,26 @@ fn delete_empty_rows() { assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "Before"); assert_eq!(model.get_formatted_cell_value(0, 6, 1).unwrap(), "After"); - // Verify diffs are in reverse order with empty data + // Verify diffs now use bulk operation let list = last_diff_list(&mut model); - assert_eq!(list.len(), 4); - let mut expected_row = 8; - for diff in &list { - match diff { - Diff::DeleteRow { row, old_data, .. } => { - assert_eq!(*row, expected_row); - assert!(old_data.data.is_empty()); - expected_row -= 1; + assert_eq!(list.len(), 1); + match &list[0] { + Diff::DeleteRows { + sheet, + row, + count, + old_data, + } => { + assert_eq!(*sheet, 0); + assert_eq!(*row, 5); + assert_eq!(*count, 4); + assert_eq!(old_data.len(), 4); + // All rows should be empty + for row_data in old_data { + assert!(row_data.data.is_empty()); } - _ => panic!("Unexpected diff variant"), } + _ => panic!("Unexpected diff variant"), } // Undo/redo @@ -209,15 +228,28 @@ fn delete_mixed_empty_and_filled_rows() { // Verify mix of empty and filled row diffs let list = last_diff_list(&mut model); - assert_eq!(list.len(), 5); - let filled_count = list - .iter() - .filter(|diff| match diff { - Diff::DeleteRow { old_data, .. } => !old_data.data.is_empty(), - _ => false, - }) - .count(); - assert_eq!(filled_count, 3); + assert_eq!(list.len(), 1); + match &list[0] { + Diff::DeleteRows { + sheet, + row, + count, + old_data, + } => { + assert_eq!(*sheet, 0); + assert_eq!(*row, 5); + assert_eq!(*count, 5); + assert_eq!(old_data.len(), 5); + + // Count filled rows (should be 3: rows 5, 7, 9) + let filled_count = old_data + .iter() + .filter(|row_data| !row_data.data.is_empty()) + .count(); + assert_eq!(filled_count, 3); + } + _ => panic!("Unexpected diff variant"), + } // Undo model.undo().unwrap(); @@ -227,6 +259,409 @@ fn delete_mixed_empty_and_filled_rows() { assert_eq!(model.get_formatted_cell_value(0, 10, 1).unwrap(), "After"); } +#[test] +fn bulk_insert_rows_undo_redo() { + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Set up initial data + model.set_user_input(0, 1, 1, "A1").unwrap(); + model.set_user_input(0, 2, 1, "A2").unwrap(); + model.set_user_input(0, 5, 1, "A5").unwrap(); + + // Insert 3 rows at position 3 + assert!(model.insert_rows(0, 3, 3).is_ok()); + + // Verify data has shifted + assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "A1"); + assert_eq!(model.get_formatted_cell_value(0, 2, 1).unwrap(), "A2"); + assert_eq!(model.get_formatted_cell_value(0, 8, 1).unwrap(), "A5"); // A5 moved to A8 + + // Check diff structure + let list = last_diff_list(&mut model); + assert_eq!(list.len(), 1); + match &list[0] { + Diff::InsertRows { sheet, row, count } => { + assert_eq!(*sheet, 0); + assert_eq!(*row, 3); + assert_eq!(*count, 3); + } + _ => panic!("Expected InsertRows diff"), + } + + // Undo + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 5, 1).unwrap(), "A5"); // Back to original position + + // Redo + model.redo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 8, 1).unwrap(), "A5"); // Shifted again +} + +#[test] +fn bulk_insert_columns_undo_redo() { + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Set up initial data + model.set_user_input(0, 1, 1, "A1").unwrap(); + model.set_user_input(0, 1, 2, "B1").unwrap(); + model.set_user_input(0, 1, 5, "E1").unwrap(); + + // Insert 3 columns at position 3 + assert!(model.insert_columns(0, 3, 3).is_ok()); + + // Verify data has shifted + assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "A1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 2).unwrap(), "B1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 8).unwrap(), "E1"); // E1 moved to H1 + + // Check diff structure + let list = last_diff_list(&mut model); + assert_eq!(list.len(), 1); + match &list[0] { + Diff::InsertColumns { + sheet, + column, + count, + } => { + assert_eq!(*sheet, 0); + assert_eq!(*column, 3); + assert_eq!(*count, 3); + } + _ => panic!("Expected InsertColumns diff"), + } + + // Undo + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 1, 5).unwrap(), "E1"); // Back to original position + + // Redo + model.redo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 1, 8).unwrap(), "E1"); // Shifted again +} + +#[test] +fn bulk_delete_rows_round_trip() { + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Set up data with styles + model.set_user_input(0, 3, 1, "Row3").unwrap(); + model.set_user_input(0, 4, 1, "Row4").unwrap(); + model.set_user_input(0, 5, 1, "Row5").unwrap(); + model.set_user_input(0, 6, 1, "Row6").unwrap(); + model.set_user_input(0, 7, 1, "After").unwrap(); + + // Set some row heights to verify they're preserved + model.set_rows_height(0, 4, 4, 30.0).unwrap(); + model.set_rows_height(0, 5, 5, 40.0).unwrap(); + + // Delete rows 3-6 + assert!(model.delete_rows(0, 3, 4).is_ok()); + + // Verify deletion + assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "After"); + + // Check diff structure + let list = last_diff_list(&mut model); + assert_eq!(list.len(), 1); + match &list[0] { + Diff::DeleteRows { + sheet, + row, + count, + old_data, + } => { + assert_eq!(*sheet, 0); + assert_eq!(*row, 3); + assert_eq!(*count, 4); + assert_eq!(old_data.len(), 4); + // Verify data was preserved + assert!(old_data[0].data.contains_key(&1)); // Row3 + assert!(old_data[1].data.contains_key(&1)); // Row4 + assert!(old_data[2].data.contains_key(&1)); // Row5 + assert!(old_data[3].data.contains_key(&1)); // Row6 + } + _ => panic!("Expected DeleteRows diff"), + } + + // Undo - should restore all data and row heights + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "Row3"); + assert_eq!(model.get_formatted_cell_value(0, 4, 1).unwrap(), "Row4"); + assert_eq!(model.get_formatted_cell_value(0, 5, 1).unwrap(), "Row5"); + assert_eq!(model.get_formatted_cell_value(0, 6, 1).unwrap(), "Row6"); + assert_eq!(model.get_formatted_cell_value(0, 7, 1).unwrap(), "After"); + assert_eq!(model.get_row_height(0, 4).unwrap(), 30.0); + assert_eq!(model.get_row_height(0, 5).unwrap(), 40.0); + + // Redo - should delete again + model.redo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "After"); + + // Final undo to verify round-trip + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 3, 1).unwrap(), "Row3"); + assert_eq!(model.get_formatted_cell_value(0, 4, 1).unwrap(), "Row4"); + assert_eq!(model.get_formatted_cell_value(0, 5, 1).unwrap(), "Row5"); + assert_eq!(model.get_formatted_cell_value(0, 6, 1).unwrap(), "Row6"); +} + +#[test] +fn bulk_delete_columns_round_trip() { + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Set up data with styles + model.set_user_input(0, 1, 3, "C1").unwrap(); + model.set_user_input(0, 1, 4, "D1").unwrap(); + model.set_user_input(0, 1, 5, "E1").unwrap(); + model.set_user_input(0, 1, 6, "F1").unwrap(); + model.set_user_input(0, 1, 7, "After").unwrap(); + + // Set some column widths to verify they're preserved + model.set_columns_width(0, 4, 4, 100.0).unwrap(); + model.set_columns_width(0, 5, 5, 120.0).unwrap(); + + // Delete columns 3-6 + assert!(model.delete_columns(0, 3, 4).is_ok()); + + // Verify deletion + assert_eq!(model.get_formatted_cell_value(0, 1, 3).unwrap(), "After"); + + // Check diff structure + let list = last_diff_list(&mut model); + assert_eq!(list.len(), 1); + match &list[0] { + Diff::DeleteColumns { + sheet, + column, + count, + old_data, + } => { + assert_eq!(*sheet, 0); + assert_eq!(*column, 3); + assert_eq!(*count, 4); + assert_eq!(old_data.len(), 4); + // Verify data was preserved + assert!(old_data[0].data.contains_key(&1)); // C1 + assert!(old_data[1].data.contains_key(&1)); // D1 + assert!(old_data[2].data.contains_key(&1)); // E1 + assert!(old_data[3].data.contains_key(&1)); // F1 + } + _ => panic!("Expected DeleteColumns diff"), + } + + // Undo - should restore all data and column widths + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 1, 3).unwrap(), "C1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 4).unwrap(), "D1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 5).unwrap(), "E1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 6).unwrap(), "F1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 7).unwrap(), "After"); + assert_eq!(model.get_column_width(0, 4).unwrap(), 100.0); + assert_eq!(model.get_column_width(0, 5).unwrap(), 120.0); + + // Redo - should delete again + model.redo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 1, 3).unwrap(), "After"); + + // Final undo to verify round-trip + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 1, 3).unwrap(), "C1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 4).unwrap(), "D1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 5).unwrap(), "E1"); + assert_eq!(model.get_formatted_cell_value(0, 1, 6).unwrap(), "F1"); +} + +#[test] +fn complex_bulk_operations_sequence() { + // Test a complex sequence of bulk operations + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Initial setup + model.set_user_input(0, 1, 1, "A1").unwrap(); + model.set_user_input(0, 2, 2, "B2").unwrap(); + model.set_user_input(0, 3, 3, "C3").unwrap(); + + // Operation 1: Insert 2 rows at position 2 + model.insert_rows(0, 2, 2).unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "A1"); + assert_eq!(model.get_formatted_cell_value(0, 4, 2).unwrap(), "B2"); // B2 moved down + assert_eq!(model.get_formatted_cell_value(0, 5, 3).unwrap(), "C3"); // C3 moved down + + // Operation 2: Insert 2 columns at position 2 + model.insert_columns(0, 2, 2).unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "A1"); + assert_eq!(model.get_formatted_cell_value(0, 4, 4).unwrap(), "B2"); // B2 moved right + assert_eq!(model.get_formatted_cell_value(0, 5, 5).unwrap(), "C3"); // C3 moved right + + // Operation 3: Delete the inserted rows + model.delete_rows(0, 2, 2).unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 2, 4).unwrap(), "B2"); + assert_eq!(model.get_formatted_cell_value(0, 3, 5).unwrap(), "C3"); + + // Undo all operations + model.undo().unwrap(); // Undo delete rows + assert_eq!(model.get_formatted_cell_value(0, 4, 4).unwrap(), "B2"); + assert_eq!(model.get_formatted_cell_value(0, 5, 5).unwrap(), "C3"); + + model.undo().unwrap(); // Undo insert columns + assert_eq!(model.get_formatted_cell_value(0, 4, 2).unwrap(), "B2"); + assert_eq!(model.get_formatted_cell_value(0, 5, 3).unwrap(), "C3"); + + model.undo().unwrap(); // Undo insert rows + assert_eq!(model.get_formatted_cell_value(0, 2, 2).unwrap(), "B2"); + assert_eq!(model.get_formatted_cell_value(0, 3, 3).unwrap(), "C3"); + + // Redo all operations + model.redo().unwrap(); // Redo insert rows + model.redo().unwrap(); // Redo insert columns + model.redo().unwrap(); // Redo delete rows + assert_eq!(model.get_formatted_cell_value(0, 2, 4).unwrap(), "B2"); + assert_eq!(model.get_formatted_cell_value(0, 3, 5).unwrap(), "C3"); +} + +#[test] +fn bulk_operations_with_formulas_update() { + // Test that formulas update correctly with bulk operations + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Set up data and formulas + model.set_user_input(0, 1, 1, "10").unwrap(); + model.set_user_input(0, 5, 1, "20").unwrap(); + model.set_user_input(0, 10, 1, "=A1+A5").unwrap(); // Formula referencing A1 and A5 + + // Insert 3 rows at position 3 + model.insert_rows(0, 3, 3).unwrap(); + + // Formula should update to reference the shifted cells + assert_eq!(model.get_formatted_cell_value(0, 13, 1).unwrap(), "30"); // Formula moved down + assert_eq!(model.get_cell_content(0, 13, 1).unwrap(), "=A1+A8"); // A5 became A8 + + // Undo + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 10, 1).unwrap(), "30"); + assert_eq!(model.get_cell_content(0, 10, 1).unwrap(), "=A1+A5"); + + // Now test column insertion + model.set_user_input(0, 1, 5, "20").unwrap(); // Add value in E1 + model.set_user_input(0, 1, 10, "=A1+E1").unwrap(); + model.insert_columns(0, 3, 2).unwrap(); + + assert_eq!(model.get_formatted_cell_value(0, 1, 12).unwrap(), "30"); // Formula moved right + assert_eq!(model.get_cell_content(0, 1, 12).unwrap(), "=A1+G1"); // E1 became G1 +} + +#[test] +fn bulk_delete_with_styles() { + // Test that cell and row/column styles are preserved + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Set up data with various styles + for r in 5..10 { + model.set_user_input(0, r, 1, &format!("Row{}", r)).unwrap(); + model.set_rows_height(0, r, r, (r * 10) as f64).unwrap(); + } + + // Delete and verify style preservation + model.delete_rows(0, 5, 5).unwrap(); + + // Undo should restore all styles + model.undo().unwrap(); + for r in 5..10 { + assert_eq!( + model.get_formatted_cell_value(0, r, 1).unwrap(), + format!("Row{}", r) + ); + assert_eq!(model.get_row_height(0, r).unwrap(), (r * 10) as f64); + } +} + +#[test] +fn bulk_operations_large_count() { + // Test operations with large counts + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Insert a large number of rows + model.set_user_input(0, 1, 1, "Before").unwrap(); + model.set_user_input(0, 100, 1, "After").unwrap(); + + assert!(model.insert_rows(0, 50, 100).is_ok()); + + // Verify shift + assert_eq!(model.get_formatted_cell_value(0, 1, 1).unwrap(), "Before"); + assert_eq!(model.get_formatted_cell_value(0, 200, 1).unwrap(), "After"); // Moved by 100 + + // Check diff + let list = last_diff_list(&mut model); + assert_eq!(list.len(), 1); + match &list[0] { + Diff::InsertRows { count, .. } => { + assert_eq!(*count, 100); + } + _ => panic!("Expected InsertRows diff"), + } + + // Undo and redo + model.undo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 100, 1).unwrap(), "After"); + + model.redo().unwrap(); + assert_eq!(model.get_formatted_cell_value(0, 200, 1).unwrap(), "After"); +} + +#[test] +fn bulk_operations_error_cases() { + // Test error conditions + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Negative count should fail + assert!(model.insert_rows(0, 1, -5).is_err()); + assert!(model.insert_columns(0, 1, -5).is_err()); + assert!(model.delete_rows(0, 1, -5).is_err()); + assert!(model.delete_columns(0, 1, -5).is_err()); + + // Zero count should fail + assert!(model.insert_rows(0, 1, 0).is_err()); + assert!(model.insert_columns(0, 1, 0).is_err()); + assert!(model.delete_rows(0, 1, 0).is_err()); + assert!(model.delete_columns(0, 1, 0).is_err()); + + // Out of bounds operations should fail + assert!(model.delete_rows(0, LAST_ROW - 5, 10).is_err()); + assert!(model.delete_columns(0, LAST_COLUMN - 5, 10).is_err()); +} + +#[test] +fn bulk_diff_serialization() { + // Test that bulk diffs can be serialized/deserialized correctly + let base = new_empty_model(); + let mut model = UserModel::from_model(base); + + // Create some data + model.set_user_input(0, 1, 1, "Test").unwrap(); + model.insert_rows(0, 2, 3).unwrap(); + + // Flush and get the serialized diffs + let bytes = model.flush_send_queue(); + + // Create a new model and apply the diffs + let base2 = new_empty_model(); + let mut model2 = UserModel::from_model(base2); + + assert!(model2.apply_external_diffs(&bytes).is_ok()); + + // Verify the state matches + assert_eq!(model2.get_formatted_cell_value(0, 1, 1).unwrap(), "Test"); +} + #[test] fn boundary_validation() { let base = new_empty_model(); diff --git a/base/src/user_model/common.rs b/base/src/user_model/common.rs index c975649..c99589c 100644 --- a/base/src/user_model/common.rs +++ b/base/src/user_model/common.rs @@ -874,10 +874,11 @@ impl UserModel { pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> { self.model.insert_rows(sheet, row, row_count)?; - let mut diff_list = Vec::new(); - for _ in 0..row_count { - diff_list.push(Diff::InsertRow { sheet, row }); - } + let diff_list = vec![Diff::InsertRows { + sheet, + row, + count: row_count, + }]; self.push_diff_list(diff_list); self.evaluate_if_not_paused(); Ok(()) @@ -903,10 +904,11 @@ impl UserModel { ) -> Result<(), String> { self.model.insert_columns(sheet, column, column_count)?; - let mut diff_list = Vec::new(); - for _ in 0..column_count { - diff_list.push(Diff::InsertColumn { sheet, column }); - } + let diff_list = vec![Diff::InsertColumns { + sheet, + column, + count: column_count, + }]; self.push_diff_list(diff_list); self.evaluate_if_not_paused(); Ok(()) @@ -921,9 +923,9 @@ impl UserModel { /// See also [`Model::delete_rows`]. pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> { let worksheet = self.model.workbook.worksheet(sheet)?; - let mut diff_list = Vec::new(); - // Bottom to top order prevents index drift during undo - for r in (row..row + row_count).rev() { + let mut old_data = Vec::new(); + // Collect data for all rows to be deleted + for r in row..row + row_count { let mut row_data = None; for rd in &worksheet.rows { if rd.r == r { @@ -935,18 +937,20 @@ impl UserModel { Some(s) => s.clone(), None => HashMap::new(), }; - diff_list.push(Diff::DeleteRow { - sheet, - row: r, - old_data: Box::new(RowData { - row: row_data, - data, - }), + old_data.push(RowData { + row: row_data, + data, }); } self.model.delete_rows(sheet, row, row_count)?; + let diff_list = vec![Diff::DeleteRows { + sheet, + row, + count: row_count, + old_data, + }]; self.push_diff_list(diff_list); self.evaluate_if_not_paused(); Ok(()) @@ -966,9 +970,9 @@ impl UserModel { column_count: i32, ) -> Result<(), String> { let worksheet = self.model.workbook.worksheet(sheet)?; - let mut diff_list = Vec::new(); - // Right to left order prevents index drift during undo - for c in (column..column + column_count).rev() { + let mut old_data = Vec::new(); + // Collect data for all columns to be deleted + for c in column..column + column_count { let mut column_data = None; for col in &worksheet.cols { if c >= col.min && c <= col.max { @@ -990,18 +994,20 @@ impl UserModel { } } - diff_list.push(Diff::DeleteColumn { - sheet, - column: c, - old_data: Box::new(ColumnData { - column: column_data, - data, - }), + old_data.push(ColumnData { + column: column_data, + data, }); } self.model.delete_columns(sheet, column, column_count)?; + let diff_list = vec![Diff::DeleteColumns { + sheet, + column, + count: column_count, + old_data, + }]; self.push_diff_list(diff_list); self.evaluate_if_not_paused(); Ok(()) @@ -2001,6 +2007,7 @@ impl UserModel { fn apply_undo_diff_list(&mut self, diff_list: &DiffList) -> Result<(), String> { let mut needs_evaluation = false; for diff in diff_list.iter().rev() { + #[allow(deprecated)] match diff { Diff::SetCellValue { sheet, @@ -2121,6 +2128,58 @@ impl UserModel { worksheet.set_column_width_and_style(*column, width, style)?; } } + Diff::InsertRows { sheet, row, count } => { + self.model.delete_rows(*sheet, *row, *count)?; + needs_evaluation = true; + } + Diff::DeleteRows { + sheet, + row, + count: _, + old_data, + } => { + needs_evaluation = true; + self.model + .insert_rows(*sheet, *row, old_data.len() as i32)?; + let worksheet = self.model.workbook.worksheet_mut(*sheet)?; + for (i, row_data) in old_data.iter().enumerate() { + let r = *row + i as i32; + if let Some(row_style) = row_data.row.clone() { + worksheet.rows.push(row_style); + } + worksheet.sheet_data.insert(r, row_data.data.clone()); + } + } + Diff::InsertColumns { + sheet, + column, + count, + } => { + self.model.delete_columns(*sheet, *column, *count)?; + needs_evaluation = true; + } + Diff::DeleteColumns { + sheet, + column, + count: _, + old_data, + } => { + needs_evaluation = true; + self.model + .insert_columns(*sheet, *column, old_data.len() as i32)?; + let worksheet = self.model.workbook.worksheet_mut(*sheet)?; + for (i, col_data) in old_data.iter().enumerate() { + let c = *column + i as i32; + for (row, cell) in &col_data.data { + worksheet.update_cell(*row, c, cell.clone())?; + } + if let Some(col) = &col_data.column { + let width = col.width * constants::COLUMN_WIDTH_FACTOR; + let style = col.style; + worksheet.set_column_width_and_style(c, width, style)?; + } + } + } Diff::SetFrozenRowsCount { sheet, new_value: _, @@ -2288,6 +2347,7 @@ impl UserModel { fn apply_diff_list(&mut self, diff_list: &DiffList) -> Result<(), String> { let mut needs_evaluation = false; for diff in diff_list { + #[allow(deprecated)] match diff { Diff::SetCellValue { sheet, @@ -2368,6 +2428,36 @@ impl UserModel { self.model.delete_columns(*sheet, *column, 1)?; needs_evaluation = true; } + Diff::InsertRows { sheet, row, count } => { + self.model.insert_rows(*sheet, *row, *count)?; + needs_evaluation = true; + } + Diff::DeleteRows { + sheet, + row, + count, + old_data: _, + } => { + self.model.delete_rows(*sheet, *row, *count)?; + needs_evaluation = true; + } + Diff::InsertColumns { + sheet, + column, + count, + } => { + self.model.insert_columns(*sheet, *column, *count)?; + needs_evaluation = true; + } + Diff::DeleteColumns { + sheet, + column, + count, + old_data: _, + } => { + self.model.delete_columns(*sheet, *column, *count)?; + needs_evaluation = true; + } Diff::SetFrozenRowsCount { sheet, new_value, diff --git a/base/src/user_model/history.rs b/base/src/user_model/history.rs index 53268e1..de2514f 100644 --- a/base/src/user_model/history.rs +++ b/base/src/user_model/history.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use std::collections::HashMap; use bitcode::{Decode, Encode}; @@ -17,6 +19,7 @@ pub(crate) struct ColumnData { } #[derive(Clone, Encode, Decode)] +#[allow(deprecated)] pub(crate) enum Diff { // Cell diffs SetCellValue { @@ -87,23 +90,83 @@ pub(crate) enum Diff { row: i32, old_value: Box>, }, + /// **DEPRECATED**: Use `InsertRows` with count=1 instead. + /// + /// This variant is kept for backward compatibility to handle old persisted diffs. + /// New code should always use `InsertRows` even for single row insertions. + #[deprecated(since = "0.5.0", note = "Use InsertRows with count=1 instead")] + #[allow(dead_code)] + #[allow(deprecated)] InsertRow { + #[allow(deprecated)] sheet: u32, + #[allow(deprecated)] row: i32, }, + /// **DEPRECATED**: Use `DeleteRows` with count=1 instead. + /// + /// This variant is kept for backward compatibility to handle old persisted diffs. + /// New code should always use `DeleteRows` even for single row deletions. + #[deprecated(since = "0.5.0", note = "Use DeleteRows with count=1 instead")] + #[allow(dead_code)] + #[allow(deprecated)] DeleteRow { + #[allow(deprecated)] sheet: u32, + #[allow(deprecated)] row: i32, + #[allow(deprecated)] old_data: Box, }, + /// **DEPRECATED**: Use `InsertColumns` with count=1 instead. + /// + /// This variant is kept for backward compatibility to handle old persisted diffs. + /// New code should always use `InsertColumns` even for single column insertions. + #[deprecated(since = "0.5.0", note = "Use InsertColumns with count=1 instead")] + #[allow(dead_code)] + #[allow(deprecated)] InsertColumn { + #[allow(deprecated)] sheet: u32, + #[allow(deprecated)] column: i32, }, + /// **DEPRECATED**: Use `DeleteColumns` with count=1 instead. + /// + /// This variant is kept for backward compatibility to handle old persisted diffs. + /// New code should always use `DeleteColumns` even for single column deletions. + #[deprecated(since = "0.5.0", note = "Use DeleteColumns with count=1 instead")] + #[allow(dead_code)] + #[allow(deprecated)] DeleteColumn { + #[allow(deprecated)] + sheet: u32, + #[allow(deprecated)] + column: i32, + #[allow(deprecated)] + old_data: Box, + }, + InsertRows { + sheet: u32, + row: i32, + count: i32, + }, + DeleteRows { + sheet: u32, + row: i32, + count: i32, + old_data: Vec, + }, + InsertColumns { sheet: u32, column: i32, - old_data: Box, + count: i32, + }, + DeleteColumns { + sheet: u32, + column: i32, + count: i32, + old_data: Vec, }, DeleteSheet { sheet: u32,