Fix #903: Reselect repeats a Same command after Deselect

Select › Reselect repeated the last selection command, but a Select Same
command needs a selected object to compare with, so after Deselect it
failed with "nothing selected". Reselect now remembers the objects that
were selected when the command ran and, with nothing selected, takes the
reference from those still in the document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic 2026-10-10 12:48:46 +07:00
parent ba2fc64d89
commit 6917984329
4 changed files with 33 additions and 6 deletions

View File

@ -246,8 +246,9 @@ fn candidates(s: &Session, f: impl Fn(&Node) -> bool) -> Result<Vec<NodeId>> {
fn finish_same(s: &mut Session, cmd: &str, ids: Vec<NodeId>) -> Result<Value> {
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 }))
}

View File

@ -216,8 +216,20 @@ fn none(s: &mut Session, _: &Value) -> Result<Value> {
}
fn reselect(s: &mut Session, _: &Value) -> Result<Value> {
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<NodeId> = 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<Value> {
@ -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() }))
}

View File

@ -126,8 +126,9 @@ pub struct DocState {
pub undo_group: Option<UndoGroup>,
/// 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<NodeId>)>,
/// 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

View File

@ -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]