From b26bd3d47230a9d34c846b79b46ef2eb4649e596 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 5 Nov 2026 19:28:36 -0800 Subject: [PATCH] Reject invalid Guide Lock parameters --- crates/engine/src/cmd/docmenu.rs | 4 ++-- crates/engine/src/tests/menucmds.rs | 17 +++++++++++++++++ docs/mcp.md | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/docmenu.rs b/crates/engine/src/cmd/docmenu.rs index fc146fdec..5b1b4300b 100644 --- a/crates/engine/src/cmd/docmenu.rs +++ b/crates/engine/src/cmd/docmenu.rs @@ -14,7 +14,7 @@ use super::pathops::Beside; use super::select::{first_selectable, is_selectable, selectable_ids, walk_selectable}; use super::{ CommandSpec, EngineError, Point, Result, Session, bad, bool_or, checked_ids_param, cmd, f64_or, f64_req, has_doc, has_selection, id_param, ok, - str_param, + str_param, switch_or, }; pub fn specs() -> Vec { @@ -716,7 +716,7 @@ fn release_guides(s: &mut Session, p: &Value) -> Result { } fn lock_guides(s: &mut Session, p: &Value) -> Result { - let v = p.get("locked").and_then(Value::as_bool).unwrap_or(!s.menu.guides_locked); + let v = switch_or(p, "locked", !s.menu.guides_locked, "view.guides.lock")?; s.menu.guides_locked = v; // Locked guides can't stay selected, ruler guides or guide paths, in any open document // ([`Session::select`] lets those go in the active one). diff --git a/crates/engine/src/tests/menucmds.rs b/crates/engine/src/tests/menucmds.rs index 450baabc1..82dc07e55 100644 --- a/crates/engine/src/tests/menucmds.rs +++ b/crates/engine/src/tests/menucmds.rs @@ -3539,6 +3539,23 @@ fn ruler_guides_and_lock() { assert!(s.execute("guide.remove", &json!({"index": 0})).is_err()); } +#[test] +fn invalid_guide_lock_parameters_keep_guides_selected() { + let mut s = session(); + s.execute("guide.add", &json!({"vertical": false, "pos": 50})).unwrap(); + s.execute("guide.select", &json!({"indexes": [0]})).unwrap(); + let before_undo = s.doc().unwrap().history.undo.len(); + for params in [json!({"locked": "false"}), json!({"locked": 1})] { + assert!(s.execute("view.guides.lock", ¶ms).is_err(), "accepted {params}"); + assert!(!s.guides_locked(), "locked guides for {params}"); + assert_eq!(s.doc().unwrap().selection.guides, vec![0], "cleared selection for {params}"); + assert_eq!(s.doc().unwrap().history.undo.len(), before_undo, "recorded undo for {params}"); + } + // Left out, or null as for every command, it toggles. + assert_eq!(s.execute("view.guides.lock", &json!(null)).unwrap(), json!({"locked": true})); + assert_eq!(s.execute("view.guides.lock", &json!({"locked": null})).unwrap(), json!({"locked": false})); +} + /// #1534: Lock Guides locks the paths made into guides too: they can't be selected (so neither /// moved, reshaped nor deleted), by a click, a marquee or Select All, and locking lets them go. #[test] diff --git a/docs/mcp.md b/docs/mcp.md index 4934da0c7..87ac5085c 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1631,6 +1631,7 @@ step. With guides selected, `edit.clear` (Delete) deletes them and `object.nudge View › Guides › Lock Guides (`view.guides.lock`) deselects them and the paths made into guides, in every open document, and refuses these commands until unlocked. While it is on, a command that selects art, such as `edit.undo`, `edit.redo`, `object.exitIsolation`, `object.unlockAll`, `object.showAll` or `edit.paste`, leaves the guides out. +`view.guides.lock` rejects a `locked` value that is not a boolean. Omitting `locked` toggles the current value. `view.guides.release {index?, ids?}` (View › Guides › Release Guides) turns guides into paths → `{count}`. With `index` it releases that ruler guide, and with `ids` it releases those guide paths (and the guide paths inside them).