keymap: import-only paths on Bindable

Bindable::import_paths (with_import_path) are read by import matching
like menu paths, and make no areas or groups in the dialog, so a tool's
["Tools", label] path no longer shows a Tools Menu entry.
This commit is contained in:
aiongg committed 2026-10-20 20:59:43 +00:00
1 parent 02ba09ee9a
commit 636fc8b850
5 files changed
+50 -4

No files matched your search

+3 -1
View File
@@ -242,7 +242,9 @@ assert_eq!((report.unmatched[0].item.as_str(), report.unmatched[0].line), ("File
it `in_panel_menu()`. Tool entries match only tools.
- Names are compared after `import::normalize_label`: case, `…`, `...`, `&`, colons and extra
spaces aside, so the alias `Preferences: General…` matches `Edit > Preferences > General...`.
Aliases (`Bindable::with_alias`) count wherever labels do.
Aliases (`Bindable::with_alias`) count wherever labels do, and import paths
(`Bindable::with_import_path`, such as `["Tools", label]` for a tool) wherever menu paths do;
the dialog makes no areas or groups from import paths.
- Panel menus: sources list panel-menu items with the panel's name in front (InDesign's Panel
Menus area writes `Character: All Caps`; Illustrator and Photoshop sets have similar areas).
The reader splits the name into segments and marks the entry `ForeignEntry::in_panel_menu()`.
+7 -3
View File
@@ -860,7 +860,7 @@ impl Names {
for name in &names {
add(&mut self.anywhere, name.clone(), i);
}
for path in &b.paths {
for path in b.paths.iter().chain(&b.import_paths) {
let mut segs: Vec<String> = path.iter().map(|s| normalize_label(s)).collect();
if let [_, .., last] = segs.as_mut_slice() {
*last = form(last);
@@ -936,7 +936,7 @@ impl Index {
ix.tool.push(matches!(b.kind, Kind::Tool { .. }));
ix.keys.insert(b.command.key(), i);
ix.names.add(i, b, str::to_string);
ix.segments.extend(b.paths.iter().flatten().map(|s| normalize_label(s)));
ix.segments.extend(b.paths.iter().chain(&b.import_paths).flatten().map(|s| normalize_label(s)));
if b.is_toggle {
ix.toggles.add(i, b, toggle_name);
}
@@ -1000,7 +1000,11 @@ fn suggest(keymap: &Keymap, entries: &[ToSuggest]) -> Vec<Suggestion> {
let candidates: Vec<namematch::Candidate> = pool
.iter()
.map(|b| {
let labels = std::iter::once(&b.label).chain(&b.aliases).chain(b.paths.iter().filter_map(|p| p.last())).cloned().collect();
let labels = std::iter::once(&b.label)
.chain(&b.aliases)
.chain(b.paths.iter().chain(&b.import_paths).filter_map(|p| p.last()))
.cloned()
.collect();
namematch::Candidate { id: b.command.id.clone(), labels }
})
.collect();
+11
View File
@@ -141,6 +141,10 @@ pub struct Bindable {
/// Menu paths, the main one first; used by search and import.
#[serde(default)]
pub paths: Vec<Vec<String>>,
/// Paths only import matching reads: where foreign sets file the command and the app's menus
/// do not, such as `["Tools", label]` for a tool. They make no areas or groups in the dialog.
#[serde(default)]
pub import_paths: Vec<Vec<String>>,
/// Other names: labels in other UI languages, older names.
#[serde(default)]
pub aliases: Vec<String>,
@@ -163,6 +167,7 @@ impl Bindable {
command: command.into(),
label: label.into(),
paths: Vec::new(),
import_paths: Vec::new(),
aliases: Vec::new(),
scope,
kind: Kind::Command,
@@ -192,6 +197,12 @@ impl Bindable {
self
}
/// Adds a path only import matching reads ([`Bindable::import_paths`]).
pub fn with_import_path(mut self, path: Vec<String>) -> Bindable {
self.import_paths.push(path);
self
}
pub fn with_alias(mut self, alias: impl Into<String>) -> Bindable {
self.aliases.push(alias.into());
self
+15
View File
@@ -607,6 +607,21 @@ pub(crate) mod tests {
Ok(())
}
#[test]
fn import_paths_make_no_areas_or_groups() {
let tool = |id: &str, label: &str| {
Bindable::new(id, label, Scope::app())
.with_kind(Kind::Tool { group: None })
.with_import_path(vec!["Tools".into(), label.into()])
.with_default(key("P"))
};
let km = Keymap::new(vec![tool("tool.pen", "Pen Tool")], Scopes::new(), Platform::Mac);
let o = Options::default();
let names: Vec<String> = areas(&km, &o, &English).into_iter().map(|a| a.label).collect();
assert_eq!(names, ["All Commands", "Tools"]);
assert_eq!(lines(&km, &o, &English, "", AreaId::All, Filter::All), ["# Tools", "Pen Tool: P"]);
}
#[test]
fn hidden_entries_are_not_listed_but_still_conflict() -> Result<(), crate::AssignError> {
let mut km = fixture(Platform::Mac);
+14
View File
@@ -676,6 +676,20 @@ fn aliases_match_like_labels() {
assert!(r.unmatched.is_empty(), "{:?}", r.unmatched);
}
#[test]
fn import_paths_match_like_menu_paths() {
let catalog = vec![
command("object.color", "Apply Color", &["Object", "Apply Color"], &[]),
command("color.applyColor", "Apply Color", &[], &[]).with_import_path(vec!["Tools".into(), "Apply Color".into()]),
];
let d = "Default";
let entries = vec![win("Tools", &["Apply Color"], &[(d, "Ctrl+,")])];
let (km, r) = import_into(catalog, entries, &default_context());
has(&km, "color.applyColor", &["Cmd+,"]);
has(&km, "object.color", &[]);
assert!(r.unmatched.is_empty(), "{:?}", r.unmatched);
}
#[test]
fn show_and_hide_are_read_off_only_for_toggles() {
let catalog = vec![