diff --git a/crates/engine/src/cmd/blocks.rs b/crates/engine/src/cmd/blocks.rs index 1458ec8d..99d9ba2c 100644 --- a/crates/engine/src/cmd/blocks.rs +++ b/crates/engine/src/cmd/blocks.rs @@ -225,7 +225,9 @@ fn run_insert(s: &mut Session, p: &Value) -> Result { let vals = p.get("attribs").and_then(Value::as_object).cloned().unwrap_or_default(); let h = insert_with(s, &name, at, scale, rot, point_param(p, "basePoint"), &vals)?; if bool_or(p, "explode", false) { - return s.execute("explode", &json!({ "handles": [h.hex()] })); + // The EXPLODE command body, not `execute`: INSERT records the one undo step. + let explode = find_command("explode").ok_or_else(|| EngineError::UnknownCommand("explode".into()))?; + return (explode.run)(s, &json!({ "handles": [h.hex()] })); } Ok(attedit::with_unknown_tags(s, h, &vals, json!({ "handle": h.hex() }))) } diff --git a/crates/engine/tests/insert_file.rs b/crates/engine/tests/insert_file.rs index 545d58d0..77bfd9d8 100644 --- a/crates/engine/tests/insert_file.rs +++ b/crates/engine/tests/insert_file.rs @@ -150,3 +150,27 @@ fn a_drawing_file_becomes_a_block_with_its_insbase_and_tables() { assert!(s.prompt_text().contains("block name"), "{}", s.prompt_text()); assert_eq!(inserts(&s).len(), n); } + +#[test] +fn exploded_json_insert_is_one_undo_step() { + for params in [json!({ "name": "*B", "at": [5, 5] }), json!({ "name": "B", "at": [5, 5], "explode": true })] { + let mut s = Session::new(); + s.execute("line", &json!({ "points": [[0, 0], [10, 0]] })).unwrap(); + s.execute("block", &json!({ "name": "B", "base": [0, 0], "keep": "delete", "handles": ["100"] })).unwrap(); + let undo = s.state().unwrap().undo.len(); + s.execute("insert", ¶ms).unwrap(); + assert_eq!(lines(&s), vec![(5.0, 5.0, 15.0, 5.0)], "{params}"); + assert_eq!(s.state().unwrap().undo.len(), undo + 1, "{params}"); + + s.execute("undo", &json!({})).unwrap(); + assert!(lines(&s).is_empty(), "{params}"); + assert!(inserts(&s).is_empty(), "{params}"); + assert!(s.doc().unwrap().block("B").is_some(), "{params}"); + + // The second undo reverts the block creation, not a leftover EXPLODE step. + s.execute("undo", &json!({})).unwrap(); + assert!(inserts(&s).is_empty(), "{params}"); + assert!(s.doc().unwrap().block("B").is_none(), "{params}"); + assert_eq!(lines(&s), vec![(0.0, 0.0, 10.0, 0.0)], "{params}"); + } +}