mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-02 22:20:50 -06:00
Fix #4918: delete nested picks safely, keep picks per document
symbol.delete {names} expands every instance of the named symbols,
round by round, before it removes any definition, so a symbol and the
one placed inside it delete alike in either order. The Symbols panel's
picks belong to the document they were made in, and a single picked
symbol is deleted only while symbol.unused still lists it.
This commit is contained in:
@@ -448,6 +448,9 @@ pub(super) fn symbol_update(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({ "name": name, "id": id.0 }))
|
||||
}
|
||||
|
||||
/// How many levels of nested instances Delete Symbol expands before it gives up.
|
||||
const MAX_DELETE_ROUNDS: usize = 256;
|
||||
|
||||
pub(super) fn symbol_delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "symbol.delete";
|
||||
// `names` deletes several symbols in one undo step (the Symbols panel's Select All Unused, #4918).
|
||||
@@ -475,13 +478,36 @@ pub(super) fn symbol_delete(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let expand = switch_or(p, "expandInstances", true, C)?;
|
||||
let label = if names.len() == 1 { "Delete Symbol" } else { "Delete Symbols" };
|
||||
s.edit(label, |d, _| {
|
||||
for name in &names {
|
||||
let inst = instances_of(d, Some(name));
|
||||
// Every instance goes before any definition, whatever the order of `names`: expanding an
|
||||
// instance of one symbol can bring out instances of another being deleted (nested in its
|
||||
// art), which expand in turn while their definitions are still there. Each round expands
|
||||
// one level of nesting, so the rounds are capped.
|
||||
let of_names = |d: &Document| {
|
||||
let mut v = vec![];
|
||||
d.walk(|n| {
|
||||
if let NodeKind::SymbolInstance { symbol, .. } = &n.kind
|
||||
&& names.contains(symbol)
|
||||
{
|
||||
v.push(n.id);
|
||||
}
|
||||
});
|
||||
v
|
||||
};
|
||||
for _ in 0..=MAX_DELETE_ROUNDS {
|
||||
let inst = of_names(d);
|
||||
if inst.is_empty() {
|
||||
break;
|
||||
}
|
||||
if expand {
|
||||
break_links(d, &inst, true)?;
|
||||
} else {
|
||||
remove_each(d, &inst)?;
|
||||
}
|
||||
}
|
||||
if !of_names(d).is_empty() {
|
||||
return Err(EngineError::Other("the symbols are nested too deeply to expand their instances".into()));
|
||||
}
|
||||
for name in &names {
|
||||
d.symbols.retain(|x| &x.name != name);
|
||||
set_natural_size(d, name, None);
|
||||
d.set_symbol_registration(name, SymbolRegistration::CENTER);
|
||||
|
||||
@@ -1483,6 +1483,18 @@ fn symbol_unused_lists_the_symbols_nothing_places_and_delete_takes_several() {
|
||||
assert!(unused(&mut s).is_empty());
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(names(&s), ["Placed", "Inner", "Outer", "Nested", "Lonely", "Brushed"]);
|
||||
// Deleting a symbol and the one placed inside it, in either order, expands every instance
|
||||
// while both definitions are still there: no instance is left without its symbol.
|
||||
for order in [["Inner", "Outer"], ["Outer", "Inner"]] {
|
||||
s.execute("symbol.delete", &json!({"names": order})).unwrap();
|
||||
let mut dangling = 0;
|
||||
s.doc()
|
||||
.unwrap()
|
||||
.doc
|
||||
.walk(|n| dangling += usize::from(matches!(&n.kind, NodeKind::SymbolInstance { symbol, .. } if symbol == "Inner" || symbol == "Outer")));
|
||||
assert_eq!(dangling, 0, "{order:?}");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
}
|
||||
// Bad `names` are refused and change nothing.
|
||||
for bad in [json!({"names": []}), json!({"names": "Placed"}), json!({"names": [1]}), json!({"names": ["Placed", "Nope"]})] {
|
||||
assert!(s.execute("symbol.delete", &bad).is_err(), "{bad}");
|
||||
|
||||
Reference in New Issue
Block a user