Merge pull request #912 from storytold/fix/903-reselect-after-deselect

Fix #903: Reselect repeats a Same command after Deselect
This commit is contained in:
BFlat 2026-10-10 13:15:17 +07:00 committed by GitHub
commit 994e821f53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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]