From a14abc317703426a82ea588f4bff62a68b21fabd Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 15 Oct 2026 10:13:12 -0700 Subject: [PATCH] Control bar: buttons isolate the selected object and exit isolation mode The Control bar had no isolation controls. In isolation mode it now shows an Exit Isolation Mode button (`object.exitIsolation`), except while a pattern is edited: the isolation bar's Done and Cancel end that edit. Outside isolation mode, with one group, compound path, path, image, mesh or placed file selected, it shows an Isolate Selected Object button (`object.isolate`). After a Direct Selection click on a path inside a group, the button isolates that path. An envelope's contents get no button: for them the Control bar shows the envelope's Edit Envelope and Edit Contents buttons. The button offers the kinds of object that the Selection tool's double-click isolates. The double-click's check moves unchanged into `vectorcraft_tools::select::isolates`, and both use it. Docs: docs/target-app-parity.md. --- crates/tools/src/select.rs | 18 ++--- crates/ui-egui/src/chrome.rs | 101 ++++++++++++++++++++++++++++ crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + docs/target-app-parity.md | 1 + 18 files changed, 127 insertions(+), 8 deletions(-) diff --git a/crates/tools/src/select.rs b/crates/tools/src/select.rs index 928268931..d956bae82 100644 --- a/crates/tools/src/select.rs +++ b/crates/tools/src/select.rs @@ -279,14 +279,8 @@ impl Tool for SelectionTool { if cx.doc.node(top).is_some_and(|n| matches!(n.kind, vectorcraft_doc::NodeKind::Text(_))) { return vec![Action::SwitchTool("type".into())]; } - // A group, a compound path or a single path, image, mesh or placed file, not - // the one already isolated. Blends, envelopes, repeats and symbol instances - // aren't: they have editing modes of their own. - use vectorcraft_doc::NodeKind as K; - let isolates = cx.doc.node(top).is_some_and(|n| { - matches!(n.kind, K::Group { .. } | K::Compound { .. } | K::Path { .. } | K::Image(_) | K::Mesh(_) | K::PlacedDocument(_)) - }); - if cx.double_click_isolate && isolates && cx.isolation != Some(top) { + // An object that [`isolates`], not the one already isolated. + if cx.double_click_isolate && cx.doc.node(top).is_some_and(|n| isolates(&n.kind)) && cx.isolation != Some(top) { return vec![Action::Exec("object.isolate".into(), json!({ "id": top.0 }))]; } } else if cx.isolation.is_some() { @@ -568,6 +562,14 @@ pub(crate) fn is_area_type(n: &vectorcraft_doc::Node) -> bool { && matches!(&n.kind, vectorcraft_doc::NodeKind::Text(t) if matches!(t.kind, vectorcraft_doc::TextKind::Area { .. })) } +/// Does a double-click or the Control bar's Isolate Selected Object button isolate an object of +/// this kind? A group, a compound path or a single path, image, mesh or placed file does. Blends, +/// envelopes, repeats and symbol instances do not; they have editing modes of their own. +pub fn isolates(kind: &vectorcraft_doc::NodeKind) -> bool { + use vectorcraft_doc::NodeKind as K; + matches!(kind, K::Group { .. } | K::Compound { .. } | K::Path { .. } | K::Image(_) | K::Mesh(_) | K::PlacedDocument(_)) +} + /// The resize cursor for handle `h` of a box turned by `angle` (counter-clockwise degrees): the /// handle that sits where `h` appears on screen, in 45° steps. fn handle_cursor(h: Handle, angle: f64) -> Cursor { diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index dc04bf9bf..8b40ae8ac 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -208,6 +208,36 @@ pub fn anchor_buttons(app: &mut VectorcraftApp, ui: &mut Ui, controls: AnchorCon } } +/// The id of the Control bar's isolation button. +const ISOLATION_BUTTON: &str = "cb-isolation"; + +/// The Control bar's isolation button. In isolation mode it is Exit Isolation Mode, except while a +/// pattern is edited (the isolation bar's Done and Cancel end that). Otherwise, with one object +/// selected that [`vectorcraft_tools::select::isolates`], it is Isolate Selected Object, which +/// also isolates a path inside a group that Direct Selection selected. An envelope's contents +/// get no button: the envelope's Edit Envelope and Edit Contents buttons show instead. +fn isolation_button(app: &mut VectorcraftApp, ui: &mut Ui) { + let Some(st) = app.session.active() else { return }; + let (icon, tip, id) = if st.isolation.is_some() { + if st.doc.pattern_edit.is_some() { + return; + } + ("chevron-left", tl!("Exit Isolation Mode"), "object.exitIsolation") + } else if let [one] = st.selection.objects[..] + && st.doc.node(one).is_some_and(|n| vectorcraft_tools::select::isolates(&n.kind)) + && vectorcraft_doc::live::envelope_of(&st.doc, one).is_none() + { + ("square-dashed", tl!("Isolate Selected Object"), "object.isolate") + } else { + return; + }; + let (rect, _) = ui.allocate_exact_size(vec2(24.0, 24.0), Sense::hover()); + let resp = ui.interact(rect, egui::Id::new(ISOLATION_BUTTON), Sense::click()); + if widgets::paint_icon_button(ui, resp, icon, tip, false).clicked() { + crate::menus::invoke(app, id, json!({})); + } +} + /// The room the Control bar's inline X/Y/W/H fields need; with less, only the Transform link shows. const INLINE_TRANSFORM_WIDTH: f32 = 440.0; @@ -252,6 +282,7 @@ pub fn control_bar(app: &mut VectorcraftApp, ui: &mut Ui) { if anchors != AnchorControls::None { anchor_buttons(app, ui, anchors); } + isolation_button(app, ui); ui.add_space(6.0); // An image or an Image Trace object shows its own controls in place of Fill and Stroke. let image = crate::place::control_bar_details(app, ui) || crate::panels::image_trace::control_bar(app, ui); @@ -1158,6 +1189,76 @@ mod tests { assert!(title(&s).ends_with("(RGB/Preview)")); } + /// One headless frame of the Control bar; returns the isolation button's rectangle, if it shows. + fn isolation_button(app: &mut crate::VectorcraftApp, ctx: &egui::Context, events: Vec) -> Option { + let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1600.0, 300.0)); + let mut out = ctx.run_ui(egui::RawInput { screen_rect: Some(screen), events, ..Default::default() }, |ui| super::control_bar(app, ui)); + out.textures_delta.clear(); + ctx.viewport(|vp| vp.prev_pass.widgets.get(egui::Id::new(super::ISOLATION_BUTTON)).map(|w| w.rect)) + } + + /// The Control bar's Isolate Selected Object isolates a path inside a group that Direct + /// Selection selected, and Exit Isolation Mode leaves with the path selected. Type, symbol + /// instances, blends, two objects and an envelope's contents get no isolate button, and pattern + /// editing gets no exit button. + #[test] + fn the_control_bar_isolates_the_selected_object_and_exits_isolation_mode() { + use vectorcraft_doc::NodeId; + let mut app = crate::VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.new", json!({"width": 400, "height": 200})).unwrap(); + let rect = |app: &mut crate::VectorcraftApp, x: f64| { + app.run("shape.rectangle", json!({"x": x, "y": 10, "width": 30, "height": 20})).unwrap()["id"].as_u64().unwrap() + }; + let (a, b) = (rect(&mut app, 10.0), rect(&mut app, 50.0)); + app.run("select.set", json!({"ids": [a, b]})).unwrap(); + app.run("object.group", json!({})).unwrap(); + app.select_tool("directSelection"); + app.run("select.anchors", json!({"id": a, "anchors": [[0, 0]]})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let click = |app: &mut crate::VectorcraftApp| { + let r = isolation_button(app, &ctx, vec![]).expect("the bar shows the isolation button"); + let press = |pressed| egui::Event::PointerButton { + pos: r.center(), + button: egui::PointerButton::Primary, + pressed, + modifiers: Default::default(), + }; + for e in [egui::Event::PointerMoved(r.center()), press(true), press(false)] { + isolation_button(app, &ctx, vec![e]); + } + }; + click(&mut app); + assert_eq!(app.session.active().unwrap().isolation, Some(NodeId(a)), "Isolate Selected Object isolates the path"); + click(&mut app); + let st = app.session.active().unwrap(); + assert_eq!((st.isolation, st.selection.objects.clone()), (None, vec![NodeId(a)]), "Exit Isolation Mode selects the path"); + app.select_tool("selection"); + // Type, a symbol instance, a blend and two objects. + let text = app.run("text.create", json!({"x": 10, "y": 100, "text": "Hi"})).unwrap()["id"].as_u64().unwrap(); + let c = rect(&mut app, 100.0); + app.run("select.set", json!({"ids": [c]})).unwrap(); + let symbol = app.run("symbol.new", json!({"name": "Box"})).unwrap()["id"].as_u64().unwrap(); + let (d, e) = (rect(&mut app, 150.0), rect(&mut app, 250.0)); + let blend = app.run("object.blend.make", json!({"ids": [d, e], "steps": 1})).unwrap()["id"].as_u64().unwrap(); + for ids in [json!([text]), json!([symbol]), json!([blend]), json!([text, symbol])] { + app.run("select.set", json!({ "ids": ids })).unwrap(); + assert_eq!(isolation_button(&mut app, &ctx, vec![]), None, "{ids}"); + } + // An envelope's contents, while Edit Contents is on. + let g = rect(&mut app, 350.0); + app.run("select.set", json!({"ids": [g]})).unwrap(); + app.run("object.envelope.makeWithWarp", json!({})).unwrap(); + app.run("object.envelope.editContents", json!({"editing": true})).unwrap(); + assert_eq!(app.session.active().unwrap().selection.objects, vec![NodeId(g)]); + assert_eq!(isolation_button(&mut app, &ctx, vec![]), None, "the envelope's controls show instead"); + let f = rect(&mut app, 300.0); + app.run("select.set", json!({"ids": [f]})).unwrap(); + app.run("object.pattern.make", json!({"name": "Dots", "width": 20, "height": 20})).unwrap(); + assert!(app.session.active().unwrap().isolation.is_some()); + assert_eq!(isolation_button(&mut app, &ctx, vec![]), None, "the isolation bar ends pattern editing"); + } + /// The Zoom tool's hint follows Performance › Animated Zoom (#394): a drag zooms as it goes, /// or (off) zooms into the area dragged across. #[test] diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 86badfdfc..44a75eb6c 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -665,6 +665,7 @@ Auto Color Automatická barva Automatic Automaticky Isolate Selected Group Izolovat vybranou skupinu + Isolate Selected Object Izolovat vybraný objekt Exit Isolation Mode Ukončit izolovaný režim Make Clipping Mask Vytvořit ořezovou masku Release Clipping Mask Uvolnit ořezovou masku diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 7692adb70..ac6a1adb1 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3320,6 +3320,7 @@ imageTrace Outline Kontur to set spacing and orientation legt Abstand und Ausrichtung fest : : Isolate Selected Group Ausgewählte Gruppe isolieren + Isolate Selected Object Ausgewähltes Objekt isolieren Release Clipping Mask Schnittmaske zurückwandeln Make Compound Path Zusammengesetzten Pfad erstellen Release Compound Path Zusammengesetzten Pfad zurückwandeln diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index c31f03400..a8fa9ee26 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3419,6 +3419,7 @@ imageTrace Outline Contorno to set spacing and orientation para definir espaciado y orientación : : Isolate Selected Group Aislar grupo seleccionado + Isolate Selected Object Aislar objeto seleccionado Release Clipping Mask Soltar máscara de recorte Make Compound Path Crear trazado compuesto Release Compound Path Soltar trazado compuesto diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 081925708..a94c06e41 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3416,6 +3416,7 @@ imageTrace Outline Contour to set spacing and orientation pour définir l’espacement et l’orientation :  : Isolate Selected Group Isoler le groupe sélectionné + Isolate Selected Object Isoler l’objet sélectionné Release Clipping Mask Annuler le masque d’écrêtage Make Compound Path Créer un tracé transparent Release Compound Path Annuler le tracé transparent diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 8ad3f6b00..661c699aa 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1746,6 +1746,7 @@ Isolate Blending Keverés elkülönítése Isolate Group Csoport elkülönítése Isolate Selected Group Kijelölt csoport elkülönítése + Isolate Selected Object Kijelölt objektum elkülönítése Italian Olasz Italic Dőlt Its graphics, colors and text styles are deleted from this machine. A grafikái, színei és szövegstílusai törlődnek erről a gépről. diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index a0360a01c..aa7ee5ec2 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3421,6 +3421,7 @@ imageTrace Outline Contorno to set spacing and orientation per impostare spaziatura e orientamento : : Isolate Selected Group Isola gruppo selezionato + Isolate Selected Object Isola oggetto selezionato Release Clipping Mask Rilascia maschera di ritaglio Make Compound Path Crea tracciato composto Release Compound Path Rilascia tracciato composto diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 2c5caebf5..ca806f2ff 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -670,6 +670,7 @@ Auto Color 自動カラー Automatic 自動 Isolate Selected Group 選択したグループを分離 + Isolate Selected Object 選択したオブジェクトを分離 Exit Isolation Mode 分離モードを終了 Make Clipping Mask クリッピングマスクを作成 Release Clipping Mask クリッピングマスクを解除 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index a9a69ffe4..892027449 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -668,6 +668,7 @@ Auto Color 자동 색상 Automatic 자동 Isolate Selected Group 선택한 그룹 격리 + Isolate Selected Object 선택한 개체 격리 Exit Isolation Mode 격리 모드 종료 Make Clipping Mask 클리핑 마스크 만들기 Release Clipping Mask 클리핑 마스크 해제 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 9dc5940a2..99edaaf7f 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -612,6 +612,7 @@ Auto Color Kolor automatyczny Automatic Automatycznie Isolate Selected Group Izoluj zaznaczoną grupę + Isolate Selected Object Izoluj zaznaczony obiekt Exit Isolation Mode Wyjdź z trybu izolacji Make Clipping Mask Utwórz maskę przycinającą Release Clipping Mask Uwolnij maskę przycinającą diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 6134770d7..84adeccfb 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1179,6 +1179,7 @@ Isolate Blending Isolar Mesclagem Isolate Group Isolar Grupo Isolate Selected Group Isolar Grupo Selecionado + Isolate Selected Object Isolar Objeto Selecionado Italian Italiano Join Unir Join Our Discord Junte-se ao nosso Discord diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1aac992e8..0da866fc0 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3417,6 +3417,7 @@ imageTrace Outline Контур to set spacing and orientation для задания интервала и ориентации : : Isolate Selected Group Изолировать выбранную группу + Isolate Selected Object Изолировать выбранный объект Release Clipping Mask Освободить маску обрезки Make Compound Path Создать составной контур Release Compound Path Освободить составной контур diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 1d51cec36..7f051faac 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3414,6 +3414,7 @@ imageTrace Outline Контур to set spacing and orientation для завдання інтервалу та орієнтації : : Isolate Selected Group Ізолювати обрану групу + Isolate Selected Object Ізолювати обраний об’єкт Release Clipping Mask Звільнити відсічну маску Make Compound Path Створити складений контур Release Compound Path Звільнити складений контур diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 12f9c97cc..84ca83fc4 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1608,6 +1608,7 @@ Isolate Blending Tách biệt hòa trộn Isolate Group Tách biệt nhóm Isolate Selected Group Tách biệt nhóm đã chọn + Isolate Selected Object Tách biệt đối tượng đã chọn Isometric Bottom Đẳng góc dưới Isometric Left Đẳng góc trái Isometric Right Đẳng góc phải diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index b9eb52569..2d30f84fb 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3276,6 +3276,7 @@ imageTrace Outline 轮廓 to set spacing and orientation 可设置间距与方向 : : Isolate Selected Group 隔离选择的编组 + Isolate Selected Object 隔离选择的对象 Release Clipping Mask 释放剪切蒙版 Make Compound Path 制作复合路径 Release Compound Path 释放复合路径 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index f435042f0..83216fe94 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3407,6 +3407,7 @@ imageTrace Outline 輪廓 to set spacing and orientation 可設定間距與方向 : : Isolate Selected Group 隔離選取的群組 + Isolate Selected Object 隔離選取的物件 Release Clipping Mask 釋放剪裁遮色片 Make Compound Path 製作複合路徑 Release Compound Path 釋放複合路徑 diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 6d26eea20..6ebddb89f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -429,6 +429,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Dialogs open with their first field focused and its value selected: type a value and press Enter (Move, Rotate, Scale…). A dialog opened from a menu takes the keyboard focus every time it opens, not only the first (#1367), and one whose first field starts greyed out (Blend Options on Smooth Color, Layers Panel Options) gives the focus to that field once it is enabled (#1395). Number fields in panels, the Control bar and dialogs (preferences too) step with Up/Down, or with the mouse wheel over the focused field (#485: a notch a step; the panel under it then stays put, and an unfocused field leaves the wheel to scrolling) (Shift: ten, Ctrl/Cmd: a tenth of their unit), applied at once. - Scrubby labels (#400, beyond Illustrator, as in Photoshop): dragging a numeric field's label left or right (the ↔ cursor shows over it) steps the value one unit every 2 points (Shift: ten, Ctrl/Cmd: a tenth; counts stay whole), in the field's unit and within its limits, applied live as the art follows; the whole drag is one undo step and Escape puts the value back. A click on the label focuses the field; the text box itself never scrubs, and a field with no label doesn't. Text labels before a field (Transform, Properties, Control bar, Stroke, Transparency, form rows in dialogs) and the Character and Paragraph panels' icons act as labels. Preferences › General › Scrub Numeric Fields by Dragging (on by default) turns it off. - The Control bar's underlined Transform link opens the Transform panel in a popover under it (reference point, X/Y/W/H, Rotate and Shear with their icons and angle presets, flips, shape properties, Scale Corners and Scale Strokes & Effects), as its Stroke link does the Stroke panel; X/Y/W/H also show inline while the bar has room. The reference point locator is nine squares, the outer eight joined around the box. + - In isolation mode the Control bar has an Exit Isolation Mode button (`object.exitIsolation`), except while a pattern is edited. Outside isolation mode, with one group, compound path, path, image, mesh or placed file selected, it has an Isolate Selected Object button (`object.isolate`), which also isolates a path inside a group that Direct Selection selected. - Tab shows and hides the panels (in the Type tool it types a tab) without moving the keyboard focus onto a field, so the next shortcut or letter still works; in dialogs and panel fields it moves between fields. - Cmd/Ctrl+A follows the keyboard: in a panel, dialog or search field it selects the field's text, while the Type tool edits it selects all of that text (also `select.all` over MCP), and only otherwise every object. On macOS the menu bar's Select All, Cut, Copy and Paste (keys or clicks) act on a focused field too, and elsewhere the in-window menus' do: the field that had the keyboard when the menu opened gets them, with its selection, and the keyboard back (#516). Not yet: Select All across threaded frames (one frame's text is selected). - Shortcut sets other apps save as text (`.kys`) open in Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts › Import… in the desktop app, or from a drop on the window. It matches each entry to a tool, command or panel by name and decodes the file's key and modifier codes where VectorCraft's default shortcuts agree on them; the user answers the rest, and Apply fills the Keyboard Shortcuts dialog's working set. The answers are kept with the preferences for the next import (`shortcutSet.read`, `importMatches.*`).