Honor search in the desktop engine.commands handler so connected list_commands filters (#136)

This commit is contained in:
MarekSpodBiedry 2026-10-10 19:03:57 +02:00
parent 19094e2d62
commit 2faf8ffc78
2 changed files with 41 additions and 3 deletions

View File

@ -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<Json>, search: Option<&str>) -> Vec<Json> {
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::<Vec<_>>())),
"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<Json> {
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);
}
}

View File

@ -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 |