From 2faf8ffc789f6f0bbb053d278c904c304d481589 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sat, 10 Oct 2026 19:03:57 +0200 Subject: [PATCH] Honor search in the desktop engine.commands handler so connected list_commands filters (#136) --- crates/ui-egui/src/control.rs | 42 +++++++++++++++++++++++++++++++++-- docs/control-protocol.md | 2 +- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index 902ec25..28c40d3 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -2,7 +2,7 @@ //! //! Methods (JSON lines over the host's transport, see `docs/control-protocol.md`): //! - `engine.execute {command, params}` — run any engine or UI command -//! - `engine.commands` — every command with enablement and params docs +//! - `engine.commands {search?}` — every command with enablement and params docs //! - `document.inspect`, `sheet.read {range?}`, `cell.get {cell?}` — read state //! - `ui.inspect` — UI state (ribbon tab, editor, dialog, grid geometry, perf) //! - `ui.click {x, y, button?, count?, shift?, cmd?, alt?}`, `ui.drag {x, y, toX, toY}`, @@ -144,6 +144,17 @@ fn push_drag(app: &mut SheetApp, a: egui::Pos2, b: egui::Pos2, steps: usize, m: app.synthetic.push_back(vec![egui::Event::PointerButton { pos: b, button: egui::PointerButton::Primary, pressed: false, modifiers: m }]); } +/// Keeps the commands whose id, label or ribbon path contains `search` (case-insensitive); `None` keeps all. +fn filter_commands(commands: Vec, search: Option<&str>) -> Vec { + let Some(q) = search.map(str::to_ascii_lowercase) else { return commands }; + let has = |c: &Json, k: &str| c.get(k).and_then(Json::as_str).is_some_and(|v| v.to_ascii_lowercase().contains(&q)); + let in_menu = |c: &Json| { + let menu = c.get("menu").and_then(Json::as_array); + menu.is_some_and(|m| m.iter().filter_map(Json::as_str).any(|v| v.to_ascii_lowercase().contains(&q))) + }; + commands.into_iter().filter(|c| has(c, "id") || has(c, "label") || in_menu(c)).collect() +} + pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -> Outcome { let s = |k: &str| p.get(k).and_then(Json::as_str); let f = |k: &str| p.get(k).and_then(Json::as_f64).unwrap_or(0.0) as f32; @@ -166,7 +177,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) - ] { v.push(json!({"id": id, "label": label, "enabled": true, "ui": true})); } - ok(Json::Array(v)) + ok(Json::Array(filter_commands(v, s("search")))) } "engine.journal" => ok(json!(app.session.journal.iter().map(|(id, p)| json!({"command": id, "params": p})).collect::>())), "document.inspect" | "sheet.read" | "cell.get" | "selection.stats" => wrap(app.session.run(method, p.clone())), @@ -430,3 +441,30 @@ pub fn save_image(image: &egui::ColorImage, path: Option<&str>) -> Json { None => json!({"ok": true, "result": {"base64": gridcraft_engine::io::base64_encode(&png), "width": w, "height": h}}), } } + +#[cfg(test)] +mod tests { + use super::*; + + fn sample() -> Vec { + vec![ + json!({"id": "home.borders", "label": "Borders", "menu": ["Home", "Font"]}), + json!({"id": "home.bold", "label": "Bold", "menu": ["Home", "Font"]}), + json!({"id": "ui.ribbonTab", "label": "Ribbon Tab", "enabled": true, "ui": true}), + ] + } + + fn ids(v: &[Json]) -> Vec<&str> { + v.iter().filter_map(|c| c.get("id").and_then(Json::as_str)).collect() + } + + #[test] + fn search_filters_case_insensitively() { + assert_eq!(ids(&filter_commands(sample(), Some("border"))), ["home.borders"]); + assert_eq!(filter_commands(sample(), Some("BORDER")), filter_commands(sample(), Some("border"))); + assert_eq!(ids(&filter_commands(sample(), Some("ribbon"))), ["ui.ribbonTab"]); + assert_eq!(ids(&filter_commands(sample(), Some("font"))), ["home.borders", "home.bold"]); + assert!(filter_commands(sample(), Some("catalog_no_match_7391")).is_empty()); + assert_eq!(filter_commands(sample(), None).len(), 3); + } +} diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 9c6c31b..6a7b966 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -20,7 +20,7 @@ Errors come back as `{"id": …, "ok": false, "error": "…"}`. | Method | Params | What it does | |---|---|---| | `engine.execute` | `{command, params}` | Runs any engine command (see `engine.commands` or `gridcraft-cli commands`) | -| `engine.commands` | | Every command: id, label, ribbon path, shortcut, params doc, enabled | +| `engine.commands` | `{search?}` | Every command: id, label, ribbon path, shortcut, params doc, enabled; `search` keeps those whose id, label or ribbon path contains it (case-insensitive) | | `engine.journal` | | Commands run so far (replayable) | | `document.inspect` | | Workbook summary: sheets, used ranges, tables, charts, names, selection, undo labels | | `sheet.read` | `{range?, sheet?, formulas?, formatted?}` | Values (or formulas / displayed text) of a range |