From c142c70dbe38df105ef27dffc45acd37704ea347 Mon Sep 17 00:00:00 2001 From: prxnvt <53838599+prxnvt@users.noreply.github.com> Date: Sun, 25 Oct 2026 02:55:55 -0400 Subject: [PATCH] UI: the macro editor keeps an input to a shape a macro may declare The format now refuses a single-cell input with a header, several column types or more than one row or column, a least above its most, and more column types than columns; the editor could make each of these, and the save then failed. - Switching an input to "single cell" sets one row and one column, takes the header off and keeps at most one column type; "Use current selection as example" does the same for one cell. - Setting "at least" above "at most" moves "at most" up (and the other way round), and lowering the columns' "at most" drops the column types past it. - Test: `an_edited_input_keeps_a_shape_a_macro_may_declare` (each result passes `check_limits`). --- crates/ui-egui/src/macro_editor.rs | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/crates/ui-egui/src/macro_editor.rs b/crates/ui-egui/src/macro_editor.rs index a225eee06..621caae22 100644 --- a/crates/ui-egui/src/macro_editor.rs +++ b/crates/ui-egui/src/macro_editor.rs @@ -2013,6 +2013,7 @@ fn inputs_tab(app: &mut SheetApp, ui: &mut Ui, d: &mut Dialog, ed: &mut Editor) }); } if i != before { + tidy_input(&mut i, &before); if let Some(slot) = ed.draft.inputs.get_mut(k) { *slot = i; } @@ -2060,6 +2061,30 @@ fn example_headers(app: &SheetApp, example: &str, header: bool) -> Vec { .collect() } +/// Keeps an input the person is editing to the shapes a macro may declare, as the edit is made: +/// a single cell is one row and one column with no header and at most one column type; a least +/// above its most moves the other end along (the one just set wins); and a range lists no more +/// column types than it allows columns. +pub fn tidy_input(i: &mut NamedInput, before: &NamedInput) { + if i.kind == InputKind::Cell { + (i.rows, i.cols, i.header) = ([1, 1], [1, 1], false); + i.col_types.truncate(1); + return; + } + for (now, was) in [(&mut i.rows, before.rows), (&mut i.cols, before.cols)] { + if now[1] != 0 && now[0] > now[1] { + if now[0] != was[0] { + now[1] = now[0]; + } else { + now[0] = now[1]; + } + } + } + if i.cols[1] != 0 { + i.col_types.truncate(i.cols[1] as usize); + } +} + /// "Use current selection as example": the selection becomes the first input's example, and its /// shape, header and column types are read from it. pub fn use_selection_as_example(app: &SheetApp, ed: &mut Editor) { @@ -2069,11 +2094,13 @@ pub fn use_selection_as_example(app: &SheetApp, ed: &mut Editor) { ed.example = r.a1(); let Some(first) = ed.draft.inputs.first_mut() else { return }; let inferred = infer_input(sh, r); + let before = first.clone(); first.kind = if r.is_single() { InputKind::Cell } else { InputKind::Range }; first.header = inferred.0; first.col_types = inferred.1; first.cols = [r.width(), r.width()]; first.rows = [1, 0]; + tidy_input(first, &before); if first.default_ref.is_some() { first.default_ref = Some(ed.example.clone()); } @@ -2241,6 +2268,31 @@ pub fn paint_overlay(p: &egui::Painter, geo: &crate::grid::Geo, sh: &gridcraft_e mod tests { use super::*; + #[test] + fn an_edited_input_keeps_a_shape_a_macro_may_declare() { + let range = NamedInput { rows: [2, 10], cols: [3, 3], header: true, col_types: vec![ColType::Number; 3], ..NamedInput::default() }; + // Switching a range to a single cell drops what a cell can't have. + let mut cell = NamedInput { kind: InputKind::Cell, ..range.clone() }; + tidy_input(&mut cell, &range); + assert_eq!((cell.rows, cell.cols, cell.header, cell.col_types.len()), ([1, 1], [1, 1], false, 1)); + // A least set above the most moves the most up; a most set below the least moves the least. + let mut i = NamedInput { rows: [12, 10], ..range.clone() }; + tidy_input(&mut i, &range); + assert_eq!(i.rows, [12, 12]); + let mut i = NamedInput { rows: [2, 1], ..range.clone() }; + tidy_input(&mut i, &range); + assert_eq!(i.rows, [1, 1]); + // At most 0 is no limit; fewer columns drop the column types past them. + let mut i = NamedInput { rows: [12, 0], cols: [1, 2], ..range.clone() }; + tidy_input(&mut i, &range); + assert_eq!((i.rows, i.cols, i.col_types.len()), ([12, 0], [1, 2], 2)); + // Each passes the format's own check. + for input in [cell, i] { + let m = Macro { name: "M".into(), inputs: vec![input], ..Macro::default() }; + assert_eq!(m.check_limits(), Ok(()), "{m:?}"); + } + } + fn steps() -> Vec { vec![ Step::command("cell.set", json!({"cell": "A1", "input": "x"})),