diff --git a/crates/engine/src/cmd/docmenu.rs b/crates/engine/src/cmd/docmenu.rs index 31413ffe..bf38e445 100644 --- a/crates/engine/src/cmd/docmenu.rs +++ b/crates/engine/src/cmd/docmenu.rs @@ -246,8 +246,9 @@ fn candidates(s: &Session, f: impl Fn(&Node) -> bool) -> Result> { fn finish_same(s: &mut Session, cmd: &str, ids: Vec) -> Result { let n = ids.len(); + let from = s.doc()?.selection.objects.clone(); s.select(|_, sel| sel.set(ids))?; - s.doc_mut()?.last_selection_cmd = Some((cmd.to_string(), json!({}))); + s.doc_mut()?.last_selection_cmd = Some((cmd.to_string(), json!({}), from)); Ok(json!({ "count": n })) } diff --git a/crates/engine/src/cmd/select.rs b/crates/engine/src/cmd/select.rs index 0dbda515..8e018e79 100644 --- a/crates/engine/src/cmd/select.rs +++ b/crates/engine/src/cmd/select.rs @@ -216,8 +216,20 @@ fn none(s: &mut Session, _: &Value) -> Result { } fn reselect(s: &mut Session, _: &Value) -> Result { - let Some((c, p)) = s.doc()?.last_selection_cmd.clone() else { return ok() }; - s.execute(&c, &p) + let st = s.doc()?; + let Some((c, p, from)) = st.last_selection_cmd.clone() else { return ok() }; + // After Deselect, a Same command takes its reference again from the objects that were selected + // when it ran, those still there (#903). + let from: Vec = from.into_iter().filter(|id| st.doc.node(*id).is_some()).collect(); + let restore = st.selection.objects.is_empty() && !from.is_empty(); + if restore { + s.select(|_, sel| sel.set(from))?; + } + let r = s.execute(&c, &p); + if restore && r.is_err() { + s.select(|_, sel| sel.clear())?; + } + r } fn inverse(s: &mut Session, _: &Value) -> Result { @@ -401,8 +413,9 @@ fn select_where(s: &mut Session, cmd: &str, p: &Value, f: impl Fn(&Document, &No for l in st.doc.layers.iter().filter(|l| l.visible && !l.locked) { visit(&st.doc, l, &f, &mut ids); } + let from = st.selection.objects.clone(); s.select(|_, sel| sel.set(ids.iter().copied()))?; - s.doc_mut()?.last_selection_cmd = Some((cmd.to_string(), p.clone())); + s.doc_mut()?.last_selection_cmd = Some((cmd.to_string(), p.clone(), from)); Ok(json!({ "count": ids.len() })) } diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 6921d818..487b26f6 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -126,8 +126,9 @@ pub struct DocState { pub undo_group: Option, /// For Object → Transform → Transform Again (⌘D). pub last_transform: Option<(Affine, bool)>, - /// Selection saved by Select → Reselect. - pub last_selection_cmd: Option<(String, Value)>, + /// What Select → Reselect repeats: the last selection command, its params and the objects + /// selected when it ran (a Same command's reference objects). + pub last_selection_cmd: Option<(String, Value, Vec)>, /// Process-unique id of this open document (tab indices shift when tabs close). pub uid: u64, /// View Opacity Mask (Alt-click the mask thumbnail): the masked object whose mask the canvas diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index ffb7e829..ad04e948 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -149,6 +149,18 @@ fn select_same_fill() { let r = s.execute("select.same.fillColor", &json!({})).unwrap(); assert_eq!(r["count"], 2); assert!(!s.doc().unwrap().selection.contains(a)); + // Reselect after Deselect repeats it from the same reference object (#903). + let blue = s.doc().unwrap().selection.objects.clone(); + s.execute("select.none", &json!({})).unwrap(); + s.execute("select.reselect", &json!({})).unwrap(); + assert_eq!(s.doc().unwrap().selection.objects, blue); + // With its reference object gone, it fails as Select Same does, and selects nothing. + s.execute("select.none", &json!({})).unwrap(); + for id in &blue { + s.execute("edit.clear", &json!({"ids": [id.0]})).unwrap(); + } + assert!(s.execute("select.reselect", &json!({})).is_err()); + assert!(s.doc().unwrap().selection.objects.is_empty()); } #[test]