Merge pull request #2318 from dotCooCoo/fix/data-set-reports-overflow

Variables: selecting a data set reports the area type it overflows
This commit is contained in:
BFlat
2026-10-16 15:13:43 +07:00
committed by GitHub
4 changed files with 49 additions and 6 deletions
+13 -5
View File
@@ -430,7 +430,7 @@ fn dataset_list(s: &mut Session, _p: &Value) -> Result<Value> {
fn apply_dataset(s: &mut Session, name: &str) -> Result<Value> {
const C: &str = "dataset.select";
let (applied, skipped) = s.edit("Apply Data Set", |d, sel| {
let (applied, skipped, mut typed) = s.edit("Apply Data Set", |d, sel| {
let ds = d.variables.dataset(name).cloned().ok_or_else(|| bad(C, format!("no dataset named `{name}`")))?;
// One object may be driven by several variables; walk them in definition order, so two
// text variables on the same object land the later one's value and both are counted.
@@ -462,6 +462,8 @@ fn apply_dataset(s: &mut Session, name: &str) -> Result<Value> {
});
let mut applied = 0usize;
let mut skipped = 0usize;
// Collects the type objects whose text the row rewrote, for the reply's `overflow`.
let mut typed = Vec::new();
for (id, var) in pairs {
// A binding whose variable is gone, whose value this row doesn't carry, or whose
// value doesn't fit the variable's kind, is left alone and counted, never
@@ -483,7 +485,10 @@ fn apply_dataset(s: &mut Session, name: &str) -> Result<Value> {
let done = match (kind, &value) {
// The Type tool's own whole-range replace, so a dataset's text is styled and
// laid out exactly as typing it would be.
(VariableKind::Text, DataValue::Text(text)) => set_plain_text(d, id, text).is_ok(),
(VariableKind::Text, DataValue::Text(text)) => set_plain_text(d, id, text).is_ok_and(|()| {
typed.push(id);
true
}),
(VariableKind::Visibility, DataValue::Visible(show)) => d.node_mut(id).is_some_and(|n| {
n.visible = *show;
true
@@ -502,9 +507,12 @@ fn apply_dataset(s: &mut Session, name: &str) -> Result<Value> {
d.variables.active_dataset = Some(name.to_string());
// Every selected object left hidden or locked, such as one the row hid, is deselected.
sel.deselect_uneditable(d);
Ok((applied, skipped))
Ok((applied, skipped, typed))
})?;
Ok(json!({"name": name, "applied": applied, "skipped": skipped}))
// Two text variables on one object rewrite it twice; it is named once.
typed.sort_unstable();
typed.dedup();
Ok(super::typecmd::with_overflow(s, &typed, json!({"name": name, "applied": applied, "skipped": skipped})))
}
fn dataset_select(s: &mut Session, p: &Value) -> Result<Value> {
@@ -912,7 +920,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Select Data Set",
["Window", "Variables"],
None,
"{name} apply a dataset in one undo step, deselecting every selected object left hidden or locked (the key object too) → {applied, skipped}",
"{name} apply a dataset in one undo step, deselecting every selected object left hidden or locked (the key object too) → {applied, skipped, overflow?: [id…] (area type and type on a path whose text it rewrote and that no longer fits; left out when all fits)}",
has_doc,
dataset_select
),
+32
View File
@@ -82,6 +82,38 @@ fn dataset_next_and_prev_wrap_around() {
assert_eq!(v["active"], "B");
}
#[test]
fn applying_a_dataset_reports_the_area_type_it_overflows() {
let mut s = session();
let area = |s: &mut Session, y: u32, content: &str| {
let r = s.execute("text.create", &json!({"x": 10, "y": y, "text": content, "size": 12, "area": {"width": 120, "height": 40}})).unwrap();
NodeId(r["id"].as_u64().unwrap())
};
let long = "word ".repeat(60);
let a = area(&mut s, 10, "x");
let p = text(&mut s, "x");
// Area type that already overflows, bound to no variable, isn't named.
area(&mut s, 200, &long);
s.execute("variable.define", &json!({"name": "Name", "kind": "text"})).unwrap();
s.execute("variable.define", &json!({"name": "Title", "kind": "text"})).unwrap();
s.execute("variable.bind", &json!({"variable": "Name", "ids": [a.0, p.0]})).unwrap();
s.execute("variable.bind", &json!({"variable": "Title", "ids": [a.0]})).unwrap();
s.execute("dataset.new", &json!({"name": "Short", "values": {"Name": "Ann", "Title": "Ann"}})).unwrap();
s.execute("dataset.new", &json!({"name": "Long", "values": {"Name": long, "Title": long}})).unwrap();
// The area type is named once though two variables rewrote it; the point type is never named.
let v = s.execute("dataset.select", &json!({"name": "Long"})).unwrap();
assert_eq!((v["applied"].as_u64(), &v["overflow"]), (Some(3), &json!([a.0])), "{v}");
let v = s.execute("dataset.select", &json!({"name": "Short"})).unwrap();
assert!(v.get("overflow").is_none(), "{v}");
let v = s.execute("dataset.next", &json!({})).unwrap();
assert_eq!((&v["name"], &v["overflow"]), (&json!("Long"), &json!([a.0])), "{v}");
let v = s.execute("dataset.prev", &json!({})).unwrap();
assert!(v.get("overflow").is_none(), "{v}");
let v = s.execute("dataset.prev", &json!({})).unwrap();
assert_eq!((&v["name"], &v["overflow"]), (&json!("Long"), &json!([a.0])), "{v}");
}
#[test]
fn mismatched_and_missing_bindings_are_skipped_and_counted() {
let mut s = session();
+3 -1
View File
@@ -1002,7 +1002,9 @@ already walks Image Trace. Short agent entry points:
- Image Trace: `imageTrace.presets`, `imageTrace.make {id?, preset?, params?, view?}` (or `imageTrace.makeAndExpand`),
`imageTrace.setView`, `imageTrace.expand`, `imageTrace.release`.
`dataset.select`, `dataset.next` and `dataset.prev` reply `{name, applied, skipped}`. They skip an object that is
`dataset.select`, `dataset.next` and `dataset.prev` reply `{name, applied, skipped}`. They add `overflow: [id…]`
when area type or type on a path whose text they rewrote doesn't fit, as `text.setText` does (see
[Fitting area type](#fitting-area-type-auto-size-shrink-text-to-fit-overflow)). They skip an object that is
locked or inside a hidden or locked layer or group. They apply the data set's visibility values that show objects
first, outermost object first, then its other values in the order the variables are defined, then its visibility
values that hide objects, innermost object first. A data set that shows a group sets the objects inside it in the
+1
View File
@@ -407,6 +407,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- **Architecture:** 19+ crates with enforced layering (`cargo xtask layers`). Every action is a command (~400 engine + ~50 UI). Undo is unlimited via structural sharing. `command.batch` runs several commands as one transaction.
- **Automation:**
- Variables: Update Data Set writes what the art holds into the active data set for the variables that data set holds, and keeps the data set's value for a variable whose objects are locked or inside a hidden or locked layer or group; `dataset.update {all: true}` also adds the other variables that the art reports a value for.
- Variables: `dataset.select`, `dataset.next` and `dataset.prev` name the area type and type on a path whose text they rewrote and that doesn't fit, in the reply's `overflow` key, as `text.setText` does.
- Actions panel (record/playback, persisted), generic parameter dialogs for every "…" command.
- Variables (data merge): text, visibility and image variables bound to objects (an object may carry several), dataset rows applied in one undo step (`variable.*`, `dataset.*`) from a Variables panel and Window › Variables, with dialogs to define, bind and edit, Make Text/Visibility/Image Dynamic, Capture/Update Data Set, CSV import/export (`dataset.import` / `dataset.export`), and a row in italics when an edit leaves the art not matching it; an image dataset value is a file path and apply keeps the object's bounds; bindings let go of deleted art and locked art is left alone; every command, like the rest, is drivable over MCP and the CLI.
- JSON-lines control channel with real egui pointer and keyboard injection.