fix(shortcuts): Clear and reassign also drop a command's panel copy of its key (part of #1333)

This commit is contained in:
Marco Arnulfo committed 2026-10-12 17:44:34 +02:00
1 parent 0879bd7a7e
commit 528e525d67
2 files changed
+56 -2

No files matched your search

+20 -2
View File
@@ -737,7 +737,8 @@ impl Shortcuts {
let nb = Binding { command: command.into(), keys: chord.canonical(), panel: panel.map(str::to_string) };
let before = self.snapshot();
if !add {
self.bindings.retain(|b| !(same_shortcut_command(&b.command, command) && b.context() == nb.context()));
let copies = if nb.panel.is_none() { self.app_chords(command, None) } else { Vec::new() };
self.bindings.retain(|b| !(same_shortcut_command(&b.command, command) && (b.context() == nb.context() || is_copy(b, &copies))));
}
let mut reassigned = Vec::new();
if !keep_conflicts {
@@ -769,6 +770,7 @@ impl Shortcuts {
let before = self.snapshot();
let n0 = self.bindings.len();
let twins = shortcut_twins(command);
let copies = if panel == Some(None) { self.app_chords(command, chord.as_ref()) } else { Vec::new() };
self.bindings.retain(|b| {
let cmd = same_shortcut_command(&b.command, command);
let key_ok = chord.as_ref().is_none_or(|c| b.chord().as_ref() == Some(c));
@@ -778,7 +780,7 @@ impl Shortcuts {
let panel_ok = match &panel {
None => true,
Some(p) if p.is_none() && twins => true,
Some(p) => &b.panel == p,
Some(p) => &b.panel == p || is_copy(b, &copies),
};
!(cmd && key_ok && panel_ok)
});
@@ -916,6 +918,16 @@ impl Shortcuts {
Ok(name)
}
/// The application-wide keys of `command` (only `key`, when given).
fn app_chords(&self, command: &str, key: Option<&Chord>) -> Vec<Chord> {
self.bindings
.iter()
.filter(|b| same_shortcut_command(&b.command, command) && b.panel.is_none())
.filter_map(Binding::chord)
.filter(|c| key.is_none_or(|k| k == c))
.collect()
}
// ---------------------------------------------------------------- queries
pub fn for_command(&self, id: &str) -> Vec<&Binding> {
@@ -1017,6 +1029,12 @@ fn same_shortcut_command(bound: &str, target: &str) -> bool {
bound == target || (shortcut_twins(bound) && shortcut_twins(target))
}
/// A panel binding on one of the command's application keys (New Bin's Cmd+B in the Project
/// panel) is a copy of that shortcut, so editing the application shortcut edits it too (#1333).
fn is_copy(b: &Binding, app_chords: &[Chord]) -> bool {
b.panel.is_some() && b.chord().is_some_and(|c| app_chords.contains(&c))
}
fn check_panel(panel: Option<&str>) -> std::result::Result<Option<&'static str>, String> {
match panel {
None => Ok(None),
+36
View File
@@ -133,6 +133,42 @@ fn clear_close_sequence_also_clears_file_close() {
assert_eq!(s.shortcuts.resolve(&w, None, Platform::current()).unwrap().command, "sequence.addEdit");
}
#[test]
fn clear_new_bin_frees_cmd_b_in_the_project_panel_too() {
// #1333: New Bin has Cmd+B application-wide and again in the Project panel. The editor's Clear
// sends panel Application, so only the first went; the Project panel kept Cmd+B.
let dir = temp_dir("new-bin");
let mut s = Session::default();
s.shortcuts.set_dir(&dir);
let b = Chord::parse("Cmd+B").unwrap();
let p = Platform::current();
assert_eq!(s.shortcuts.resolve(&b, Some("Project"), p).unwrap().command, "file.newBin");
s.execute("shortcuts.clear", json!({"command": "file.newBin", "panel": "Application"})).unwrap();
assert!(s.shortcuts.for_command("file.newBin").is_empty(), "{:?}", s.shortcuts.for_command("file.newBin"));
s.execute("shortcuts.set", json!({"command": "sequence.addEdit", "keys": "Cmd+B"})).unwrap();
let mut t = Shortcuts::new();
t.set_dir(&dir);
for sc in [&s.shortcuts, &t] {
assert_eq!(sc.resolve(&b, None, p).unwrap().command, "sequence.addEdit");
assert_eq!(sc.resolve(&b, Some("Project"), p).unwrap().command, "sequence.addEdit");
assert!(sc.for_command("file.newBin").is_empty());
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn reassigning_new_bin_moves_its_project_panel_key_too() {
// #1333: assigning New Bin another key replaced Cmd+B application-wide only.
let mut s = Session::default();
s.execute("shortcuts.set", json!({"command": "file.newBin", "keys": "Cmd+Alt+B"})).unwrap();
let keys: Vec<(&str, Option<&str>)> = s.shortcuts.for_command("file.newBin").iter().map(|b| (b.keys.as_str(), b.panel.as_deref())).collect();
assert_eq!(keys, [("Cmd+Alt+B", None)]);
assert!(s.shortcuts.resolve(&Chord::parse("Cmd+B").unwrap(), Some("Project"), Platform::Mac).is_none());
// a panel key of its own (History ▸ Left for Undo) is not a copy of the application one: it stays
s.execute("shortcuts.set", json!({"command": "edit.undo", "keys": "Cmd+Alt+Z"})).unwrap();
assert!(s.shortcuts.for_command("edit.undo").iter().any(|b| b.panel.as_deref() == Some("History") && b.keys == "Left"));
}
#[test]
fn panel_shortcuts_override_application_ones_with_focus() {
let s = Session::default();