diff --git a/crates/engine/src/cmd/sheet.rs b/crates/engine/src/cmd/sheet.rs index 837f0806a..3377d52e9 100644 --- a/crates/engine/src/cmd/sheet.rs +++ b/crates/engine/src/cmd/sheet.rs @@ -196,6 +196,15 @@ fn rewrite(wb: &mut gridcraft_model::Workbook, target: &str, e: &Edit) { } } +/// Inserting or deleting cells moves the copied block away from the stored source range, so, like +/// Excel, it cancels copy/cut mode for this workbook (a later paste would take the wrong cells). +fn cancel_copy_mode(s: &mut Session) { + let uid = s.doc().map(|d| d.uid).ok(); + if s.clipboard.as_ref().is_some_and(|c| Some(c.doc_uid) == uid) { + s.clipboard = None; + } +} + fn insert_lines(s: &mut Session, p: &Json, axis: Axis) -> Result { let (at, count) = lines(s, p, axis)?; let sheet = target_sheet(s, p)?; @@ -207,7 +216,7 @@ fn insert_lines(s: &mut Session, p: &Json, axis: Axis) -> Result { return Err(EngineError::Other("To prevent possible loss of data, Excel cannot shift nonblank cells off of the worksheet.".into())); } } - edit(s, |cx| { + let out = edit(s, |cx| { let name = cx.wb.sheet(sheet).map(|s| s.name.clone()).unwrap_or_default(); { let sh = cx.sheet_mut(sheet)?; @@ -230,13 +239,15 @@ fn insert_lines(s: &mut Session, p: &Json, axis: Axis) -> Result { rewrite(&mut cx.wb, &name, &Edit::Insert { axis, at, count }); cx.structural = true; Ok(Json::Null) - }) + })?; + cancel_copy_mode(s); + Ok(out) } fn delete_lines(s: &mut Session, p: &Json, axis: Axis) -> Result { let (at, count) = lines(s, p, axis)?; let sheet = target_sheet(s, p)?; - edit(s, |cx| { + let out = edit(s, |cx| { let name = cx.wb.sheet(sheet).map(|s| s.name.clone()).unwrap_or_default(); { let sh = cx.sheet_mut(sheet)?; @@ -254,7 +265,9 @@ fn delete_lines(s: &mut Session, p: &Json, axis: Axis) -> Result { rewrite(&mut cx.wb, &name, &Edit::Delete { axis, at, count }); cx.structural = true; Ok(Json::Null) - }) + })?; + cancel_copy_mode(s); + Ok(out) } fn insert_cells(s: &mut Session, p: &Json) -> Result { @@ -267,7 +280,7 @@ fn insert_cells(s: &mut Session, p: &Json) -> Result { } let sheet = target_sheet(s, p)?; let right = shift == "right"; - edit(s, |cx| { + let out = edit(s, |cx| { let sh = cx.sheet_mut(sheet)?; if right { sh.cells.shift_cols_in_rows(r.start.row, r.end.row, r.start.col, r.width() as i64); @@ -285,7 +298,9 @@ fn insert_cells(s: &mut Session, p: &Json) -> Result { super::edit::rewrite_all_formulas(&mut cx.wb, &name, &Edit::Move { from: moved, to_row: to.0, to_col: to.1 }); cx.structural = true; Ok(Json::Null) - }) + })?; + cancel_copy_mode(s); + Ok(out) } fn delete_cells(s: &mut Session, p: &Json) -> Result { @@ -298,7 +313,7 @@ fn delete_cells(s: &mut Session, p: &Json) -> Result { } let sheet = target_sheet(s, p)?; let left = shift == "left"; - edit(s, |cx| { + let out = edit(s, |cx| { let name = cx.wb.sheet(sheet).map(|s| s.name.clone()).unwrap_or_default(); { let sh = cx.sheet_mut(sheet)?; @@ -320,7 +335,9 @@ fn delete_cells(s: &mut Session, p: &Json) -> Result { super::edit::rewrite_all_formulas(&mut cx.wb, &name, &Edit::Move { from: moved_from, to_row: r.start.row, to_col: r.start.col }); cx.structural = true; Ok(Json::Null) - }) + })?; + cancel_copy_mode(s); + Ok(out) } fn insert_sheet(s: &mut Session, p: &Json) -> Result { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f3e36cdf4..8b8c0d820 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -70,6 +70,27 @@ fn insert_delete_rows_adjust() { assert_eq!(v(&s, "A1"), Value::Number(10.0)); } +#[test] +fn inserting_or_deleting_cells_cancels_copy_mode() { + let mut s = s(); + s.execute("range.setValues", json!({"range": "A1", "values": [[1], [2], [3]]})).unwrap(); + s.execute("edit.cut", json!({"range": "3:3"})).unwrap(); + s.execute("home.insertRows", json!({"rows": "3:3"})).unwrap(); + assert!(s.clipboard.is_none()); + // The cut data is now in row 4; a paste must not move the new blank row 3 instead. + assert!(s.execute("edit.paste", json!({"at": "A10"})).is_err()); + assert_eq!(v(&s, "A4"), Value::Number(3.0)); + s.execute("edit.copy", json!({"range": "A:A"})).unwrap(); + s.execute("home.deleteColumns", json!({"cols": "B:B"})).unwrap(); + assert!(s.clipboard.is_none()); + s.execute("edit.copy", json!({"range": "A1"})).unwrap(); + s.execute("home.insertCells", json!({"range": "A1", "shift": "down"})).unwrap(); + assert!(s.clipboard.is_none()); + s.execute("edit.copy", json!({"range": "A2"})).unwrap(); + s.execute("home.deleteCells", json!({"range": "A1", "shift": "up"})).unwrap(); + assert!(s.clipboard.is_none()); +} + #[test] fn fill_series() { let mut s = s();