keymap_editor: Fix incorrect keystroke being reported (#36998)

This PR fixes two bugs and also changes one behavior in the **Keymap
Editor**.

As shown in the video, when I press `ctrl-shift-2` in the Keymap Editor,
the first keystroke is displayed as `ctrl-shift-@`, which is incorrect.
On macOS and Linux, it should be `ctrl-@`.



https://github.com/user-attachments/assets/69cfcfa0-b422-45d6-8e69-80f8608180fd



Also, after pressing `ctrl-shift-2` and then releasing `2` and `ctrl`, a
`shift` keystroke was incorrectly added.



https://github.com/user-attachments/assets/892124fd-847d-4fde-9b20-a27ba49ac934



Now, when you enter a sequence like `+ctrl+alt-alt+f` in the Keymap
Editor, it will output `ctrl-f` instead of `ctrl-alt-f`, matching VS
Code’s behavior.


Release Notes:

- Fixed incorrect keystroke reporting in the Keymap Editor.
This commit is contained in:
张小白
2025-08-28 15:16:13 +08:00
committed by GitHub
parent 78c2f1621d
commit 38e5c8fb66
2 changed files with 137 additions and 19 deletions
+7
View File
@@ -4578,6 +4578,13 @@ impl Window {
}
None
}
/// For testing: set the current modifier keys state.
/// This does not generate any events.
#[cfg(any(test, feature = "test-support"))]
pub fn set_modifiers(&mut self, modifiers: Modifiers) {
self.modifiers = modifiers;
}
}
// #[derive(Clone, Copy, Eq, PartialEq, Hash)]
@@ -303,17 +303,12 @@ impl KeystrokeInput {
return;
}
let mut keystroke =
let keystroke =
KeybindingKeystroke::new(keystroke.clone(), false, cx.keyboard_mapper().as_ref());
if let Some(last) = self.keystrokes.last()
&& last.display_key.is_empty()
&& (!self.search || self.previous_modifiers.modified())
{
let display_key = keystroke.display_key.clone();
let inner_key = keystroke.inner.key.clone();
keystroke = last.clone();
keystroke.display_key = display_key;
keystroke.inner.key = inner_key;
self.keystrokes.pop();
}
@@ -329,18 +324,19 @@ impl KeystrokeInput {
return;
}
self.keystrokes.push(keystroke.clone());
self.keystrokes.push(keystroke);
self.keystrokes_changed(cx);
// The reason we use the real modifiers from the window instead of the keystroke's modifiers
// is that for keystrokes like `ctrl-$` the modifiers reported by keystroke is `ctrl` which
// is wrong, it should be `ctrl-shift`. The window's modifiers are always correct.
let real_modifiers = window.modifiers();
if self.search {
self.previous_modifiers = keystroke.display_modifiers;
self.previous_modifiers = real_modifiers;
return;
}
if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX
&& keystroke.display_modifiers.modified()
{
self.keystrokes
.push(Self::dummy(keystroke.display_modifiers));
if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX && real_modifiers.modified() {
self.keystrokes.push(Self::dummy(real_modifiers));
}
}
@@ -718,8 +714,11 @@ mod tests {
// Combine current modifiers with keystroke modifiers
keystroke.modifiers |= self.current_modifiers;
let real_modifiers = keystroke.modifiers;
keystroke = to_gpui_keystroke(keystroke);
self.update_input(|input, window, cx| {
window.set_modifiers(real_modifiers);
input.handle_keystroke(&keystroke, window, cx);
});
@@ -747,6 +746,7 @@ mod tests {
};
self.update_input(|input, window, cx| {
window.set_modifiers(new_modifiers);
input.on_modifiers_changed(&event, window, cx);
});
@@ -954,6 +954,100 @@ mod tests {
}
}
/// For GPUI, when you press `ctrl-shift-2`, it produces `ctrl-@` without the shift modifier.
fn to_gpui_keystroke(mut keystroke: Keystroke) -> Keystroke {
if keystroke.modifiers.shift {
match keystroke.key.as_str() {
"`" => {
keystroke.key = "~".into();
keystroke.modifiers.shift = false;
}
"1" => {
keystroke.key = "!".into();
keystroke.modifiers.shift = false;
}
"2" => {
keystroke.key = "@".into();
keystroke.modifiers.shift = false;
}
"3" => {
keystroke.key = "#".into();
keystroke.modifiers.shift = false;
}
"4" => {
keystroke.key = "$".into();
keystroke.modifiers.shift = false;
}
"5" => {
keystroke.key = "%".into();
keystroke.modifiers.shift = false;
}
"6" => {
keystroke.key = "^".into();
keystroke.modifiers.shift = false;
}
"7" => {
keystroke.key = "&".into();
keystroke.modifiers.shift = false;
}
"8" => {
keystroke.key = "*".into();
keystroke.modifiers.shift = false;
}
"9" => {
keystroke.key = "(".into();
keystroke.modifiers.shift = false;
}
"0" => {
keystroke.key = ")".into();
keystroke.modifiers.shift = false;
}
"-" => {
keystroke.key = "_".into();
keystroke.modifiers.shift = false;
}
"=" => {
keystroke.key = "+".into();
keystroke.modifiers.shift = false;
}
"[" => {
keystroke.key = "{".into();
keystroke.modifiers.shift = false;
}
"]" => {
keystroke.key = "}".into();
keystroke.modifiers.shift = false;
}
"\\" => {
keystroke.key = "|".into();
keystroke.modifiers.shift = false;
}
";" => {
keystroke.key = ":".into();
keystroke.modifiers.shift = false;
}
"'" => {
keystroke.key = "\"".into();
keystroke.modifiers.shift = false;
}
"," => {
keystroke.key = "<".into();
keystroke.modifiers.shift = false;
}
"." => {
keystroke.key = ">".into();
keystroke.modifiers.shift = false;
}
"/" => {
keystroke.key = "?".into();
keystroke.modifiers.shift = false;
}
_ => {}
}
}
keystroke
}
struct KeystrokeUpdateTracker {
initial_keystrokes: Vec<KeybindingKeystroke>,
_subscription: Subscription,
@@ -1057,7 +1151,15 @@ mod tests {
.send_events(&["+cmd", "shift-f", "-cmd"])
// In search mode, when completing a modifier-only keystroke with a key,
// only the original modifiers are preserved, not the keystroke's modifiers
.expect_keystrokes(&["cmd-f"]);
//
// Update:
// This behavior was changed to preserve all modifiers in search mode, this is now reflected in the expected keystrokes.
// Specifically, considering the sequence: `+cmd +shift -shift 2`, we expect it to produce the same result as `+cmd +shift 2`
// which is `cmd-@`. But in the case of `+cmd +shift -shift 2`, the keystroke we receive is `cmd-2`, which means that
// we need to dynamically map the key from `2` to `@` when the shift modifier is not present, which is not possible.
// Therefore, we now preserve all modifiers in search mode to ensure consistent behavior.
// And also, VSCode seems to preserve all modifiers in search mode as well.
.expect_keystrokes(&["cmd-shift-f"]);
}
#[gpui::test]
@@ -1234,7 +1336,7 @@ mod tests {
.await
.with_search_mode(true)
.send_events(&["+ctrl", "+shift", "-shift", "a", "-ctrl"])
.expect_keystrokes(&["ctrl-shift-a"]);
.expect_keystrokes(&["ctrl-a"]);
}
#[gpui::test]
@@ -1342,7 +1444,7 @@ mod tests {
.await
.with_search_mode(true)
.send_events(&["+ctrl+alt", "-ctrl", "j"])
.expect_keystrokes(&["ctrl-alt-j"]);
.expect_keystrokes(&["alt-j"]);
}
#[gpui::test]
@@ -1364,11 +1466,11 @@ mod tests {
.send_events(&["+ctrl+alt", "-ctrl", "+shift"])
.expect_keystrokes(&["ctrl-shift-alt-"])
.send_keystroke("j")
.expect_keystrokes(&["ctrl-shift-alt-j"])
.expect_keystrokes(&["shift-alt-j"])
.send_keystroke("i")
.expect_keystrokes(&["ctrl-shift-alt-j", "shift-alt-i"])
.expect_keystrokes(&["shift-alt-j", "shift-alt-i"])
.send_events(&["-shift-alt", "+cmd"])
.expect_keystrokes(&["ctrl-shift-alt-j", "shift-alt-i", "cmd-"]);
.expect_keystrokes(&["shift-alt-j", "shift-alt-i", "cmd-"]);
}
#[gpui::test]
@@ -1401,4 +1503,13 @@ mod tests {
.send_events(&["+ctrl", "-ctrl", "+alt", "-alt", "+shift", "-shift"])
.expect_empty();
}
#[gpui::test]
async fn test_not_search_shifted_keys(cx: &mut TestAppContext) {
init_test(cx)
.await
.with_search_mode(false)
.send_events(&["+ctrl", "+shift", "4", "-all"])
.expect_keystrokes(&["ctrl-$"]);
}
}