diff --git a/crates/ui-egui/src/shortcut_editor.rs b/crates/ui-egui/src/shortcut_editor.rs index 4e02180017..26606c2763 100644 --- a/crates/ui-egui/src/shortcut_editor.rs +++ b/crates/ui-egui/src/shortcut_editor.rs @@ -765,12 +765,10 @@ fn chosen_path(items: &[(String, Value)]) -> Option<&str> { items.iter().find(|(_, p)| is_bare(p)).or(items.first()).map(|(path, _)| path.as_str()) } -/// Does a command's params doc (`{a?, b?} …`) say it runs with no params, as a shortcut runs it? +/// Does a command's params doc (`{a?, b?: bool} …`) say it runs with no params, as a shortcut runs +/// it? It does when it starts with `{` and [`names_a_required_field`] finds no field a call must give. fn runs_without_params(params: &str) -> bool { - params - .strip_prefix('{') - .and_then(|r| r.split_once('}')) - .is_some_and(|(fields, _)| fields.split(',').all(|f| f.trim().is_empty() || f.trim_end().ends_with('?'))) + params.starts_with('{') && !names_a_required_field(params) } /// Does the editor list `e` under `overrides`? An entry that [`needs_a_value`] is listed only while @@ -783,8 +781,13 @@ fn listed(overrides: &Overrides, e: &Entry) -> bool { /// with none, and that no item of the menu bar runs: Transparency and Opacity Mask Options need /// a value to set, Align and Distribute a direction, Delete Swatch and Move Swatch a swatch name, /// and the library, data set, text style and SVG filter loaders a file, which their menu items ask -/// for. -const NEEDS_A_PARAM: [&str; 20] = [ +/// for. The commands after them have no menu path: the setters of panels and dialogs (Fill, Stroke +/// Options, Character, Set Bounds), the exports and saves that give the file back without a `path` +/// (Save Document gives the file back when the document has no file yet; File › Save takes a key +/// instead), the undo group's begin and end, and the commands that need a point, a value or a name +/// (Create Dimension, Edit Freeform Point, Select by Color, Move Brush). New Symbol is left out +/// because the Symbols panel runs it through the Symbol Options dialog. +const NEEDS_A_PARAM: [&str; 74] = [ "transparency.set", "transparency.setOpacityMask", "stroke.set", @@ -805,6 +808,60 @@ const NEEDS_A_PARAM: [&str; 20] = [ "paraStyle.load", "textStyle.loadAll", "svgFilter.import", + "addon.pathtools.setAnchor", + "addon.shapes.configure", + "appearance.addEffect", + "artboard.align", + "artboard.distribute", + "assets.export", + "assets.export3D", + "assets.settings.set", + "attributes.set", + "brush.move", + "css.export", + "dimension.create", + "document.export", + "document.exportDxf", + "document.exportEps", + "document.exportForWeb", + "document.exportPdf", + "document.save", + "document.setup", + "edit.undoGroup.begin", + "edit.undoGroup.end", + "eyedropper.setOptions", + "file.package", + "library.createGroup", + "links.placementOptions", + "links.unembed", + "livePaint.gapOptions", + "object.blend.createSpine", + "object.blend.setEasing", + "object.intertwine.arrange", + "object.objectsOnPath.options", + "object.puppetWarp.pins", + "object.setBounds", + "object.setLiveShape", + "object.setOverprint", + "object.setProps", + "object.slice.select", + "paint.freeform.setPoint", + "paint.setFill", + "paint.setGradientGeom", + "paint.setStroke", + "perspective.grid.define", + "perspective.grid.set", + "perspective.plane.move", + "print.setup", + "select.color", + "select.globalEditOptions", + "stroke.setAdvanced", + "symbol.new", + "symbol.options", + "text.setFormat", + "text.setRangeStyle", + "text.setStyle", + "type.ruby", ]; /// The (label, params) of each menu bar item, by command id. @@ -907,7 +964,7 @@ fn a_menu_bar_item_runs(id: &str, params: &str) -> bool { /// Is engine command `c` listed in Edit › Keyboard Shortcuts whether or not an item of the menu bar /// runs it? A command with a menu path of its own is. One without (a panel menu item) is when a key /// alone can run it: it has a default key, or it is an undoable command, not an alias, whose params -/// doc ends each field with `?`. +/// doc names no field a call must give ([`runs_without_params`]). fn listed_on_its_own(c: &vectorcraft_engine::CommandSpec) -> bool { !c.menu.is_empty() || c.shortcut.is_some() || (c.journal && !vectorcraft_engine::cmd::is_alias(c.id) && runs_without_params(c.params)) } @@ -2309,11 +2366,39 @@ mod tests { for id in ["brush.duplicate", "path.convertAnchors", "document.json", "prefs.list"] { assert!(entry(id).is_none(), "{id}"); } - for (doc, bare) in - [("{}", true), ("{dx?, dy?} x", true), ("{id?|ids?, clip?}", true), ("{name, newName?}", false), ("{a?: {b}}", false), ("same as", false)] - { + // The Symbols panel runs New Symbol through Symbol Options, so it waits for a key the user gives it. + assert!(entry("symbol.new").is_some_and(|e| !listed(&Overrides::new(), &e))); + for (doc, bare) in [ + ("{}", true), + ("{dx?, dy?} x", true), + ("{id?|ids?, clip?}", true), + ("{name, newName?}", false), + ("{a?: {b}}", true), + ("{ids?|id?, on?: bool} turn it on or off", true), + ("{ids?: [id…] (the selection), anchors?: [index…]}", true), + ("{format?: a|b, size?: 1|3|5}", true), + ("{artboard?: index=0}", true), + ("{mode: a|b, on?: bool}", false), + ("same as", false), + ] { assert_eq!(runs_without_params(doc), bare, "{doc}"); } + // Panel menu commands whose params doc gives each optional field a type are listed too. + let none = Overrides::new(); + for id in [ + "type.fractionalWidths", + "type.systemLayout", + "object.setPixelAligned", + "links.embed", + "links.update", + "object.slice.delete", + "artboard.switchOrientation", + "appearance.targetContents", + "paint.invert", + "artboard.distributeSpacing", + ] { + assert!(entry(id).is_some_and(|e| listed(&none, &e)), "{id}"); + } let mut ov = Overrides::new(); assert!(assign(&mut ov, "symbol.breakLink", Some("Cmd+Alt+Shift+F9"), true).is_ok()); assert_eq!(effective_in(&ov, "symbol.breakLink").as_deref(), Some("Cmd+Alt+Shift+F9")); diff --git a/docs/mcp.md b/docs/mcp.md index 2779d95d44..98b9b7f3cc 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -5728,6 +5728,15 @@ tool's active artboard in place, and the Distribute buttons then send the Distri can then be negative. Align to Selection sends `to: "selection"`. The Distribute buttons need two or more selected artboards with Align to Artboard, and three or more with Align to Selection. +Edit › Keyboard Shortcuts and `shortcuts.list` list an undoable command that is not an alias and has no menu path or +default shortcut, such as a panel menu's Break Link to Symbol (`symbol.breakLink`), when its params doc names no +required field and it is not one of the commands left out below. A field is optional when each of its names ends with +`?`, whatever type follows it (`on?: bool`, `ids?: [id…]`), when its name is followed directly by an `=` default +(`rows=10`), or when it starts with `…`. Such a command is listed in the group named by the first part of its id. +Commands listed this way include `type.fractionalWidths`, `type.systemLayout`, `object.setPixelAligned`, +`links.embed`, `links.update`, `object.slice.delete`, `artboard.switchOrientation`, `artboard.distributeSpacing`, +`appearance.targetContents` and `paint.invert`. + Edit › Keyboard Shortcuts and `shortcuts.list` leave out a command with no default shortcut when a key press cannot run it: no item of the menu bar runs it, it opens no dialog when run without params, and it is one of these: @@ -5737,7 +5746,22 @@ run it: no item of the menu bar runs it, it opens no dialog when run without par - a command that sets nothing or fails without a param: `transparency.set`, `transparency.setOpacityMask`, `stroke.set`, `layer.setProps`, `object.align`, `object.distribute`, `swatch.delete`, `swatch.move`, `swatch.spotOptions`, `edit.colors.adjustBalance`, `swatch.library.load`, `graphicStyle.loadLibrary`, - `symbol.loadLibrary`, `brush.loadLibrary`, `dataset.import` and `variable.loadLibrary`. + `symbol.loadLibrary`, `brush.loadLibrary`, `dataset.import`, `variable.loadLibrary`, `charStyle.load`, + `paraStyle.load`, `textStyle.loadAll` and `svgFilter.import`; +- a command without a menu path that sets nothing, fails or only gives data back without a param: + `addon.pathtools.setAnchor`, `addon.shapes.configure`, `appearance.addEffect`, `artboard.align`, + `artboard.distribute`, `assets.export`, `assets.export3D`, `assets.settings.set`, + `attributes.set`, `brush.move`, `css.export`, `dimension.create`, `document.export`, `document.exportDxf`, + `document.exportEps`, `document.exportForWeb`, `document.exportPdf`, `document.save`, `document.setup`, + `edit.undoGroup.begin`, `edit.undoGroup.end`, `eyedropper.setOptions`, `file.package`, `library.createGroup`, + `links.placementOptions`, `links.unembed`, `livePaint.gapOptions`, `object.blend.createSpine`, + `object.blend.setEasing`, `object.intertwine.arrange`, `object.objectsOnPath.options`, `object.puppetWarp.pins`, + `object.setBounds`, `object.setLiveShape`, `object.setOverprint`, `object.setProps`, `object.slice.select`, + `paint.freeform.setPoint`, `paint.setFill`, `paint.setGradientGeom`, `paint.setStroke`, `perspective.grid.define`, + `perspective.grid.set`, `perspective.plane.move`, `print.setup`, `select.color`, `select.globalEditOptions`, + `stroke.setAdvanced`, `symbol.options`, `text.setFormat`, `text.setRangeStyle`, `text.setStyle` and `type.ruby`; +- `symbol.new`, which the Symbols panel's New Symbol button and its New Symbol… menu item run through the Symbol + Options dialog. They list such a command while the user has changed its shortcut (`overridden: true` in `shortcuts.list`), and `shortcuts.set` can change or clear it. When every menu bar item that runs a command gives it the same params, a