keymap: add Keymap::conflicts_with and document the edit calls

conflicts_with lists the commands that share one command's chords, with
that command as a, without filtering every conflict. The README shows
set_bindings with one binding for an editor with one shortcut per
command; assign adds a binding.
This commit is contained in:
aiongg committed 2026-10-19 04:23:26 +00:00
1 parent 5208562c28
commit be51941628
3 files changed
+62 -1

No files matched your search

+14 -1
View File
@@ -113,10 +113,23 @@ let mut keymap = Keymap::new(catalogue, Scopes::new(), Platform::current()).with
let _problems = keymap.problems();
// After each change in the shortcut editor:
keymap.assign(&"file.save".into(), Binding::parse("Cmd+Alt+S").unwrap(), Policy::Steal).ok();
keymap.set_bindings(&"file.save".into(), vec![Binding::parse("Cmd+Alt+S").unwrap()], Policy::Steal).ok();
store.update(|p| p.shortcuts = keymap.overrides().clone());
```
## Editing
- `set_bindings(&command, bindings, policy)` replaces a command's bindings. An editor with one
shortcut per command calls it with the one binding (`vec![binding]`); `assign` adds a binding
to those the command has.
- `remove` takes one binding away, `clear` leaves the command without shortcuts, `reset` gives it
its defaults back and `reset_all` does so for every command.
- `Policy` says what happens to other commands holding the chord: `Steal` takes it from them,
`Keep` lets both keep it and reports the clash, `Refuse` changes nothing and returns
`AssignError::Taken`.
- `conflicts()` lists every pair of commands on one chord; `conflicts_with(&command)` lists the
commands that share one command's chords, with that command as `a`.
## Importing a shortcut set
The app's reader turns a file (an InDesign Show Set, an Illustrator `.kys`) into an
+26
View File
@@ -594,6 +594,32 @@ impl Keymap {
out
}
/// The conflicts [`Keymap::conflicts`] reports for one command's bindings, with that command
/// as `a`, in the order of its bindings, then catalogue order. Empty for an unknown command.
pub fn conflicts_with(&self, command: &CommandRef) -> Vec<Conflict> {
let Some(i) = self.find(command) else { return Vec::new() };
let mut out = Vec::new();
for a in &self.current[i] {
let chord = a.0.effective(self.platform);
let Some(holders) = self.by_trigger.get(&chord) else { continue };
for &(j, k) in holders {
if j == i || self.same_group(i, j) {
continue;
}
let Some(b) = self.at(j, k) else { continue };
let Some(clash) = self.conflict_relation(&a.1, &b.1) else { continue };
out.push(Conflict {
chord,
a: (self.catalogue[i].command.clone(), self.public(i, a)),
b: (self.catalogue[j].command.clone(), self.public(j, b)),
kind: if clash { ConflictKind::Clash } else { ConflictKind::Shadow },
in_defaults: self.defaults[i].contains(a) && self.defaults[j].contains(b),
});
}
}
out
}
/// What is wrong with the stored overrides: load notes, unknown commands, texts that do not
/// parse, reserved chords, unknown scopes, duplicate entries.
pub fn problems(&self) -> Vec<Problem> {
+22
View File
@@ -246,6 +246,28 @@ fn conflicts_tell_clashes_from_shadows() {
assert!(c("edit.copy", "color.copy") < c("edit.copy", "timeline.copy"), "{order:?}");
assert!(c("edit.copy", "timeline.copy") < c("color.copy", "timeline.copy"), "{order:?}");
// One command's conflicts, with that command as `a`.
let with = |id: &str| -> Vec<(String, String, ConflictKind)> {
km.conflicts_with(&cmd(id)).into_iter().map(|c| (c.chord.to_string(), c.b.0.id, c.kind)).collect()
};
let w = |chord: &str, other: &str, kind| (chord.to_string(), other.to_string(), kind);
assert_eq!(with("edit.copy"), [w("Cmd+C", "color.copy", ConflictKind::Clash), w("Cmd+C", "timeline.copy", ConflictKind::Shadow)]);
assert_eq!(with("edit.cut"), [w("Cmd+X", "text.cut", ConflictKind::Shadow), w("Cmd+D", "edit.find", ConflictKind::Clash)]);
assert!(with("nope").is_empty());
for entry in km.catalogue() {
let id = entry.command.id.as_str();
let mut direct: Vec<_> = km.conflicts_with(&entry.command).into_iter().map(|c| (c.chord, c.a.0.id, c.b.0.id, c.kind, c.in_defaults)).collect();
let mut filtered: Vec<_> = km
.conflicts()
.into_iter()
.filter(|c| c.a.0.id == id || c.b.0.id == id)
.map(|c| if c.a.0.id == id { (c.chord, c.a.0.id, c.b.0.id, c.kind, c.in_defaults) } else { (c.chord, c.b.0.id, c.a.0.id, c.kind, c.in_defaults) })
.collect();
direct.sort();
filtered.sort();
assert_eq!(direct, filtered, "{id}");
}
let strict = Keymap::new(km.catalogue().to_vec(), scopes().with_panel_fallthrough(false), Platform::Mac);
assert!(!strict.conflicts().iter().any(|c| c.chord.to_string() == "Cmd+R"));
}