From 2402f8e1aa8a3d825fa30e5722e666ede891bae7 Mon Sep 17 00:00:00 2001 From: Don Omondi <282319807+mockydevs@users.noreply.github.com> Date: Sun, 1 Nov 2026 11:47:20 +0300 Subject: [PATCH] Copy and paste guides: Cut with direct-selected anchors takes the guides too With anchors and ruler guides selected, Cut deletes the anchors and the selected guides in one undo step (edit.clear's guides: true reaches the anchor-deleting path). --- crates/engine/src/cmd/edit.rs | 2 +- crates/engine/src/cmd/path.rs | 8 +++++++- crates/engine/src/tests/guide_clipboard.rs | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index 08f765c83..41276c232 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -713,7 +713,7 @@ fn cut(s: &mut Session, p: &Value) -> Result { let ids = selected_roots(s)?; copy(s, p)?; // The selected ruler guides it copied go with the objects, in the same undo step (#3226). - if s.doc()?.selection.anchors.is_empty() { clear(s, &json!({ "ids": ids, "guides": true })) } else { clear(s, &json!({})) } + if s.doc()?.selection.anchors.is_empty() { clear(s, &json!({ "ids": ids, "guides": true })) } else { clear(s, &json!({ "guides": true })) } } fn clear(s: &mut Session, p: &Value) -> Result { diff --git a/crates/engine/src/cmd/path.rs b/crates/engine/src/cmd/path.rs index f1bc3e501..2def5ce9e 100644 --- a/crates/engine/src/cmd/path.rs +++ b/crates/engine/src/cmd/path.rs @@ -1189,7 +1189,9 @@ fn convert_anchors(s: &mut Session, p: &Value) -> Result { ok() } -pub(crate) fn delete_anchors(s: &mut Session, _: &Value) -> Result { +pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result { + // `guides: true` (Cut, #3226): the selected ruler guides go in the same undo step. + let with_guides = p.get("guides").and_then(Value::as_bool).unwrap_or(false); let st = s.doc()?; let sel = st.selection.anchors.clone(); let segments = st.selection.segments.clone(); @@ -1197,6 +1199,10 @@ pub(crate) fn delete_anchors(s: &mut Session, _: &Value) -> Result { // also catches part of another path must still delete those whole objects. let whole = st.selection.whole(); s.edit("Clear", |d, selection| { + if with_guides { + let gone = selection.guides.clone(); + d.retain_guides(selection, |i, _| !gone.contains(&i)); + } let targets: BTreeSet = whole.into_iter().filter(|id| d.node(*id).is_some_and(|n| !n.is_layer())).collect(); let whole_ids: Vec = targets .iter() diff --git a/crates/engine/src/tests/guide_clipboard.rs b/crates/engine/src/tests/guide_clipboard.rs index ca1f9fd79..d71c1dd42 100644 --- a/crates/engine/src/tests/guide_clipboard.rs +++ b/crates/engine/src/tests/guide_clipboard.rs @@ -74,6 +74,16 @@ fn guides_paste_in_place_onto_another_artboard_and_cut_removes_them() { assert!(s.doc().unwrap().doc.node(NodeId(rect)).is_none()); assert_eq!(guides(&s).len(), before - 1, "the selected guide goes too"); assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1); + // Cut with direct-selected anchors and a guide: the anchors and the guide go, one undo step. + let path = run(&mut s, "shape.rectangle", json!({"x": 40, "y": 40, "width": 20, "height": 20}))["id"].as_u64().unwrap(); + run(&mut s, "guide.add", json!({"vertical": false, "pos": 90})); + let n = guides(&s).len(); + run(&mut s, "select.anchors", json!({"id": path, "anchors": [[0, 0]], "mode": "set"})); + run(&mut s, "guide.select", json!({"indexes": [n - 1], "toggle": true})); + let undo = s.doc().unwrap().history.undo.len(); + run(&mut s, "edit.cut", json!({})); + assert_eq!(guides(&s).len(), n - 1, "the guide goes with the anchors"); + assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1); // Nothing selected: Copy is off. run(&mut s, "select.none", json!({})); assert!(s.execute("edit.copy", &json!({})).is_err());