feat: add bulk diff types for insert/delete row/column operations

This commit is contained in:
Brian Hung
2025-07-23 12:49:23 -07:00
committed by Nicolás Hatcher Andrés
parent d5ccd9dbdd
commit 037766c744
3 changed files with 662 additions and 74 deletions

View File

@@ -25,15 +25,14 @@ fn diff_invariant_insert_rows() {
assert!(model.insert_rows(0, 5, 3).is_ok()); assert!(model.insert_rows(0, 5, 3).is_ok());
let list = last_diff_list(&mut model); let list = last_diff_list(&mut model);
assert_eq!(list.len(), 3); assert_eq!(list.len(), 1);
for diff in list { match &list[0] {
match diff { Diff::InsertRows { sheet, row, count } => {
Diff::InsertRow { sheet, row } => { assert_eq!(*sheet, 0);
assert_eq!(sheet, 0); assert_eq!(*row, 5);
assert_eq!(row, 5); assert_eq!(*count, 3);
}
_ => panic!("Unexpected diff variant"),
} }
_ => panic!("Unexpected diff variant"),
} }
} }
@@ -44,15 +43,18 @@ fn diff_invariant_insert_columns() {
assert!(model.insert_columns(0, 2, 4).is_ok()); assert!(model.insert_columns(0, 2, 4).is_ok());
let list = last_diff_list(&mut model); let list = last_diff_list(&mut model);
assert_eq!(list.len(), 4); assert_eq!(list.len(), 1);
for diff in list { match &list[0] {
match diff { Diff::InsertColumns {
Diff::InsertColumn { sheet, column } => { sheet,
assert_eq!(sheet, 0); column,
assert_eq!(column, 2); count,
} } => {
_ => panic!("Unexpected diff variant"), 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] #[test]
fn diff_order_delete_rows() { 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 base = new_empty_model();
let mut model = UserModel::from_model(base); 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()); assert!(model.delete_rows(0, 5, 5).is_ok());
let list = last_diff_list(&mut model); 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 // Should have one bulk diff with all the row data
let mut expected_row = 9; match &list[0] {
for diff in list { Diff::DeleteRows {
match diff { sheet,
Diff::DeleteRow { row, .. } => { row,
assert_eq!(row, expected_row); count,
expected_row -= 1; 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] #[test]
fn delete_empty_rows() { fn delete_empty_rows() {
// Delete multiple empty rows and verify behavior
let base = new_empty_model(); let base = new_empty_model();
let mut model = UserModel::from_model(base); 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, 1, 1).unwrap(), "Before");
assert_eq!(model.get_formatted_cell_value(0, 6, 1).unwrap(), "After"); 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); let list = last_diff_list(&mut model);
assert_eq!(list.len(), 4); assert_eq!(list.len(), 1);
let mut expected_row = 8; match &list[0] {
for diff in &list { Diff::DeleteRows {
match diff { sheet,
Diff::DeleteRow { row, old_data, .. } => { row,
assert_eq!(*row, expected_row); count,
assert!(old_data.data.is_empty()); old_data,
expected_row -= 1; } => {
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 // Undo/redo
@@ -209,15 +228,28 @@ fn delete_mixed_empty_and_filled_rows() {
// Verify mix of empty and filled row diffs // Verify mix of empty and filled row diffs
let list = last_diff_list(&mut model); let list = last_diff_list(&mut model);
assert_eq!(list.len(), 5); assert_eq!(list.len(), 1);
let filled_count = list match &list[0] {
.iter() Diff::DeleteRows {
.filter(|diff| match diff { sheet,
Diff::DeleteRow { old_data, .. } => !old_data.data.is_empty(), row,
_ => false, count,
}) old_data,
.count(); } => {
assert_eq!(filled_count, 3); 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 // Undo
model.undo().unwrap(); 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"); 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] #[test]
fn boundary_validation() { fn boundary_validation() {
let base = new_empty_model(); let base = new_empty_model();

View File

@@ -874,10 +874,11 @@ impl UserModel {
pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> { pub fn insert_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
self.model.insert_rows(sheet, row, row_count)?; self.model.insert_rows(sheet, row, row_count)?;
let mut diff_list = Vec::new(); let diff_list = vec![Diff::InsertRows {
for _ in 0..row_count { sheet,
diff_list.push(Diff::InsertRow { sheet, row }); row,
} count: row_count,
}];
self.push_diff_list(diff_list); self.push_diff_list(diff_list);
self.evaluate_if_not_paused(); self.evaluate_if_not_paused();
Ok(()) Ok(())
@@ -903,10 +904,11 @@ impl UserModel {
) -> Result<(), String> { ) -> Result<(), String> {
self.model.insert_columns(sheet, column, column_count)?; self.model.insert_columns(sheet, column, column_count)?;
let mut diff_list = Vec::new(); let diff_list = vec![Diff::InsertColumns {
for _ in 0..column_count { sheet,
diff_list.push(Diff::InsertColumn { sheet, column }); column,
} count: column_count,
}];
self.push_diff_list(diff_list); self.push_diff_list(diff_list);
self.evaluate_if_not_paused(); self.evaluate_if_not_paused();
Ok(()) Ok(())
@@ -921,9 +923,9 @@ impl UserModel {
/// See also [`Model::delete_rows`]. /// See also [`Model::delete_rows`].
pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> { pub fn delete_rows(&mut self, sheet: u32, row: i32, row_count: i32) -> Result<(), String> {
let worksheet = self.model.workbook.worksheet(sheet)?; let worksheet = self.model.workbook.worksheet(sheet)?;
let mut diff_list = Vec::new(); let mut old_data = Vec::new();
// Bottom to top order prevents index drift during undo // Collect data for all rows to be deleted
for r in (row..row + row_count).rev() { for r in row..row + row_count {
let mut row_data = None; let mut row_data = None;
for rd in &worksheet.rows { for rd in &worksheet.rows {
if rd.r == r { if rd.r == r {
@@ -935,18 +937,20 @@ impl UserModel {
Some(s) => s.clone(), Some(s) => s.clone(),
None => HashMap::new(), None => HashMap::new(),
}; };
diff_list.push(Diff::DeleteRow { old_data.push(RowData {
sheet, row: row_data,
row: r, data,
old_data: Box::new(RowData {
row: row_data,
data,
}),
}); });
} }
self.model.delete_rows(sheet, row, row_count)?; 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.push_diff_list(diff_list);
self.evaluate_if_not_paused(); self.evaluate_if_not_paused();
Ok(()) Ok(())
@@ -966,9 +970,9 @@ impl UserModel {
column_count: i32, column_count: i32,
) -> Result<(), String> { ) -> Result<(), String> {
let worksheet = self.model.workbook.worksheet(sheet)?; let worksheet = self.model.workbook.worksheet(sheet)?;
let mut diff_list = Vec::new(); let mut old_data = Vec::new();
// Right to left order prevents index drift during undo // Collect data for all columns to be deleted
for c in (column..column + column_count).rev() { for c in column..column + column_count {
let mut column_data = None; let mut column_data = None;
for col in &worksheet.cols { for col in &worksheet.cols {
if c >= col.min && c <= col.max { if c >= col.min && c <= col.max {
@@ -990,18 +994,20 @@ impl UserModel {
} }
} }
diff_list.push(Diff::DeleteColumn { old_data.push(ColumnData {
sheet, column: column_data,
column: c, data,
old_data: Box::new(ColumnData {
column: column_data,
data,
}),
}); });
} }
self.model.delete_columns(sheet, column, column_count)?; 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.push_diff_list(diff_list);
self.evaluate_if_not_paused(); self.evaluate_if_not_paused();
Ok(()) Ok(())
@@ -2001,6 +2007,7 @@ impl UserModel {
fn apply_undo_diff_list(&mut self, diff_list: &DiffList) -> Result<(), String> { fn apply_undo_diff_list(&mut self, diff_list: &DiffList) -> Result<(), String> {
let mut needs_evaluation = false; let mut needs_evaluation = false;
for diff in diff_list.iter().rev() { for diff in diff_list.iter().rev() {
#[allow(deprecated)]
match diff { match diff {
Diff::SetCellValue { Diff::SetCellValue {
sheet, sheet,
@@ -2121,6 +2128,58 @@ impl UserModel {
worksheet.set_column_width_and_style(*column, width, style)?; 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 { Diff::SetFrozenRowsCount {
sheet, sheet,
new_value: _, new_value: _,
@@ -2288,6 +2347,7 @@ impl UserModel {
fn apply_diff_list(&mut self, diff_list: &DiffList) -> Result<(), String> { fn apply_diff_list(&mut self, diff_list: &DiffList) -> Result<(), String> {
let mut needs_evaluation = false; let mut needs_evaluation = false;
for diff in diff_list { for diff in diff_list {
#[allow(deprecated)]
match diff { match diff {
Diff::SetCellValue { Diff::SetCellValue {
sheet, sheet,
@@ -2368,6 +2428,36 @@ impl UserModel {
self.model.delete_columns(*sheet, *column, 1)?; self.model.delete_columns(*sheet, *column, 1)?;
needs_evaluation = true; 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 { Diff::SetFrozenRowsCount {
sheet, sheet,
new_value, new_value,

View File

@@ -1,3 +1,5 @@
#![allow(deprecated)]
use std::collections::HashMap; use std::collections::HashMap;
use bitcode::{Decode, Encode}; use bitcode::{Decode, Encode};
@@ -17,6 +19,7 @@ pub(crate) struct ColumnData {
} }
#[derive(Clone, Encode, Decode)] #[derive(Clone, Encode, Decode)]
#[allow(deprecated)]
pub(crate) enum Diff { pub(crate) enum Diff {
// Cell diffs // Cell diffs
SetCellValue { SetCellValue {
@@ -87,23 +90,83 @@ pub(crate) enum Diff {
row: i32, row: i32,
old_value: Box<Option<Style>>, old_value: Box<Option<Style>>,
}, },
/// **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 { InsertRow {
#[allow(deprecated)]
sheet: u32, sheet: u32,
#[allow(deprecated)]
row: i32, 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 { DeleteRow {
#[allow(deprecated)]
sheet: u32, sheet: u32,
#[allow(deprecated)]
row: i32, row: i32,
#[allow(deprecated)]
old_data: Box<RowData>, old_data: Box<RowData>,
}, },
/// **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 { InsertColumn {
#[allow(deprecated)]
sheet: u32, sheet: u32,
#[allow(deprecated)]
column: i32, 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 { DeleteColumn {
#[allow(deprecated)]
sheet: u32,
#[allow(deprecated)]
column: i32,
#[allow(deprecated)]
old_data: Box<ColumnData>,
},
InsertRows {
sheet: u32,
row: i32,
count: i32,
},
DeleteRows {
sheet: u32,
row: i32,
count: i32,
old_data: Vec<RowData>,
},
InsertColumns {
sheet: u32, sheet: u32,
column: i32, column: i32,
old_data: Box<ColumnData>, count: i32,
},
DeleteColumns {
sheet: u32,
column: i32,
count: i32,
old_data: Vec<ColumnData>,
}, },
DeleteSheet { DeleteSheet {
sheet: u32, sheet: u32,