From 345377544b27bb63592dd625d83858c95b49c7f8 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 14:14:11 +0800 Subject: [PATCH 01/35] init --- crates/gpui/src/platform/keystroke.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 24601eefd6de45..2bdbe628fe2c73 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -5,6 +5,17 @@ use std::{ fmt::{Display, Write}, }; +/// TODO: +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct KeystrokeDisplay { + /// TODO: + pub inner: Keystroke, + /// TODO: + pub modifiers: Modifiers, + /// TODO: + pub key: String, +} + /// A keystroke and associated metadata generated by the platform #[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] pub struct Keystroke { From 49278ceec0f292d15f186a6d10c3a7758200f6ef Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 14:45:37 +0800 Subject: [PATCH 02/35] add tests --- crates/gpui/src/platform/keystroke.rs | 274 ++++++++++++++++++++++++-- 1 file changed, 263 insertions(+), 11 deletions(-) diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 2bdbe628fe2c73..f05005ea7bfb0c 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -5,17 +5,6 @@ use std::{ fmt::{Display, Write}, }; -/// TODO: -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct KeystrokeDisplay { - /// TODO: - pub inner: Keystroke, - /// TODO: - pub modifiers: Modifiers, - /// TODO: - pub key: String, -} - /// A keystroke and associated metadata generated by the platform #[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] pub struct Keystroke { @@ -35,6 +24,17 @@ pub struct Keystroke { pub key_char: Option, } +/// TODO: +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct KeystrokeDisplay { + /// TODO: + pub inner: Keystroke, + /// TODO: + pub modifiers: Modifiers, + /// TODO: + pub key: String, +} + /// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use /// markdown to display it. #[derive(Debug)] @@ -275,6 +275,34 @@ impl Keystroke { } self } + + fn into_shifted(self) -> Self { + let Keystroke { + modifiers, + key, + key_char, + } = self; + let (key, modifiers) = into_shifted_key(key, modifiers); + Self { + key, + modifiers, + key_char, + } + } +} + +impl KeystrokeDisplay { + /// TODO: + pub fn parse(source: &str) -> std::result::Result { + let keystroke = Keystroke::parse(source)?; + let (key, modifiers) = to_unshifted_key(&keystroke.key, &keystroke.modifiers); + let inner = keystroke.into_shifted(); + Ok(KeystrokeDisplay { + inner, + key, + modifiers, + }) + } } fn is_printable_key(key: &str) -> bool { @@ -611,3 +639,227 @@ pub struct Capslock { #[serde(default)] pub on: bool, } + +fn to_unshifted_key(key: &str, modifiers: &Modifiers) -> (String, Modifiers) { + let mut modifiers = modifiers.clone(); + match key { + "~" => { + modifiers.shift = true; + ("`".to_string(), modifiers) + } + "!" => { + modifiers.shift = true; + ("1".to_string(), modifiers) + } + "@" => { + modifiers.shift = true; + ("2".to_string(), modifiers) + } + "#" => { + modifiers.shift = true; + ("3".to_string(), modifiers) + } + "$" => { + modifiers.shift = true; + ("4".to_string(), modifiers) + } + "%" => { + modifiers.shift = true; + ("5".to_string(), modifiers) + } + "^" => { + modifiers.shift = true; + ("6".to_string(), modifiers) + } + "&" => { + modifiers.shift = true; + ("7".to_string(), modifiers) + } + "*" => { + modifiers.shift = true; + ("8".to_string(), modifiers) + } + "(" => { + modifiers.shift = true; + ("9".to_string(), modifiers) + } + ")" => { + modifiers.shift = true; + ("0".to_string(), modifiers) + } + "_" => { + modifiers.shift = true; + ("-".to_string(), modifiers) + } + "+" => { + modifiers.shift = true; + ("=".to_string(), modifiers) + } + "{" => { + modifiers.shift = true; + ("[".to_string(), modifiers) + } + "}" => { + modifiers.shift = true; + ("]".to_string(), modifiers) + } + "|" => { + modifiers.shift = true; + ("\\".to_string(), modifiers) + } + ":" => { + modifiers.shift = true; + (";".to_string(), modifiers) + } + "\"" => { + modifiers.shift = true; + ("'".to_string(), modifiers) + } + "<" => { + modifiers.shift = true; + (",".to_string(), modifiers) + } + ">" => { + modifiers.shift = true; + (">".to_string(), modifiers) + } + "?" => { + modifiers.shift = true; + ("/".to_string(), modifiers) + } + _ => (key.to_string(), modifiers), + } +} + +fn into_shifted_key(key: String, mut modifiers: Modifiers) -> (String, Modifiers) { + if !modifiers.shift { + (key, modifiers) + } else { + match key.as_str() { + "`" => { + modifiers.shift = false; + ("~".to_string(), modifiers) + } + "1" => { + modifiers.shift = false; + ("!".to_string(), modifiers) + } + "2" => { + modifiers.shift = false; + ("@".to_string(), modifiers) + } + "3" => { + modifiers.shift = false; + ("#".to_string(), modifiers) + } + "4" => { + modifiers.shift = false; + ("$".to_string(), modifiers) + } + "5" => { + modifiers.shift = false; + ("%".to_string(), modifiers) + } + "6" => { + modifiers.shift = false; + ("^".to_string(), modifiers) + } + "7" => { + modifiers.shift = false; + ("&".to_string(), modifiers) + } + "8" => { + modifiers.shift = false; + ("*".to_string(), modifiers) + } + "9" => { + modifiers.shift = false; + ("(".to_string(), modifiers) + } + "0" => { + modifiers.shift = false; + (")".to_string(), modifiers) + } + "-" => { + modifiers.shift = false; + ("_".to_string(), modifiers) + } + "=" => { + modifiers.shift = false; + ("+".to_string(), modifiers) + } + "[" => { + modifiers.shift = false; + ("{".to_string(), modifiers) + } + "]" => { + modifiers.shift = false; + ("}".to_string(), modifiers) + } + "\\" => { + modifiers.shift = false; + ("|".to_string(), modifiers) + } + ";" => { + modifiers.shift = false; + (":".to_string(), modifiers) + } + "'" => { + modifiers.shift = false; + ("\"".to_string(), modifiers) + } + "," => { + modifiers.shift = false; + ("<".to_string(), modifiers) + } + "." => { + modifiers.shift = false; + (">".to_string(), modifiers) + } + "/" => { + modifiers.shift = false; + ("?".to_string(), modifiers) + } + _ => (key, modifiers), + } + } +} + +#[cfg(test)] +mod tests { + use crate::{Keystroke, KeystrokeDisplay, Modifiers}; + + #[test] + fn test_parsing_keystroke_on_windows() { + // On windows, users prefer to use "ctrl-shift-key", so here we support + // both "ctrl-$" and "ctrl-shift-4" + let source = "ctrl-$"; + let keystroke = Keystroke::parse(source).unwrap(); + assert_eq!(keystroke.modifiers, Modifiers::control()); + assert_eq!(keystroke.key, "$"); + assert_eq!(keystroke.key_char, None); + + let keystroke_display = KeystrokeDisplay::parse(source).unwrap(); + assert_eq!(keystroke_display.inner, keystroke); + assert_eq!(keystroke_display.key, "4"); + assert_eq!(keystroke_display.modifiers, Modifiers::control_shift()); + + let source = "ctrl-shift-4"; + let keystroke = Keystroke::parse(source).unwrap(); + assert_eq!(keystroke.modifiers, Modifiers::control_shift()); + assert_eq!(keystroke.key, "4"); + assert_eq!(keystroke.key_char, None); + + let keystroke_display = KeystrokeDisplay::parse(source).unwrap(); + assert_eq!( + keystroke_display.inner, + Keystroke { + modifiers: Modifiers::control(), + key: "$".to_string(), + key_char: None + } + ); + assert_eq!(keystroke_display.key, "4"); + assert_eq!(keystroke_display.modifiers, Modifiers::control_shift()); + } +} From f602c1d69e9dea4a35848b6ed696d9ccea430098 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 19:11:58 +0800 Subject: [PATCH 03/35] wip --- crates/gpui/src/keymap.rs | 7 +- crates/gpui/src/keymap/binding.rs | 20 +- crates/gpui/src/platform/keystroke.rs | 185 +++++++++++------- crates/settings/src/keymap_file.rs | 13 +- .../src/ui_components/keystroke_input.rs | 2 +- crates/ui/src/components/keybinding.rs | 106 ++++++---- .../zed/src/zed/quick_action_bar/preview.rs | 5 +- 7 files changed, 209 insertions(+), 129 deletions(-) diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index 757205fcc3d2a8..b3db09d8214d18 100644 --- a/crates/gpui/src/keymap.rs +++ b/crates/gpui/src/keymap.rs @@ -4,7 +4,7 @@ mod context; pub use binding::*; pub use context::*; -use crate::{Action, Keystroke, is_no_action}; +use crate::{Action, AsKeystroke, Keystroke, is_no_action}; use collections::{HashMap, HashSet}; use smallvec::SmallVec; use std::any::TypeId; @@ -141,7 +141,7 @@ impl Keymap { /// only. pub fn bindings_for_input( &self, - input: &[Keystroke], + input: &[impl AsKeystroke], context_stack: &[KeyContext], ) -> (SmallVec<[KeyBinding; 1]>, bool) { let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new(); @@ -192,7 +192,6 @@ impl Keymap { (bindings, !pending.is_empty()) } - /// Check if the given binding is enabled, given a certain key context. /// Returns the deepest depth at which the binding matches, or None if it doesn't match. fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option { @@ -639,7 +638,7 @@ mod tests { fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) { let actual = keymap .bindings_for_action(action) - .map(|binding| binding.keystrokes[0].unparse()) + .map(|binding| binding.keystrokes[0].inner.unparse()) .collect::>(); assert_eq!(actual, expected, "{:?}", action); } diff --git a/crates/gpui/src/keymap/binding.rs b/crates/gpui/src/keymap/binding.rs index 729498d153b62b..730da7fe27b4eb 100644 --- a/crates/gpui/src/keymap/binding.rs +++ b/crates/gpui/src/keymap/binding.rs @@ -2,13 +2,16 @@ use std::rc::Rc; use collections::HashMap; -use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke, SharedString}; +use crate::{ + Action, AsKeystroke, InvalidKeystrokeError, KeyBindingContextPredicate, KeybindingKeystroke, + Keystroke, SharedString, +}; use smallvec::SmallVec; /// A keybinding and its associated metadata, from the keymap. pub struct KeyBinding { pub(crate) action: Box, - pub(crate) keystrokes: SmallVec<[Keystroke; 2]>, + pub(crate) keystrokes: SmallVec<[KeybindingKeystroke; 2]>, pub(crate) context_predicate: Option>, pub(crate) meta: Option, /// The json input string used when building the keybinding, if any @@ -45,7 +48,7 @@ impl KeyBinding { ) -> std::result::Result { let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes .split_whitespace() - .map(Keystroke::parse) + .map(|source| Keystroke::parse(source).map(|keystroke| keystroke.into_shifted())) .collect::>()?; if let Some(equivalents) = key_equivalents { @@ -58,6 +61,11 @@ impl KeyBinding { } } + let keystrokes = keystrokes + .into_iter() + .map(KeybindingKeystroke::new) + .collect(); + Ok(Self { keystrokes, action, @@ -79,13 +87,13 @@ impl KeyBinding { } /// Check if the given keystrokes match this binding. - pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option { + pub fn match_keystrokes(&self, typed: &[impl AsKeystroke]) -> Option { if self.keystrokes.len() < typed.len() { return None; } for (target, typed) in self.keystrokes.iter().zip(typed.iter()) { - if !typed.should_match(target) { + if !typed.as_keystroke().should_match(target) { return None; } } @@ -94,7 +102,7 @@ impl KeyBinding { } /// Get the keystrokes associated with this binding - pub fn keystrokes(&self) -> &[Keystroke] { + pub fn keystrokes(&self) -> &[KeybindingKeystroke] { self.keystrokes.as_slice() } diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index f05005ea7bfb0c..fcd70a57e610a1 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -5,6 +5,12 @@ use std::{ fmt::{Display, Write}, }; +/// TODO: +pub trait AsKeystroke { + /// TODO: + fn as_keystroke(&self) -> &Keystroke; +} + /// A keystroke and associated metadata generated by the platform #[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] pub struct Keystroke { @@ -25,8 +31,8 @@ pub struct Keystroke { } /// TODO: -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct KeystrokeDisplay { +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeybindingKeystroke { /// TODO: pub inner: Keystroke, /// TODO: @@ -69,7 +75,7 @@ impl Keystroke { /// /// This method assumes that `self` was typed and `target' is in the keymap, and checks /// both possibilities for self against the target. - pub fn should_match(&self, target: &Keystroke) -> bool { + pub fn should_match(&self, target: &KeybindingKeystroke) -> bool { #[cfg(not(target_os = "windows"))] if let Some(key_char) = self .key_char @@ -82,7 +88,7 @@ impl Keystroke { ..Default::default() }; - if &target.key == key_char && target.modifiers == ime_modifiers { + if &target.inner.key == key_char && target.inner.modifiers == ime_modifiers { return true; } } @@ -94,12 +100,12 @@ impl Keystroke { .filter(|key_char| key_char != &&self.key) { // On Windows, if key_char is set, then the typed keystroke produced the key_char - if &target.key == key_char && target.modifiers == Modifiers::none() { + if &target.inner.key == key_char && target.inner.modifiers == Modifiers::none() { return true; } } - target.modifiers == self.modifiers && target.key == self.key + target.inner.modifiers == self.modifiers && target.inner.key == self.key } /// key syntax is: @@ -276,7 +282,8 @@ impl Keystroke { self } - fn into_shifted(self) -> Self { + /// TODO: + pub fn into_shifted(self) -> Self { let Keystroke { modifiers, key, @@ -291,17 +298,16 @@ impl Keystroke { } } -impl KeystrokeDisplay { - /// TODO: - pub fn parse(source: &str) -> std::result::Result { - let keystroke = Keystroke::parse(source)?; +impl KeybindingKeystroke { + /// Create a new keybinding keystroke from the given keystroke + pub fn new(keystroke: Keystroke) -> Self { let (key, modifiers) = to_unshifted_key(&keystroke.key, &keystroke.modifiers); let inner = keystroke.into_shifted(); - Ok(KeystrokeDisplay { + KeybindingKeystroke { inner, key, modifiers, - }) + } } } @@ -361,65 +367,15 @@ fn is_printable_key(key: &str) -> bool { impl std::fmt::Display for Keystroke { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.modifiers.control { - #[cfg(target_os = "macos")] - f.write_char('^')?; - - #[cfg(not(target_os = "macos"))] - write!(f, "ctrl-")?; - } - if self.modifiers.alt { - #[cfg(target_os = "macos")] - f.write_char('⌥')?; - - #[cfg(not(target_os = "macos"))] - write!(f, "alt-")?; - } - if self.modifiers.platform { - #[cfg(target_os = "macos")] - f.write_char('⌘')?; - - #[cfg(any(target_os = "linux", target_os = "freebsd"))] - f.write_char('❖')?; - - #[cfg(target_os = "windows")] - f.write_char('⊞')?; - } - if self.modifiers.shift { - #[cfg(target_os = "macos")] - f.write_char('⇧')?; - - #[cfg(not(target_os = "macos"))] - write!(f, "shift-")?; - } - let key = match self.key.as_str() { - #[cfg(target_os = "macos")] - "backspace" => '⌫', - #[cfg(target_os = "macos")] - "up" => '↑', - #[cfg(target_os = "macos")] - "down" => '↓', - #[cfg(target_os = "macos")] - "left" => '←', - #[cfg(target_os = "macos")] - "right" => '→', - #[cfg(target_os = "macos")] - "tab" => '⇥', - #[cfg(target_os = "macos")] - "escape" => '⎋', - #[cfg(target_os = "macos")] - "shift" => '⇧', - #[cfg(target_os = "macos")] - "control" => '⌃', - #[cfg(target_os = "macos")] - "alt" => '⌥', - #[cfg(target_os = "macos")] - "platform" => '⌘', + display_modifiers(&self.modifiers, f)?; + display_key(&self.key, f) + } +} - key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(), - key => return f.write_str(key), - }; - f.write_char(key) +impl std::fmt::Display for KeybindingKeystroke { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + display_modifiers(&self.modifiers, f)?; + display_key(&self.key, f) } } @@ -640,6 +596,18 @@ pub struct Capslock { pub on: bool, } +impl AsKeystroke for Keystroke { + fn as_keystroke(&self) -> &Keystroke { + self + } +} + +impl AsKeystroke for KeybindingKeystroke { + fn as_keystroke(&self) -> &Keystroke { + &self.inner + } +} + fn to_unshifted_key(key: &str, modifiers: &Modifiers) -> (String, Modifiers) { let mut modifiers = modifiers.clone(); match key { @@ -825,9 +793,75 @@ fn into_shifted_key(key: String, mut modifiers: Modifiers) -> (String, Modifiers } } +fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if modifiers.control { + #[cfg(target_os = "macos")] + f.write_char('^')?; + + #[cfg(not(target_os = "macos"))] + write!(f, "ctrl-")?; + } + if modifiers.alt { + #[cfg(target_os = "macos")] + f.write_char('⌥')?; + + #[cfg(not(target_os = "macos"))] + write!(f, "alt-")?; + } + if modifiers.platform { + #[cfg(target_os = "macos")] + f.write_char('⌘')?; + + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + f.write_char('❖')?; + + #[cfg(target_os = "windows")] + f.write_char('⊞')?; + } + if modifiers.shift { + #[cfg(target_os = "macos")] + f.write_char('⇧')?; + + #[cfg(not(target_os = "macos"))] + write!(f, "shift-")?; + } + Ok(()) +} + +fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let key = match key { + #[cfg(target_os = "macos")] + "backspace" => '⌫', + #[cfg(target_os = "macos")] + "up" => '↑', + #[cfg(target_os = "macos")] + "down" => '↓', + #[cfg(target_os = "macos")] + "left" => '←', + #[cfg(target_os = "macos")] + "right" => '→', + #[cfg(target_os = "macos")] + "tab" => '⇥', + #[cfg(target_os = "macos")] + "escape" => '⎋', + #[cfg(target_os = "macos")] + "shift" => '⇧', + #[cfg(target_os = "macos")] + "control" => '⌃', + #[cfg(target_os = "macos")] + "alt" => '⌥', + #[cfg(target_os = "macos")] + "platform" => '⌘', + + key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(), + key => return f.write_str(key), + }; + f.write_char(key) +} + #[cfg(test)] mod tests { - use crate::{Keystroke, KeystrokeDisplay, Modifiers}; + use crate::{KeybindingKeystroke, Keystroke, Modifiers}; #[test] fn test_parsing_keystroke_on_windows() { @@ -839,7 +873,7 @@ mod tests { assert_eq!(keystroke.key, "$"); assert_eq!(keystroke.key_char, None); - let keystroke_display = KeystrokeDisplay::parse(source).unwrap(); + let keystroke_display = KeybindingKeystroke::new(keystroke.clone()); assert_eq!(keystroke_display.inner, keystroke); assert_eq!(keystroke_display.key, "4"); assert_eq!(keystroke_display.modifiers, Modifiers::control_shift()); @@ -850,7 +884,10 @@ mod tests { assert_eq!(keystroke.key, "4"); assert_eq!(keystroke.key_char, None); - let keystroke_display = KeystrokeDisplay::parse(source).unwrap(); + let keystroke = keystroke.into_shifted(); + assert_eq!(keystroke.modifiers, Modifiers::control()); + assert_eq!(keystroke.key, "$"); + let keystroke_display = KeybindingKeystroke::new(keystroke.clone()); assert_eq!( keystroke_display.inner, Keystroke { diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index ae3f42853ac11a..f77b71a6fea689 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -3,7 +3,8 @@ use collections::{BTreeMap, HashMap, IndexMap}; use fs::Fs; use gpui::{ Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, - KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, Keystroke, NoAction, SharedString, + KeyBinding, KeyBindingContextPredicate, KeyBindingMetaIndex, KeybindingKeystroke, Keystroke, + NoAction, SharedString, }; use schemars::{JsonSchema, json_schema}; use serde::Deserialize; @@ -916,7 +917,7 @@ impl<'a> KeybindUpdateOperation<'a> { #[derive(Debug, Clone)] pub struct KeybindUpdateTarget<'a> { pub context: Option<&'a str>, - pub keystrokes: &'a [Keystroke], + pub keystrokes: &'a [KeybindingKeystroke], pub action_name: &'a str, pub action_arguments: Option<&'a str>, } @@ -941,7 +942,7 @@ impl<'a> KeybindUpdateTarget<'a> { fn keystrokes_unparsed(&self) -> String { let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8); for keystroke in self.keystrokes { - keystrokes.push_str(&keystroke.unparse()); + keystrokes.push_str(&keystroke.inner.unparse()); keystrokes.push(' '); } keystrokes.pop(); @@ -1020,7 +1021,7 @@ impl From for KeyBindingMetaIndex { #[cfg(test)] mod tests { - use gpui::Keystroke; + use gpui::{KeybindingKeystroke, Keystroke}; use unindent::Unindent; use crate::{ @@ -1055,10 +1056,10 @@ mod tests { } #[track_caller] - fn parse_keystrokes(keystrokes: &str) -> Vec { + fn parse_keystrokes(keystrokes: &str) -> Vec { keystrokes .split(' ') - .map(|s| Keystroke::parse(s).expect("Keystrokes valid")) + .map(|s| KeybindingKeystroke::new(Keystroke::parse(s).expect("Keystrokes valid"))) .collect() } diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index 1b8010853ecabc..56c31e3157f8f6 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -364,7 +364,7 @@ impl KeystrokeInput { &self.keystrokes }; keystrokes.iter().map(move |keystroke| { - h_flex().children(ui::render_keystroke( + h_flex().children(ui::render_keybinding_keystroke( keystroke, Some(Color::Default), Some(rems(0.875).into()), diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 1e7bb40c400e05..b7f8705c3164da 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,8 +1,8 @@ use crate::PlatformStyle; use crate::{Icon, IconName, IconSize, h_flex, prelude::*}; use gpui::{ - Action, AnyElement, App, FocusHandle, Global, IntoElement, Keystroke, Modifiers, Window, - relative, + Action, AnyElement, App, FocusHandle, Global, IntoElement, KeybindingKeystroke, Modifiers, + Window, relative, }; use itertools::Itertools; @@ -13,7 +13,7 @@ pub struct KeyBinding { /// More than one keystroke produces a chord. /// /// This should always contain at least one keystroke. - pub keystrokes: Vec, + pub keystrokes: Vec, /// The [`PlatformStyle`] to use when displaying this keybinding. platform_style: PlatformStyle, @@ -59,7 +59,7 @@ impl KeyBinding { cx.try_global::().is_some_and(|g| g.0) } - pub fn new(keystrokes: Vec, cx: &App) -> Self { + pub fn new(keystrokes: Vec, cx: &App) -> Self { Self { keystrokes, platform_style: PlatformStyle::platform(), @@ -99,16 +99,16 @@ impl KeyBinding { } fn render_key( - keystroke: &Keystroke, + key: &str, color: Option, platform_style: PlatformStyle, size: impl Into>, ) -> AnyElement { - let key_icon = icon_for_key(keystroke, platform_style); + let key_icon = icon_for_key(key, platform_style); match key_icon { Some(icon) => KeyIcon::new(icon, color).size(size).into_any_element(), None => { - let key = util::capitalize(&keystroke.key); + let key = util::capitalize(key); Key::new(&key, color).size(size).into_any_element() } } @@ -137,7 +137,7 @@ impl RenderOnce for KeyBinding { .py_0p5() .rounded_xs() .text_color(cx.theme().colors().text_muted) - .children(render_keystroke( + .children(render_keybinding_keystroke( keystroke, color, self.size, @@ -148,8 +148,8 @@ impl RenderOnce for KeyBinding { } } -pub fn render_keystroke( - keystroke: &Keystroke, +pub fn render_keybinding_keystroke( + keystroke: &KeybindingKeystroke, color: Option, size: impl Into>, platform_style: PlatformStyle, @@ -163,9 +163,17 @@ pub fn render_keystroke( let size = size.into(); if use_text { - let element = Key::new(keystroke_text(keystroke, platform_style, vim_mode), color) - .size(size) - .into_any_element(); + let element = Key::new( + keystroke_text( + &keystroke.modifiers, + &keystroke.key, + platform_style, + vim_mode, + ), + color, + ) + .size(size) + .into_any_element(); vec![element] } else { let mut elements = Vec::new(); @@ -176,13 +184,13 @@ pub fn render_keystroke( size, true, )); - elements.push(render_key(keystroke, color, platform_style, size)); + elements.push(render_key(&keystroke.key, color, platform_style, size)); elements } } -fn icon_for_key(keystroke: &Keystroke, platform_style: PlatformStyle) -> Option { - match keystroke.key.as_str() { +fn icon_for_key(key: &str, platform_style: PlatformStyle) -> Option { + match key { "left" => Some(IconName::ArrowLeft), "right" => Some(IconName::ArrowRight), "up" => Some(IconName::ArrowUp), @@ -382,27 +390,39 @@ pub fn text_for_action(action: &dyn Action, window: &Window, cx: &App) -> Option Some(text_for_keystrokes(key_binding.keystrokes(), cx)) } -pub fn text_for_keystrokes(keystrokes: &[Keystroke], cx: &App) -> String { +pub fn text_for_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &App) -> String { let platform_style = PlatformStyle::platform(); let vim_enabled = cx.try_global::().is_some(); keystrokes .iter() - .map(|keystroke| keystroke_text(keystroke, platform_style, vim_enabled)) + .map(|keystroke| { + keystroke_text( + &keystroke.modifiers, + &keystroke.key, + platform_style, + vim_enabled, + ) + }) .join(" ") } -pub fn text_for_keystroke(keystroke: &Keystroke, cx: &App) -> String { +pub fn text_for_keystroke(modifiers: &Modifiers, key: &str, cx: &App) -> String { let platform_style = PlatformStyle::platform(); let vim_enabled = cx.try_global::().is_some(); - keystroke_text(keystroke, platform_style, vim_enabled) + keystroke_text(modifiers, key, platform_style, vim_enabled) } /// Returns a textual representation of the given [`Keystroke`]. -fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode: bool) -> String { +fn keystroke_text( + modifiers: &Modifiers, + key: &str, + platform_style: PlatformStyle, + vim_mode: bool, +) -> String { let mut text = String::new(); let delimiter = '-'; - if keystroke.modifiers.function { + if modifiers.function { match vim_mode { false => text.push_str("Fn"), true => text.push_str("fn"), @@ -411,7 +431,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode text.push(delimiter); } - if keystroke.modifiers.control { + if modifiers.control { match (platform_style, vim_mode) { (PlatformStyle::Mac, false) => text.push_str("Control"), (PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Ctrl"), @@ -421,7 +441,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode text.push(delimiter); } - if keystroke.modifiers.platform { + if modifiers.platform { match (platform_style, vim_mode) { (PlatformStyle::Mac, false) => text.push_str("Command"), (PlatformStyle::Mac, true) => text.push_str("cmd"), @@ -434,7 +454,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode text.push(delimiter); } - if keystroke.modifiers.alt { + if modifiers.alt { match (platform_style, vim_mode) { (PlatformStyle::Mac, false) => text.push_str("Option"), (PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Alt"), @@ -444,7 +464,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode text.push(delimiter); } - if keystroke.modifiers.shift { + if modifiers.shift { match (platform_style, vim_mode) { (_, false) => text.push_str("Shift"), (_, true) => text.push_str("shift"), @@ -453,9 +473,9 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode } if vim_mode { - text.push_str(&keystroke.key) + text.push_str(key) } else { - let key = match keystroke.key.as_str() { + let key = match key { "pageup" => "PageUp", "pagedown" => "PageDown", key => &util::capitalize(key), @@ -562,9 +582,11 @@ mod tests { #[test] fn test_text_for_keystroke() { + let keystroke = Keystroke::parse("cmd-c").unwrap(); assert_eq!( keystroke_text( - &Keystroke::parse("cmd-c").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Mac, false ), @@ -572,7 +594,8 @@ mod tests { ); assert_eq!( keystroke_text( - &Keystroke::parse("cmd-c").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Linux, false ), @@ -580,16 +603,19 @@ mod tests { ); assert_eq!( keystroke_text( - &Keystroke::parse("cmd-c").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Windows, false ), "Win-C".to_string() ); + let keystroke = Keystroke::parse("ctrl-alt-delete").unwrap(); assert_eq!( keystroke_text( - &Keystroke::parse("ctrl-alt-delete").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Mac, false ), @@ -597,7 +623,8 @@ mod tests { ); assert_eq!( keystroke_text( - &Keystroke::parse("ctrl-alt-delete").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Linux, false ), @@ -605,16 +632,19 @@ mod tests { ); assert_eq!( keystroke_text( - &Keystroke::parse("ctrl-alt-delete").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Windows, false ), "Ctrl-Alt-Delete".to_string() ); + let keystroke = Keystroke::parse("shift-pageup").unwrap(); assert_eq!( keystroke_text( - &Keystroke::parse("shift-pageup").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Mac, false ), @@ -622,7 +652,8 @@ mod tests { ); assert_eq!( keystroke_text( - &Keystroke::parse("shift-pageup").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Linux, false, ), @@ -630,7 +661,8 @@ mod tests { ); assert_eq!( keystroke_text( - &Keystroke::parse("shift-pageup").unwrap(), + &keystroke.modifiers, + &keystroke.key, PlatformStyle::Windows, false ), diff --git a/crates/zed/src/zed/quick_action_bar/preview.rs b/crates/zed/src/zed/quick_action_bar/preview.rs index 3772104f39050c..fb5a75f78d834a 100644 --- a/crates/zed/src/zed/quick_action_bar/preview.rs +++ b/crates/zed/src/zed/quick_action_bar/preview.rs @@ -72,7 +72,10 @@ impl QuickActionBar { Tooltip::with_meta( tooltip_text, Some(open_action_for_tooltip), - format!("{} to open in a split", text_for_keystroke(&alt_click, cx)), + format!( + "{} to open in a split", + text_for_keystroke(&alt_click.modifiers, &alt_click.key, cx) + ), window, cx, ) From 8e357210f19c75d36ee87bae8b6272d0ff5683ca Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 21:36:56 +0800 Subject: [PATCH 04/35] checkpoint --- crates/editor/src/editor.rs | 2 +- crates/editor/src/element.rs | 10 ++-- crates/settings_ui/src/keybindings.rs | 59 ++++++++++++------- .../src/ui_components/keystroke_input.rs | 40 ++++++++----- crates/ui/src/components/keybinding.rs | 24 ++++++-- 5 files changed, 87 insertions(+), 48 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 29e009fdf8d5a8..3d17fcaaaf38b9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9171,7 +9171,7 @@ impl Editor { max_width: Pixels, cursor_point: Point, style: &EditorStyle, - accept_keystroke: Option<&gpui::Keystroke>, + accept_keystroke: Option<&gpui::KeybindingKeystroke>, _window: &Window, cx: &mut Context, ) -> Option { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4f3580da07750d..91034829f78966 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -43,10 +43,10 @@ use gpui::{ Bounds, ClickEvent, ClipboardItem, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, - Keystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollHandle, - ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, - TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill, + KeybindingKeystroke, Length, ModifiersChangedEvent, MouseButton, MouseClickEvent, + MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, + ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, + Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Window, anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, solid_background, transparent_black, }; @@ -7150,7 +7150,7 @@ fn header_jump_data( pub struct AcceptEditPredictionBinding(pub(crate) Option); impl AcceptEditPredictionBinding { - pub fn keystroke(&self) -> Option<&Keystroke> { + pub fn keystroke(&self) -> Option<&KeybindingKeystroke> { if let Some(binding) = self.0.as_ref() { match &binding.keystrokes() { [keystroke, ..] => Some(keystroke), diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 288f59c8e045be..8d5e0a6faed8a1 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -14,7 +14,7 @@ use gpui::{ Action, AppContext as _, AsyncApp, Axis, ClickEvent, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global, IsZero, KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or}, - KeyContext, Keystroke, MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful, + KeyContext, KeybindingKeystroke,Keystroke, MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful, StyledText, Subscription, Task, TextStyleRefinement, WeakEntity, actions, anchored, deferred, div, }; @@ -174,7 +174,7 @@ impl FilterState { #[derive(Debug, Default, PartialEq, Eq, Clone, Hash)] struct ActionMapping { - keystrokes: Vec, + keystrokes: Vec, context: Option, } @@ -414,12 +414,14 @@ impl Focusable for KeymapEditor { } } /// Helper function to check if two keystroke sequences match exactly -fn keystrokes_match_exactly(keystrokes1: &[Keystroke], keystrokes2: &[Keystroke]) -> bool { +fn keystrokes_match_exactly( + keystrokes1: &[KeybindingKeystroke], + keystrokes2: &[KeybindingKeystroke], +) -> bool { keystrokes1.len() == keystrokes2.len() - && keystrokes1 - .iter() - .zip(keystrokes2) - .all(|(k1, k2)| k1.key == k2.key && k1.modifiers == k2.modifiers) + && keystrokes1.iter().zip(keystrokes2).all(|(k1, k2)| { + k1.inner.key == k2.inner.key && k1.inner.modifiers == k2.inner.modifiers + }) } impl KeymapEditor { @@ -509,7 +511,7 @@ impl KeymapEditor { self.filter_editor.read(cx).text(cx) } - fn current_keystroke_query(&self, cx: &App) -> Vec { + fn current_keystroke_query(&self, cx: &App) -> Vec { match self.search_mode { SearchMode::KeyStroke { .. } => self.keystroke_editor.read(cx).keystrokes().to_vec(), SearchMode::Normal => Default::default(), @@ -530,7 +532,7 @@ impl KeymapEditor { let keystroke_query = keystroke_query .into_iter() - .map(|keystroke| keystroke.unparse()) + .map(|keystroke| keystroke.inner.unparse()) .collect::>() .join(" "); @@ -554,7 +556,7 @@ impl KeymapEditor { async fn update_matches( this: WeakEntity, action_query: String, - keystroke_query: Vec, + keystroke_query: Vec, cx: &mut AsyncApp, ) -> anyhow::Result<()> { let action_query = command_palette::normalize_action_query(&action_query); @@ -604,12 +606,15 @@ impl KeymapEditor { let query = &keystroke_query[query_cursor]; let keystroke = &keystrokes[keystroke_cursor]; let matches = - query.modifiers.is_subset_of(&keystroke.modifiers) - && ((query.key.is_empty() - || query.key == keystroke.key) - && query.key_char.as_ref().is_none_or( - |q_kc| q_kc == &keystroke.key, - )); + query.inner.modifiers.is_subset_of(&keystroke.inner.modifiers) + && ((query.inner.key.is_empty() + || query.inner.key == keystroke.inner.key) + && query.inner + .key_char + .as_ref() + .is_none_or(|q_kc| { + q_kc == &keystroke.inner.key + }); if matches { found_count += 1; query_cursor += 1; @@ -678,7 +683,7 @@ impl KeymapEditor { .map(KeybindSource::from_meta) .unwrap_or(KeybindSource::Unknown); - let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx); + let keystroke_text = ui::text_for_keybinding_keystrokes(key_binding.keystrokes(), cx); let ui_key_binding = ui::KeyBinding::new_from_gpui(key_binding.clone(), cx) .vim_mode(source == KeybindSource::Vim); @@ -1422,7 +1427,7 @@ impl ProcessedBinding { .map(|keybind| keybind.get_action_mapping()) } - fn keystrokes(&self) -> Option<&[Keystroke]> { + fn keystrokes(&self) -> Option<&[KeybindingKeystroke]> { self.ui_key_binding() .map(|binding| binding.keystrokes.as_slice()) } @@ -2220,7 +2225,7 @@ impl KeybindingEditorModal { Ok(action_arguments) } - fn validate_keystrokes(&self, cx: &App) -> anyhow::Result> { + fn validate_keystrokes(&self, cx: &App) -> anyhow::Result> { let new_keystrokes = self .keybind_editor .read_with(cx, |editor, _| editor.keystrokes().to_vec()); @@ -2445,11 +2450,21 @@ impl KeybindingEditorModal { } } -fn remove_key_char(Keystroke { modifiers, key, .. }: Keystroke) -> Keystroke { - Keystroke { +fn remove_key_char( + KeybindingKeystroke { + inner, + modifiers, + key, + }: KeybindingKeystroke, +) -> KeybindingKeystroke { + KeybindingKeystroke { + inner: Keystroke { + modifiers: inner.modifiers, + key: inner.key, + key_char: None, + }, modifiers, key, - ..Default::default() } } diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index 56c31e3157f8f6..b37f6e20f7340f 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -1,6 +1,6 @@ use gpui::{ Animation, AnimationExt, Context, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, - Keystroke, Modifiers, ModifiersChangedEvent, Subscription, Task, actions, + KeybindingKeystroke, Keystroke, Modifiers, ModifiersChangedEvent, Subscription, Task, actions, }; use ui::{ ActiveTheme as _, Color, IconButton, IconButtonShape, IconName, IconSize, Label, LabelSize, @@ -42,8 +42,8 @@ impl PartialEq for CloseKeystrokeResult { } pub struct KeystrokeInput { - keystrokes: Vec, - placeholder_keystrokes: Option>, + keystrokes: Vec, + placeholder_keystrokes: Option>, outer_focus_handle: FocusHandle, inner_focus_handle: FocusHandle, intercept_subscription: Option, @@ -70,7 +70,7 @@ impl KeystrokeInput { const KEYSTROKE_COUNT_MAX: usize = 3; pub fn new( - placeholder_keystrokes: Option>, + placeholder_keystrokes: Option>, window: &mut Window, cx: &mut Context, ) -> Self { @@ -97,7 +97,7 @@ impl KeystrokeInput { } } - pub fn set_keystrokes(&mut self, keystrokes: Vec, cx: &mut Context) { + pub fn set_keystrokes(&mut self, keystrokes: Vec, cx: &mut Context) { self.keystrokes = keystrokes; self.keystrokes_changed(cx); } @@ -106,7 +106,7 @@ impl KeystrokeInput { self.search = search; } - pub fn keystrokes(&self) -> &[Keystroke] { + pub fn keystrokes(&self) -> &[KeybindingKeystroke] { if let Some(placeholders) = self.placeholder_keystrokes.as_ref() && self.keystrokes.is_empty() { @@ -123,11 +123,15 @@ impl KeystrokeInput { &self.keystrokes } - fn dummy(modifiers: Modifiers) -> Keystroke { - Keystroke { + fn dummy(modifiers: Modifiers) -> KeybindingKeystroke { + KeybindingKeystroke { + inner: Keystroke { + modifiers, + key: "".to_string(), + key_char: None, + }, modifiers, key: "".to_string(), - key_char: None, } } @@ -297,7 +301,7 @@ impl KeystrokeInput { return; } - let mut keystroke = keystroke.clone(); + let mut keystroke = KeybindingKeystroke::new(keystroke.clone()); if let Some(last) = self.keystrokes.last() && last.key.is_empty() && (!self.search || self.previous_modifiers.modified()) @@ -809,9 +813,13 @@ mod tests { /// Verifies that the keystrokes match the expected strings #[track_caller] pub fn expect_keystrokes(&mut self, expected: &[&str]) -> &mut Self { - let actual = self - .input - .read_with(&self.cx, |input, _| input.keystrokes.clone()); + let actual: Vec = self.input.read_with(&self.cx, |input, _| { + input + .keystrokes + .iter() + .map(|keystroke| keystroke.inner.clone()) + .collect() + }); Self::expect_keystrokes_equal(&actual, expected); self } @@ -939,7 +947,7 @@ mod tests { } struct KeystrokeUpdateTracker { - initial_keystrokes: Vec, + initial_keystrokes: Vec, _subscription: Subscription, input: Entity, received_keystrokes_updated: bool, @@ -983,8 +991,8 @@ mod tests { ); } - fn keystrokes_str(ks: &[Keystroke]) -> String { - ks.iter().map(|ks| ks.unparse()).join(" ") + fn keystrokes_str(ks: &[KeybindingKeystroke]) -> String { + ks.iter().map(|ks| ks.inner.unparse()).join(" ") } } } diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index b7f8705c3164da..10af95d5cdc7ef 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -1,8 +1,8 @@ use crate::PlatformStyle; use crate::{Icon, IconName, IconSize, h_flex, prelude::*}; use gpui::{ - Action, AnyElement, App, FocusHandle, Global, IntoElement, KeybindingKeystroke, Modifiers, - Window, relative, + Action, AnyElement, App, FocusHandle, Global, IntoElement, KeybindingKeystroke, Keystroke, + Modifiers, Window, relative, }; use itertools::Itertools; @@ -387,10 +387,26 @@ impl KeyIcon { /// Returns a textual representation of the key binding for the given [`Action`]. pub fn text_for_action(action: &dyn Action, window: &Window, cx: &App) -> Option { let key_binding = window.highest_precedence_binding_for_action(action)?; - Some(text_for_keystrokes(key_binding.keystrokes(), cx)) + Some(text_for_keybinding_keystrokes(key_binding.keystrokes(), cx)) } -pub fn text_for_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &App) -> String { +pub fn text_for_keystrokes(keystrokes: &[Keystroke], cx: &App) -> String { + let platform_style = PlatformStyle::platform(); + let vim_enabled = cx.try_global::().is_some(); + keystrokes + .iter() + .map(|keystroke| { + keystroke_text( + &keystroke.modifiers, + &keystroke.key, + platform_style, + vim_enabled, + ) + }) + .join(" ") +} + +pub fn text_for_keybinding_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &App) -> String { let platform_style = PlatformStyle::platform(); let vim_enabled = cx.try_global::().is_some(); keystrokes From 422e67bda42ca8e23094d503326453af5b2a1c7a Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 21:40:01 +0800 Subject: [PATCH 05/35] init `default-windows.json` --- assets/keymaps/default-windows.json | 1193 +++++++++++++++++++++++++++ 1 file changed, 1193 insertions(+) create mode 100644 assets/keymaps/default-windows.json diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json new file mode 100644 index 00000000000000..01c0b4e9696f3e --- /dev/null +++ b/assets/keymaps/default-windows.json @@ -0,0 +1,1193 @@ +[ + // Standard Linux bindings + { + "bindings": { + "home": "menu::SelectFirst", + "shift-pageup": "menu::SelectFirst", + "pageup": "menu::SelectFirst", + "end": "menu::SelectLast", + "shift-pagedown": "menu::SelectLast", + "pagedown": "menu::SelectLast", + "ctrl-n": "menu::SelectNext", + "tab": "menu::SelectNext", + "down": "menu::SelectNext", + "ctrl-p": "menu::SelectPrevious", + "shift-tab": "menu::SelectPrevious", + "up": "menu::SelectPrevious", + "enter": "menu::Confirm", + "ctrl-enter": "menu::SecondaryConfirm", + "ctrl-escape": "menu::Cancel", + "ctrl-c": "menu::Cancel", + "escape": "menu::Cancel", + "alt-shift-enter": "menu::Restart", + "alt-enter": ["picker::ConfirmInput", { "secondary": false }], + "ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }], + "ctrl-shift-w": "workspace::CloseWindow", + "shift-escape": "workspace::ToggleZoom", + "open": "workspace::Open", + "ctrl-o": "workspace::Open", + "ctrl-=": ["zed::IncreaseBufferFontSize", { "persist": false }], + "ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }], + "ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }], + "ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }], + "ctrl-,": "zed::OpenSettings", + "ctrl-q": "zed::Quit", + "f4": "debugger::Start", + "shift-f5": "debugger::Stop", + "ctrl-shift-f5": "debugger::RerunSession", + "f6": "debugger::Pause", + "f7": "debugger::StepOver", + "ctrl-f11": "debugger::StepInto", + "shift-f11": "debugger::StepOut", + "f11": "zed::ToggleFullScreen", + "ctrl-alt-z": "edit_prediction::RateCompletions", + "ctrl-shift-i": "edit_prediction::ToggleMenu", + "ctrl-alt-l": "lsp_tool::ToggleMenu" + } + }, + { + "context": "Picker || menu", + "bindings": { + "up": "menu::SelectPrevious", + "down": "menu::SelectNext" + } + }, + { + "context": "Editor", + "bindings": { + "escape": "editor::Cancel", + "shift-backspace": "editor::Backspace", + "backspace": "editor::Backspace", + "delete": "editor::Delete", + "tab": "editor::Tab", + "shift-tab": "editor::Backtab", + "ctrl-k": "editor::CutToEndOfLine", + "ctrl-k ctrl-q": "editor::Rewrap", + "ctrl-k q": "editor::Rewrap", + "ctrl-backspace": "editor::DeleteToPreviousWordStart", + "ctrl-delete": "editor::DeleteToNextWordEnd", + "cut": "editor::Cut", + "shift-delete": "editor::Cut", + "ctrl-x": "editor::Cut", + "copy": "editor::Copy", + "ctrl-insert": "editor::Copy", + "ctrl-c": "editor::Copy", + "paste": "editor::Paste", + "shift-insert": "editor::Paste", + "ctrl-v": "editor::Paste", + "undo": "editor::Undo", + "ctrl-z": "editor::Undo", + "redo": "editor::Redo", + "ctrl-y": "editor::Redo", + "ctrl-shift-z": "editor::Redo", + "up": "editor::MoveUp", + "ctrl-up": "editor::LineUp", + "ctrl-down": "editor::LineDown", + "pageup": "editor::MovePageUp", + "alt-pageup": "editor::PageUp", + "shift-pageup": "editor::SelectPageUp", + "home": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], + "down": "editor::MoveDown", + "pagedown": "editor::MovePageDown", + "alt-pagedown": "editor::PageDown", + "shift-pagedown": "editor::SelectPageDown", + "end": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": true }], + "left": "editor::MoveLeft", + "right": "editor::MoveRight", + "ctrl-left": "editor::MoveToPreviousWordStart", + "ctrl-right": "editor::MoveToNextWordEnd", + "ctrl-home": "editor::MoveToBeginning", + "ctrl-end": "editor::MoveToEnd", + "shift-up": "editor::SelectUp", + "shift-down": "editor::SelectDown", + "shift-left": "editor::SelectLeft", + "shift-right": "editor::SelectRight", + "ctrl-shift-left": "editor::SelectToPreviousWordStart", + "ctrl-shift-right": "editor::SelectToNextWordEnd", + "ctrl-shift-home": "editor::SelectToBeginning", + "ctrl-shift-end": "editor::SelectToEnd", + "ctrl-a": "editor::SelectAll", + "ctrl-l": "editor::SelectLine", + "ctrl-shift-i": "editor::Format", + "alt-shift-o": "editor::OrganizeImports", + "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], + "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], + "ctrl-alt-space": "editor::ShowCharacterPalette", + "ctrl-;": "editor::ToggleLineNumbers", + "ctrl-'": "editor::ToggleSelectedDiffHunks", + "ctrl-\"": "editor::ExpandAllDiffHunks", + "ctrl-i": "editor::ShowSignatureHelp", + "alt-g b": "git::Blame", + "alt-g m": "git::OpenModifiedFiles", + "menu": "editor::OpenContextMenu", + "shift-f10": "editor::OpenContextMenu", + "ctrl-shift-e": "editor::ToggleEditPrediction", + "f9": "editor::ToggleBreakpoint", + "shift-f9": "editor::EditLogBreakpoint" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "shift-enter": "editor::Newline", + "enter": "editor::Newline", + "ctrl-enter": "editor::NewlineAbove", + "ctrl-shift-enter": "editor::NewlineBelow", + "ctrl-k ctrl-z": "editor::ToggleSoftWrap", + "ctrl-k z": "editor::ToggleSoftWrap", + "find": "buffer_search::Deploy", + "ctrl-f": "buffer_search::Deploy", + "ctrl-h": "buffer_search::DeployReplace", + "ctrl->": "assistant::QuoteSelection", + "ctrl-<": "assistant::InsertIntoEditor", + "ctrl-alt-e": "editor::SelectEnclosingSymbol", + "ctrl-shift-backspace": "editor::GoToPreviousChange", + "ctrl-shift-alt-backspace": "editor::GoToNextChange", + "alt-enter": "editor::OpenSelectionsInMultibuffer" + } + }, + { + "context": "Editor && mode == full && edit_prediction", + "bindings": { + "alt-]": "editor::NextEditPrediction", + "alt-[": "editor::PreviousEditPrediction" + } + }, + { + "context": "Editor && !edit_prediction", + "bindings": { + "alt-\\": "editor::ShowEditPrediction" + } + }, + { + "context": "Editor && mode == auto_height", + "bindings": { + "ctrl-enter": "editor::Newline", + "shift-enter": "editor::Newline", + "ctrl-shift-enter": "editor::NewlineBelow" + } + }, + { + "context": "Markdown", + "bindings": { + "copy": "markdown::Copy", + "ctrl-c": "markdown::Copy" + } + }, + { + "context": "Editor && jupyter && !ContextEditor", + "bindings": { + "ctrl-shift-enter": "repl::Run", + "ctrl-alt-enter": "repl::RunInPlace" + } + }, + { + "context": "Editor && !agent_diff", + "bindings": { + "ctrl-k ctrl-r": "git::Restore", + "ctrl-alt-y": "git::ToggleStaged", + "alt-y": "git::StageAndNext", + "alt-shift-y": "git::UnstageAndNext" + } + }, + { + "context": "Editor && editor_agent_diff", + "bindings": { + "ctrl-y": "agent::Keep", + "ctrl-n": "agent::Reject", + "ctrl-shift-y": "agent::KeepAll", + "ctrl-shift-n": "agent::RejectAll", + "shift-ctrl-r": "agent::OpenAgentDiff" + } + }, + { + "context": "AgentDiff", + "bindings": { + "ctrl-y": "agent::Keep", + "ctrl-n": "agent::Reject", + "ctrl-shift-y": "agent::KeepAll", + "ctrl-shift-n": "agent::RejectAll" + } + }, + { + "context": "ContextEditor > Editor", + "bindings": { + "ctrl-enter": "assistant::Assist", + "ctrl-s": "workspace::Save", + "save": "workspace::Save", + "ctrl-<": "assistant::InsertIntoEditor", + "shift-enter": "assistant::Split", + "ctrl-r": "assistant::CycleMessageRole", + "enter": "assistant::ConfirmCommand", + "alt-enter": "editor::Newline", + "ctrl-k c": "assistant::CopyCode", + "ctrl-g": "search::SelectNextMatch", + "ctrl-shift-g": "search::SelectPreviousMatch", + "ctrl-k l": "agent::OpenRulesLibrary" + } + }, + { + "context": "AgentPanel", + "bindings": { + "ctrl-n": "agent::NewThread", + "ctrl-alt-n": "agent::NewTextThread", + "ctrl-shift-h": "agent::OpenHistory", + "ctrl-alt-c": "agent::OpenSettings", + "ctrl-alt-p": "agent::OpenRulesLibrary", + "ctrl-i": "agent::ToggleProfileSelector", + "ctrl-alt-/": "agent::ToggleModelSelector", + "ctrl-shift-a": "agent::ToggleContextPicker", + "ctrl-shift-j": "agent::ToggleNavigationMenu", + "ctrl-shift-i": "agent::ToggleOptionsMenu", + "ctrl-alt-shift-n": "agent::ToggleNewThreadMenu", + "shift-alt-escape": "agent::ExpandMessageEditor", + "ctrl->": "assistant::QuoteSelection", + "ctrl-alt-e": "agent::RemoveAllContext", + "ctrl-shift-e": "project_panel::ToggleFocus", + "ctrl-shift-enter": "agent::ContinueThread", + "super-ctrl-b": "agent::ToggleBurnMode", + "alt-enter": "agent::ContinueWithBurnMode" + } + }, + { + "context": "AgentPanel > NavigationMenu", + "bindings": { + "shift-backspace": "agent::DeleteRecentlyOpenThread" + } + }, + { + "context": "AgentPanel > Markdown", + "bindings": { + "copy": "markdown::CopyAsMarkdown", + "ctrl-c": "markdown::CopyAsMarkdown" + } + }, + { + "context": "AgentPanel && prompt_editor", + "bindings": { + "ctrl-n": "agent::NewTextThread", + "ctrl-alt-t": "agent::NewThread" + } + }, + { + "context": "AgentPanel && external_agent_thread", + "use_key_equivalents": true, + "bindings": { + "ctrl-n": "agent::NewExternalAgentThread", + "ctrl-alt-t": "agent::NewThread" + } + }, + { + "context": "MessageEditor && !Picker > Editor && !use_modifier_to_send", + "bindings": { + "enter": "agent::Chat", + "ctrl-enter": "agent::ChatWithFollow", + "ctrl-i": "agent::ToggleProfileSelector", + "shift-ctrl-r": "agent::OpenAgentDiff", + "ctrl-shift-y": "agent::KeepAll", + "ctrl-shift-n": "agent::RejectAll" + } + }, + { + "context": "MessageEditor && !Picker > Editor && use_modifier_to_send", + "bindings": { + "ctrl-enter": "agent::Chat", + "enter": "editor::Newline", + "ctrl-i": "agent::ToggleProfileSelector", + "shift-ctrl-r": "agent::OpenAgentDiff", + "ctrl-shift-y": "agent::KeepAll", + "ctrl-shift-n": "agent::RejectAll" + } + }, + { + "context": "EditMessageEditor > Editor", + "bindings": { + "escape": "menu::Cancel", + "enter": "menu::Confirm", + "alt-enter": "editor::Newline" + } + }, + { + "context": "AgentFeedbackMessageEditor > Editor", + "bindings": { + "escape": "menu::Cancel", + "enter": "menu::Confirm", + "alt-enter": "editor::Newline" + } + }, + { + "context": "ContextStrip", + "bindings": { + "up": "agent::FocusUp", + "right": "agent::FocusRight", + "left": "agent::FocusLeft", + "down": "agent::FocusDown", + "backspace": "agent::RemoveFocusedContext", + "enter": "agent::AcceptSuggestedContext" + } + }, + { + "context": "AcpThread > Editor", + "use_key_equivalents": true, + "bindings": { + "enter": "agent::Chat", + "shift-ctrl-r": "agent::OpenAgentDiff", + "ctrl-shift-y": "agent::KeepAll", + "ctrl-shift-n": "agent::RejectAll" + } + }, + { + "context": "ThreadHistory", + "bindings": { + "backspace": "agent::RemoveSelectedThread" + } + }, + { + "context": "PromptLibrary", + "bindings": { + "new": "rules_library::NewRule", + "ctrl-n": "rules_library::NewRule", + "ctrl-shift-s": "rules_library::ToggleDefaultRule" + } + }, + { + "context": "BufferSearchBar", + "bindings": { + "escape": "buffer_search::Dismiss", + "tab": "buffer_search::FocusEditor", + "enter": "search::SelectNextMatch", + "shift-enter": "search::SelectPreviousMatch", + "alt-enter": "search::SelectAllMatches", + "find": "search::FocusSearch", + "ctrl-f": "search::FocusSearch", + "ctrl-h": "search::ToggleReplace", + "ctrl-l": "search::ToggleSelection" + } + }, + { + "context": "BufferSearchBar && in_replace > Editor", + "bindings": { + "enter": "search::ReplaceNext", + "ctrl-enter": "search::ReplaceAll" + } + }, + { + "context": "BufferSearchBar && !in_replace > Editor", + "bindings": { + "up": "search::PreviousHistoryQuery", + "down": "search::NextHistoryQuery" + } + }, + { + "context": "ProjectSearchBar", + "bindings": { + "escape": "project_search::ToggleFocus", + "shift-find": "search::FocusSearch", + "ctrl-shift-f": "search::FocusSearch", + "ctrl-shift-h": "search::ToggleReplace", + "alt-ctrl-g": "search::ToggleRegex", + "alt-ctrl-x": "search::ToggleRegex" + } + }, + { + "context": "ProjectSearchBar > Editor", + "bindings": { + "up": "search::PreviousHistoryQuery", + "down": "search::NextHistoryQuery" + } + }, + { + "context": "ProjectSearchBar && in_replace > Editor", + "bindings": { + "enter": "search::ReplaceNext", + "ctrl-alt-enter": "search::ReplaceAll" + } + }, + { + "context": "ProjectSearchView", + "bindings": { + "escape": "project_search::ToggleFocus", + "ctrl-shift-h": "search::ToggleReplace", + "alt-ctrl-g": "search::ToggleRegex", + "alt-ctrl-x": "search::ToggleRegex" + } + }, + { + "context": "Pane", + "bindings": { + "alt-1": ["pane::ActivateItem", 0], + "alt-2": ["pane::ActivateItem", 1], + "alt-3": ["pane::ActivateItem", 2], + "alt-4": ["pane::ActivateItem", 3], + "alt-5": ["pane::ActivateItem", 4], + "alt-6": ["pane::ActivateItem", 5], + "alt-7": ["pane::ActivateItem", 6], + "alt-8": ["pane::ActivateItem", 7], + "alt-9": ["pane::ActivateItem", 8], + "alt-0": "pane::ActivateLastItem", + "ctrl-pageup": "pane::ActivatePreviousItem", + "ctrl-pagedown": "pane::ActivateNextItem", + "ctrl-shift-pageup": "pane::SwapItemLeft", + "ctrl-shift-pagedown": "pane::SwapItemRight", + "ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }], + "ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }], + "alt-ctrl-t": ["pane::CloseOtherItems", { "close_pinned": false }], + "alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes", + "ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }], + "ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }], + "ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }], + "ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }], + "ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes", + "back": "pane::GoBack", + "ctrl-alt--": "pane::GoBack", + "ctrl-alt-_": "pane::GoForward", + "forward": "pane::GoForward", + "ctrl-alt-g": "search::SelectNextMatch", + "f3": "search::SelectNextMatch", + "ctrl-alt-shift-g": "search::SelectPreviousMatch", + "shift-f3": "search::SelectPreviousMatch", + "shift-find": "project_search::ToggleFocus", + "ctrl-shift-f": "project_search::ToggleFocus", + "ctrl-alt-shift-h": "search::ToggleReplace", + "ctrl-alt-shift-l": "search::ToggleSelection", + "alt-enter": "search::SelectAllMatches", + "alt-c": "search::ToggleCaseSensitive", + "alt-w": "search::ToggleWholeWord", + "alt-find": "project_search::ToggleFilters", + "alt-ctrl-f": "project_search::ToggleFilters", + "ctrl-alt-shift-r": "search::ToggleRegex", + "ctrl-alt-shift-x": "search::ToggleRegex", + "alt-r": "search::ToggleRegex", + "ctrl-k shift-enter": "pane::TogglePinTab" + } + }, + // Bindings from VS Code + { + "context": "Editor", + "bindings": { + "ctrl-[": "editor::Outdent", + "ctrl-]": "editor::Indent", + "shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above + "shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below + "ctrl-shift-k": "editor::DeleteLine", + "alt-up": "editor::MoveLineUp", + "alt-down": "editor::MoveLineDown", + "ctrl-alt-shift-up": "editor::DuplicateLineUp", + "ctrl-alt-shift-down": "editor::DuplicateLineDown", + "alt-shift-right": "editor::SelectLargerSyntaxNode", // Expand Selection + "alt-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection + "ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection + "ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word + "ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand + "ctrl-shift-down": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch + "ctrl-shift-up": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch + "ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip + "ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch + "ctrl-k ctrl-i": "editor::Hover", + "ctrl-k ctrl-b": "editor::BlameHover", + "ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }], + "f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }], + "shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }], + "f2": "editor::Rename", + "f12": "editor::GoToDefinition", + "alt-f12": "editor::GoToDefinitionSplit", + "ctrl-shift-f10": "editor::GoToDefinitionSplit", + "ctrl-f12": "editor::GoToTypeDefinition", + "shift-f12": "editor::GoToImplementation", + "alt-ctrl-f12": "editor::GoToTypeDefinitionSplit", + "alt-shift-f12": "editor::FindAllReferences", + "ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains + "ctrl-|": "editor::MoveToEnclosingBracket", + "ctrl-{": "editor::Fold", + "ctrl-}": "editor::UnfoldLines", + "ctrl-k ctrl-l": "editor::ToggleFold", + "ctrl-k ctrl-[": "editor::FoldRecursive", + "ctrl-k ctrl-]": "editor::UnfoldRecursive", + "ctrl-k ctrl-1": ["editor::FoldAtLevel", 1], + "ctrl-k ctrl-2": ["editor::FoldAtLevel", 2], + "ctrl-k ctrl-3": ["editor::FoldAtLevel", 3], + "ctrl-k ctrl-4": ["editor::FoldAtLevel", 4], + "ctrl-k ctrl-5": ["editor::FoldAtLevel", 5], + "ctrl-k ctrl-6": ["editor::FoldAtLevel", 6], + "ctrl-k ctrl-7": ["editor::FoldAtLevel", 7], + "ctrl-k ctrl-8": ["editor::FoldAtLevel", 8], + "ctrl-k ctrl-9": ["editor::FoldAtLevel", 9], + "ctrl-k ctrl-0": "editor::FoldAll", + "ctrl-k ctrl-j": "editor::UnfoldAll", + "ctrl-space": "editor::ShowCompletions", + "ctrl-shift-space": "editor::ShowWordCompletions", + "ctrl-.": "editor::ToggleCodeActions", + "ctrl-k r": "editor::RevealInFileManager", + "ctrl-k p": "editor::CopyPath", + "ctrl-\\": "pane::SplitRight", + "ctrl-alt-shift-c": "editor::DisplayCursorNames", + "alt-.": "editor::GoToHunk", + "alt-,": "editor::GoToPreviousHunk" + } + }, + { + "context": "Editor && extension == md", + "use_key_equivalents": true, + "bindings": { + "ctrl-k v": "markdown::OpenPreviewToTheSide", + "ctrl-shift-v": "markdown::OpenPreview" + } + }, + { + "context": "Editor && extension == svg", + "use_key_equivalents": true, + "bindings": { + "ctrl-k v": "svg::OpenPreviewToTheSide", + "ctrl-shift-v": "svg::OpenPreview" + } + }, + { + "context": "Editor && mode == full", + "bindings": { + "ctrl-shift-o": "outline::Toggle", + "ctrl-g": "go_to_line::Toggle" + } + }, + { + "context": "Workspace", + "bindings": { + "alt-open": ["projects::OpenRecent", { "create_new_window": false }], + // Change the default action on `menu::Confirm` by setting the parameter + // "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }], + "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": false }], + "alt-shift-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], + // Change to open path modal for existing remote connection by setting the parameter + // "alt-ctrl-shift-o": "["projects::OpenRemote", { "from_existing_connection": true }]", + "alt-ctrl-shift-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], + "alt-ctrl-shift-b": "branches::OpenRecent", + "alt-shift-enter": "toast::RunAction", + "ctrl-~": "workspace::NewTerminal", + "save": "workspace::Save", + "ctrl-s": "workspace::Save", + "ctrl-k s": "workspace::SaveWithoutFormat", + "shift-save": "workspace::SaveAs", + "ctrl-shift-s": "workspace::SaveAs", + "new": "workspace::NewFile", + "ctrl-n": "workspace::NewFile", + "shift-new": "workspace::NewWindow", + "ctrl-shift-n": "workspace::NewWindow", + "ctrl-`": "terminal_panel::ToggleFocus", + "f10": ["app_menu::OpenApplicationMenu", "Zed"], + "alt-1": ["workspace::ActivatePane", 0], + "alt-2": ["workspace::ActivatePane", 1], + "alt-3": ["workspace::ActivatePane", 2], + "alt-4": ["workspace::ActivatePane", 3], + "alt-5": ["workspace::ActivatePane", 4], + "alt-6": ["workspace::ActivatePane", 5], + "alt-7": ["workspace::ActivatePane", 6], + "alt-8": ["workspace::ActivatePane", 7], + "alt-9": ["workspace::ActivatePane", 8], + "ctrl-alt-b": "workspace::ToggleRightDock", + "ctrl-b": "workspace::ToggleLeftDock", + "ctrl-j": "workspace::ToggleBottomDock", + "ctrl-alt-y": "workspace::CloseAllDocks", + "ctrl-alt-0": "workspace::ResetActiveDockSize", + // For 0px parameter, uses UI font size value. + "ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], + "ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], + "ctrl-alt-)": "workspace::ResetOpenDocksSize", + "ctrl-alt-_": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], + "ctrl-alt-+": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], + "shift-find": "pane::DeploySearch", + "ctrl-shift-f": "pane::DeploySearch", + "ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }], + "ctrl-shift-t": "pane::ReopenClosedItem", + "ctrl-k ctrl-s": "zed::OpenKeymapEditor", + "ctrl-k ctrl-t": "theme_selector::Toggle", + "ctrl-alt-super-p": "settings_profile_selector::Toggle", + "ctrl-t": "project_symbols::Toggle", + "ctrl-p": "file_finder::Toggle", + "ctrl-tab": "tab_switcher::Toggle", + "ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }], + "ctrl-e": "file_finder::Toggle", + "f1": "command_palette::Toggle", + "ctrl-shift-p": "command_palette::Toggle", + "ctrl-shift-m": "diagnostics::Deploy", + "ctrl-shift-e": "project_panel::ToggleFocus", + "ctrl-shift-b": "outline_panel::ToggleFocus", + "ctrl-shift-g": "git_panel::ToggleFocus", + "ctrl-shift-d": "debug_panel::ToggleFocus", + "ctrl-?": "agent::ToggleFocus", + "alt-save": "workspace::SaveAll", + "ctrl-alt-s": "workspace::SaveAll", + "ctrl-k m": "language_selector::Toggle", + "escape": "workspace::Unfollow", + "ctrl-k ctrl-left": "workspace::ActivatePaneLeft", + "ctrl-k ctrl-right": "workspace::ActivatePaneRight", + "ctrl-k ctrl-up": "workspace::ActivatePaneUp", + "ctrl-k ctrl-down": "workspace::ActivatePaneDown", + "ctrl-k shift-left": "workspace::SwapPaneLeft", + "ctrl-k shift-right": "workspace::SwapPaneRight", + "ctrl-k shift-up": "workspace::SwapPaneUp", + "ctrl-k shift-down": "workspace::SwapPaneDown", + "ctrl-shift-x": "zed::Extensions", + "ctrl-shift-r": "task::Rerun", + "ctrl-alt-r": "task::Rerun", + "alt-t": "task::Rerun", + "alt-shift-t": "task::Spawn", + "alt-shift-r": ["task::Spawn", { "reveal_target": "center" }], + // also possible to spawn tasks by name: + // "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }] + // or by tag: + // "foo-bar": ["task::Spawn", { "task_tag": "MyTag" }], + "f5": "debugger::Rerun", + "ctrl-f4": "workspace::CloseActiveDock", + "ctrl-w": "workspace::CloseActiveDock" + } + }, + { + "context": "Workspace && debugger_running", + "bindings": { + "f5": "zed::NoAction" + } + }, + { + "context": "Workspace && debugger_stopped", + "bindings": { + "f5": "debugger::Continue" + } + }, + { + "context": "ApplicationMenu", + "bindings": { + "f10": "menu::Cancel", + "left": "app_menu::ActivateMenuLeft", + "right": "app_menu::ActivateMenuRight" + } + }, + // Bindings from Sublime Text + { + "context": "Editor", + "bindings": { + "ctrl-u": "editor::UndoSelection", + "ctrl-shift-u": "editor::RedoSelection", + "ctrl-shift-j": "editor::JoinLines", + "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", + "ctrl-alt-right": "editor::MoveToNextSubwordEnd", + "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", + "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", + "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + } + }, + // Bindings from Atom + { + "context": "Pane", + "bindings": { + "ctrl-k up": "pane::SplitUp", + "ctrl-k down": "pane::SplitDown", + "ctrl-k left": "pane::SplitLeft", + "ctrl-k right": "pane::SplitRight" + } + }, + // Bindings that should be unified with bindings for more general actions + { + "context": "Editor && renaming", + "bindings": { + "enter": "editor::ConfirmRename" + } + }, + { + "context": "Editor && showing_completions", + "bindings": { + "enter": "editor::ConfirmCompletion", + "shift-enter": "editor::ConfirmCompletionReplace", + "tab": "editor::ComposeCompletion" + } + }, + // Bindings for accepting edit predictions + // + // alt-l is provided as an alternative to tab/alt-tab. and will be displayed in the UI. This is + // because alt-tab may not be available, as it is often used for window switching. + { + "context": "Editor && edit_prediction", + "bindings": { + "alt-tab": "editor::AcceptEditPrediction", + "alt-l": "editor::AcceptEditPrediction", + "tab": "editor::AcceptEditPrediction", + "alt-right": "editor::AcceptPartialEditPrediction" + } + }, + { + "context": "Editor && edit_prediction_conflict", + "bindings": { + "alt-tab": "editor::AcceptEditPrediction", + "alt-l": "editor::AcceptEditPrediction", + "alt-right": "editor::AcceptPartialEditPrediction" + } + }, + { + "context": "Editor && showing_code_actions", + "bindings": { + "enter": "editor::ConfirmCodeAction" + } + }, + { + "context": "Editor && (showing_code_actions || showing_completions)", + "bindings": { + "ctrl-p": "editor::ContextMenuPrevious", + "up": "editor::ContextMenuPrevious", + "ctrl-n": "editor::ContextMenuNext", + "down": "editor::ContextMenuNext", + "pageup": "editor::ContextMenuFirst", + "pagedown": "editor::ContextMenuLast" + } + }, + { + "context": "Editor && showing_signature_help && !showing_completions", + "bindings": { + "up": "editor::SignatureHelpPrevious", + "down": "editor::SignatureHelpNext" + } + }, + // Custom bindings + { + "bindings": { + "ctrl-alt-shift-f": "workspace::FollowNextCollaborator", + // Only available in debug builds: opens an element inspector for development. + "ctrl-alt-i": "dev::ToggleInspector" + } + }, + { + "context": "!Terminal", + "bindings": { + "ctrl-shift-c": "collab_panel::ToggleFocus" + } + }, + { + "context": "!ContextEditor > Editor && mode == full", + "bindings": { + "alt-enter": "editor::OpenExcerpts", + "shift-enter": "editor::ExpandExcerpts", + "ctrl-alt-enter": "editor::OpenExcerptsSplit", + "ctrl-shift-e": "pane::RevealInProjectPanel", + "ctrl-f8": "editor::GoToHunk", + "ctrl-shift-f8": "editor::GoToPreviousHunk", + "ctrl-enter": "assistant::InlineAssist", + "ctrl-:": "editor::ToggleInlayHints" + } + }, + { + "context": "PromptEditor", + "bindings": { + "ctrl-[": "agent::CyclePreviousInlineAssist", + "ctrl-]": "agent::CycleNextInlineAssist", + "ctrl-alt-e": "agent::RemoveAllContext" + } + }, + { + "context": "Prompt", + "bindings": { + "left": "menu::SelectPrevious", + "right": "menu::SelectNext", + "h": "menu::SelectPrevious", + "l": "menu::SelectNext" + } + }, + { + "context": "ProjectSearchBar && !in_replace", + "bindings": { + "ctrl-enter": "project_search::SearchInNew" + } + }, + { + "context": "OutlinePanel && not_editing", + "bindings": { + "escape": "menu::Cancel", + "left": "outline_panel::CollapseSelectedEntry", + "right": "outline_panel::ExpandSelectedEntry", + "alt-copy": "outline_panel::CopyPath", + "ctrl-alt-c": "outline_panel::CopyPath", + "alt-shift-copy": "workspace::CopyRelativePath", + "alt-ctrl-shift-c": "workspace::CopyRelativePath", + "alt-ctrl-r": "outline_panel::RevealInFileManager", + "space": "outline_panel::OpenSelectedEntry", + "shift-down": "menu::SelectNext", + "shift-up": "menu::SelectPrevious", + "alt-enter": "editor::OpenExcerpts", + "ctrl-alt-enter": "editor::OpenExcerptsSplit" + } + }, + { + "context": "ProjectPanel", + "bindings": { + "left": "project_panel::CollapseSelectedEntry", + "right": "project_panel::ExpandSelectedEntry", + "new": "project_panel::NewFile", + "ctrl-n": "project_panel::NewFile", + "alt-new": "project_panel::NewDirectory", + "alt-ctrl-n": "project_panel::NewDirectory", + "cut": "project_panel::Cut", + "ctrl-x": "project_panel::Cut", + "copy": "project_panel::Copy", + "ctrl-insert": "project_panel::Copy", + "ctrl-c": "project_panel::Copy", + "paste": "project_panel::Paste", + "shift-insert": "project_panel::Paste", + "ctrl-v": "project_panel::Paste", + "alt-copy": "project_panel::CopyPath", + "ctrl-alt-c": "project_panel::CopyPath", + "alt-shift-copy": "workspace::CopyRelativePath", + "alt-ctrl-shift-c": "workspace::CopyRelativePath", + "enter": "project_panel::Rename", + "f2": "project_panel::Rename", + "backspace": ["project_panel::Trash", { "skip_prompt": false }], + "delete": ["project_panel::Trash", { "skip_prompt": false }], + "shift-delete": ["project_panel::Delete", { "skip_prompt": false }], + "ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }], + "ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }], + "alt-ctrl-r": "project_panel::RevealInFileManager", + "ctrl-shift-enter": "project_panel::OpenWithSystem", + "alt-d": "project_panel::CompareMarkedFiles", + "shift-find": "project_panel::NewSearchInDirectory", + "ctrl-alt-shift-f": "project_panel::NewSearchInDirectory", + "shift-down": "menu::SelectNext", + "shift-up": "menu::SelectPrevious", + "escape": "menu::Cancel" + } + }, + { + "context": "ProjectPanel && not_editing", + "bindings": { + "space": "project_panel::Open" + } + }, + { + "context": "GitPanel && ChangesList", + "bindings": { + "up": "menu::SelectPrevious", + "down": "menu::SelectNext", + "enter": "menu::Confirm", + "alt-y": "git::StageFile", + "alt-shift-y": "git::UnstageFile", + "ctrl-alt-y": "git::ToggleStaged", + "space": "git::ToggleStaged", + "shift-space": "git::StageRange", + "tab": "git_panel::FocusEditor", + "shift-tab": "git_panel::FocusEditor", + "escape": "git_panel::ToggleFocus", + "alt-enter": "menu::SecondaryConfirm", + "delete": ["git::RestoreFile", { "skip_prompt": false }], + "backspace": ["git::RestoreFile", { "skip_prompt": false }], + "shift-delete": ["git::RestoreFile", { "skip_prompt": false }], + "ctrl-backspace": ["git::RestoreFile", { "skip_prompt": false }], + "ctrl-delete": ["git::RestoreFile", { "skip_prompt": false }] + } + }, + { + "context": "GitPanel && CommitEditor", + "use_key_equivalents": true, + "bindings": { + "escape": "git::Cancel" + } + }, + { + "context": "GitCommit > Editor", + "bindings": { + "escape": "menu::Cancel", + "enter": "editor::Newline", + "ctrl-enter": "git::Commit", + "ctrl-shift-enter": "git::Amend", + "alt-l": "git::GenerateCommitMessage" + } + }, + { + "context": "GitPanel", + "bindings": { + "ctrl-g ctrl-g": "git::Fetch", + "ctrl-g up": "git::Push", + "ctrl-g down": "git::Pull", + "ctrl-g shift-up": "git::ForcePush", + "ctrl-g d": "git::Diff", + "ctrl-g backspace": "git::RestoreTrackedFiles", + "ctrl-g shift-backspace": "git::TrashUntrackedFiles", + "ctrl-space": "git::StageAll", + "ctrl-shift-space": "git::UnstageAll", + "ctrl-enter": "git::Commit", + "ctrl-shift-enter": "git::Amend" + } + }, + { + "context": "GitDiff > Editor", + "bindings": { + "ctrl-enter": "git::Commit", + "ctrl-shift-enter": "git::Amend", + "ctrl-space": "git::StageAll", + "ctrl-shift-space": "git::UnstageAll" + } + }, + { + "context": "AskPass > Editor", + "bindings": { + "enter": "menu::Confirm" + } + }, + { + "context": "CommitEditor > Editor", + "bindings": { + "escape": "git_panel::FocusChanges", + "tab": "git_panel::FocusChanges", + "shift-tab": "git_panel::FocusChanges", + "enter": "editor::Newline", + "ctrl-enter": "git::Commit", + "ctrl-shift-enter": "git::Amend", + "alt-up": "git_panel::FocusChanges", + "alt-l": "git::GenerateCommitMessage" + } + }, + { + "context": "DebugPanel", + "bindings": { + "ctrl-t": "debugger::ToggleThreadPicker", + "ctrl-i": "debugger::ToggleSessionPicker", + "shift-alt-escape": "debugger::ToggleExpandItem" + } + }, + { + "context": "VariableList", + "bindings": { + "left": "variable_list::CollapseSelectedEntry", + "right": "variable_list::ExpandSelectedEntry", + "enter": "variable_list::EditVariable", + "ctrl-c": "variable_list::CopyVariableValue", + "ctrl-alt-c": "variable_list::CopyVariableName", + "delete": "variable_list::RemoveWatch", + "backspace": "variable_list::RemoveWatch", + "alt-enter": "variable_list::AddWatch" + } + }, + { + "context": "BreakpointList", + "bindings": { + "space": "debugger::ToggleEnableBreakpoint", + "backspace": "debugger::UnsetBreakpoint", + "left": "debugger::PreviousBreakpointProperty", + "right": "debugger::NextBreakpointProperty" + } + }, + { + "context": "CollabPanel && not_editing", + "bindings": { + "ctrl-backspace": "collab_panel::Remove", + "space": "menu::Confirm" + } + }, + { + "context": "CollabPanel", + "bindings": { + "alt-up": "collab_panel::MoveChannelUp", + "alt-down": "collab_panel::MoveChannelDown" + } + }, + { + "context": "(CollabPanel && editing) > Editor", + "bindings": { + "space": "collab_panel::InsertSpace" + } + }, + { + "context": "ChannelModal", + "bindings": { + "tab": "channel_modal::ToggleMode" + } + }, + { + "context": "Picker > Editor", + "bindings": { + "escape": "menu::Cancel", + "up": "menu::SelectPrevious", + "down": "menu::SelectNext", + "tab": "picker::ConfirmCompletion", + "alt-enter": ["picker::ConfirmInput", { "secondary": false }] + } + }, + { + "context": "ChannelModal > Picker > Editor", + "bindings": { + "tab": "channel_modal::ToggleMode" + } + }, + { + "context": "FileFinder || (FileFinder > Picker > Editor)", + "bindings": { + "ctrl-p": "file_finder::Toggle", + "ctrl-shift-a": "file_finder::ToggleSplitMenu", + "ctrl-shift-i": "file_finder::ToggleFilterMenu" + } + }, + { + "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", + "bindings": { + "ctrl-shift-p": "file_finder::SelectPrevious", + "ctrl-j": "pane::SplitDown", + "ctrl-k": "pane::SplitUp", + "ctrl-h": "pane::SplitLeft", + "ctrl-l": "pane::SplitRight" + } + }, + { + "context": "TabSwitcher", + "bindings": { + "ctrl-shift-tab": "menu::SelectPrevious", + "ctrl-up": "menu::SelectPrevious", + "ctrl-down": "menu::SelectNext", + "ctrl-backspace": "tab_switcher::CloseSelectedItem" + } + }, + { + "context": "Terminal", + "bindings": { + "ctrl-alt-space": "terminal::ShowCharacterPalette", + "copy": "terminal::Copy", + "ctrl-insert": "terminal::Copy", + "ctrl-shift-c": "terminal::Copy", + "paste": "terminal::Paste", + "shift-insert": "terminal::Paste", + "ctrl-shift-v": "terminal::Paste", + "ctrl-enter": "assistant::InlineAssist", + "alt-b": ["terminal::SendText", "\u001bb"], + "alt-f": ["terminal::SendText", "\u001bf"], + "alt-.": ["terminal::SendText", "\u001b."], + "ctrl-delete": ["terminal::SendText", "\u001bd"], + // Overrides for conflicting keybindings + "ctrl-b": ["terminal::SendKeystroke", "ctrl-b"], + "ctrl-c": ["terminal::SendKeystroke", "ctrl-c"], + "ctrl-e": ["terminal::SendKeystroke", "ctrl-e"], + "ctrl-o": ["terminal::SendKeystroke", "ctrl-o"], + "ctrl-w": ["terminal::SendKeystroke", "ctrl-w"], + "ctrl-backspace": ["terminal::SendKeystroke", "ctrl-w"], + "ctrl-shift-a": "editor::SelectAll", + "find": "buffer_search::Deploy", + "ctrl-shift-f": "buffer_search::Deploy", + "ctrl-shift-l": "terminal::Clear", + "ctrl-shift-w": "pane::CloseActiveItem", + "up": ["terminal::SendKeystroke", "up"], + "pageup": ["terminal::SendKeystroke", "pageup"], + "down": ["terminal::SendKeystroke", "down"], + "pagedown": ["terminal::SendKeystroke", "pagedown"], + "escape": ["terminal::SendKeystroke", "escape"], + "enter": ["terminal::SendKeystroke", "enter"], + "shift-pageup": "terminal::ScrollPageUp", + "shift-pagedown": "terminal::ScrollPageDown", + "shift-up": "terminal::ScrollLineUp", + "shift-down": "terminal::ScrollLineDown", + "shift-home": "terminal::ScrollToTop", + "shift-end": "terminal::ScrollToBottom", + "ctrl-shift-space": "terminal::ToggleViMode", + "ctrl-shift-r": "terminal::RerunTask", + "ctrl-alt-r": "terminal::RerunTask", + "alt-t": "terminal::RerunTask" + } + }, + { + "context": "ZedPredictModal", + "bindings": { + "escape": "menu::Cancel" + } + }, + { + "context": "ConfigureContextServerModal > Editor", + "bindings": { + "escape": "menu::Cancel", + "enter": "editor::Newline", + "ctrl-enter": "menu::Confirm" + } + }, + { + "context": "OnboardingAiConfigurationModal", + "use_key_equivalents": true, + "bindings": { + "escape": "menu::Cancel" + } + }, + { + "context": "Diagnostics", + "use_key_equivalents": true, + "bindings": { + "ctrl-r": "diagnostics::ToggleDiagnosticsRefresh" + } + }, + { + "context": "DebugConsole > Editor", + "use_key_equivalents": true, + "bindings": { + "enter": "menu::Confirm", + "alt-enter": "console::WatchExpression" + } + }, + { + "context": "RunModal", + "bindings": { + "ctrl-tab": "pane::ActivateNextItem", + "ctrl-shift-tab": "pane::ActivatePreviousItem" + } + }, + { + "context": "MarkdownPreview", + "bindings": { + "pageup": "markdown::MovePageUp", + "pagedown": "markdown::MovePageDown" + } + }, + { + "context": "KeymapEditor", + "use_key_equivalents": true, + "bindings": { + "ctrl-f": "search::FocusSearch", + "alt-find": "keymap_editor::ToggleKeystrokeSearch", + "alt-ctrl-f": "keymap_editor::ToggleKeystrokeSearch", + "alt-c": "keymap_editor::ToggleConflictFilter", + "enter": "keymap_editor::EditBinding", + "alt-enter": "keymap_editor::CreateBinding", + "ctrl-c": "keymap_editor::CopyAction", + "ctrl-shift-c": "keymap_editor::CopyContext", + "ctrl-t": "keymap_editor::ShowMatchingKeybinds" + } + }, + { + "context": "KeystrokeInput", + "use_key_equivalents": true, + "bindings": { + "enter": "keystroke_input::StartRecording", + "escape escape escape": "keystroke_input::StopRecording", + "delete": "keystroke_input::ClearKeystrokes" + } + }, + { + "context": "KeybindEditorModal", + "use_key_equivalents": true, + "bindings": { + "ctrl-enter": "menu::Confirm", + "escape": "menu::Cancel" + } + }, + { + "context": "KeybindEditorModal > Editor", + "use_key_equivalents": true, + "bindings": { + "up": "menu::SelectPrevious", + "down": "menu::SelectNext" + } + }, + { + "context": "Onboarding", + "use_key_equivalents": true, + "bindings": { + "ctrl-1": "onboarding::ActivateBasicsPage", + "ctrl-2": "onboarding::ActivateEditingPage", + "ctrl-3": "onboarding::ActivateAISetupPage", + "ctrl-escape": "onboarding::Finish", + "alt-tab": "onboarding::SignIn", + "alt-shift-a": "onboarding::OpenAccount" + } + } +] From 44973ef981b8c266bdf9b866f16608fb181381ac Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 21:48:55 +0800 Subject: [PATCH 06/35] add `"use_key_equivalents": true,` --- assets/keymaps/default-windows.json | 82 ++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 01c0b4e9696f3e..80d5ea01d4ff08 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -1,6 +1,7 @@ [ - // Standard Linux bindings + // Standard Windows bindings { + "use_key_equivalents": true, "bindings": { "home": "menu::SelectFirst", "shift-pageup": "menu::SelectFirst", @@ -47,6 +48,7 @@ }, { "context": "Picker || menu", + "use_key_equivalents": true, "bindings": { "up": "menu::SelectPrevious", "down": "menu::SelectNext" @@ -54,6 +56,7 @@ }, { "context": "Editor", + "use_key_equivalents": true, "bindings": { "escape": "editor::Cancel", "shift-backspace": "editor::Backspace", @@ -128,6 +131,7 @@ }, { "context": "Editor && mode == full", + "use_key_equivalents": true, "bindings": { "shift-enter": "editor::Newline", "enter": "editor::Newline", @@ -148,6 +152,7 @@ }, { "context": "Editor && mode == full && edit_prediction", + "use_key_equivalents": true, "bindings": { "alt-]": "editor::NextEditPrediction", "alt-[": "editor::PreviousEditPrediction" @@ -155,12 +160,14 @@ }, { "context": "Editor && !edit_prediction", + "use_key_equivalents": true, "bindings": { "alt-\\": "editor::ShowEditPrediction" } }, { "context": "Editor && mode == auto_height", + "use_key_equivalents": true, "bindings": { "ctrl-enter": "editor::Newline", "shift-enter": "editor::Newline", @@ -169,6 +176,7 @@ }, { "context": "Markdown", + "use_key_equivalents": true, "bindings": { "copy": "markdown::Copy", "ctrl-c": "markdown::Copy" @@ -176,6 +184,7 @@ }, { "context": "Editor && jupyter && !ContextEditor", + "use_key_equivalents": true, "bindings": { "ctrl-shift-enter": "repl::Run", "ctrl-alt-enter": "repl::RunInPlace" @@ -183,6 +192,7 @@ }, { "context": "Editor && !agent_diff", + "use_key_equivalents": true, "bindings": { "ctrl-k ctrl-r": "git::Restore", "ctrl-alt-y": "git::ToggleStaged", @@ -192,6 +202,7 @@ }, { "context": "Editor && editor_agent_diff", + "use_key_equivalents": true, "bindings": { "ctrl-y": "agent::Keep", "ctrl-n": "agent::Reject", @@ -202,6 +213,7 @@ }, { "context": "AgentDiff", + "use_key_equivalents": true, "bindings": { "ctrl-y": "agent::Keep", "ctrl-n": "agent::Reject", @@ -211,6 +223,7 @@ }, { "context": "ContextEditor > Editor", + "use_key_equivalents": true, "bindings": { "ctrl-enter": "assistant::Assist", "ctrl-s": "workspace::Save", @@ -228,6 +241,7 @@ }, { "context": "AgentPanel", + "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewThread", "ctrl-alt-n": "agent::NewTextThread", @@ -251,12 +265,14 @@ }, { "context": "AgentPanel > NavigationMenu", + "use_key_equivalents": true, "bindings": { "shift-backspace": "agent::DeleteRecentlyOpenThread" } }, { "context": "AgentPanel > Markdown", + "use_key_equivalents": true, "bindings": { "copy": "markdown::CopyAsMarkdown", "ctrl-c": "markdown::CopyAsMarkdown" @@ -264,6 +280,7 @@ }, { "context": "AgentPanel && prompt_editor", + "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewTextThread", "ctrl-alt-t": "agent::NewThread" @@ -272,6 +289,7 @@ { "context": "AgentPanel && external_agent_thread", "use_key_equivalents": true, + "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewExternalAgentThread", "ctrl-alt-t": "agent::NewThread" @@ -279,6 +297,7 @@ }, { "context": "MessageEditor && !Picker > Editor && !use_modifier_to_send", + "use_key_equivalents": true, "bindings": { "enter": "agent::Chat", "ctrl-enter": "agent::ChatWithFollow", @@ -290,6 +309,7 @@ }, { "context": "MessageEditor && !Picker > Editor && use_modifier_to_send", + "use_key_equivalents": true, "bindings": { "ctrl-enter": "agent::Chat", "enter": "editor::Newline", @@ -301,6 +321,7 @@ }, { "context": "EditMessageEditor > Editor", + "use_key_equivalents": true, "bindings": { "escape": "menu::Cancel", "enter": "menu::Confirm", @@ -309,6 +330,7 @@ }, { "context": "AgentFeedbackMessageEditor > Editor", + "use_key_equivalents": true, "bindings": { "escape": "menu::Cancel", "enter": "menu::Confirm", @@ -317,6 +339,7 @@ }, { "context": "ContextStrip", + "use_key_equivalents": true, "bindings": { "up": "agent::FocusUp", "right": "agent::FocusRight", @@ -338,12 +361,14 @@ }, { "context": "ThreadHistory", + "use_key_equivalents": true, "bindings": { "backspace": "agent::RemoveSelectedThread" } }, { "context": "PromptLibrary", + "use_key_equivalents": true, "bindings": { "new": "rules_library::NewRule", "ctrl-n": "rules_library::NewRule", @@ -352,6 +377,7 @@ }, { "context": "BufferSearchBar", + "use_key_equivalents": true, "bindings": { "escape": "buffer_search::Dismiss", "tab": "buffer_search::FocusEditor", @@ -366,6 +392,7 @@ }, { "context": "BufferSearchBar && in_replace > Editor", + "use_key_equivalents": true, "bindings": { "enter": "search::ReplaceNext", "ctrl-enter": "search::ReplaceAll" @@ -373,6 +400,7 @@ }, { "context": "BufferSearchBar && !in_replace > Editor", + "use_key_equivalents": true, "bindings": { "up": "search::PreviousHistoryQuery", "down": "search::NextHistoryQuery" @@ -380,6 +408,7 @@ }, { "context": "ProjectSearchBar", + "use_key_equivalents": true, "bindings": { "escape": "project_search::ToggleFocus", "shift-find": "search::FocusSearch", @@ -391,6 +420,7 @@ }, { "context": "ProjectSearchBar > Editor", + "use_key_equivalents": true, "bindings": { "up": "search::PreviousHistoryQuery", "down": "search::NextHistoryQuery" @@ -398,6 +428,7 @@ }, { "context": "ProjectSearchBar && in_replace > Editor", + "use_key_equivalents": true, "bindings": { "enter": "search::ReplaceNext", "ctrl-alt-enter": "search::ReplaceAll" @@ -405,6 +436,7 @@ }, { "context": "ProjectSearchView", + "use_key_equivalents": true, "bindings": { "escape": "project_search::ToggleFocus", "ctrl-shift-h": "search::ToggleReplace", @@ -414,6 +446,7 @@ }, { "context": "Pane", + "use_key_equivalents": true, "bindings": { "alt-1": ["pane::ActivateItem", 0], "alt-2": ["pane::ActivateItem", 1], @@ -464,6 +497,7 @@ // Bindings from VS Code { "context": "Editor", + "use_key_equivalents": true, "bindings": { "ctrl-[": "editor::Outdent", "ctrl-]": "editor::Indent", @@ -543,6 +577,7 @@ }, { "context": "Editor && mode == full", + "use_key_equivalents": true, "bindings": { "ctrl-shift-o": "outline::Toggle", "ctrl-g": "go_to_line::Toggle" @@ -550,6 +585,7 @@ }, { "context": "Workspace", + "use_key_equivalents": true, "bindings": { "alt-open": ["projects::OpenRecent", { "create_new_window": false }], // Change the default action on `menu::Confirm` by setting the parameter @@ -642,18 +678,21 @@ }, { "context": "Workspace && debugger_running", + "use_key_equivalents": true, "bindings": { "f5": "zed::NoAction" } }, { "context": "Workspace && debugger_stopped", + "use_key_equivalents": true, "bindings": { "f5": "debugger::Continue" } }, { "context": "ApplicationMenu", + "use_key_equivalents": true, "bindings": { "f10": "menu::Cancel", "left": "app_menu::ActivateMenuLeft", @@ -663,6 +702,7 @@ // Bindings from Sublime Text { "context": "Editor", + "use_key_equivalents": true, "bindings": { "ctrl-u": "editor::UndoSelection", "ctrl-shift-u": "editor::RedoSelection", @@ -682,6 +722,7 @@ // Bindings from Atom { "context": "Pane", + "use_key_equivalents": true, "bindings": { "ctrl-k up": "pane::SplitUp", "ctrl-k down": "pane::SplitDown", @@ -692,12 +733,14 @@ // Bindings that should be unified with bindings for more general actions { "context": "Editor && renaming", + "use_key_equivalents": true, "bindings": { "enter": "editor::ConfirmRename" } }, { "context": "Editor && showing_completions", + "use_key_equivalents": true, "bindings": { "enter": "editor::ConfirmCompletion", "shift-enter": "editor::ConfirmCompletionReplace", @@ -710,6 +753,7 @@ // because alt-tab may not be available, as it is often used for window switching. { "context": "Editor && edit_prediction", + "use_key_equivalents": true, "bindings": { "alt-tab": "editor::AcceptEditPrediction", "alt-l": "editor::AcceptEditPrediction", @@ -719,6 +763,7 @@ }, { "context": "Editor && edit_prediction_conflict", + "use_key_equivalents": true, "bindings": { "alt-tab": "editor::AcceptEditPrediction", "alt-l": "editor::AcceptEditPrediction", @@ -727,12 +772,14 @@ }, { "context": "Editor && showing_code_actions", + "use_key_equivalents": true, "bindings": { "enter": "editor::ConfirmCodeAction" } }, { "context": "Editor && (showing_code_actions || showing_completions)", + "use_key_equivalents": true, "bindings": { "ctrl-p": "editor::ContextMenuPrevious", "up": "editor::ContextMenuPrevious", @@ -744,6 +791,7 @@ }, { "context": "Editor && showing_signature_help && !showing_completions", + "use_key_equivalents": true, "bindings": { "up": "editor::SignatureHelpPrevious", "down": "editor::SignatureHelpNext" @@ -751,6 +799,7 @@ }, // Custom bindings { + "use_key_equivalents": true, "bindings": { "ctrl-alt-shift-f": "workspace::FollowNextCollaborator", // Only available in debug builds: opens an element inspector for development. @@ -759,12 +808,14 @@ }, { "context": "!Terminal", + "use_key_equivalents": true, "bindings": { "ctrl-shift-c": "collab_panel::ToggleFocus" } }, { "context": "!ContextEditor > Editor && mode == full", + "use_key_equivalents": true, "bindings": { "alt-enter": "editor::OpenExcerpts", "shift-enter": "editor::ExpandExcerpts", @@ -778,6 +829,7 @@ }, { "context": "PromptEditor", + "use_key_equivalents": true, "bindings": { "ctrl-[": "agent::CyclePreviousInlineAssist", "ctrl-]": "agent::CycleNextInlineAssist", @@ -786,6 +838,7 @@ }, { "context": "Prompt", + "use_key_equivalents": true, "bindings": { "left": "menu::SelectPrevious", "right": "menu::SelectNext", @@ -795,12 +848,14 @@ }, { "context": "ProjectSearchBar && !in_replace", + "use_key_equivalents": true, "bindings": { "ctrl-enter": "project_search::SearchInNew" } }, { "context": "OutlinePanel && not_editing", + "use_key_equivalents": true, "bindings": { "escape": "menu::Cancel", "left": "outline_panel::CollapseSelectedEntry", @@ -819,6 +874,7 @@ }, { "context": "ProjectPanel", + "use_key_equivalents": true, "bindings": { "left": "project_panel::CollapseSelectedEntry", "right": "project_panel::ExpandSelectedEntry", @@ -857,12 +913,14 @@ }, { "context": "ProjectPanel && not_editing", + "use_key_equivalents": true, "bindings": { "space": "project_panel::Open" } }, { "context": "GitPanel && ChangesList", + "use_key_equivalents": true, "bindings": { "up": "menu::SelectPrevious", "down": "menu::SelectNext", @@ -892,6 +950,7 @@ }, { "context": "GitCommit > Editor", + "use_key_equivalents": true, "bindings": { "escape": "menu::Cancel", "enter": "editor::Newline", @@ -902,6 +961,7 @@ }, { "context": "GitPanel", + "use_key_equivalents": true, "bindings": { "ctrl-g ctrl-g": "git::Fetch", "ctrl-g up": "git::Push", @@ -918,6 +978,7 @@ }, { "context": "GitDiff > Editor", + "use_key_equivalents": true, "bindings": { "ctrl-enter": "git::Commit", "ctrl-shift-enter": "git::Amend", @@ -927,12 +988,14 @@ }, { "context": "AskPass > Editor", + "use_key_equivalents": true, "bindings": { "enter": "menu::Confirm" } }, { "context": "CommitEditor > Editor", + "use_key_equivalents": true, "bindings": { "escape": "git_panel::FocusChanges", "tab": "git_panel::FocusChanges", @@ -946,6 +1009,7 @@ }, { "context": "DebugPanel", + "use_key_equivalents": true, "bindings": { "ctrl-t": "debugger::ToggleThreadPicker", "ctrl-i": "debugger::ToggleSessionPicker", @@ -954,6 +1018,7 @@ }, { "context": "VariableList", + "use_key_equivalents": true, "bindings": { "left": "variable_list::CollapseSelectedEntry", "right": "variable_list::ExpandSelectedEntry", @@ -967,6 +1032,7 @@ }, { "context": "BreakpointList", + "use_key_equivalents": true, "bindings": { "space": "debugger::ToggleEnableBreakpoint", "backspace": "debugger::UnsetBreakpoint", @@ -976,6 +1042,7 @@ }, { "context": "CollabPanel && not_editing", + "use_key_equivalents": true, "bindings": { "ctrl-backspace": "collab_panel::Remove", "space": "menu::Confirm" @@ -983,6 +1050,7 @@ }, { "context": "CollabPanel", + "use_key_equivalents": true, "bindings": { "alt-up": "collab_panel::MoveChannelUp", "alt-down": "collab_panel::MoveChannelDown" @@ -990,18 +1058,21 @@ }, { "context": "(CollabPanel && editing) > Editor", + "use_key_equivalents": true, "bindings": { "space": "collab_panel::InsertSpace" } }, { "context": "ChannelModal", + "use_key_equivalents": true, "bindings": { "tab": "channel_modal::ToggleMode" } }, { "context": "Picker > Editor", + "use_key_equivalents": true, "bindings": { "escape": "menu::Cancel", "up": "menu::SelectPrevious", @@ -1012,12 +1083,14 @@ }, { "context": "ChannelModal > Picker > Editor", + "use_key_equivalents": true, "bindings": { "tab": "channel_modal::ToggleMode" } }, { "context": "FileFinder || (FileFinder > Picker > Editor)", + "use_key_equivalents": true, "bindings": { "ctrl-p": "file_finder::Toggle", "ctrl-shift-a": "file_finder::ToggleSplitMenu", @@ -1026,6 +1099,7 @@ }, { "context": "FileFinder || (FileFinder > Picker > Editor) || (FileFinder > Picker > menu)", + "use_key_equivalents": true, "bindings": { "ctrl-shift-p": "file_finder::SelectPrevious", "ctrl-j": "pane::SplitDown", @@ -1036,6 +1110,7 @@ }, { "context": "TabSwitcher", + "use_key_equivalents": true, "bindings": { "ctrl-shift-tab": "menu::SelectPrevious", "ctrl-up": "menu::SelectPrevious", @@ -1045,6 +1120,7 @@ }, { "context": "Terminal", + "use_key_equivalents": true, "bindings": { "ctrl-alt-space": "terminal::ShowCharacterPalette", "copy": "terminal::Copy", @@ -1090,12 +1166,14 @@ }, { "context": "ZedPredictModal", + "use_key_equivalents": true, "bindings": { "escape": "menu::Cancel" } }, { "context": "ConfigureContextServerModal > Editor", + "use_key_equivalents": true, "bindings": { "escape": "menu::Cancel", "enter": "editor::Newline", @@ -1126,6 +1204,7 @@ }, { "context": "RunModal", + "use_key_equivalents": true, "bindings": { "ctrl-tab": "pane::ActivateNextItem", "ctrl-shift-tab": "pane::ActivatePreviousItem" @@ -1133,6 +1212,7 @@ }, { "context": "MarkdownPreview", + "use_key_equivalents": true, "bindings": { "pageup": "markdown::MovePageUp", "pagedown": "markdown::MovePageDown" From d949e30378c192f8070cfa082afe521a9997c33c Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 22:06:32 +0800 Subject: [PATCH 07/35] add windows special shorcuts --- assets/keymaps/default-windows.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 80d5ea01d4ff08..9ae22af7d52ed8 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -111,7 +111,7 @@ "ctrl-shift-end": "editor::SelectToEnd", "ctrl-a": "editor::SelectAll", "ctrl-l": "editor::SelectLine", - "ctrl-shift-i": "editor::Format", + "shift-alt-f": "editor::Format", "alt-shift-o": "editor::OrganizeImports", "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], @@ -501,13 +501,13 @@ "bindings": { "ctrl-[": "editor::Outdent", "ctrl-]": "editor::Indent", - "shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above - "shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below + "ctrl-shift-alt-up": "editor::AddSelectionAbove", // Insert Cursor Above + "ctrl-shift-alt-down": "editor::AddSelectionBelow", // Insert Cursor Below "ctrl-shift-k": "editor::DeleteLine", "alt-up": "editor::MoveLineUp", "alt-down": "editor::MoveLineDown", - "ctrl-alt-shift-up": "editor::DuplicateLineUp", - "ctrl-alt-shift-down": "editor::DuplicateLineDown", + "alt-shift-up": "editor::DuplicateLineUp", + "alt-shift-down": "editor::DuplicateLineDown", "alt-shift-right": "editor::SelectLargerSyntaxNode", // Expand Selection "alt-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection "ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection @@ -526,8 +526,8 @@ "f12": "editor::GoToDefinition", "alt-f12": "editor::GoToDefinitionSplit", "ctrl-shift-f10": "editor::GoToDefinitionSplit", - "ctrl-f12": "editor::GoToTypeDefinition", - "shift-f12": "editor::GoToImplementation", + "ctrl-f12": "editor::GoToImplementation", + "shift-f12": "editor::GoToTypeDefinition", "alt-ctrl-f12": "editor::GoToTypeDefinitionSplit", "alt-shift-f12": "editor::FindAllReferences", "ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains From 38970d8bf2de7ba03d933bed4bc6f3808405f075 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 22:13:09 +0800 Subject: [PATCH 08/35] repos `alt` key --- assets/keymaps/default-windows.json | 92 ++++++++++++++--------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 9ae22af7d52ed8..7e1df5d0a85136 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -20,7 +20,7 @@ "ctrl-escape": "menu::Cancel", "ctrl-c": "menu::Cancel", "escape": "menu::Cancel", - "alt-shift-enter": "menu::Restart", + "shift-alt-enter": "menu::Restart", "alt-enter": ["picker::ConfirmInput", { "secondary": false }], "ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }], "ctrl-shift-w": "workspace::CloseWindow", @@ -112,7 +112,7 @@ "ctrl-a": "editor::SelectAll", "ctrl-l": "editor::SelectLine", "shift-alt-f": "editor::Format", - "alt-shift-o": "editor::OrganizeImports", + "shift-alt-o": "editor::OrganizeImports", "shift-home": ["editor::SelectToBeginningOfLine", { "stop_at_soft_wraps": true, "stop_at_indent": true }], "shift-end": ["editor::SelectToEndOfLine", { "stop_at_soft_wraps": true }], "ctrl-alt-space": "editor::ShowCharacterPalette", @@ -197,7 +197,7 @@ "ctrl-k ctrl-r": "git::Restore", "ctrl-alt-y": "git::ToggleStaged", "alt-y": "git::StageAndNext", - "alt-shift-y": "git::UnstageAndNext" + "shift-alt-y": "git::UnstageAndNext" } }, { @@ -253,7 +253,7 @@ "ctrl-shift-a": "agent::ToggleContextPicker", "ctrl-shift-j": "agent::ToggleNavigationMenu", "ctrl-shift-i": "agent::ToggleOptionsMenu", - "ctrl-alt-shift-n": "agent::ToggleNewThreadMenu", + "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu", "shift-alt-escape": "agent::ExpandMessageEditor", "ctrl->": "assistant::QuoteSelection", "ctrl-alt-e": "agent::RemoveAllContext", @@ -414,8 +414,8 @@ "shift-find": "search::FocusSearch", "ctrl-shift-f": "search::FocusSearch", "ctrl-shift-h": "search::ToggleReplace", - "alt-ctrl-g": "search::ToggleRegex", - "alt-ctrl-x": "search::ToggleRegex" + "ctrl-alt-g": "search::ToggleRegex", + "ctrl-alt-x": "search::ToggleRegex" } }, { @@ -440,8 +440,8 @@ "bindings": { "escape": "project_search::ToggleFocus", "ctrl-shift-h": "search::ToggleReplace", - "alt-ctrl-g": "search::ToggleRegex", - "alt-ctrl-x": "search::ToggleRegex" + "ctrl-alt-g": "search::ToggleRegex", + "ctrl-alt-x": "search::ToggleRegex" } }, { @@ -464,8 +464,8 @@ "ctrl-shift-pagedown": "pane::SwapItemRight", "ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }], "ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }], - "alt-ctrl-t": ["pane::CloseOtherItems", { "close_pinned": false }], - "alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes", + "ctrl-alt-t": ["pane::CloseOtherItems", { "close_pinned": false }], + "ctrl-shift-alt-w": "workspace::CloseInactiveTabsAndPanes", "ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }], "ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }], "ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }], @@ -477,19 +477,19 @@ "forward": "pane::GoForward", "ctrl-alt-g": "search::SelectNextMatch", "f3": "search::SelectNextMatch", - "ctrl-alt-shift-g": "search::SelectPreviousMatch", + "ctrl-shift-alt-g": "search::SelectPreviousMatch", "shift-f3": "search::SelectPreviousMatch", "shift-find": "project_search::ToggleFocus", "ctrl-shift-f": "project_search::ToggleFocus", - "ctrl-alt-shift-h": "search::ToggleReplace", - "ctrl-alt-shift-l": "search::ToggleSelection", + "ctrl-shift-alt-h": "search::ToggleReplace", + "ctrl-shift-alt-l": "search::ToggleSelection", "alt-enter": "search::SelectAllMatches", "alt-c": "search::ToggleCaseSensitive", "alt-w": "search::ToggleWholeWord", "alt-find": "project_search::ToggleFilters", - "alt-ctrl-f": "project_search::ToggleFilters", - "ctrl-alt-shift-r": "search::ToggleRegex", - "ctrl-alt-shift-x": "search::ToggleRegex", + "ctrl-alt-f": "project_search::ToggleFilters", + "ctrl-shift-alt-r": "search::ToggleRegex", + "ctrl-shift-alt-x": "search::ToggleRegex", "alt-r": "search::ToggleRegex", "ctrl-k shift-enter": "pane::TogglePinTab" } @@ -506,10 +506,10 @@ "ctrl-shift-k": "editor::DeleteLine", "alt-up": "editor::MoveLineUp", "alt-down": "editor::MoveLineDown", - "alt-shift-up": "editor::DuplicateLineUp", - "alt-shift-down": "editor::DuplicateLineDown", - "alt-shift-right": "editor::SelectLargerSyntaxNode", // Expand Selection - "alt-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection + "shift-alt-up": "editor::DuplicateLineUp", + "shift-alt-down": "editor::DuplicateLineDown", + "shift-alt-right": "editor::SelectLargerSyntaxNode", // Expand Selection + "shift-alt-left": "editor::SelectSmallerSyntaxNode", // Shrink Selection "ctrl-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection "ctrl-f2": "editor::SelectAllMatches", // Select all occurrences of current word "ctrl-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand @@ -528,8 +528,8 @@ "ctrl-shift-f10": "editor::GoToDefinitionSplit", "ctrl-f12": "editor::GoToImplementation", "shift-f12": "editor::GoToTypeDefinition", - "alt-ctrl-f12": "editor::GoToTypeDefinitionSplit", - "alt-shift-f12": "editor::FindAllReferences", + "ctrl-alt-f12": "editor::GoToTypeDefinitionSplit", + "shift-alt-f12": "editor::FindAllReferences", "ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains "ctrl-|": "editor::MoveToEnclosingBracket", "ctrl-{": "editor::Fold", @@ -554,7 +554,7 @@ "ctrl-k r": "editor::RevealInFileManager", "ctrl-k p": "editor::CopyPath", "ctrl-\\": "pane::SplitRight", - "ctrl-alt-shift-c": "editor::DisplayCursorNames", + "ctrl-shift-alt-c": "editor::DisplayCursorNames", "alt-.": "editor::GoToHunk", "alt-,": "editor::GoToPreviousHunk" } @@ -590,13 +590,13 @@ "alt-open": ["projects::OpenRecent", { "create_new_window": false }], // Change the default action on `menu::Confirm` by setting the parameter // "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }], - "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": false }], - "alt-shift-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], + "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": false }], + "shift-alt-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], // Change to open path modal for existing remote connection by setting the parameter - // "alt-ctrl-shift-o": "["projects::OpenRemote", { "from_existing_connection": true }]", - "alt-ctrl-shift-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], - "alt-ctrl-shift-b": "branches::OpenRecent", - "alt-shift-enter": "toast::RunAction", + // "ctrl-shift-alt-o": "["projects::OpenRemote", { "from_existing_connection": true }]", + "ctrl-shift-alt-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], + "ctrl-shift-alt-b": "branches::OpenRecent", + "shift-alt-enter": "toast::RunAction", "ctrl-~": "workspace::NewTerminal", "save": "workspace::Save", "ctrl-s": "workspace::Save", @@ -665,8 +665,8 @@ "ctrl-shift-r": "task::Rerun", "ctrl-alt-r": "task::Rerun", "alt-t": "task::Rerun", - "alt-shift-t": "task::Spawn", - "alt-shift-r": ["task::Spawn", { "reveal_target": "center" }], + "shift-alt-t": "task::Spawn", + "shift-alt-r": ["task::Spawn", { "reveal_target": "center" }], // also possible to spawn tasks by name: // "foo-bar": ["task::Spawn", { "task_name": "MyTask", "reveal_target": "dock" }] // or by tag: @@ -713,10 +713,10 @@ "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", "ctrl-alt-right": "editor::MoveToNextSubwordEnd", - "ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart", - "ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd", - "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" + "ctrl-shift-alt-left": "editor::SelectToPreviousSubwordStart", + "ctrl-shift-alt-b": "editor::SelectToPreviousSubwordStart", + "ctrl-shift-alt-right": "editor::SelectToNextSubwordEnd", + "ctrl-shift-alt-f": "editor::SelectToNextSubwordEnd" } }, // Bindings from Atom @@ -801,7 +801,7 @@ { "use_key_equivalents": true, "bindings": { - "ctrl-alt-shift-f": "workspace::FollowNextCollaborator", + "ctrl-shift-alt-f": "workspace::FollowNextCollaborator", // Only available in debug builds: opens an element inspector for development. "ctrl-alt-i": "dev::ToggleInspector" } @@ -862,9 +862,9 @@ "right": "outline_panel::ExpandSelectedEntry", "alt-copy": "outline_panel::CopyPath", "ctrl-alt-c": "outline_panel::CopyPath", - "alt-shift-copy": "workspace::CopyRelativePath", - "alt-ctrl-shift-c": "workspace::CopyRelativePath", - "alt-ctrl-r": "outline_panel::RevealInFileManager", + "shift-alt-copy": "workspace::CopyRelativePath", + "ctrl-shift-alt-c": "workspace::CopyRelativePath", + "ctrl-alt-r": "outline_panel::RevealInFileManager", "space": "outline_panel::OpenSelectedEntry", "shift-down": "menu::SelectNext", "shift-up": "menu::SelectPrevious", @@ -881,7 +881,7 @@ "new": "project_panel::NewFile", "ctrl-n": "project_panel::NewFile", "alt-new": "project_panel::NewDirectory", - "alt-ctrl-n": "project_panel::NewDirectory", + "ctrl-alt-n": "project_panel::NewDirectory", "cut": "project_panel::Cut", "ctrl-x": "project_panel::Cut", "copy": "project_panel::Copy", @@ -892,8 +892,8 @@ "ctrl-v": "project_panel::Paste", "alt-copy": "project_panel::CopyPath", "ctrl-alt-c": "project_panel::CopyPath", - "alt-shift-copy": "workspace::CopyRelativePath", - "alt-ctrl-shift-c": "workspace::CopyRelativePath", + "shift-alt-copy": "workspace::CopyRelativePath", + "ctrl-shift-alt-c": "workspace::CopyRelativePath", "enter": "project_panel::Rename", "f2": "project_panel::Rename", "backspace": ["project_panel::Trash", { "skip_prompt": false }], @@ -901,11 +901,11 @@ "shift-delete": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }], - "alt-ctrl-r": "project_panel::RevealInFileManager", + "ctrl-alt-r": "project_panel::RevealInFileManager", "ctrl-shift-enter": "project_panel::OpenWithSystem", "alt-d": "project_panel::CompareMarkedFiles", "shift-find": "project_panel::NewSearchInDirectory", - "ctrl-alt-shift-f": "project_panel::NewSearchInDirectory", + "ctrl-shift-alt-f": "project_panel::NewSearchInDirectory", "shift-down": "menu::SelectNext", "shift-up": "menu::SelectPrevious", "escape": "menu::Cancel" @@ -926,7 +926,7 @@ "down": "menu::SelectNext", "enter": "menu::Confirm", "alt-y": "git::StageFile", - "alt-shift-y": "git::UnstageFile", + "shift-alt-y": "git::UnstageFile", "ctrl-alt-y": "git::ToggleStaged", "space": "git::ToggleStaged", "shift-space": "git::StageRange", @@ -1224,7 +1224,7 @@ "bindings": { "ctrl-f": "search::FocusSearch", "alt-find": "keymap_editor::ToggleKeystrokeSearch", - "alt-ctrl-f": "keymap_editor::ToggleKeystrokeSearch", + "ctrl-alt-f": "keymap_editor::ToggleKeystrokeSearch", "alt-c": "keymap_editor::ToggleConflictFilter", "enter": "keymap_editor::EditBinding", "alt-enter": "keymap_editor::CreateBinding", From 60f4aa1a8c2d5c726b189e7ccbafefa0921df77c Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 22:18:09 +0800 Subject: [PATCH 09/35] unshifted keys --- assets/keymaps/default-windows.json | 31 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 7e1df5d0a85136..e9d29a5777037c 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -28,7 +28,7 @@ "open": "workspace::Open", "ctrl-o": "workspace::Open", "ctrl-=": ["zed::IncreaseBufferFontSize", { "persist": false }], - "ctrl-+": ["zed::IncreaseBufferFontSize", { "persist": false }], + "ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }], "ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }], "ctrl-0": ["zed::ResetBufferFontSize", { "persist": false }], "ctrl-,": "zed::OpenSettings", @@ -142,8 +142,8 @@ "find": "buffer_search::Deploy", "ctrl-f": "buffer_search::Deploy", "ctrl-h": "buffer_search::DeployReplace", - "ctrl->": "assistant::QuoteSelection", - "ctrl-<": "assistant::InsertIntoEditor", + "ctrl-shift-.": "assistant::QuoteSelection", + "ctrl-shift-,": "assistant::InsertIntoEditor", "ctrl-alt-e": "editor::SelectEnclosingSymbol", "ctrl-shift-backspace": "editor::GoToPreviousChange", "ctrl-shift-alt-backspace": "editor::GoToNextChange", @@ -228,7 +228,7 @@ "ctrl-enter": "assistant::Assist", "ctrl-s": "workspace::Save", "save": "workspace::Save", - "ctrl-<": "assistant::InsertIntoEditor", + "ctrl-shift-,": "assistant::InsertIntoEditor", "shift-enter": "assistant::Split", "ctrl-r": "assistant::CycleMessageRole", "enter": "assistant::ConfirmCommand", @@ -255,7 +255,7 @@ "ctrl-shift-i": "agent::ToggleOptionsMenu", "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu", "shift-alt-escape": "agent::ExpandMessageEditor", - "ctrl->": "assistant::QuoteSelection", + "ctrl-shift-.": "assistant::QuoteSelection", "ctrl-alt-e": "agent::RemoveAllContext", "ctrl-shift-e": "project_panel::ToggleFocus", "ctrl-shift-enter": "agent::ContinueThread", @@ -289,7 +289,6 @@ { "context": "AgentPanel && external_agent_thread", "use_key_equivalents": true, - "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewExternalAgentThread", "ctrl-alt-t": "agent::NewThread" @@ -473,7 +472,7 @@ "ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes", "back": "pane::GoBack", "ctrl-alt--": "pane::GoBack", - "ctrl-alt-_": "pane::GoForward", + "ctrl-shift-alt--": "pane::GoForward", "forward": "pane::GoForward", "ctrl-alt-g": "search::SelectNextMatch", "f3": "search::SelectNextMatch", @@ -531,9 +530,9 @@ "ctrl-alt-f12": "editor::GoToTypeDefinitionSplit", "shift-alt-f12": "editor::FindAllReferences", "ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains - "ctrl-|": "editor::MoveToEnclosingBracket", - "ctrl-{": "editor::Fold", - "ctrl-}": "editor::UnfoldLines", + "ctrl-shift-\\": "editor::MoveToEnclosingBracket", + "ctrl-shift-[": "editor::Fold", + "ctrl-shift-]": "editor::UnfoldLines", "ctrl-k ctrl-l": "editor::ToggleFold", "ctrl-k ctrl-[": "editor::FoldRecursive", "ctrl-k ctrl-]": "editor::UnfoldRecursive", @@ -597,7 +596,7 @@ "ctrl-shift-alt-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], "ctrl-shift-alt-b": "branches::OpenRecent", "shift-alt-enter": "toast::RunAction", - "ctrl-~": "workspace::NewTerminal", + "ctrl-shift-`": "workspace::NewTerminal", "save": "workspace::Save", "ctrl-s": "workspace::Save", "ctrl-k s": "workspace::SaveWithoutFormat", @@ -626,9 +625,9 @@ // For 0px parameter, uses UI font size value. "ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], "ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], - "ctrl-alt-)": "workspace::ResetOpenDocksSize", - "ctrl-alt-_": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], - "ctrl-alt-+": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], + "ctrl-shift-alt-0": "workspace::ResetOpenDocksSize", + "ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], + "ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], "shift-find": "pane::DeploySearch", "ctrl-shift-f": "pane::DeploySearch", "ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }], @@ -648,7 +647,7 @@ "ctrl-shift-b": "outline_panel::ToggleFocus", "ctrl-shift-g": "git_panel::ToggleFocus", "ctrl-shift-d": "debug_panel::ToggleFocus", - "ctrl-?": "agent::ToggleFocus", + "ctrl-shift-/": "agent::ToggleFocus", "alt-save": "workspace::SaveAll", "ctrl-alt-s": "workspace::SaveAll", "ctrl-k m": "language_selector::Toggle", @@ -824,7 +823,7 @@ "ctrl-f8": "editor::GoToHunk", "ctrl-shift-f8": "editor::GoToPreviousHunk", "ctrl-enter": "assistant::InlineAssist", - "ctrl-:": "editor::ToggleInlayHints" + "ctrl-shift-;": "editor::ToggleInlayHints" } }, { From ca13d025ed03ebb61f73cbe6f2768042e61d3b86 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 22:39:58 +0800 Subject: [PATCH 10/35] mark all `ctrl-alt` related shortcuts --- assets/keymaps/default-windows.json | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index e9d29a5777037c..6c55720e84976b 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -41,8 +41,10 @@ "ctrl-f11": "debugger::StepInto", "shift-f11": "debugger::StepOut", "f11": "zed::ToggleFullScreen", + // todo(windows) "ctrl-alt-z": "edit_prediction::RateCompletions", "ctrl-shift-i": "edit_prediction::ToggleMenu", + // todo(windows) "ctrl-alt-l": "lsp_tool::ToggleMenu" } }, @@ -144,6 +146,7 @@ "ctrl-h": "buffer_search::DeployReplace", "ctrl-shift-.": "assistant::QuoteSelection", "ctrl-shift-,": "assistant::InsertIntoEditor", + // todo(windows) "ctrl-alt-e": "editor::SelectEnclosingSymbol", "ctrl-shift-backspace": "editor::GoToPreviousChange", "ctrl-shift-alt-backspace": "editor::GoToNextChange", @@ -195,6 +198,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-k ctrl-r": "git::Restore", + // todo(windows) "ctrl-alt-y": "git::ToggleStaged", "alt-y": "git::StageAndNext", "shift-alt-y": "git::UnstageAndNext" @@ -244,18 +248,24 @@ "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewThread", + // todo(windows) "ctrl-alt-n": "agent::NewTextThread", "ctrl-shift-h": "agent::OpenHistory", + // todo(windows) "ctrl-alt-c": "agent::OpenSettings", + // todo(windows) "ctrl-alt-p": "agent::OpenRulesLibrary", "ctrl-i": "agent::ToggleProfileSelector", + // todo(windows) "ctrl-alt-/": "agent::ToggleModelSelector", "ctrl-shift-a": "agent::ToggleContextPicker", "ctrl-shift-j": "agent::ToggleNavigationMenu", "ctrl-shift-i": "agent::ToggleOptionsMenu", + // todo(windows) "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu", "shift-alt-escape": "agent::ExpandMessageEditor", "ctrl-shift-.": "assistant::QuoteSelection", + // todo(windows) "ctrl-alt-e": "agent::RemoveAllContext", "ctrl-shift-e": "project_panel::ToggleFocus", "ctrl-shift-enter": "agent::ContinueThread", @@ -283,6 +293,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewTextThread", + // todo(windows) "ctrl-alt-t": "agent::NewThread" } }, @@ -291,6 +302,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewExternalAgentThread", + // todo(windows) "ctrl-alt-t": "agent::NewThread" } }, @@ -413,7 +425,9 @@ "shift-find": "search::FocusSearch", "ctrl-shift-f": "search::FocusSearch", "ctrl-shift-h": "search::ToggleReplace", + // todo(windows) "ctrl-alt-g": "search::ToggleRegex", + // todo(windows) "ctrl-alt-x": "search::ToggleRegex" } }, @@ -439,7 +453,9 @@ "bindings": { "escape": "project_search::ToggleFocus", "ctrl-shift-h": "search::ToggleReplace", + // todo(windows) "ctrl-alt-g": "search::ToggleRegex", + // todo(windows) "ctrl-alt-x": "search::ToggleRegex" } }, @@ -463,7 +479,9 @@ "ctrl-shift-pagedown": "pane::SwapItemRight", "ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }], "ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }], + // todo(windows) "ctrl-alt-t": ["pane::CloseOtherItems", { "close_pinned": false }], + // todo(windows) "ctrl-shift-alt-w": "workspace::CloseInactiveTabsAndPanes", "ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }], "ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }], @@ -471,23 +489,32 @@ "ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }], "ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes", "back": "pane::GoBack", + // todo(windows) "ctrl-alt--": "pane::GoBack", + // todo(windows) "ctrl-shift-alt--": "pane::GoForward", "forward": "pane::GoForward", + // todo(windows) "ctrl-alt-g": "search::SelectNextMatch", "f3": "search::SelectNextMatch", + // todo(windows) "ctrl-shift-alt-g": "search::SelectPreviousMatch", "shift-f3": "search::SelectPreviousMatch", "shift-find": "project_search::ToggleFocus", "ctrl-shift-f": "project_search::ToggleFocus", + // todo(windows) "ctrl-shift-alt-h": "search::ToggleReplace", + // todo(windows) "ctrl-shift-alt-l": "search::ToggleSelection", "alt-enter": "search::SelectAllMatches", "alt-c": "search::ToggleCaseSensitive", "alt-w": "search::ToggleWholeWord", "alt-find": "project_search::ToggleFilters", + // todo(windows) "ctrl-alt-f": "project_search::ToggleFilters", + // todo(windows) "ctrl-shift-alt-r": "search::ToggleRegex", + // todo(windows) "ctrl-shift-alt-x": "search::ToggleRegex", "alt-r": "search::ToggleRegex", "ctrl-k shift-enter": "pane::TogglePinTab" @@ -553,6 +580,7 @@ "ctrl-k r": "editor::RevealInFileManager", "ctrl-k p": "editor::CopyPath", "ctrl-\\": "pane::SplitRight", + // todo(windows) "ctrl-shift-alt-c": "editor::DisplayCursorNames", "alt-.": "editor::GoToHunk", "alt-,": "editor::GoToPreviousHunk" @@ -589,11 +617,14 @@ "alt-open": ["projects::OpenRecent", { "create_new_window": false }], // Change the default action on `menu::Confirm` by setting the parameter // "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }], + // todo(windows) "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": false }], "shift-alt-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], // Change to open path modal for existing remote connection by setting the parameter // "ctrl-shift-alt-o": "["projects::OpenRemote", { "from_existing_connection": true }]", + // todo(windows) "ctrl-shift-alt-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], + // todo(windows) "ctrl-shift-alt-b": "branches::OpenRecent", "shift-alt-enter": "toast::RunAction", "ctrl-shift-`": "workspace::NewTerminal", @@ -617,16 +648,23 @@ "alt-7": ["workspace::ActivatePane", 6], "alt-8": ["workspace::ActivatePane", 7], "alt-9": ["workspace::ActivatePane", 8], + // todo(windows) "ctrl-alt-b": "workspace::ToggleRightDock", "ctrl-b": "workspace::ToggleLeftDock", "ctrl-j": "workspace::ToggleBottomDock", + // todo(windows) "ctrl-alt-y": "workspace::CloseAllDocks", + // todo(windows) "ctrl-alt-0": "workspace::ResetActiveDockSize", // For 0px parameter, uses UI font size value. + // todo(windows) "ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], + // todo(windows) "ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], "ctrl-shift-alt-0": "workspace::ResetOpenDocksSize", + // todo(windows) "ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], + // todo(windows) "ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], "shift-find": "pane::DeploySearch", "ctrl-shift-f": "pane::DeploySearch", @@ -649,6 +687,7 @@ "ctrl-shift-d": "debug_panel::ToggleFocus", "ctrl-shift-/": "agent::ToggleFocus", "alt-save": "workspace::SaveAll", + // todo(windows) "ctrl-alt-s": "workspace::SaveAll", "ctrl-k m": "language_selector::Toggle", "escape": "workspace::Unfollow", @@ -662,6 +701,7 @@ "ctrl-k shift-down": "workspace::SwapPaneDown", "ctrl-shift-x": "zed::Extensions", "ctrl-shift-r": "task::Rerun", + // todo(windows) "ctrl-alt-r": "task::Rerun", "alt-t": "task::Rerun", "shift-alt-t": "task::Spawn", @@ -707,14 +747,18 @@ "ctrl-shift-u": "editor::RedoSelection", "ctrl-shift-j": "editor::JoinLines", "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", + // todo(windows) "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", + // todo(windows) "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", "ctrl-alt-right": "editor::MoveToNextSubwordEnd", "ctrl-shift-alt-left": "editor::SelectToPreviousSubwordStart", + // todo(windows) "ctrl-shift-alt-b": "editor::SelectToPreviousSubwordStart", "ctrl-shift-alt-right": "editor::SelectToNextSubwordEnd", + // todo(windows) "ctrl-shift-alt-f": "editor::SelectToNextSubwordEnd" } }, @@ -800,8 +844,10 @@ { "use_key_equivalents": true, "bindings": { + // todo(windows) "ctrl-shift-alt-f": "workspace::FollowNextCollaborator", // Only available in debug builds: opens an element inspector for development. + // todo(windows) "ctrl-alt-i": "dev::ToggleInspector" } }, @@ -832,6 +878,7 @@ "bindings": { "ctrl-[": "agent::CyclePreviousInlineAssist", "ctrl-]": "agent::CycleNextInlineAssist", + // todo(windows) "ctrl-alt-e": "agent::RemoveAllContext" } }, @@ -860,9 +907,12 @@ "left": "outline_panel::CollapseSelectedEntry", "right": "outline_panel::ExpandSelectedEntry", "alt-copy": "outline_panel::CopyPath", + // todo(windows) "ctrl-alt-c": "outline_panel::CopyPath", "shift-alt-copy": "workspace::CopyRelativePath", + // todo(windows) "ctrl-shift-alt-c": "workspace::CopyRelativePath", + // todo(windows) "ctrl-alt-r": "outline_panel::RevealInFileManager", "space": "outline_panel::OpenSelectedEntry", "shift-down": "menu::SelectNext", @@ -880,6 +930,7 @@ "new": "project_panel::NewFile", "ctrl-n": "project_panel::NewFile", "alt-new": "project_panel::NewDirectory", + // todo(windows) "ctrl-alt-n": "project_panel::NewDirectory", "cut": "project_panel::Cut", "ctrl-x": "project_panel::Cut", @@ -890,8 +941,10 @@ "shift-insert": "project_panel::Paste", "ctrl-v": "project_panel::Paste", "alt-copy": "project_panel::CopyPath", + // todo(windows) "ctrl-alt-c": "project_panel::CopyPath", "shift-alt-copy": "workspace::CopyRelativePath", + // todo(windows) "ctrl-shift-alt-c": "workspace::CopyRelativePath", "enter": "project_panel::Rename", "f2": "project_panel::Rename", @@ -900,10 +953,12 @@ "shift-delete": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }], + // todo(windows) "ctrl-alt-r": "project_panel::RevealInFileManager", "ctrl-shift-enter": "project_panel::OpenWithSystem", "alt-d": "project_panel::CompareMarkedFiles", "shift-find": "project_panel::NewSearchInDirectory", + // todo(windows) "ctrl-shift-alt-f": "project_panel::NewSearchInDirectory", "shift-down": "menu::SelectNext", "shift-up": "menu::SelectPrevious", @@ -926,6 +981,7 @@ "enter": "menu::Confirm", "alt-y": "git::StageFile", "shift-alt-y": "git::UnstageFile", + // todo(windows) "ctrl-alt-y": "git::ToggleStaged", "space": "git::ToggleStaged", "shift-space": "git::StageRange", @@ -1023,6 +1079,7 @@ "right": "variable_list::ExpandSelectedEntry", "enter": "variable_list::EditVariable", "ctrl-c": "variable_list::CopyVariableValue", + // todo(windows) "ctrl-alt-c": "variable_list::CopyVariableName", "delete": "variable_list::RemoveWatch", "backspace": "variable_list::RemoveWatch", @@ -1159,6 +1216,7 @@ "shift-end": "terminal::ScrollToBottom", "ctrl-shift-space": "terminal::ToggleViMode", "ctrl-shift-r": "terminal::RerunTask", + // todo(windows) "ctrl-alt-r": "terminal::RerunTask", "alt-t": "terminal::RerunTask" } @@ -1223,6 +1281,7 @@ "bindings": { "ctrl-f": "search::FocusSearch", "alt-find": "keymap_editor::ToggleKeystrokeSearch", + // todo(windows) "ctrl-alt-f": "keymap_editor::ToggleKeystrokeSearch", "alt-c": "keymap_editor::ToggleConflictFilter", "enter": "keymap_editor::EditBinding", From 1d088ecebed6533042e58c5209d2587b85f45470 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 19 Aug 2025 23:52:37 +0800 Subject: [PATCH 11/35] add keyboard mapper --- crates/gpui/src/app.rs | 17 ++- crates/gpui/src/platform.rs | 6 +- crates/gpui/src/platform/keyboard.rs | 8 ++ crates/gpui/src/platform/keystroke.rs | 8 +- crates/gpui/src/platform/windows/keyboard.rs | 129 ++++++++++++++++++- crates/gpui/src/platform/windows/platform.rs | 4 + 6 files changed, 156 insertions(+), 16 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index bbd59fa7bc1276..c1abf87d281771 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -37,10 +37,10 @@ use crate::{ AssetSource, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DispatchPhase, DisplayId, EventEmitter, FocusHandle, FocusMap, ForegroundExecutor, Global, KeyBinding, KeyContext, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, - PlatformDisplay, PlatformKeyboardLayout, Point, PromptBuilder, PromptButton, PromptHandle, - PromptLevel, Render, RenderImage, RenderablePromptHandle, Reservation, ScreenCaptureSource, - SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, Window, WindowAppearance, - WindowHandle, WindowId, WindowInvalidator, + PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, Point, PromptBuilder, + PromptButton, PromptHandle, PromptLevel, Render, RenderImage, RenderablePromptHandle, + Reservation, ScreenCaptureSource, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, + Window, WindowAppearance, WindowHandle, WindowId, WindowInvalidator, colors::{Colors, GlobalColors}, current_platform, hash, init_app_menus, }; @@ -263,6 +263,7 @@ pub struct App { pub(crate) focus_handles: Arc, pub(crate) keymap: Rc>, pub(crate) keyboard_layout: Box, + pub(crate) keyboard_mapper: Rc, pub(crate) global_action_listeners: FxHashMap>>, pending_effects: VecDeque, @@ -312,6 +313,7 @@ impl App { let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); let keyboard_layout = platform.keyboard_layout(); + let keyboard_mapper = platform.keyboard_mapper(); let app = Rc::new_cyclic(|this| AppCell { app: RefCell::new(App { @@ -337,6 +339,7 @@ impl App { focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), keymap: Rc::new(RefCell::new(Keymap::default())), keyboard_layout, + keyboard_mapper, global_action_listeners: FxHashMap::default(), pending_effects: VecDeque::new(), pending_notifications: FxHashSet::default(), @@ -376,6 +379,7 @@ impl App { if let Some(app) = app.upgrade() { let cx = &mut app.borrow_mut(); cx.keyboard_layout = cx.platform.keyboard_layout(); + cx.keyboard_mapper = cx.platform.keyboard_mapper(); cx.keyboard_layout_observers .clone() .retain(&(), move |callback| (callback)(cx)); @@ -424,6 +428,11 @@ impl App { self.keyboard_layout.as_ref() } + /// Get the current keyboard mapper. + pub fn keyboard_mapper(&self) -> &dyn PlatformKeyboardMapper { + self.keyboard_mapper.as_ref() + } + /// Invokes a handler when the current keyboard layout changes pub fn on_keyboard_layout_change(&self, mut callback: F) -> Subscription where diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 4d2feeaf1d0412..f64710bc562146 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -231,7 +231,6 @@ pub(crate) trait Platform: 'static { fn on_quit(&self, callback: Box); fn on_reopen(&self, callback: Box); - fn on_keyboard_layout_change(&self, callback: Box); fn set_menus(&self, menus: Vec, keymap: &Keymap); fn get_menus(&self) -> Option> { @@ -251,7 +250,6 @@ pub(crate) trait Platform: 'static { fn on_app_menu_action(&self, callback: Box); fn on_will_open_app_menu(&self, callback: Box); fn on_validate_app_menu_command(&self, callback: Box bool>); - fn keyboard_layout(&self) -> Box; fn compositor_name(&self) -> &'static str { "" @@ -272,6 +270,10 @@ pub(crate) trait Platform: 'static { fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task>; fn read_credentials(&self, url: &str) -> Task)>>>; fn delete_credentials(&self, url: &str) -> Task>; + + fn keyboard_layout(&self) -> Box; + fn keyboard_mapper(&self) -> Rc; + fn on_keyboard_layout_change(&self, callback: Box); } /// A handle to a platform's display, e.g. a monitor or laptop screen. diff --git a/crates/gpui/src/platform/keyboard.rs b/crates/gpui/src/platform/keyboard.rs index e28d7815200800..b751ceebebf51a 100644 --- a/crates/gpui/src/platform/keyboard.rs +++ b/crates/gpui/src/platform/keyboard.rs @@ -1,3 +1,5 @@ +use crate::{KeybindingKeystroke, Keystroke}; + /// A trait for platform-specific keyboard layouts pub trait PlatformKeyboardLayout { /// Get the keyboard layout ID, which should be unique to the layout @@ -5,3 +7,9 @@ pub trait PlatformKeyboardLayout { /// Get the keyboard layout display name fn name(&self) -> &str; } + +/// A trait for platform-specific keyboard mappings +pub trait PlatformKeyboardMapper { + /// Map a key equivalent to its platform-specific representation + fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke; +} diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index fcd70a57e610a1..5c5fce01168b76 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -300,13 +300,11 @@ impl Keystroke { impl KeybindingKeystroke { /// Create a new keybinding keystroke from the given keystroke - pub fn new(keystroke: Keystroke) -> Self { - let (key, modifiers) = to_unshifted_key(&keystroke.key, &keystroke.modifiers); - let inner = keystroke.into_shifted(); + pub fn new(inner: Keystroke) -> Self { KeybindingKeystroke { inner, - key, - modifiers, + modifiers: inner.modifiers, + key: inner.key.clone(), } } } diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 371feb70c25ab5..138c356ab69c19 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -1,22 +1,28 @@ +use std::borrow::Cow; + use anyhow::Result; use windows::Win32::UI::{ Input::KeyboardAndMouse::{ - GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MapVirtualKeyW, ToUnicode, VIRTUAL_KEY, VK_0, - VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1, VK_CONTROL, VK_MENU, - VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8, VK_OEM_102, - VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT, + GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode, + VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_ABNT_C1, + VK_CONTROL, VK_MENU, VK_OEM_1, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, + VK_OEM_8, VK_OEM_102, VK_OEM_COMMA, VK_OEM_MINUS, VK_OEM_PERIOD, VK_OEM_PLUS, VK_SHIFT, }, WindowsAndMessaging::KL_NAMELENGTH, }; use windows_core::HSTRING; -use crate::{Modifiers, PlatformKeyboardLayout}; +use crate::{ + KeybindingKeystroke, Keystroke, Modifiers, PlatformKeyboardLayout, PlatformKeyboardMapper, +}; pub(crate) struct WindowsKeyboardLayout { id: String, name: String, } +pub(crate) struct WindowsKeyboardMapper; + impl PlatformKeyboardLayout for WindowsKeyboardLayout { fn id(&self) -> &str { &self.id @@ -27,6 +33,65 @@ impl PlatformKeyboardLayout for WindowsKeyboardLayout { } } +impl PlatformKeyboardMapper for WindowsKeyboardMapper { + fn map_key_equivalent(&self, mut keystroke: Keystroke) -> KeybindingKeystroke { + let Some((vkey, shift)) = key_needs_processing(&keystroke.key) else { + return KeybindingKeystroke::new(keystroke); + }; + if shift && keystroke.modifiers.shift { + log::warn!( + "Keystroke '{}' has both shift and a shifted key, this is likely a bug", + keystroke.key + ); + keystroke.modifiers.shift = false; + } + // translate to unshifted key first + let Some(key) = get_key_from_vkey(vkey) else { + log::error!( + "Failed to map key equivalent '{:?}' to a valid key", + keystroke + ); + return KeybindingKeystroke::new(keystroke); + }; + let modifiers = Modifiers { + control: keystroke.modifiers.control, + alt: keystroke.modifiers.alt, + shift, + platform: keystroke.modifiers.platform, + function: keystroke.modifiers.function, + }; + + keystroke.key = if shift { + let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) }; + if scan_code == 0 { + log::error!( + "Failed to map keystroke {:?} with virtual key '{:?}' to a scan code", + keystroke, + vkey + ); + return KeybindingKeystroke::new(keystroke); + } + let Some(shifted_key) = get_shifted_key(vkey, scan_code) else { + log::error!( + "Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key", + keystroke, + vkey + ); + return KeybindingKeystroke::new(keystroke); + }; + shifted_key + } else { + key.clone() + }; + + KeybindingKeystroke { + inner: keystroke, + modifiers, + key, + } + } +} + impl WindowsKeyboardLayout { pub(crate) fn new() -> Result { let mut buffer = [0u16; KL_NAMELENGTH as usize]; @@ -48,6 +113,12 @@ impl WindowsKeyboardLayout { } } +impl WindowsKeyboardMapper { + pub(crate) fn new() -> Self { + Self + } +} + pub(crate) fn get_keystroke_key( vkey: VIRTUAL_KEY, scan_code: u32, @@ -140,3 +211,51 @@ pub(crate) fn generate_key_char( _ => None, } } + +fn key_needs_processing(key: &str) -> Option<(VIRTUAL_KEY, bool)> { + match key { + "`" => Some((VK_OEM_3, false)), + "~" => Some((VK_OEM_3, true)), + "1" => Some((VK_1, false)), + "!" => Some((VK_1, true)), + "2" => Some((VK_2, false)), + "@" => Some((VK_2, true)), + "3" => Some((VK_3, false)), + "#" => Some((VK_3, true)), + "4" => Some((VK_4, false)), + "$" => Some((VK_4, true)), + "5" => Some((VK_5, false)), + "%" => Some((VK_5, true)), + "6" => Some((VK_6, false)), + "^" => Some((VK_6, true)), + "7" => Some((VK_7, false)), + "&" => Some((VK_7, true)), + "8" => Some((VK_8, false)), + "*" => Some((VK_8, true)), + "9" => Some((VK_9, false)), + "(" => Some((VK_9, true)), + "0" => Some((VK_0, false)), + ")" => Some((VK_0, true)), + "-" => Some((VK_OEM_MINUS, false)), + "_" => Some((VK_OEM_MINUS, true)), + "=" => Some((VK_OEM_PLUS, false)), + "+" => Some((VK_OEM_PLUS, true)), + "[" => Some((VK_OEM_4, false)), + "{" => Some((VK_OEM_4, true)), + "]" => Some((VK_OEM_6, false)), + "}" => Some((VK_OEM_6, true)), + "\\" => Some((VK_OEM_5, false)), + "|" => Some((VK_OEM_5, true)), + ";" => Some((VK_OEM_1, false)), + ":" => Some((VK_OEM_1, true)), + "'" => Some((VK_OEM_7, false)), + "\"" => Some((VK_OEM_7, true)), + "," => Some((VK_OEM_COMMA, false)), + "<" => Some((VK_OEM_COMMA, true)), + "." => Some((VK_OEM_PERIOD, false)), + ">" => Some((VK_OEM_PERIOD, true)), + "/" => Some((VK_OEM_2, false)), + "?" => Some((VK_OEM_2, true)), + _ => None, + } +} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 6202e05fb3b26f..5ac2be2f238e9c 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -351,6 +351,10 @@ impl Platform for WindowsPlatform { ) } + fn keyboard_mapper(&self) -> Rc { + Rc::new(WindowsKeyboardMapper::new()) + } + fn on_keyboard_layout_change(&self, callback: Box) { self.state.borrow_mut().callbacks.keyboard_layout_change = Some(callback); } From cb8b7b03cc1d71e3615f77255d715b48c5d13749 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 20 Aug 2025 00:21:15 +0800 Subject: [PATCH 12/35] checkpoint --- crates/gpui/src/platform/keyboard.rs | 8 ++ crates/gpui/src/platform/keystroke.rs | 6 +- crates/gpui/src/platform/test/platform.rs | 11 ++- crates/gpui/src/platform/windows/keyboard.rs | 79 ++++++++++++++++---- 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/crates/gpui/src/platform/keyboard.rs b/crates/gpui/src/platform/keyboard.rs index b751ceebebf51a..2f91cb976b360a 100644 --- a/crates/gpui/src/platform/keyboard.rs +++ b/crates/gpui/src/platform/keyboard.rs @@ -13,3 +13,11 @@ pub trait PlatformKeyboardMapper { /// Map a key equivalent to its platform-specific representation fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke; } + +pub(crate) struct DummyKeyboardMapper; + +impl PlatformKeyboardMapper for DummyKeyboardMapper { + fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke { + KeybindingKeystroke::new(keystroke) + } +} diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 5c5fce01168b76..c463caad2879d5 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -301,10 +301,12 @@ impl Keystroke { impl KeybindingKeystroke { /// Create a new keybinding keystroke from the given keystroke pub fn new(inner: Keystroke) -> Self { + let key = inner.key.clone(); + let modifiers = inner.modifiers; KeybindingKeystroke { inner, - modifiers: inner.modifiers, - key: inner.key.clone(), + modifiers, + key, } } } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 00afcd81b599cc..15b909199fbd53 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,8 +1,9 @@ use crate::{ AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DevicePixels, - ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, PlatformKeyboardLayout, - PlatformTextSystem, PromptButton, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, - SourceMetadata, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, size, + DummyKeyboardMapper, ForegroundExecutor, Keymap, NoopTextSystem, Platform, PlatformDisplay, + PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, PromptButton, + ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, SourceMetadata, Task, + TestDisplay, TestWindow, WindowAppearance, WindowParams, size, }; use anyhow::Result; use collections::VecDeque; @@ -237,6 +238,10 @@ impl Platform for TestPlatform { Box::new(TestKeyboardLayout) } + fn keyboard_mapper(&self) -> Rc { + Rc::new(DummyKeyboardMapper) + } + fn on_keyboard_layout_change(&self, _: Box) {} fn run(&self, _on_finish_launching: Box) { diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 138c356ab69c19..526522511c715d 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -1,5 +1,3 @@ -use std::borrow::Cow; - use anyhow::Result; use windows::Win32::UI::{ Input::KeyboardAndMouse::{ @@ -35,17 +33,19 @@ impl PlatformKeyboardLayout for WindowsKeyboardLayout { impl PlatformKeyboardMapper for WindowsKeyboardMapper { fn map_key_equivalent(&self, mut keystroke: Keystroke) -> KeybindingKeystroke { - let Some((vkey, shift)) = key_needs_processing(&keystroke.key) else { + let Some((vkey, shifted_key)) = key_needs_processing(&keystroke.key) else { return KeybindingKeystroke::new(keystroke); }; - if shift && keystroke.modifiers.shift { + if shifted_key && keystroke.modifiers.shift { log::warn!( "Keystroke '{}' has both shift and a shifted key, this is likely a bug", keystroke.key ); - keystroke.modifiers.shift = false; } - // translate to unshifted key first + + let shift = shifted_key || keystroke.modifiers.shift; + keystroke.modifiers.shift = false; + let Some(key) = get_key_from_vkey(vkey) else { log::error!( "Failed to map key equivalent '{:?}' to a valid key", @@ -53,13 +53,6 @@ impl PlatformKeyboardMapper for WindowsKeyboardMapper { ); return KeybindingKeystroke::new(keystroke); }; - let modifiers = Modifiers { - control: keystroke.modifiers.control, - alt: keystroke.modifiers.alt, - shift, - platform: keystroke.modifiers.platform, - function: keystroke.modifiers.function, - }; keystroke.key = if shift { let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) }; @@ -84,6 +77,11 @@ impl PlatformKeyboardMapper for WindowsKeyboardMapper { key.clone() }; + let modifiers = Modifiers { + shift, + ..keystroke.modifiers + }; + KeybindingKeystroke { inner: keystroke, modifiers, @@ -259,3 +257,58 @@ fn key_needs_processing(key: &str) -> Option<(VIRTUAL_KEY, bool)> { _ => None, } } + +#[cfg(test)] +mod tests { + use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper}; + + #[test] + fn test_keyboard_mapper() { + let mapper = WindowsKeyboardMapper::new(); + + // Normal case + let keystroke = Keystroke { + modifiers: Modifiers::control(), + key: "a".to_string(), + key_char: None, + }; + let mapped = mapper.map_key_equivalent(keystroke.clone()); + assert_eq!(mapped.inner, keystroke); + assert_eq!(mapped.key, "a"); + assert_eq!(mapped.modifiers, Modifiers::control()); + + // Shifted case, ctrl-$ + let keystroke = Keystroke { + modifiers: Modifiers::control(), + key: "$".to_string(), + key_char: None, + }; + let mapped = mapper.map_key_equivalent(keystroke.clone()); + assert_eq!(mapped.inner, keystroke); + assert_eq!(mapped.key, "4"); + assert_eq!(mapped.modifiers, Modifiers::control_shift()); + + // Shifted case, but shift is true + let keystroke = Keystroke { + modifiers: Modifiers::control_shift(), + key: "$".to_string(), + key_char: None, + }; + let mapped = mapper.map_key_equivalent(keystroke.clone()); + assert_eq!(mapped.inner.modifiers, Modifiers::control()); + assert_eq!(mapped.key, "4"); + assert_eq!(mapped.modifiers, Modifiers::control_shift()); + + // Windows style + let keystroke = Keystroke { + modifiers: Modifiers::control_shift(), + key: "4".to_string(), + key_char: None, + }; + let mapped = mapper.map_key_equivalent(keystroke.clone()); + assert_eq!(mapped.inner.modifiers, Modifiers::control()); + assert_eq!(mapped.inner.key, "$"); + assert_eq!(mapped.key, "4"); + assert_eq!(mapped.modifiers, Modifiers::control_shift()); + } +} From fa3abe789c6dd96fb2cfe1900177fa403becf711 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 20 Aug 2025 02:25:14 +0800 Subject: [PATCH 13/35] wip --- crates/docs_preprocessor/src/main.rs | 5 + crates/gpui/src/keymap/binding.rs | 23 +- crates/gpui/src/platform/keyboard.rs | 17 +- crates/gpui/src/platform/keystroke.rs | 260 +----------------- crates/gpui/src/platform/windows/keyboard.rs | 187 ++++++++----- crates/settings/src/keymap_file.rs | 14 +- crates/settings/src/settings.rs | 5 +- .../src/ui_components/keystroke_input.rs | 3 +- 8 files changed, 190 insertions(+), 324 deletions(-) diff --git a/crates/docs_preprocessor/src/main.rs b/crates/docs_preprocessor/src/main.rs index c900eb692aee34..c8c3dc54b76085 100644 --- a/crates/docs_preprocessor/src/main.rs +++ b/crates/docs_preprocessor/src/main.rs @@ -19,6 +19,10 @@ static KEYMAP_LINUX: LazyLock = LazyLock::new(|| { load_keymap("keymaps/default-linux.json").expect("Failed to load Linux keymap") }); +static KEYMAP_WINDOWS: LazyLock = LazyLock::new(|| { + load_keymap("keymaps/default-windows.json").expect("Failed to load Windows keymap") +}); + static ALL_ACTIONS: LazyLock> = LazyLock::new(dump_all_gpui_actions); const FRONT_MATTER_COMMENT: &str = ""; @@ -216,6 +220,7 @@ fn find_binding(os: &str, action: &str) -> Option { let keymap = match os { "macos" => &KEYMAP_MACOS, "linux" | "freebsd" => &KEYMAP_LINUX, + "windows" => &KEYMAP_WINDOWS, _ => unreachable!("Not a valid OS: {}", os), }; diff --git a/crates/gpui/src/keymap/binding.rs b/crates/gpui/src/keymap/binding.rs index 730da7fe27b4eb..edc772b4e9086a 100644 --- a/crates/gpui/src/keymap/binding.rs +++ b/crates/gpui/src/keymap/binding.rs @@ -3,8 +3,8 @@ use std::rc::Rc; use collections::HashMap; use crate::{ - Action, AsKeystroke, InvalidKeystrokeError, KeyBindingContextPredicate, KeybindingKeystroke, - Keystroke, SharedString, + Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate, + KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString, }; use smallvec::SmallVec; @@ -35,7 +35,16 @@ impl KeyBinding { pub fn new(keystrokes: &str, action: A, context: Option<&str>) -> Self { let context_predicate = context.map(|context| KeyBindingContextPredicate::parse(context).unwrap().into()); - Self::load(keystrokes, Box::new(action), context_predicate, None, None).unwrap() + Self::load( + keystrokes, + Box::new(action), + context_predicate, + false, + None, + None, + &DummyKeyboardMapper, + ) + .unwrap() } /// Load a keybinding from the given raw data. @@ -43,12 +52,14 @@ impl KeyBinding { keystrokes: &str, action: Box, context_predicate: Option>, + use_key_equivalents: bool, key_equivalents: Option<&HashMap>, action_input: Option, + keyboard_mapper: &dyn PlatformKeyboardMapper, ) -> std::result::Result { let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes .split_whitespace() - .map(|source| Keystroke::parse(source).map(|keystroke| keystroke.into_shifted())) + .map(Keystroke::parse) .collect::>()?; if let Some(equivalents) = key_equivalents { @@ -63,7 +74,9 @@ impl KeyBinding { let keystrokes = keystrokes .into_iter() - .map(KeybindingKeystroke::new) + .map(|keystroke| { + KeybindingKeystroke::new(keystroke, use_key_equivalents, keyboard_mapper) + }) .collect(); Ok(Self { diff --git a/crates/gpui/src/platform/keyboard.rs b/crates/gpui/src/platform/keyboard.rs index 2f91cb976b360a..8f52e11cb23dc9 100644 --- a/crates/gpui/src/platform/keyboard.rs +++ b/crates/gpui/src/platform/keyboard.rs @@ -11,13 +11,22 @@ pub trait PlatformKeyboardLayout { /// A trait for platform-specific keyboard mappings pub trait PlatformKeyboardMapper { /// Map a key equivalent to its platform-specific representation - fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke; + fn map_key_equivalent( + &self, + keystroke: Keystroke, + use_key_equivalents: bool, + ) -> KeybindingKeystroke; } -pub(crate) struct DummyKeyboardMapper; +/// A dummy implementation of the platform keyboard mapper +pub struct DummyKeyboardMapper; impl PlatformKeyboardMapper for DummyKeyboardMapper { - fn map_key_equivalent(&self, keystroke: Keystroke) -> KeybindingKeystroke { - KeybindingKeystroke::new(keystroke) + fn map_key_equivalent( + &self, + keystroke: Keystroke, + _use_key_equivalents: bool, + ) -> KeybindingKeystroke { + KeybindingKeystroke::from_keystroke(keystroke) } } diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index c463caad2879d5..c3a2f7d7a21b33 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -5,6 +5,8 @@ use std::{ fmt::{Display, Write}, }; +use crate::PlatformKeyboardMapper; + /// TODO: pub trait AsKeystroke { /// TODO: @@ -281,30 +283,23 @@ impl Keystroke { } self } - - /// TODO: - pub fn into_shifted(self) -> Self { - let Keystroke { - modifiers, - key, - key_char, - } = self; - let (key, modifiers) = into_shifted_key(key, modifiers); - Self { - key, - modifiers, - key_char, - } - } } impl KeybindingKeystroke { /// Create a new keybinding keystroke from the given keystroke - pub fn new(inner: Keystroke) -> Self { - let key = inner.key.clone(); - let modifiers = inner.modifiers; + pub fn new( + inner: Keystroke, + use_key_equivalents: bool, + keyboard_mapper: &dyn PlatformKeyboardMapper, + ) -> Self { + keyboard_mapper.map_key_equivalent(inner, use_key_equivalents) + } + + pub(crate) fn from_keystroke(keystroke: Keystroke) -> Self { + let key = keystroke.key.clone(); + let modifiers = keystroke.modifiers; KeybindingKeystroke { - inner, + inner: keystroke, modifiers, key, } @@ -608,191 +603,6 @@ impl AsKeystroke for KeybindingKeystroke { } } -fn to_unshifted_key(key: &str, modifiers: &Modifiers) -> (String, Modifiers) { - let mut modifiers = modifiers.clone(); - match key { - "~" => { - modifiers.shift = true; - ("`".to_string(), modifiers) - } - "!" => { - modifiers.shift = true; - ("1".to_string(), modifiers) - } - "@" => { - modifiers.shift = true; - ("2".to_string(), modifiers) - } - "#" => { - modifiers.shift = true; - ("3".to_string(), modifiers) - } - "$" => { - modifiers.shift = true; - ("4".to_string(), modifiers) - } - "%" => { - modifiers.shift = true; - ("5".to_string(), modifiers) - } - "^" => { - modifiers.shift = true; - ("6".to_string(), modifiers) - } - "&" => { - modifiers.shift = true; - ("7".to_string(), modifiers) - } - "*" => { - modifiers.shift = true; - ("8".to_string(), modifiers) - } - "(" => { - modifiers.shift = true; - ("9".to_string(), modifiers) - } - ")" => { - modifiers.shift = true; - ("0".to_string(), modifiers) - } - "_" => { - modifiers.shift = true; - ("-".to_string(), modifiers) - } - "+" => { - modifiers.shift = true; - ("=".to_string(), modifiers) - } - "{" => { - modifiers.shift = true; - ("[".to_string(), modifiers) - } - "}" => { - modifiers.shift = true; - ("]".to_string(), modifiers) - } - "|" => { - modifiers.shift = true; - ("\\".to_string(), modifiers) - } - ":" => { - modifiers.shift = true; - (";".to_string(), modifiers) - } - "\"" => { - modifiers.shift = true; - ("'".to_string(), modifiers) - } - "<" => { - modifiers.shift = true; - (",".to_string(), modifiers) - } - ">" => { - modifiers.shift = true; - (">".to_string(), modifiers) - } - "?" => { - modifiers.shift = true; - ("/".to_string(), modifiers) - } - _ => (key.to_string(), modifiers), - } -} - -fn into_shifted_key(key: String, mut modifiers: Modifiers) -> (String, Modifiers) { - if !modifiers.shift { - (key, modifiers) - } else { - match key.as_str() { - "`" => { - modifiers.shift = false; - ("~".to_string(), modifiers) - } - "1" => { - modifiers.shift = false; - ("!".to_string(), modifiers) - } - "2" => { - modifiers.shift = false; - ("@".to_string(), modifiers) - } - "3" => { - modifiers.shift = false; - ("#".to_string(), modifiers) - } - "4" => { - modifiers.shift = false; - ("$".to_string(), modifiers) - } - "5" => { - modifiers.shift = false; - ("%".to_string(), modifiers) - } - "6" => { - modifiers.shift = false; - ("^".to_string(), modifiers) - } - "7" => { - modifiers.shift = false; - ("&".to_string(), modifiers) - } - "8" => { - modifiers.shift = false; - ("*".to_string(), modifiers) - } - "9" => { - modifiers.shift = false; - ("(".to_string(), modifiers) - } - "0" => { - modifiers.shift = false; - (")".to_string(), modifiers) - } - "-" => { - modifiers.shift = false; - ("_".to_string(), modifiers) - } - "=" => { - modifiers.shift = false; - ("+".to_string(), modifiers) - } - "[" => { - modifiers.shift = false; - ("{".to_string(), modifiers) - } - "]" => { - modifiers.shift = false; - ("}".to_string(), modifiers) - } - "\\" => { - modifiers.shift = false; - ("|".to_string(), modifiers) - } - ";" => { - modifiers.shift = false; - (":".to_string(), modifiers) - } - "'" => { - modifiers.shift = false; - ("\"".to_string(), modifiers) - } - "," => { - modifiers.shift = false; - ("<".to_string(), modifiers) - } - "." => { - modifiers.shift = false; - (">".to_string(), modifiers) - } - "/" => { - modifiers.shift = false; - ("?".to_string(), modifiers) - } - _ => (key, modifiers), - } - } -} - fn display_modifiers(modifiers: &Modifiers, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if modifiers.control { #[cfg(target_os = "macos")] @@ -858,45 +668,3 @@ fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { }; f.write_char(key) } - -#[cfg(test)] -mod tests { - use crate::{KeybindingKeystroke, Keystroke, Modifiers}; - - #[test] - fn test_parsing_keystroke_on_windows() { - // On windows, users prefer to use "ctrl-shift-key", so here we support - // both "ctrl-$" and "ctrl-shift-4" - let source = "ctrl-$"; - let keystroke = Keystroke::parse(source).unwrap(); - assert_eq!(keystroke.modifiers, Modifiers::control()); - assert_eq!(keystroke.key, "$"); - assert_eq!(keystroke.key_char, None); - - let keystroke_display = KeybindingKeystroke::new(keystroke.clone()); - assert_eq!(keystroke_display.inner, keystroke); - assert_eq!(keystroke_display.key, "4"); - assert_eq!(keystroke_display.modifiers, Modifiers::control_shift()); - - let source = "ctrl-shift-4"; - let keystroke = Keystroke::parse(source).unwrap(); - assert_eq!(keystroke.modifiers, Modifiers::control_shift()); - assert_eq!(keystroke.key, "4"); - assert_eq!(keystroke.key_char, None); - - let keystroke = keystroke.into_shifted(); - assert_eq!(keystroke.modifiers, Modifiers::control()); - assert_eq!(keystroke.key, "$"); - let keystroke_display = KeybindingKeystroke::new(keystroke.clone()); - assert_eq!( - keystroke_display.inner, - Keystroke { - modifiers: Modifiers::control(), - key: "$".to_string(), - key_char: None - } - ); - assert_eq!(keystroke_display.key, "4"); - assert_eq!(keystroke_display.modifiers, Modifiers::control_shift()); - } -} diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 526522511c715d..25fcffb2366689 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use collections::HashMap; use windows::Win32::UI::{ Input::KeyboardAndMouse::{ GetKeyboardLayoutNameW, MAPVK_VK_TO_CHAR, MAPVK_VK_TO_VSC, MapVirtualKeyW, ToUnicode, @@ -19,7 +20,11 @@ pub(crate) struct WindowsKeyboardLayout { name: String, } -pub(crate) struct WindowsKeyboardMapper; +pub(crate) struct WindowsKeyboardMapper { + key_to_vkey: HashMap, + vkey_to_key: HashMap, + vkey_to_shifted: HashMap, +} impl PlatformKeyboardLayout for WindowsKeyboardLayout { fn id(&self) -> &str { @@ -32,9 +37,14 @@ impl PlatformKeyboardLayout for WindowsKeyboardLayout { } impl PlatformKeyboardMapper for WindowsKeyboardMapper { - fn map_key_equivalent(&self, mut keystroke: Keystroke) -> KeybindingKeystroke { - let Some((vkey, shifted_key)) = key_needs_processing(&keystroke.key) else { - return KeybindingKeystroke::new(keystroke); + fn map_key_equivalent( + &self, + mut keystroke: Keystroke, + use_key_equivalents: bool, + ) -> KeybindingKeystroke { + let Some((vkey, shifted_key)) = self.get_vkey_from_key(&keystroke.key, use_key_equivalents) + else { + return KeybindingKeystroke::from_keystroke(keystroke); }; if shifted_key && keystroke.modifiers.shift { log::warn!( @@ -46,31 +56,22 @@ impl PlatformKeyboardMapper for WindowsKeyboardMapper { let shift = shifted_key || keystroke.modifiers.shift; keystroke.modifiers.shift = false; - let Some(key) = get_key_from_vkey(vkey) else { + let Some(key) = self.vkey_to_key.get(&vkey).cloned() else { log::error!( "Failed to map key equivalent '{:?}' to a valid key", keystroke ); - return KeybindingKeystroke::new(keystroke); + return KeybindingKeystroke::from_keystroke(keystroke); }; keystroke.key = if shift { - let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) }; - if scan_code == 0 { - log::error!( - "Failed to map keystroke {:?} with virtual key '{:?}' to a scan code", - keystroke, - vkey - ); - return KeybindingKeystroke::new(keystroke); - } - let Some(shifted_key) = get_shifted_key(vkey, scan_code) else { + let Some(shifted_key) = self.vkey_to_shifted.get(&vkey).cloned() else { log::error!( "Failed to map keystroke {:?} with virtual key '{:?}' to a shifted key", keystroke, vkey ); - return KeybindingKeystroke::new(keystroke); + return KeybindingKeystroke::from_keystroke(keystroke); }; shifted_key } else { @@ -113,7 +114,36 @@ impl WindowsKeyboardLayout { impl WindowsKeyboardMapper { pub(crate) fn new() -> Self { - Self + let mut key_to_vkey = HashMap::default(); + let mut vkey_to_key = HashMap::default(); + let mut vkey_to_shifted = HashMap::default(); + for vkey in CANDIDATE_VKEYS { + if let Some(key) = get_key_from_vkey(*vkey) { + key_to_vkey.insert(key.clone(), (vkey.0, false)); + vkey_to_key.insert(vkey.0, key); + } + let scan_code = unsafe { MapVirtualKeyW(vkey.0 as u32, MAPVK_VK_TO_VSC) }; + if scan_code == 0 { + continue; + } + if let Some(shifted_key) = get_shifted_key(*vkey, scan_code) { + key_to_vkey.insert(shifted_key.clone(), (vkey.0, true)); + vkey_to_shifted.insert(vkey.0, shifted_key); + } + } + Self { + key_to_vkey, + vkey_to_key, + vkey_to_shifted, + } + } + + fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> { + if use_key_equivalents { + key_needs_processing(key) + } else { + self.key_to_vkey.get(key).cloned() + } } } @@ -210,54 +240,81 @@ pub(crate) fn generate_key_char( } } -fn key_needs_processing(key: &str) -> Option<(VIRTUAL_KEY, bool)> { +fn key_needs_processing(key: &str) -> Option<(u16, bool)> { match key { - "`" => Some((VK_OEM_3, false)), - "~" => Some((VK_OEM_3, true)), - "1" => Some((VK_1, false)), - "!" => Some((VK_1, true)), - "2" => Some((VK_2, false)), - "@" => Some((VK_2, true)), - "3" => Some((VK_3, false)), - "#" => Some((VK_3, true)), - "4" => Some((VK_4, false)), - "$" => Some((VK_4, true)), - "5" => Some((VK_5, false)), - "%" => Some((VK_5, true)), - "6" => Some((VK_6, false)), - "^" => Some((VK_6, true)), - "7" => Some((VK_7, false)), - "&" => Some((VK_7, true)), - "8" => Some((VK_8, false)), - "*" => Some((VK_8, true)), - "9" => Some((VK_9, false)), - "(" => Some((VK_9, true)), - "0" => Some((VK_0, false)), - ")" => Some((VK_0, true)), - "-" => Some((VK_OEM_MINUS, false)), - "_" => Some((VK_OEM_MINUS, true)), - "=" => Some((VK_OEM_PLUS, false)), - "+" => Some((VK_OEM_PLUS, true)), - "[" => Some((VK_OEM_4, false)), - "{" => Some((VK_OEM_4, true)), - "]" => Some((VK_OEM_6, false)), - "}" => Some((VK_OEM_6, true)), - "\\" => Some((VK_OEM_5, false)), - "|" => Some((VK_OEM_5, true)), - ";" => Some((VK_OEM_1, false)), - ":" => Some((VK_OEM_1, true)), - "'" => Some((VK_OEM_7, false)), - "\"" => Some((VK_OEM_7, true)), - "," => Some((VK_OEM_COMMA, false)), - "<" => Some((VK_OEM_COMMA, true)), - "." => Some((VK_OEM_PERIOD, false)), - ">" => Some((VK_OEM_PERIOD, true)), - "/" => Some((VK_OEM_2, false)), - "?" => Some((VK_OEM_2, true)), + "`" => Some((VK_OEM_3.0, false)), + "~" => Some((VK_OEM_3.0, true)), + "1" => Some((VK_1.0, false)), + "!" => Some((VK_1.0, true)), + "2" => Some((VK_2.0, false)), + "@" => Some((VK_2.0, true)), + "3" => Some((VK_3.0, false)), + "#" => Some((VK_3.0, true)), + "4" => Some((VK_4.0, false)), + "$" => Some((VK_4.0, true)), + "5" => Some((VK_5.0, false)), + "%" => Some((VK_5.0, true)), + "6" => Some((VK_6.0, false)), + "^" => Some((VK_6.0, true)), + "7" => Some((VK_7.0, false)), + "&" => Some((VK_7.0, true)), + "8" => Some((VK_8.0, false)), + "*" => Some((VK_8.0, true)), + "9" => Some((VK_9.0, false)), + "(" => Some((VK_9.0, true)), + "0" => Some((VK_0.0, false)), + ")" => Some((VK_0.0, true)), + "-" => Some((VK_OEM_MINUS.0, false)), + "_" => Some((VK_OEM_MINUS.0, true)), + "=" => Some((VK_OEM_PLUS.0, false)), + "+" => Some((VK_OEM_PLUS.0, true)), + "[" => Some((VK_OEM_4.0, false)), + "{" => Some((VK_OEM_4.0, true)), + "]" => Some((VK_OEM_6.0, false)), + "}" => Some((VK_OEM_6.0, true)), + "\\" => Some((VK_OEM_5.0, false)), + "|" => Some((VK_OEM_5.0, true)), + ";" => Some((VK_OEM_1.0, false)), + ":" => Some((VK_OEM_1.0, true)), + "'" => Some((VK_OEM_7.0, false)), + "\"" => Some((VK_OEM_7.0, true)), + "," => Some((VK_OEM_COMMA.0, false)), + "<" => Some((VK_OEM_COMMA.0, true)), + "." => Some((VK_OEM_PERIOD.0, false)), + ">" => Some((VK_OEM_PERIOD.0, true)), + "/" => Some((VK_OEM_2.0, false)), + "?" => Some((VK_OEM_2.0, true)), _ => None, } } +const CANDIDATE_VKEYS: &[VIRTUAL_KEY] = &[ + VK_OEM_3, + VK_OEM_MINUS, + VK_OEM_PLUS, + VK_OEM_4, + VK_OEM_5, + VK_OEM_6, + VK_OEM_1, + VK_OEM_7, + VK_OEM_COMMA, + VK_OEM_PERIOD, + VK_OEM_2, + VK_OEM_102, + VK_OEM_8, + VK_ABNT_C1, + VK_0, + VK_1, + VK_2, + VK_3, + VK_4, + VK_5, + VK_6, + VK_7, + VK_8, + VK_9, +]; + #[cfg(test)] mod tests { use crate::{Keystroke, Modifiers, PlatformKeyboardMapper, WindowsKeyboardMapper}; @@ -272,7 +329,7 @@ mod tests { key: "a".to_string(), key_char: None, }; - let mapped = mapper.map_key_equivalent(keystroke.clone()); + let mapped = mapper.map_key_equivalent(keystroke.clone(), true); assert_eq!(mapped.inner, keystroke); assert_eq!(mapped.key, "a"); assert_eq!(mapped.modifiers, Modifiers::control()); @@ -283,7 +340,7 @@ mod tests { key: "$".to_string(), key_char: None, }; - let mapped = mapper.map_key_equivalent(keystroke.clone()); + let mapped = mapper.map_key_equivalent(keystroke.clone(), true); assert_eq!(mapped.inner, keystroke); assert_eq!(mapped.key, "4"); assert_eq!(mapped.modifiers, Modifiers::control_shift()); @@ -294,7 +351,7 @@ mod tests { key: "$".to_string(), key_char: None, }; - let mapped = mapper.map_key_equivalent(keystroke.clone()); + let mapped = mapper.map_key_equivalent(keystroke.clone(), true); assert_eq!(mapped.inner.modifiers, Modifiers::control()); assert_eq!(mapped.key, "4"); assert_eq!(mapped.modifiers, Modifiers::control_shift()); @@ -305,7 +362,7 @@ mod tests { key: "4".to_string(), key_char: None, }; - let mapped = mapper.map_key_equivalent(keystroke.clone()); + let mapped = mapper.map_key_equivalent(keystroke.clone(), true); assert_eq!(mapped.inner.modifiers, Modifiers::control()); assert_eq!(mapped.inner.key, "$"); assert_eq!(mapped.key, "4"); diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index f77b71a6fea689..d9ae52d1ccfb68 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -279,6 +279,7 @@ impl KeymapFile { keystrokes, action, context_predicate.clone(), + *use_key_equivalents, key_equivalents, cx, ); @@ -337,6 +338,7 @@ impl KeymapFile { keystrokes: &str, action: &KeymapAction, context: Option>, + use_key_equivalents: bool, key_equivalents: Option<&HashMap>, cx: &App, ) -> std::result::Result { @@ -405,8 +407,10 @@ impl KeymapFile { keystrokes, action, context, + use_key_equivalents, key_equivalents, action_input_string.map(SharedString::from), + cx.keyboard_mapper(), ) { Ok(key_binding) => key_binding, Err(InvalidKeystrokeError { keystroke }) => { @@ -1021,7 +1025,7 @@ impl From for KeyBindingMetaIndex { #[cfg(test)] mod tests { - use gpui::{KeybindingKeystroke, Keystroke}; + use gpui::{DummyKeyboardMapper, KeybindingKeystroke, Keystroke}; use unindent::Unindent; use crate::{ @@ -1059,7 +1063,13 @@ mod tests { fn parse_keystrokes(keystrokes: &str) -> Vec { keystrokes .split(' ') - .map(|s| KeybindingKeystroke::new(Keystroke::parse(s).expect("Keystrokes valid"))) + .map(|s| { + KeybindingKeystroke::new( + Keystroke::parse(s).expect("Keystrokes valid"), + false, + &DummyKeyboardMapper, + ) + }) .collect() } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index b73ab9ae95ae75..91a2d078271e08 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -89,9 +89,12 @@ pub fn default_settings() -> Cow<'static, str> { #[cfg(target_os = "macos")] pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-macos.json"; -#[cfg(not(target_os = "macos"))] +#[cfg(target_os = "linux")] pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-linux.json"; +#[cfg(target_os = "windows")] +pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-windows.json"; + pub fn default_keymap() -> Cow<'static, str> { asset_str::(DEFAULT_KEYMAP_PATH) } diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index b37f6e20f7340f..f0518a5e111f6c 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -301,7 +301,8 @@ impl KeystrokeInput { return; } - let mut keystroke = KeybindingKeystroke::new(keystroke.clone()); + let mut keystroke = + KeybindingKeystroke::new(keystroke.clone(), false, cx.keyboard_mapper()); if let Some(last) = self.keystrokes.last() && last.key.is_empty() && (!self.search || self.previous_modifiers.modified()) From 366b5d13b038ff7163009e36516e03cda20f9051 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 20 Aug 2025 17:17:45 +0800 Subject: [PATCH 14/35] Reload shortcuts when layout changed --- crates/zed/src/zed.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 638e1dca0e261d..8dcdbfe18953d8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1308,11 +1308,11 @@ pub fn handle_keymap_file_changes( }) .detach(); - let mut current_mapping = settings::get_key_equivalents(cx.keyboard_layout().id()); + let mut current_layout_id = cx.keyboard_layout().id().to_string(); cx.on_keyboard_layout_change(move |cx| { - let next_mapping = settings::get_key_equivalents(cx.keyboard_layout().id()); - if next_mapping != current_mapping { - current_mapping = next_mapping; + let next_layout_id = cx.keyboard_layout().id(); + if next_layout_id != current_layout_id { + current_layout_id = next_layout_id.to_string(); keyboard_layout_tx.unbounded_send(()).ok(); } }) From 029fa40109075eb171b9b9d782bdb4f5b0e11532 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 20 Aug 2025 18:06:38 +0800 Subject: [PATCH 15/35] fix merge conflicts --- crates/settings_ui/src/keybindings.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 8d5e0a6faed8a1..57d906f4c95f9f 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -605,16 +605,15 @@ impl KeymapEditor { { let query = &keystroke_query[query_cursor]; let keystroke = &keystrokes[keystroke_cursor]; - let matches = - query.inner.modifiers.is_subset_of(&keystroke.inner.modifiers) - && ((query.inner.key.is_empty() - || query.inner.key == keystroke.inner.key) - && query.inner - .key_char - .as_ref() - .is_none_or(|q_kc| { - q_kc == &keystroke.inner.key - }); + let matches = query + .inner + .modifiers + .is_subset_of(&keystroke.inner.modifiers) + && ((query.inner.key.is_empty() + || query.inner.key == keystroke.inner.key) + && query.inner.key_char.as_ref().is_none_or( + |q_kc| q_kc == &keystroke.inner.key, + )); if matches { found_count += 1; query_cursor += 1; From 211c08b14624c05f279beb0c67182d358a0dec2a Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 20 Aug 2025 23:54:53 +0800 Subject: [PATCH 16/35] fix tests --- crates/settings_ui/src/ui_components/keystroke_input.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index f0518a5e111f6c..7515c25ab04792 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -308,8 +308,10 @@ impl KeystrokeInput { && (!self.search || self.previous_modifiers.modified()) { let key = keystroke.key.clone(); + let inner_key = keystroke.inner.key.clone(); keystroke = last.clone(); keystroke.key = key; + keystroke.inner.key = inner_key; self.keystrokes.pop(); } From 5760fad8ce182294008e18a9c0fb7d688309f3c5 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 20 Aug 2025 23:57:45 +0800 Subject: [PATCH 17/35] clippy --- crates/gpui/src/platform/windows/keyboard.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 25fcffb2366689..79ae79a7f06c90 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -351,7 +351,7 @@ mod tests { key: "$".to_string(), key_char: None, }; - let mapped = mapper.map_key_equivalent(keystroke.clone(), true); + let mapped = mapper.map_key_equivalent(keystroke, true); assert_eq!(mapped.inner.modifiers, Modifiers::control()); assert_eq!(mapped.key, "4"); assert_eq!(mapped.modifiers, Modifiers::control_shift()); @@ -362,7 +362,7 @@ mod tests { key: "4".to_string(), key_char: None, }; - let mapped = mapper.map_key_equivalent(keystroke.clone(), true); + let mapped = mapper.map_key_equivalent(keystroke, true); assert_eq!(mapped.inner.modifiers, Modifiers::control()); assert_eq!(mapped.inner.key, "$"); assert_eq!(mapped.key, "4"); From 98fc52f4383c1ef3f6e1cab17aa04b2325f568f8 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Thu, 21 Aug 2025 00:19:26 +0800 Subject: [PATCH 18/35] fix tests --- crates/settings_ui/src/ui_components/keystroke_input.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index 7515c25ab04792..6dbbed62bcd0b7 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -268,12 +268,14 @@ impl KeystrokeInput { if self.search { if self.previous_modifiers.modified() { last.modifiers |= event.modifiers; + last.inner.modifiers |= event.modifiers; } else { self.keystrokes.push(Self::dummy(event.modifiers)); } self.previous_modifiers |= event.modifiers; } else { last.modifiers = event.modifiers; + last.inner.modifiers = event.modifiers; return; } } else if keystrokes_len < Self::KEYSTROKE_COUNT_MAX { From cb09b8f675049edf41612fb674c65a45c7ab06d7 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Mon, 25 Aug 2025 21:28:50 +0800 Subject: [PATCH 19/35] rename --- crates/editor/src/editor.rs | 24 ++++++++--------- crates/gpui/src/platform/keystroke.rs | 12 ++++----- crates/gpui/src/platform/windows/keyboard.rs | 25 ++++++++--------- crates/settings_ui/src/keybindings.rs | 8 +++--- .../src/ui_components/keystroke_input.rs | 27 ++++++++++--------- crates/ui/src/components/keybinding.rs | 13 ++++++--- crates/zed/src/zed.rs | 2 +- 7 files changed, 60 insertions(+), 51 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3d17fcaaaf38b9..80680ae9c00999 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2588,7 +2588,7 @@ impl Editor { || binding .keystrokes() .first() - .is_some_and(|keystroke| keystroke.modifiers.modified()) + .is_some_and(|keystroke| keystroke.display_modifiers.modified()) })) } @@ -7686,16 +7686,16 @@ impl Editor { .keystroke() { modifiers_held = modifiers_held - || (&accept_keystroke.modifiers == modifiers - && accept_keystroke.modifiers.modified()); + || (&accept_keystroke.display_modifiers == modifiers + && accept_keystroke.display_modifiers.modified()); }; if let Some(accept_partial_keystroke) = self .accept_edit_prediction_keybind(true, window, cx) .keystroke() { modifiers_held = modifiers_held - || (&accept_partial_keystroke.modifiers == modifiers - && accept_partial_keystroke.modifiers.modified()); + || (&accept_partial_keystroke.display_modifiers == modifiers + && accept_partial_keystroke.display_modifiers.modified()); } if modifiers_held { @@ -9044,7 +9044,7 @@ impl Editor { let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac; - let modifiers_color = if accept_keystroke.modifiers == window.modifiers() { + let modifiers_color = if accept_keystroke.display_modifiers == window.modifiers() { Color::Accent } else { Color::Muted @@ -9056,19 +9056,19 @@ impl Editor { .font(theme::ThemeSettings::get_global(cx).buffer_font.clone()) .text_size(TextSize::XSmall.rems(cx)) .child(h_flex().children(ui::render_modifiers( - &accept_keystroke.modifiers, + &accept_keystroke.display_modifiers, PlatformStyle::platform(), Some(modifiers_color), Some(IconSize::XSmall.rems().into()), true, ))) .when(is_platform_style_mac, |parent| { - parent.child(accept_keystroke.key.clone()) + parent.child(accept_keystroke.display_key.clone()) }) .when(!is_platform_style_mac, |parent| { parent.child( Key::new( - util::capitalize(&accept_keystroke.key), + util::capitalize(&accept_keystroke.display_key), Some(Color::Default), ) .size(Some(IconSize::XSmall.rems().into())), @@ -9249,7 +9249,7 @@ impl Editor { accept_keystroke.as_ref(), |el, accept_keystroke| { el.child(h_flex().children(ui::render_modifiers( - &accept_keystroke.modifiers, + &accept_keystroke.display_modifiers, PlatformStyle::platform(), Some(Color::Default), Some(IconSize::XSmall.rems().into()), @@ -9319,7 +9319,7 @@ impl Editor { .child(completion), ) .when_some(accept_keystroke, |el, accept_keystroke| { - if !accept_keystroke.modifiers.modified() { + if !accept_keystroke.display_modifiers.modified() { return el; } @@ -9338,7 +9338,7 @@ impl Editor { .font(theme::ThemeSettings::get_global(cx).buffer_font.clone()) .when(is_platform_style_mac, |parent| parent.gap_1()) .child(h_flex().children(ui::render_modifiers( - &accept_keystroke.modifiers, + &accept_keystroke.display_modifiers, PlatformStyle::platform(), Some(if !has_completion { Color::Muted diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index c3a2f7d7a21b33..bdb6b29eedf8a5 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -38,9 +38,9 @@ pub struct KeybindingKeystroke { /// TODO: pub inner: Keystroke, /// TODO: - pub modifiers: Modifiers, + pub display_modifiers: Modifiers, /// TODO: - pub key: String, + pub display_key: String, } /// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use @@ -300,8 +300,8 @@ impl KeybindingKeystroke { let modifiers = keystroke.modifiers; KeybindingKeystroke { inner: keystroke, - modifiers, - key, + display_modifiers: modifiers, + display_key: key, } } } @@ -369,8 +369,8 @@ impl std::fmt::Display for Keystroke { impl std::fmt::Display for KeybindingKeystroke { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - display_modifiers(&self.modifiers, f)?; - display_key(&self.key, f) + display_modifiers(&self.display_modifiers, f)?; + display_key(&self.display_key, f) } } diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 79ae79a7f06c90..182cba90340c89 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -85,8 +85,8 @@ impl PlatformKeyboardMapper for WindowsKeyboardMapper { KeybindingKeystroke { inner: keystroke, - modifiers, - key, + display_modifiers: modifiers, + display_key: key, } } } @@ -140,7 +140,7 @@ impl WindowsKeyboardMapper { fn get_vkey_from_key(&self, key: &str, use_key_equivalents: bool) -> Option<(u16, bool)> { if use_key_equivalents { - key_needs_processing(key) + get_vkey_from_key_with_us_layout(key) } else { self.key_to_vkey.get(key).cloned() } @@ -240,8 +240,9 @@ pub(crate) fn generate_key_char( } } -fn key_needs_processing(key: &str) -> Option<(u16, bool)> { +fn get_vkey_from_key_with_us_layout(key: &str) -> Option<(u16, bool)> { match key { + // ` => VK_OEM_3 "`" => Some((VK_OEM_3.0, false)), "~" => Some((VK_OEM_3.0, true)), "1" => Some((VK_1.0, false)), @@ -331,8 +332,8 @@ mod tests { }; let mapped = mapper.map_key_equivalent(keystroke.clone(), true); assert_eq!(mapped.inner, keystroke); - assert_eq!(mapped.key, "a"); - assert_eq!(mapped.modifiers, Modifiers::control()); + assert_eq!(mapped.display_key, "a"); + assert_eq!(mapped.display_modifiers, Modifiers::control()); // Shifted case, ctrl-$ let keystroke = Keystroke { @@ -342,8 +343,8 @@ mod tests { }; let mapped = mapper.map_key_equivalent(keystroke.clone(), true); assert_eq!(mapped.inner, keystroke); - assert_eq!(mapped.key, "4"); - assert_eq!(mapped.modifiers, Modifiers::control_shift()); + assert_eq!(mapped.display_key, "4"); + assert_eq!(mapped.display_modifiers, Modifiers::control_shift()); // Shifted case, but shift is true let keystroke = Keystroke { @@ -353,8 +354,8 @@ mod tests { }; let mapped = mapper.map_key_equivalent(keystroke, true); assert_eq!(mapped.inner.modifiers, Modifiers::control()); - assert_eq!(mapped.key, "4"); - assert_eq!(mapped.modifiers, Modifiers::control_shift()); + assert_eq!(mapped.display_key, "4"); + assert_eq!(mapped.display_modifiers, Modifiers::control_shift()); // Windows style let keystroke = Keystroke { @@ -365,7 +366,7 @@ mod tests { let mapped = mapper.map_key_equivalent(keystroke, true); assert_eq!(mapped.inner.modifiers, Modifiers::control()); assert_eq!(mapped.inner.key, "$"); - assert_eq!(mapped.key, "4"); - assert_eq!(mapped.modifiers, Modifiers::control_shift()); + assert_eq!(mapped.display_key, "4"); + assert_eq!(mapped.display_modifiers, Modifiers::control_shift()); } } diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 57d906f4c95f9f..762ab5affc329e 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -2452,8 +2452,8 @@ impl KeybindingEditorModal { fn remove_key_char( KeybindingKeystroke { inner, - modifiers, - key, + display_modifiers, + display_key, }: KeybindingKeystroke, ) -> KeybindingKeystroke { KeybindingKeystroke { @@ -2462,8 +2462,8 @@ fn remove_key_char( key: inner.key, key_char: None, }, - modifiers, - key, + display_modifiers, + display_key, } } diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index 6dbbed62bcd0b7..391aee3292072b 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -116,7 +116,7 @@ impl KeystrokeInput { && self .keystrokes .last() - .is_some_and(|last| last.key.is_empty()) + .is_some_and(|last| last.display_key.is_empty()) { return &self.keystrokes[..self.keystrokes.len() - 1]; } @@ -130,8 +130,8 @@ impl KeystrokeInput { key: "".to_string(), key_char: None, }, - modifiers, - key: "".to_string(), + display_modifiers: modifiers, + display_key: "".to_string(), } } @@ -258,7 +258,7 @@ impl KeystrokeInput { self.keystrokes_changed(cx); if let Some(last) = self.keystrokes.last_mut() - && last.key.is_empty() + && last.display_key.is_empty() && keystrokes_len <= Self::KEYSTROKE_COUNT_MAX { if !self.search && !event.modifiers.modified() { @@ -267,14 +267,14 @@ impl KeystrokeInput { } if self.search { if self.previous_modifiers.modified() { - last.modifiers |= event.modifiers; + last.display_modifiers |= event.modifiers; last.inner.modifiers |= event.modifiers; } else { self.keystrokes.push(Self::dummy(event.modifiers)); } self.previous_modifiers |= event.modifiers; } else { - last.modifiers = event.modifiers; + last.display_modifiers = event.modifiers; last.inner.modifiers = event.modifiers; return; } @@ -306,13 +306,13 @@ impl KeystrokeInput { let mut keystroke = KeybindingKeystroke::new(keystroke.clone(), false, cx.keyboard_mapper()); if let Some(last) = self.keystrokes.last() - && last.key.is_empty() + && last.display_key.is_empty() && (!self.search || self.previous_modifiers.modified()) { - let key = keystroke.key.clone(); + let display_key = keystroke.display_key.clone(); let inner_key = keystroke.inner.key.clone(); keystroke = last.clone(); - keystroke.key = key; + keystroke.display_key = display_key; keystroke.inner.key = inner_key; self.keystrokes.pop(); } @@ -333,11 +333,14 @@ impl KeystrokeInput { self.keystrokes_changed(cx); if self.search { - self.previous_modifiers = keystroke.modifiers; + self.previous_modifiers = keystroke.display_modifiers; return; } - if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX && keystroke.modifiers.modified() { - self.keystrokes.push(Self::dummy(keystroke.modifiers)); + if self.keystrokes.len() < Self::KEYSTROKE_COUNT_MAX + && keystroke.display_modifiers.modified() + { + self.keystrokes + .push(Self::dummy(keystroke.display_modifiers)); } } diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 10af95d5cdc7ef..c9390da4735aa6 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -124,7 +124,7 @@ impl RenderOnce for KeyBinding { "KEY_BINDING-{}", self.keystrokes .iter() - .map(|k| k.key.to_string()) + .map(|k| k.display_key.to_string()) .collect::>() .join(" ") ) @@ -166,7 +166,7 @@ pub fn render_keybinding_keystroke( let element = Key::new( keystroke_text( &keystroke.modifiers, - &keystroke.key, + &keystroke.display_key, platform_style, vim_mode, ), @@ -184,7 +184,12 @@ pub fn render_keybinding_keystroke( size, true, )); - elements.push(render_key(&keystroke.key, color, platform_style, size)); + elements.push(render_key( + &keystroke.display_key, + color, + platform_style, + size, + )); elements } } @@ -414,7 +419,7 @@ pub fn text_for_keybinding_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &A .map(|keystroke| { keystroke_text( &keystroke.modifiers, - &keystroke.key, + &keystroke.display_key, platform_style, vim_enabled, ) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 8dcdbfe18953d8..553444ebdbc2fe 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -4729,7 +4729,7 @@ mod tests { // and key strokes contain the given key bindings .into_iter() - .any(|binding| binding.keystrokes().iter().any(|k| k.key == key)), + .any(|binding| binding.keystrokes().iter().any(|k| k.display_key == key)), "On {} Failed to find {} with key binding {}", line, action.name(), From d6b334ceee3d336e4902dca5e5e7a309864ed293 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Mon, 25 Aug 2025 21:29:57 +0800 Subject: [PATCH 20/35] update keymap --- assets/keymaps/default-windows.json | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 6c55720e84976b..f3785a140619f9 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -42,10 +42,13 @@ "shift-f11": "debugger::StepOut", "f11": "zed::ToggleFullScreen", // todo(windows) - "ctrl-alt-z": "edit_prediction::RateCompletions", - "ctrl-shift-i": "edit_prediction::ToggleMenu", - // todo(windows) - "ctrl-alt-l": "lsp_tool::ToggleMenu" + // win -> but nealy none app use this + // vscode save all action => ctrl-alt-s ctrl-k s + // ctrl-alt-z => shift-alt-z or ctrl-shift-z + // "ctrl-alt-z": "edit_prediction::RateCompletions", + "ctrl-shift-i": "edit_prediction::ToggleMenu" + // todo(windows) (probably remove binding) + // "ctrl-alt-l": "lsp_tool::ToggleMenu" } }, { @@ -146,7 +149,7 @@ "ctrl-h": "buffer_search::DeployReplace", "ctrl-shift-.": "assistant::QuoteSelection", "ctrl-shift-,": "assistant::InsertIntoEditor", - // todo(windows) + // todo(windows) (if vs code has a similar action, use that, otherwise remove this binding) "ctrl-alt-e": "editor::SelectEnclosingSymbol", "ctrl-shift-backspace": "editor::GoToPreviousChange", "ctrl-shift-alt-backspace": "editor::GoToNextChange", @@ -198,7 +201,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-k ctrl-r": "git::Restore", - // todo(windows) + // todo(windows) (find something, look at what vs code does) "ctrl-alt-y": "git::ToggleStaged", "alt-y": "git::StageAndNext", "shift-alt-y": "git::UnstageAndNext" From 899e79e22672bc721d056cbbc4960e4668af9463 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Mon, 25 Aug 2025 22:18:27 +0800 Subject: [PATCH 21/35] fix merge conflicts --- crates/settings_ui/src/keybindings.rs | 8 ++++---- crates/ui/src/components/keybinding.rs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 762ab5affc329e..1d719863a11e7e 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -14,9 +14,9 @@ use gpui::{ Action, AppContext as _, AsyncApp, Axis, ClickEvent, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global, IsZero, KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or}, - KeyContext, KeybindingKeystroke,Keystroke, MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful, - StyledText, Subscription, Task, TextStyleRefinement, WeakEntity, actions, anchored, deferred, - div, + KeyContext, KeybindingKeystroke, Keystroke, MouseButton, Point, ScrollStrategy, + ScrollWheelEvent, Stateful, StyledText, Subscription, Task, TextStyleRefinement, WeakEntity, + actions, anchored, deferred, div, }; use language::{Language, LanguageConfig, ToOffset as _}; use notifications::status_toast::{StatusToast, ToastIcon}; @@ -236,7 +236,7 @@ struct ConflictState { } type ConflictKeybindMapping = HashMap< - Vec, + Vec, Vec<( Option, Vec, diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index c9390da4735aa6..81817045dc6ce0 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -165,7 +165,7 @@ pub fn render_keybinding_keystroke( if use_text { let element = Key::new( keystroke_text( - &keystroke.modifiers, + &keystroke.display_modifiers, &keystroke.display_key, platform_style, vim_mode, @@ -178,7 +178,7 @@ pub fn render_keybinding_keystroke( } else { let mut elements = Vec::new(); elements.extend(render_modifiers( - &keystroke.modifiers, + &keystroke.display_modifiers, platform_style, color, size, @@ -418,7 +418,7 @@ pub fn text_for_keybinding_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &A .iter() .map(|keystroke| { keystroke_text( - &keystroke.modifiers, + &keystroke.display_modifiers, &keystroke.display_key, platform_style, vim_enabled, From 6dad395300688affe5208ef0a9880f590b921cda Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Mon, 25 Aug 2025 23:31:53 +0800 Subject: [PATCH 22/35] try patch all `ctrl-alt-key` --- assets/keymaps/default-windows.json | 188 +++++++++++++++++++--------- 1 file changed, 130 insertions(+), 58 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index f3785a140619f9..82fac674f4d061 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -46,9 +46,10 @@ // vscode save all action => ctrl-alt-s ctrl-k s // ctrl-alt-z => shift-alt-z or ctrl-shift-z // "ctrl-alt-z": "edit_prediction::RateCompletions", - "ctrl-shift-i": "edit_prediction::ToggleMenu" + "ctrl-shift-i": "edit_prediction::ToggleMenu", // todo(windows) (probably remove binding) // "ctrl-alt-l": "lsp_tool::ToggleMenu" + "shift-alt-l": "lsp_tool::ToggleMenu" } }, { @@ -150,7 +151,9 @@ "ctrl-shift-.": "assistant::QuoteSelection", "ctrl-shift-,": "assistant::InsertIntoEditor", // todo(windows) (if vs code has a similar action, use that, otherwise remove this binding) - "ctrl-alt-e": "editor::SelectEnclosingSymbol", + // the closest equivalent in vs code is "editor.action.selectToBracket", + // no default shortcut + // "ctrl-alt-e": "editor::SelectEnclosingSymbol", "ctrl-shift-backspace": "editor::GoToPreviousChange", "ctrl-shift-alt-backspace": "editor::GoToNextChange", "alt-enter": "editor::OpenSelectionsInMultibuffer" @@ -202,7 +205,9 @@ "bindings": { "ctrl-k ctrl-r": "git::Restore", // todo(windows) (find something, look at what vs code does) - "ctrl-alt-y": "git::ToggleStaged", + // the closest equivalent in vs code is "git.stage", + // no default shortcut + // "ctrl-alt-y": "git::ToggleStaged", "alt-y": "git::StageAndNext", "shift-alt-y": "git::UnstageAndNext" } @@ -252,24 +257,29 @@ "bindings": { "ctrl-n": "agent::NewThread", // todo(windows) - "ctrl-alt-n": "agent::NewTextThread", + // "ctrl-alt-n": "agent::NewTextThread", + "shift-alt-n": "agent::NewTextThread", "ctrl-shift-h": "agent::OpenHistory", // todo(windows) - "ctrl-alt-c": "agent::OpenSettings", + // "ctrl-alt-c": "agent::OpenSettings", + "shift-alt-c": "agent::OpenSettings", // todo(windows) - "ctrl-alt-p": "agent::OpenRulesLibrary", + // "ctrl-alt-p": "agent::OpenRulesLibrary", + "shift-alt-p": "agent::OpenRulesLibrary", "ctrl-i": "agent::ToggleProfileSelector", // todo(windows) - "ctrl-alt-/": "agent::ToggleModelSelector", + // "ctrl-alt-/": "agent::ToggleModelSelector", + "shift-alt-/": "agent::ToggleModelSelector", "ctrl-shift-a": "agent::ToggleContextPicker", "ctrl-shift-j": "agent::ToggleNavigationMenu", "ctrl-shift-i": "agent::ToggleOptionsMenu", // todo(windows) - "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu", + // "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu", "shift-alt-escape": "agent::ExpandMessageEditor", "ctrl-shift-.": "assistant::QuoteSelection", // todo(windows) - "ctrl-alt-e": "agent::RemoveAllContext", + // "ctrl-alt-e": "agent::RemoveAllContext", + "shift-alt-e": "agent::RemoveAllContext", "ctrl-shift-e": "project_panel::ToggleFocus", "ctrl-shift-enter": "agent::ContinueThread", "super-ctrl-b": "agent::ToggleBurnMode", @@ -297,7 +307,8 @@ "bindings": { "ctrl-n": "agent::NewTextThread", // todo(windows) - "ctrl-alt-t": "agent::NewThread" + // "ctrl-alt-t": "agent::NewThread" + "shift-alt-t": "agent::NewThread" } }, { @@ -306,7 +317,8 @@ "bindings": { "ctrl-n": "agent::NewExternalAgentThread", // todo(windows) - "ctrl-alt-t": "agent::NewThread" + // "ctrl-alt-t": "agent::NewThread" + "shift-alt-t": "agent::NewThread" } }, { @@ -429,9 +441,10 @@ "ctrl-shift-f": "search::FocusSearch", "ctrl-shift-h": "search::ToggleReplace", // todo(windows) - "ctrl-alt-g": "search::ToggleRegex", + // "ctrl-alt-g": "search::ToggleRegex", + "alt-r": "search::ToggleRegex" // vscode // todo(windows) - "ctrl-alt-x": "search::ToggleRegex" + // "ctrl-alt-x": "search::ToggleRegex" } }, { @@ -457,9 +470,10 @@ "escape": "project_search::ToggleFocus", "ctrl-shift-h": "search::ToggleReplace", // todo(windows) - "ctrl-alt-g": "search::ToggleRegex", + // "ctrl-alt-g": "search::ToggleRegex", + "alt-r": "search::ToggleRegex" // vscode // todo(windows) - "ctrl-alt-x": "search::ToggleRegex" + // "ctrl-alt-x": "search::ToggleRegex" } }, { @@ -483,9 +497,11 @@ "ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }], "ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }], // todo(windows) - "ctrl-alt-t": ["pane::CloseOtherItems", { "close_pinned": false }], + // no default for vscode + // "ctrl-alt-t": ["pane::CloseOtherItems", { "close_pinned": false }], // todo(windows) - "ctrl-shift-alt-w": "workspace::CloseInactiveTabsAndPanes", + // no such action for vscode + // "ctrl-shift-alt-w": "workspace::CloseInactiveTabsAndPanes", "ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }], "ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }], "ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }], @@ -493,33 +509,46 @@ "ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes", "back": "pane::GoBack", // todo(windows) - "ctrl-alt--": "pane::GoBack", + // vscode: alt-left + // "ctrl-alt--": "pane::GoBack", + "alt--": "pane::GoBack", // todo(windows) - "ctrl-shift-alt--": "pane::GoForward", + // vscode: alt-right + // "ctrl-shift-alt--": "pane::GoForward", + "shift-alt--": "pane::GoForward", "forward": "pane::GoForward", // todo(windows) - "ctrl-alt-g": "search::SelectNextMatch", + // vscode: Enter + // "ctrl-alt-g": "search::SelectNextMatch", "f3": "search::SelectNextMatch", // todo(windows) - "ctrl-shift-alt-g": "search::SelectPreviousMatch", + // vscode: shift-enter + // "ctrl-shift-alt-g": "search::SelectPreviousMatch", "shift-f3": "search::SelectPreviousMatch", "shift-find": "project_search::ToggleFocus", "ctrl-shift-f": "project_search::ToggleFocus", // todo(windows) - "ctrl-shift-alt-h": "search::ToggleReplace", + // no default shortcut for vscode + // "ctrl-shift-alt-h": "search::ToggleReplace", + "shift-alt-h": "search::ToggleReplace", // todo(windows) - "ctrl-shift-alt-l": "search::ToggleSelection", + // vscode: alt-l + // "ctrl-shift-alt-l": "search::ToggleSelection", + "alt-l": "search::ToggleSelection", "alt-enter": "search::SelectAllMatches", "alt-c": "search::ToggleCaseSensitive", "alt-w": "search::ToggleWholeWord", "alt-find": "project_search::ToggleFilters", // todo(windows) - "ctrl-alt-f": "project_search::ToggleFilters", + // no default for vscode + // "ctrl-alt-f": "project_search::ToggleFilters", // todo(windows) - "ctrl-shift-alt-r": "search::ToggleRegex", - // todo(windows) - "ctrl-shift-alt-x": "search::ToggleRegex", + // vscode: alt-r + // "ctrl-shift-alt-r": "search::ToggleRegex", "alt-r": "search::ToggleRegex", + // todo(windows) + // "ctrl-shift-alt-x": "search::ToggleRegex", + // "alt-r": "search::ToggleRegex", "ctrl-k shift-enter": "pane::TogglePinTab" } }, @@ -584,7 +613,9 @@ "ctrl-k p": "editor::CopyPath", "ctrl-\\": "pane::SplitRight", // todo(windows) - "ctrl-shift-alt-c": "editor::DisplayCursorNames", + // vscode? + // "ctrl-shift-alt-c": "editor::DisplayCursorNames", + "shift-alt-c": "editor::DisplayCursorNames", "alt-.": "editor::GoToHunk", "alt-,": "editor::GoToPreviousHunk" } @@ -621,19 +652,24 @@ // Change the default action on `menu::Confirm` by setting the parameter // "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }], // todo(windows) - "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": false }], + // vscode: ctrl-r + // "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": false }], + "ctrl-r": ["projects::OpenRecent", { "create_new_window": false }], "shift-alt-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], // Change to open path modal for existing remote connection by setting the parameter // "ctrl-shift-alt-o": "["projects::OpenRemote", { "from_existing_connection": true }]", // todo(windows) - "ctrl-shift-alt-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], + // "ctrl-shift-alt-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], // todo(windows) - "ctrl-shift-alt-b": "branches::OpenRecent", + // no such for vscode + // "ctrl-shift-alt-b": "branches::OpenRecent", + "shift-alt-b": "branches::OpenRecent", "shift-alt-enter": "toast::RunAction", "ctrl-shift-`": "workspace::NewTerminal", "save": "workspace::Save", "ctrl-s": "workspace::Save", - "ctrl-k s": "workspace::SaveWithoutFormat", + // vscode? + // "ctrl-k s": "workspace::SaveWithoutFormat", "shift-save": "workspace::SaveAs", "ctrl-shift-s": "workspace::SaveAs", "new": "workspace::NewFile", @@ -652,23 +688,32 @@ "alt-8": ["workspace::ActivatePane", 7], "alt-9": ["workspace::ActivatePane", 8], // todo(windows) + // this is the default for vscode "ctrl-alt-b": "workspace::ToggleRightDock", "ctrl-b": "workspace::ToggleLeftDock", "ctrl-j": "workspace::ToggleBottomDock", // todo(windows) - "ctrl-alt-y": "workspace::CloseAllDocks", + // vscode? + // "ctrl-alt-y": "workspace::CloseAllDocks", + "ctrl-shift-y": "workspace::CloseAllDocks", // todo(windows) - "ctrl-alt-0": "workspace::ResetActiveDockSize", + // vscode? + // "ctrl-alt-0": "workspace::ResetActiveDockSize", + "shift-alt-0": "workspace::ResetActiveDockSize", // For 0px parameter, uses UI font size value. // todo(windows) - "ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], + // "ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], + "shift-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], // todo(windows) - "ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], - "ctrl-shift-alt-0": "workspace::ResetOpenDocksSize", + // "ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], + "shift-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], // todo(windows) - "ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], + // "ctrl-shift-alt-0": "workspace::ResetOpenDocksSize", + "ctrl-shift-0": "workspace::ResetOpenDocksSize", // todo(windows) - "ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], + // "ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], + // todo(windows) + // "ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], "shift-find": "pane::DeploySearch", "ctrl-shift-f": "pane::DeploySearch", "ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }], @@ -691,7 +736,9 @@ "ctrl-shift-/": "agent::ToggleFocus", "alt-save": "workspace::SaveAll", // todo(windows) - "ctrl-alt-s": "workspace::SaveAll", + // vscode: ctrl-k s + // "ctrl-alt-s": "workspace::SaveAll", + "ctrl-k s": "workspace::SaveAll", "ctrl-k m": "language_selector::Toggle", "escape": "workspace::Unfollow", "ctrl-k ctrl-left": "workspace::ActivatePaneLeft", @@ -705,7 +752,8 @@ "ctrl-shift-x": "zed::Extensions", "ctrl-shift-r": "task::Rerun", // todo(windows) - "ctrl-alt-r": "task::Rerun", + // no default for vscode + // "ctrl-alt-r": "task::Rerun", "alt-t": "task::Rerun", "shift-alt-t": "task::Spawn", "shift-alt-r": ["task::Spawn", { "reveal_target": "center" }], @@ -751,18 +799,22 @@ "ctrl-shift-j": "editor::JoinLines", "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", // todo(windows) - "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + // deleteWordLeft + // "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", // todo(windows) - "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + // deleteWordRight + // "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", "ctrl-alt-right": "editor::MoveToNextSubwordEnd", "ctrl-shift-alt-left": "editor::SelectToPreviousSubwordStart", // todo(windows) - "ctrl-shift-alt-b": "editor::SelectToPreviousSubwordStart", - "ctrl-shift-alt-right": "editor::SelectToNextSubwordEnd", + // cursorWordLeftSelect + // no default for vscode + // "ctrl-shift-alt-b": "editor::SelectToPreviousSubwordStart", + "ctrl-shift-alt-right": "editor::SelectToNextSubwordEnd" // todo(windows) - "ctrl-shift-alt-f": "editor::SelectToNextSubwordEnd" + // "ctrl-shift-alt-f": "editor::SelectToNextSubwordEnd" } }, // Bindings from Atom @@ -848,10 +900,11 @@ "use_key_equivalents": true, "bindings": { // todo(windows) - "ctrl-shift-alt-f": "workspace::FollowNextCollaborator", + // "ctrl-shift-alt-f": "workspace::FollowNextCollaborator", // Only available in debug builds: opens an element inspector for development. // todo(windows) - "ctrl-alt-i": "dev::ToggleInspector" + // "ctrl-alt-i": "dev::ToggleInspector" + "shift-alt-i": "dev::ToggleInspector" } }, { @@ -882,7 +935,8 @@ "ctrl-[": "agent::CyclePreviousInlineAssist", "ctrl-]": "agent::CycleNextInlineAssist", // todo(windows) - "ctrl-alt-e": "agent::RemoveAllContext" + // "ctrl-alt-e": "agent::RemoveAllContext" + "shift-alt-e": "agent::RemoveAllContext" } }, { @@ -911,11 +965,14 @@ "right": "outline_panel::ExpandSelectedEntry", "alt-copy": "outline_panel::CopyPath", // todo(windows) + // should be okay? "ctrl-alt-c": "outline_panel::CopyPath", "shift-alt-copy": "workspace::CopyRelativePath", // todo(windows) + // should be okay? "ctrl-shift-alt-c": "workspace::CopyRelativePath", // todo(windows) + // should be okay? "ctrl-alt-r": "outline_panel::RevealInFileManager", "space": "outline_panel::OpenSelectedEntry", "shift-down": "menu::SelectNext", @@ -934,7 +991,9 @@ "ctrl-n": "project_panel::NewFile", "alt-new": "project_panel::NewDirectory", // todo(windows) - "ctrl-alt-n": "project_panel::NewDirectory", + // no default for vscode + // "ctrl-alt-n": "project_panel::NewDirectory", + "alt-n": "project_panel::NewDirectory", "cut": "project_panel::Cut", "ctrl-x": "project_panel::Cut", "copy": "project_panel::Copy", @@ -945,10 +1004,14 @@ "ctrl-v": "project_panel::Paste", "alt-copy": "project_panel::CopyPath", // todo(windows) - "ctrl-alt-c": "project_panel::CopyPath", + // vscode: shift-alt-c + // "ctrl-alt-c": "project_panel::CopyPath", + "shift-alt-c": "project_panel::CopyPath", "shift-alt-copy": "workspace::CopyRelativePath", // todo(windows) - "ctrl-shift-alt-c": "workspace::CopyRelativePath", + // vscode: ctrl-k ctrl-shift-c + // "ctrl-shift-alt-c": "workspace::CopyRelativePath", + "ctrl-k ctrl-shift-c": "workspace::CopyRelativePath", "enter": "project_panel::Rename", "f2": "project_panel::Rename", "backspace": ["project_panel::Trash", { "skip_prompt": false }], @@ -957,12 +1020,16 @@ "ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }], // todo(windows) - "ctrl-alt-r": "project_panel::RevealInFileManager", + // vscode: shift-alt-r + // conflict with task::Spawn line 759 + // "ctrl-alt-r": "project_panel::RevealInFileManager", "ctrl-shift-enter": "project_panel::OpenWithSystem", "alt-d": "project_panel::CompareMarkedFiles", "shift-find": "project_panel::NewSearchInDirectory", // todo(windows) - "ctrl-shift-alt-f": "project_panel::NewSearchInDirectory", + // no default for vscode + // "ctrl-shift-alt-f": "project_panel::NewSearchInDirectory", + "ctrl-k ctrl-shift-f": "project_panel::NewSearchInDirectory", "shift-down": "menu::SelectNext", "shift-up": "menu::SelectPrevious", "escape": "menu::Cancel" @@ -985,7 +1052,8 @@ "alt-y": "git::StageFile", "shift-alt-y": "git::UnstageFile", // todo(windows) - "ctrl-alt-y": "git::ToggleStaged", + // vscode? + // "ctrl-alt-y": "git::ToggleStaged", "space": "git::ToggleStaged", "shift-space": "git::StageRange", "tab": "git_panel::FocusEditor", @@ -1083,7 +1151,8 @@ "enter": "variable_list::EditVariable", "ctrl-c": "variable_list::CopyVariableValue", // todo(windows) - "ctrl-alt-c": "variable_list::CopyVariableName", + // "ctrl-alt-c": "variable_list::CopyVariableName", + "ctrl-shift-c": "variable_list::CopyVariableName", "delete": "variable_list::RemoveWatch", "backspace": "variable_list::RemoveWatch", "alt-enter": "variable_list::AddWatch" @@ -1220,7 +1289,8 @@ "ctrl-shift-space": "terminal::ToggleViMode", "ctrl-shift-r": "terminal::RerunTask", // todo(windows) - "ctrl-alt-r": "terminal::RerunTask", + // no default for vscode + // "ctrl-alt-r": "terminal::RerunTask", "alt-t": "terminal::RerunTask" } }, @@ -1285,7 +1355,9 @@ "ctrl-f": "search::FocusSearch", "alt-find": "keymap_editor::ToggleKeystrokeSearch", // todo(windows) - "ctrl-alt-f": "keymap_editor::ToggleKeystrokeSearch", + // no such action for vscode + // "ctrl-alt-f": "keymap_editor::ToggleKeystrokeSearch", + "ctrl-shift-f": "keymap_editor::ToggleKeystrokeSearch", "alt-c": "keymap_editor::ToggleConflictFilter", "enter": "keymap_editor::EditBinding", "alt-enter": "keymap_editor::CreateBinding", From c08a11b4dd8ad962dbc98015f85a31127e94f211 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Mon, 25 Aug 2025 23:33:59 +0800 Subject: [PATCH 23/35] repos --- assets/keymaps/default-windows.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 82fac674f4d061..74fde38af560ec 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -220,7 +220,7 @@ "ctrl-n": "agent::Reject", "ctrl-shift-y": "agent::KeepAll", "ctrl-shift-n": "agent::RejectAll", - "shift-ctrl-r": "agent::OpenAgentDiff" + "ctrl-shift-r": "agent::OpenAgentDiff" } }, { @@ -328,7 +328,7 @@ "enter": "agent::Chat", "ctrl-enter": "agent::ChatWithFollow", "ctrl-i": "agent::ToggleProfileSelector", - "shift-ctrl-r": "agent::OpenAgentDiff", + "ctrl-shift-r": "agent::OpenAgentDiff", "ctrl-shift-y": "agent::KeepAll", "ctrl-shift-n": "agent::RejectAll" } @@ -340,7 +340,7 @@ "ctrl-enter": "agent::Chat", "enter": "editor::Newline", "ctrl-i": "agent::ToggleProfileSelector", - "shift-ctrl-r": "agent::OpenAgentDiff", + "ctrl-shift-r": "agent::OpenAgentDiff", "ctrl-shift-y": "agent::KeepAll", "ctrl-shift-n": "agent::RejectAll" } @@ -380,7 +380,7 @@ "use_key_equivalents": true, "bindings": { "enter": "agent::Chat", - "shift-ctrl-r": "agent::OpenAgentDiff", + "ctrl-shift-r": "agent::OpenAgentDiff", "ctrl-shift-y": "agent::KeepAll", "ctrl-shift-n": "agent::RejectAll" } @@ -650,7 +650,7 @@ "bindings": { "alt-open": ["projects::OpenRecent", { "create_new_window": false }], // Change the default action on `menu::Confirm` by setting the parameter - // "alt-ctrl-o": ["projects::OpenRecent", { "create_new_window": true }], + // "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": true }], // todo(windows) // vscode: ctrl-r // "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": false }], @@ -1400,7 +1400,7 @@ "ctrl-3": "onboarding::ActivateAISetupPage", "ctrl-escape": "onboarding::Finish", "alt-tab": "onboarding::SignIn", - "alt-shift-a": "onboarding::OpenAccount" + "shift-alt-a": "onboarding::OpenAccount" } } ] From d8c9affbe38fac5af7e6f22e48c6b380d953bab9 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 26 Aug 2025 00:04:25 +0800 Subject: [PATCH 24/35] init macos --- crates/gpui/src/platform/mac/keyboard.rs | 1449 +++++++++++++++++++++- 1 file changed, 1448 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/keyboard.rs b/crates/gpui/src/platform/mac/keyboard.rs index a9f6af3edb5841..b3793ba97c5475 100644 --- a/crates/gpui/src/platform/mac/keyboard.rs +++ b/crates/gpui/src/platform/mac/keyboard.rs @@ -1,8 +1,9 @@ +use collections::HashMap; use std::ffi::{CStr, c_void}; use objc::{msg_send, runtime::Object, sel, sel_impl}; -use crate::PlatformKeyboardLayout; +use crate::{KeybindingKeystroke, Keystroke, PlatformKeyboardLayout, PlatformKeyboardMapper}; use super::{ TISCopyCurrentKeyboardLayoutInputSource, TISGetInputSourceProperty, kTISPropertyInputSourceID, @@ -14,6 +15,10 @@ pub(crate) struct MacKeyboardLayout { name: String, } +pub(crate) struct MacKeyboardMapper { + key_equivalents: Option>, +} + impl PlatformKeyboardLayout for MacKeyboardLayout { fn id(&self) -> &str { &self.id @@ -24,6 +29,23 @@ impl PlatformKeyboardLayout for MacKeyboardLayout { } } +impl PlatformKeyboardMapper for MacKeyboardMapper { + fn map_key_equivalent( + &self, + mut keystroke: Keystroke, + use_key_equivalents: bool, + ) -> KeybindingKeystroke { + if use_key_equivalents && let Some(key_equivalents) = &self.key_equivalents { + if keystroke.key.chars().count() == 1 + && let Some(key) = key_equivalents.get(&keystroke.key.chars().next().unwrap()) + { + keystroke.key = key.to_string(); + } + } + KeybindingKeystroke::from_keystroke(keystroke) + } +} + impl MacKeyboardLayout { pub(crate) fn new() -> Self { unsafe { @@ -47,3 +69,1428 @@ impl MacKeyboardLayout { } } } + +impl MacKeyboardMapper { + pub(crate) fn new(layout_id: &str) -> Self { + let key_equivalents = get_key_equivalents(layout_id); + + Self { key_equivalents } + } +} + +// On some keyboards (e.g. German QWERTZ) it is not possible to type the full ASCII range +// without using option. This means that some of our built in keyboard shortcuts do not work +// for those users. +// +// The way macOS solves this problem is to move shortcuts around so that they are all reachable, +// even if the mnemonic changes. https://developer.apple.com/documentation/swiftui/keyboardshortcut/localization-swift.struct +// +// For example, cmd-> is the "switch window" shortcut because the > key is right above tab. +// To ensure this doesn't cause problems for shortcuts defined for a QWERTY layout, apple moves +// any shortcuts defined as cmd-> to cmd-:. Coincidentally this s also the same keyboard position +// as cmd-> on a QWERTY layout. +// +// Another example is cmd-[ and cmd-], as they cannot be typed without option, those keys are remapped to cmd-ö +// and cmd-ä. These shortcuts are not in the same position as a QWERTY keyboard, because on a QWERTZ keyboard +// the + key is in the way; and shortcuts bound to cmd-+ are still typed as cmd-+ on either keyboard (though the +// specific key moves) +// +// As far as I can tell, there's no way to query the mappings Apple uses except by rendering a menu with every +// possible key combination, and inspecting the UI to see what it rendered. So that's what we did... +// +// These mappings were generated by running https://github.com/ConradIrwin/keyboard-inspector, tidying up the +// output to remove languages with no mappings and other oddities, and converting it to a less verbose representation with: +// jq -s 'map(to_entries | map({key: .key, value: [(.value | to_entries | map(.key) | join("")), (.value | to_entries | map(.value) | join(""))]}) | from_entries) | add' +// From there I used multi-cursor to produce this match statement. +fn get_key_equivalents(layout_id: &str) -> Option> { + let mappings: &[(char, char)] = match layout_id { + "com.apple.keylayout.ABC-AZERTY" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.ABC-QWERTZ" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Albanian" => &[ + ('"', '\''), + (':', 'Ç'), + (';', 'ç'), + ('<', ';'), + ('>', ':'), + ('@', '"'), + ('\'', '@'), + ('\\', 'ë'), + ('`', '<'), + ('|', 'Ë'), + ('~', '>'), + ], + "com.apple.keylayout.Austrian" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Azeri" => &[ + ('"', 'Ə'), + (',', 'ç'), + ('.', 'ş'), + ('/', '.'), + (':', 'I'), + (';', 'ı'), + ('<', 'Ç'), + ('>', 'Ş'), + ('?', ','), + ('W', 'Ü'), + ('[', 'ö'), + ('\'', 'ə'), + (']', 'ğ'), + ('w', 'ü'), + ('{', 'Ö'), + ('|', '/'), + ('}', 'Ğ'), + ], + "com.apple.keylayout.Belgian" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.Brazilian-ABNT2" => &[ + ('"', '`'), + ('/', 'ç'), + ('?', 'Ç'), + ('\'', '´'), + ('\\', '~'), + ('^', '¨'), + ('`', '\''), + ('|', '^'), + ('~', '"'), + ], + "com.apple.keylayout.Brazilian-Pro" => &[('^', 'ˆ'), ('~', '˜')], + "com.apple.keylayout.British" => &[('#', '£')], + "com.apple.keylayout.Canadian-CSA" => &[ + ('"', 'È'), + ('/', 'é'), + ('<', '\''), + ('>', '"'), + ('?', 'É'), + ('[', '^'), + ('\'', 'è'), + ('\\', 'à'), + (']', 'ç'), + ('`', 'ù'), + ('{', '¨'), + ('|', 'À'), + ('}', 'Ç'), + ('~', 'Ù'), + ], + "com.apple.keylayout.Croatian" => &[ + ('"', 'Ć'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Croatian-PC" => &[ + ('"', 'Ć'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Czech" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ě'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ř'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ů'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', ')'), + ('^', '6'), + ('`', '¨'), + ('{', 'Ú'), + ('}', '('), + ('~', '`'), + ], + "com.apple.keylayout.Czech-QWERTY" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ě'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ř'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ů'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', ')'), + ('^', '6'), + ('`', '¨'), + ('{', 'Ú'), + ('}', '('), + ('~', '`'), + ], + "com.apple.keylayout.Danish" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'æ'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ø'), + ('^', '&'), + ('`', '<'), + ('{', 'Æ'), + ('|', '*'), + ('}', 'Ø'), + ('~', '>'), + ], + "com.apple.keylayout.Faroese" => &[ + ('"', 'Ø'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Æ'), + (';', 'æ'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'å'), + ('\'', 'ø'), + ('\\', '\''), + (']', 'ð'), + ('^', '&'), + ('`', '<'), + ('{', 'Å'), + ('|', '*'), + ('}', 'Ð'), + ('~', '>'), + ], + "com.apple.keylayout.Finnish" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.FinnishExtended" => &[ + ('"', 'ˆ'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.FinnishSami-PC" => &[ + ('"', 'ˆ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '@'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.French" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.French-PC" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('-', ')'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '-'), + ('7', 'è'), + ('8', '_'), + ('9', 'ç'), + (':', '§'), + (';', '!'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '*'), + (']', '$'), + ('^', '6'), + ('_', '°'), + ('`', '<'), + ('{', '¨'), + ('|', 'μ'), + ('}', '£'), + ('~', '>'), + ], + "com.apple.keylayout.French-numerical" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.German" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.German-DIN-2137" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Hawaiian" => &[('\'', 'ʻ')], + "com.apple.keylayout.Hungarian" => &[ + ('!', '\''), + ('"', 'Á'), + ('#', '+'), + ('$', '!'), + ('&', '='), + ('(', ')'), + (')', 'Ö'), + ('*', '('), + ('+', 'Ó'), + ('/', 'ü'), + ('0', 'ö'), + (':', 'É'), + (';', 'é'), + ('<', 'Ü'), + ('=', 'ó'), + ('>', ':'), + ('@', '"'), + ('[', 'ő'), + ('\'', 'á'), + ('\\', 'ű'), + (']', 'ú'), + ('^', '/'), + ('`', 'í'), + ('{', 'Ő'), + ('|', 'Ű'), + ('}', 'Ú'), + ('~', 'Í'), + ], + "com.apple.keylayout.Hungarian-QWERTY" => &[ + ('!', '\''), + ('"', 'Á'), + ('#', '+'), + ('$', '!'), + ('&', '='), + ('(', ')'), + (')', 'Ö'), + ('*', '('), + ('+', 'Ó'), + ('/', 'ü'), + ('0', 'ö'), + (':', 'É'), + (';', 'é'), + ('<', 'Ü'), + ('=', 'ó'), + ('>', ':'), + ('@', '"'), + ('[', 'ő'), + ('\'', 'á'), + ('\\', 'ű'), + (']', 'ú'), + ('^', '/'), + ('`', 'í'), + ('{', 'Ő'), + ('|', 'Ű'), + ('}', 'Ú'), + ('~', 'Í'), + ], + "com.apple.keylayout.Icelandic" => &[ + ('"', 'Ö'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'Ð'), + (';', 'ð'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'æ'), + ('\'', 'ö'), + ('\\', 'þ'), + (']', '´'), + ('^', '&'), + ('`', '<'), + ('{', 'Æ'), + ('|', 'Þ'), + ('}', '´'), + ('~', '>'), + ], + "com.apple.keylayout.Irish" => &[('#', '£')], + "com.apple.keylayout.IrishExtended" => &[('#', '£')], + "com.apple.keylayout.Italian" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + (',', ';'), + ('.', ':'), + ('/', ','), + ('0', 'é'), + ('1', '&'), + ('2', '"'), + ('3', '\''), + ('4', '('), + ('5', 'ç'), + ('6', 'è'), + ('7', ')'), + ('8', '£'), + ('9', 'à'), + (':', '!'), + (';', 'ò'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', 'ì'), + ('\'', 'ù'), + ('\\', '§'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '^'), + ('|', '°'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.Italian-Pro" => &[ + ('"', '^'), + ('#', '£'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'é'), + (';', 'è'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ò'), + ('\'', 'ì'), + ('\\', 'ù'), + (']', 'à'), + ('^', '&'), + ('`', '<'), + ('{', 'ç'), + ('|', '§'), + ('}', '°'), + ('~', '>'), + ], + "com.apple.keylayout.LatinAmerican" => &[ + ('"', '¨'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'Ñ'), + (';', 'ñ'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', '{'), + ('\'', '´'), + ('\\', '¿'), + (']', '}'), + ('^', '&'), + ('`', '<'), + ('{', '['), + ('|', '¡'), + ('}', ']'), + ('~', '>'), + ], + "com.apple.keylayout.Lithuanian" => &[ + ('!', 'Ą'), + ('#', 'Ę'), + ('$', 'Ė'), + ('%', 'Į'), + ('&', 'Ų'), + ('*', 'Ū'), + ('+', 'Ž'), + ('1', 'ą'), + ('2', 'č'), + ('3', 'ę'), + ('4', 'ė'), + ('5', 'į'), + ('6', 'š'), + ('7', 'ų'), + ('8', 'ū'), + ('=', 'ž'), + ('@', 'Č'), + ('^', 'Š'), + ], + "com.apple.keylayout.Maltese" => &[ + ('#', '£'), + ('[', 'ġ'), + (']', 'ħ'), + ('`', 'ż'), + ('{', 'Ġ'), + ('}', 'Ħ'), + ('~', 'Ż'), + ], + "com.apple.keylayout.NorthernSami" => &[ + ('"', 'Ŋ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('Q', 'Á'), + ('W', 'Š'), + ('X', 'Č'), + ('[', 'ø'), + ('\'', 'ŋ'), + ('\\', 'đ'), + (']', 'æ'), + ('^', '&'), + ('`', 'ž'), + ('q', 'á'), + ('w', 'š'), + ('x', 'č'), + ('{', 'Ø'), + ('|', 'Đ'), + ('}', 'Æ'), + ('~', 'Ž'), + ], + "com.apple.keylayout.Norwegian" => &[ + ('"', '^'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ø'), + ('\'', '¨'), + ('\\', '@'), + (']', 'æ'), + ('^', '&'), + ('`', '<'), + ('{', 'Ø'), + ('|', '*'), + ('}', 'Æ'), + ('~', '>'), + ], + "com.apple.keylayout.NorwegianExtended" => &[ + ('"', 'ˆ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ø'), + ('\\', '@'), + (']', 'æ'), + ('`', '<'), + ('}', 'Æ'), + ('~', '>'), + ], + "com.apple.keylayout.NorwegianSami-PC" => &[ + ('"', 'ˆ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ø'), + ('\'', '¨'), + ('\\', '@'), + (']', 'æ'), + ('^', '&'), + ('`', '<'), + ('{', 'Ø'), + ('|', '*'), + ('}', 'Æ'), + ('~', '>'), + ], + "com.apple.keylayout.Polish" => &[ + ('!', '§'), + ('"', 'ę'), + ('#', '!'), + ('$', '?'), + ('%', '+'), + ('&', ':'), + ('(', '/'), + (')', '"'), + ('*', '_'), + ('+', ']'), + (',', '.'), + ('.', ','), + ('/', 'ż'), + (':', 'Ł'), + (';', 'ł'), + ('<', 'ś'), + ('=', '['), + ('>', 'ń'), + ('?', 'Ż'), + ('@', '%'), + ('[', 'ó'), + ('\'', 'ą'), + ('\\', ';'), + (']', '('), + ('^', '='), + ('_', 'ć'), + ('`', '<'), + ('{', 'ź'), + ('|', '$'), + ('}', ')'), + ('~', '>'), + ], + "com.apple.keylayout.Portuguese" => &[ + ('"', '`'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'ª'), + (';', 'º'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ç'), + ('\'', '´'), + (']', '~'), + ('^', '&'), + ('`', '<'), + ('{', 'Ç'), + ('}', '^'), + ('~', '>'), + ], + "com.apple.keylayout.Sami-PC" => &[ + ('"', 'Ŋ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('Q', 'Á'), + ('W', 'Š'), + ('X', 'Č'), + ('[', 'ø'), + ('\'', 'ŋ'), + ('\\', 'đ'), + (']', 'æ'), + ('^', '&'), + ('`', 'ž'), + ('q', 'á'), + ('w', 'š'), + ('x', 'č'), + ('{', 'Ø'), + ('|', 'Đ'), + ('}', 'Æ'), + ('~', 'Ž'), + ], + "com.apple.keylayout.Serbian-Latin" => &[ + ('"', 'Ć'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Slovak" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ľ'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ť'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ô'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', 'ä'), + ('^', '6'), + ('`', 'ň'), + ('{', 'Ú'), + ('}', 'Ä'), + ('~', 'Ň'), + ], + "com.apple.keylayout.Slovak-QWERTY" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ľ'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ť'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ô'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', 'ä'), + ('^', '6'), + ('`', 'ň'), + ('{', 'Ú'), + ('}', 'Ä'), + ('~', 'Ň'), + ], + "com.apple.keylayout.Slovenian" => &[ + ('"', 'Ć'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Spanish" => &[ + ('!', '¡'), + ('"', '¨'), + ('.', 'ç'), + ('/', '.'), + (':', 'º'), + (';', '´'), + ('<', '¿'), + ('>', 'Ç'), + ('@', '!'), + ('[', 'ñ'), + ('\'', '`'), + ('\\', '\''), + (']', ';'), + ('^', '/'), + ('`', '<'), + ('{', 'Ñ'), + ('|', '"'), + ('}', ':'), + ('~', '>'), + ], + "com.apple.keylayout.Spanish-ISO" => &[ + ('"', '¨'), + ('#', '·'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('.', 'ç'), + ('/', '.'), + (':', 'º'), + (';', '´'), + ('<', '¿'), + ('>', 'Ç'), + ('@', '"'), + ('[', 'ñ'), + ('\'', '`'), + ('\\', '\''), + (']', ';'), + ('^', '&'), + ('`', '<'), + ('{', 'Ñ'), + ('|', '"'), + ('}', '`'), + ('~', '>'), + ], + "com.apple.keylayout.Swedish" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Swedish-Pro" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.SwedishSami-PC" => &[ + ('"', 'ˆ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '@'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.SwissFrench" => &[ + ('!', '+'), + ('"', '`'), + ('#', '*'), + ('$', 'ç'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('+', '!'), + ('/', '\''), + (':', 'ü'), + (';', 'è'), + ('<', ';'), + ('=', '¨'), + ('>', ':'), + ('@', '"'), + ('[', 'é'), + ('\'', '^'), + ('\\', '$'), + (']', 'à'), + ('^', '&'), + ('`', '<'), + ('{', 'ö'), + ('|', '£'), + ('}', 'ä'), + ('~', '>'), + ], + "com.apple.keylayout.SwissGerman" => &[ + ('!', '+'), + ('"', '`'), + ('#', '*'), + ('$', 'ç'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('+', '!'), + ('/', '\''), + (':', 'è'), + (';', 'ü'), + ('<', ';'), + ('=', '¨'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '^'), + ('\\', '$'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'é'), + ('|', '£'), + ('}', 'à'), + ('~', '>'), + ], + "com.apple.keylayout.Turkish" => &[ + ('"', '-'), + ('#', '"'), + ('$', '\''), + ('%', '('), + ('&', ')'), + ('(', '%'), + (')', ':'), + ('*', '_'), + (',', 'ö'), + ('-', 'ş'), + ('.', 'ç'), + ('/', '.'), + (':', '$'), + ('<', 'Ö'), + ('>', 'Ç'), + ('@', '*'), + ('[', 'ğ'), + ('\'', ','), + ('\\', 'ü'), + (']', 'ı'), + ('^', '/'), + ('_', 'Ş'), + ('`', '<'), + ('{', 'Ğ'), + ('|', 'Ü'), + ('}', 'I'), + ('~', '>'), + ], + "com.apple.keylayout.Turkish-QWERTY-PC" => &[ + ('"', 'I'), + ('#', '^'), + ('$', '+'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('+', ':'), + (',', 'ö'), + ('.', 'ç'), + ('/', '*'), + (':', 'Ş'), + (';', 'ş'), + ('<', 'Ö'), + ('=', '.'), + ('>', 'Ç'), + ('@', '\''), + ('[', 'ğ'), + ('\'', 'ı'), + ('\\', ','), + (']', 'ü'), + ('^', '&'), + ('`', '<'), + ('{', 'Ğ'), + ('|', ';'), + ('}', 'Ü'), + ('~', '>'), + ], + "com.apple.keylayout.Turkish-Standard" => &[ + ('"', 'Ş'), + ('#', '^'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (',', '.'), + ('.', ','), + (':', 'Ç'), + (';', 'ç'), + ('<', ':'), + ('=', '*'), + ('>', ';'), + ('@', '"'), + ('[', 'ğ'), + ('\'', 'ş'), + ('\\', 'ü'), + (']', 'ı'), + ('^', '&'), + ('`', 'ö'), + ('{', 'Ğ'), + ('|', 'Ü'), + ('}', 'I'), + ('~', 'Ö'), + ], + "com.apple.keylayout.Turkmen" => &[ + ('C', 'Ç'), + ('Q', 'Ä'), + ('V', 'Ý'), + ('X', 'Ü'), + ('[', 'ň'), + ('\\', 'ş'), + (']', 'ö'), + ('^', '№'), + ('`', 'ž'), + ('c', 'ç'), + ('q', 'ä'), + ('v', 'ý'), + ('x', 'ü'), + ('{', 'Ň'), + ('|', 'Ş'), + ('}', 'Ö'), + ('~', 'Ž'), + ], + "com.apple.keylayout.USInternational-PC" => &[('^', 'ˆ'), ('~', '˜')], + "com.apple.keylayout.Welsh" => &[('#', '£')], + + _ => return None, + }; + + Some(HashMap::from_iter(mappings.iter().cloned())) +} From 11bc6586d8f64c3f4aded5530fb4182f8674829e Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 26 Aug 2025 00:20:16 +0800 Subject: [PATCH 25/35] try fix macos --- crates/gpui/src/platform/mac/platform.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 832550dc46281c..780ea52f94e13a 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1,5 +1,5 @@ use super::{ - BoolExt, MacKeyboardLayout, + BoolExt, MacKeyboardLayout, MacKeyboardMapper, attributed_string::{NSAttributedString, NSMutableAttributedString}, events::key_to_native, renderer, @@ -8,8 +8,9 @@ use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher, MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform, - PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, Result, - SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, hash, + PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem, + PlatformWindow, Result, SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams, + hash, }; use anyhow::{Context as _, anyhow}; use block::ConcreteBlock; @@ -171,6 +172,7 @@ pub(crate) struct MacPlatformState { finish_launching: Option>, dock_menu: Option, menus: Option>, + keyboard_mapper: Rc, } impl Default for MacPlatform { @@ -189,6 +191,9 @@ impl MacPlatform { #[cfg(not(feature = "font-kit"))] let text_system = Arc::new(crate::NoopTextSystem::new()); + let keyboard_layout = MacKeyboardLayout::new(); + let keyboard_mapper = Rc::new(MacKeyboardMapper::new(keyboard_layout.id())); + Self(Mutex::new(MacPlatformState { headless, text_system, @@ -209,6 +214,7 @@ impl MacPlatform { dock_menu: None, on_keyboard_layout_change: None, menus: None, + keyboard_mapper, })) } @@ -882,6 +888,10 @@ impl Platform for MacPlatform { Box::new(MacKeyboardLayout::new()) } + fn keyboard_mapper(&self) -> Rc { + self.0.lock().keyboard_mapper.clone() + } + fn app_path(&self) -> Result { unsafe { let bundle: id = NSBundle::mainBundle(); @@ -1393,6 +1403,8 @@ extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { extern "C" fn on_keyboard_layout_change(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; let mut lock = platform.0.lock(); + let keyboard_layout = MacKeyboardLayout::new(); + lock.keyboard_mapper = MacKeyboardMapper::new(keyboard_layout.id()); if let Some(mut callback) = lock.on_keyboard_layout_change.take() { drop(lock); callback(); From b27e1ec5c7c263e6448ae7b8d0e4fd7ba0368727 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 26 Aug 2025 00:21:22 +0800 Subject: [PATCH 26/35] fix --- crates/gpui/src/keymap/binding.rs | 32 +++++++++--------------------- crates/settings/src/keymap_file.rs | 12 ----------- 2 files changed, 9 insertions(+), 35 deletions(-) diff --git a/crates/gpui/src/keymap/binding.rs b/crates/gpui/src/keymap/binding.rs index edc772b4e9086a..a7cf9d5c540c74 100644 --- a/crates/gpui/src/keymap/binding.rs +++ b/crates/gpui/src/keymap/binding.rs @@ -1,7 +1,5 @@ use std::rc::Rc; -use collections::HashMap; - use crate::{ Action, AsKeystroke, DummyKeyboardMapper, InvalidKeystrokeError, KeyBindingContextPredicate, KeybindingKeystroke, Keystroke, PlatformKeyboardMapper, SharedString, @@ -41,7 +39,6 @@ impl KeyBinding { context_predicate, false, None, - None, &DummyKeyboardMapper, ) .unwrap() @@ -53,31 +50,20 @@ impl KeyBinding { action: Box, context_predicate: Option>, use_key_equivalents: bool, - key_equivalents: Option<&HashMap>, action_input: Option, keyboard_mapper: &dyn PlatformKeyboardMapper, ) -> std::result::Result { - let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes + let keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes .split_whitespace() - .map(Keystroke::parse) - .collect::>()?; - - if let Some(equivalents) = key_equivalents { - for keystroke in keystrokes.iter_mut() { - if keystroke.key.chars().count() == 1 - && let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap()) - { - keystroke.key = key.to_string(); - } - } - } - - let keystrokes = keystrokes - .into_iter() - .map(|keystroke| { - KeybindingKeystroke::new(keystroke, use_key_equivalents, keyboard_mapper) + .map(|source| { + let keystroke = Keystroke::parse(source)?; + Ok(KeybindingKeystroke::new( + keystroke, + use_key_equivalents, + keyboard_mapper, + )) }) - .collect(); + .collect::>()?; Ok(Self { keystrokes, diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index d9ae52d1ccfb68..698c9315ce368c 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -212,9 +212,6 @@ impl KeymapFile { } pub fn load(content: &str, cx: &App) -> KeymapFileLoadResult { - let key_equivalents = - crate::key_equivalents::get_key_equivalents(cx.keyboard_layout().id()); - if content.is_empty() { return KeymapFileLoadResult::Success { key_bindings: Vec::new(), @@ -256,12 +253,6 @@ impl KeymapFile { } }; - let key_equivalents = if *use_key_equivalents { - key_equivalents.as_ref() - } else { - None - }; - let mut section_errors = String::new(); if !unrecognized_fields.is_empty() { @@ -280,7 +271,6 @@ impl KeymapFile { action, context_predicate.clone(), *use_key_equivalents, - key_equivalents, cx, ); match result { @@ -339,7 +329,6 @@ impl KeymapFile { action: &KeymapAction, context: Option>, use_key_equivalents: bool, - key_equivalents: Option<&HashMap>, cx: &App, ) -> std::result::Result { let (build_result, action_input_string) = match &action.0 { @@ -408,7 +397,6 @@ impl KeymapFile { action, context, use_key_equivalents, - key_equivalents, action_input_string.map(SharedString::from), cx.keyboard_mapper(), ) { From 459f168ca9579cf6198e27f2f439127b7c866b29 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 26 Aug 2025 00:27:12 +0800 Subject: [PATCH 27/35] fix macos --- crates/gpui/src/platform/mac/platform.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 780ea52f94e13a..30453def00bbac 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -354,19 +354,19 @@ impl MacPlatform { let mut mask = NSEventModifierFlags::empty(); for (modifier, flag) in &[ ( - keystroke.modifiers.platform, + keystroke.display_modifiers.platform, NSEventModifierFlags::NSCommandKeyMask, ), ( - keystroke.modifiers.control, + keystroke.display_modifiers.control, NSEventModifierFlags::NSControlKeyMask, ), ( - keystroke.modifiers.alt, + keystroke.display_modifiers.alt, NSEventModifierFlags::NSAlternateKeyMask, ), ( - keystroke.modifiers.shift, + keystroke.display_modifiers.shift, NSEventModifierFlags::NSShiftKeyMask, ), ] { @@ -379,7 +379,7 @@ impl MacPlatform { .initWithTitle_action_keyEquivalent_( ns_string(name), selector, - ns_string(key_to_native(&keystroke.key).as_ref()), + ns_string(key_to_native(&keystroke.display_key).as_ref()), ) .autorelease(); if Self::os_version() >= SemanticVersion::new(12, 0, 0) { @@ -1404,7 +1404,7 @@ extern "C" fn on_keyboard_layout_change(this: &mut Object, _: Sel, _: id) { let platform = unsafe { get_mac_platform(this) }; let mut lock = platform.0.lock(); let keyboard_layout = MacKeyboardLayout::new(); - lock.keyboard_mapper = MacKeyboardMapper::new(keyboard_layout.id()); + lock.keyboard_mapper = Rc::new(MacKeyboardMapper::new(keyboard_layout.id())); if let Some(mut callback) = lock.on_keyboard_layout_change.take() { drop(lock); callback(); From c3c540478b4214727b9204d8ec6b30a891392e72 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 26 Aug 2025 01:43:35 +0800 Subject: [PATCH 28/35] fix --- crates/gpui/src/platform/keyboard.rs | 9 + crates/gpui/src/platform/mac/keyboard.rs | 4 + crates/gpui/src/platform/windows/keyboard.rs | 4 + crates/language_tools/src/key_context_view.rs | 4 +- crates/settings/src/key_equivalents.rs | 1424 ----------------- crates/settings/src/settings.rs | 2 - 6 files changed, 19 insertions(+), 1428 deletions(-) delete mode 100644 crates/settings/src/key_equivalents.rs diff --git a/crates/gpui/src/platform/keyboard.rs b/crates/gpui/src/platform/keyboard.rs index 8f52e11cb23dc9..10b8620258ecff 100644 --- a/crates/gpui/src/platform/keyboard.rs +++ b/crates/gpui/src/platform/keyboard.rs @@ -1,3 +1,5 @@ +use collections::HashMap; + use crate::{KeybindingKeystroke, Keystroke}; /// A trait for platform-specific keyboard layouts @@ -16,6 +18,9 @@ pub trait PlatformKeyboardMapper { keystroke: Keystroke, use_key_equivalents: bool, ) -> KeybindingKeystroke; + /// Get the key equivalents for the current keyboard layout, + /// only used on macOS + fn get_key_equivalents(&self) -> Option<&HashMap>; } /// A dummy implementation of the platform keyboard mapper @@ -29,4 +34,8 @@ impl PlatformKeyboardMapper for DummyKeyboardMapper { ) -> KeybindingKeystroke { KeybindingKeystroke::from_keystroke(keystroke) } + + fn get_key_equivalents(&self) -> Option<&HashMap> { + None + } } diff --git a/crates/gpui/src/platform/mac/keyboard.rs b/crates/gpui/src/platform/mac/keyboard.rs index b3793ba97c5475..14097312468cbb 100644 --- a/crates/gpui/src/platform/mac/keyboard.rs +++ b/crates/gpui/src/platform/mac/keyboard.rs @@ -44,6 +44,10 @@ impl PlatformKeyboardMapper for MacKeyboardMapper { } KeybindingKeystroke::from_keystroke(keystroke) } + + fn get_key_equivalents(&self) -> Option<&HashMap> { + self.key_equivalents.as_ref() + } } impl MacKeyboardLayout { diff --git a/crates/gpui/src/platform/windows/keyboard.rs b/crates/gpui/src/platform/windows/keyboard.rs index 182cba90340c89..0eb97fbb0c500d 100644 --- a/crates/gpui/src/platform/windows/keyboard.rs +++ b/crates/gpui/src/platform/windows/keyboard.rs @@ -89,6 +89,10 @@ impl PlatformKeyboardMapper for WindowsKeyboardMapper { display_key: key, } } + + fn get_key_equivalents(&self) -> Option<&HashMap> { + None + } } impl WindowsKeyboardLayout { diff --git a/crates/language_tools/src/key_context_view.rs b/crates/language_tools/src/key_context_view.rs index 057259d114f88f..4140713544ed2b 100644 --- a/crates/language_tools/src/key_context_view.rs +++ b/crates/language_tools/src/key_context_view.rs @@ -4,7 +4,6 @@ use gpui::{ }; use itertools::Itertools; use serde_json::json; -use settings::get_key_equivalents; use ui::{Button, ButtonStyle}; use ui::{ ButtonCommon, Clickable, Context, FluentBuilder, InteractiveElement, Label, LabelCommon, @@ -169,7 +168,8 @@ impl Item for KeyContextView { impl Render for KeyContextView { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl ui::IntoElement { use itertools::Itertools; - let key_equivalents = get_key_equivalents(cx.keyboard_layout().id()); + + let key_equivalents = cx.keyboard_mapper().get_key_equivalents(); v_flex() .id("key-context-view") .overflow_scroll() diff --git a/crates/settings/src/key_equivalents.rs b/crates/settings/src/key_equivalents.rs deleted file mode 100644 index 65801375356289..00000000000000 --- a/crates/settings/src/key_equivalents.rs +++ /dev/null @@ -1,1424 +0,0 @@ -use collections::HashMap; - -// On some keyboards (e.g. German QWERTZ) it is not possible to type the full ASCII range -// without using option. This means that some of our built in keyboard shortcuts do not work -// for those users. -// -// The way macOS solves this problem is to move shortcuts around so that they are all reachable, -// even if the mnemonic changes. https://developer.apple.com/documentation/swiftui/keyboardshortcut/localization-swift.struct -// -// For example, cmd-> is the "switch window" shortcut because the > key is right above tab. -// To ensure this doesn't cause problems for shortcuts defined for a QWERTY layout, apple moves -// any shortcuts defined as cmd-> to cmd-:. Coincidentally this s also the same keyboard position -// as cmd-> on a QWERTY layout. -// -// Another example is cmd-[ and cmd-], as they cannot be typed without option, those keys are remapped to cmd-ö -// and cmd-ä. These shortcuts are not in the same position as a QWERTY keyboard, because on a QWERTZ keyboard -// the + key is in the way; and shortcuts bound to cmd-+ are still typed as cmd-+ on either keyboard (though the -// specific key moves) -// -// As far as I can tell, there's no way to query the mappings Apple uses except by rendering a menu with every -// possible key combination, and inspecting the UI to see what it rendered. So that's what we did... -// -// These mappings were generated by running https://github.com/ConradIrwin/keyboard-inspector, tidying up the -// output to remove languages with no mappings and other oddities, and converting it to a less verbose representation with: -// jq -s 'map(to_entries | map({key: .key, value: [(.value | to_entries | map(.key) | join("")), (.value | to_entries | map(.value) | join(""))]}) | from_entries) | add' -// From there I used multi-cursor to produce this match statement. -#[cfg(target_os = "macos")] -pub fn get_key_equivalents(layout: &str) -> Option> { - let mappings: &[(char, char)] = match layout { - "com.apple.keylayout.ABC-AZERTY" => &[ - ('!', '1'), - ('"', '%'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('.', ';'), - ('/', ':'), - ('0', 'à'), - ('1', '&'), - ('2', 'é'), - ('3', '"'), - ('4', '\''), - ('5', '('), - ('6', '§'), - ('7', 'è'), - ('8', '!'), - ('9', 'ç'), - (':', '°'), - (';', ')'), - ('<', '.'), - ('>', '/'), - ('@', '2'), - ('[', '^'), - ('\'', 'ù'), - ('\\', '`'), - (']', '$'), - ('^', '6'), - ('`', '<'), - ('{', '¨'), - ('|', '£'), - ('}', '*'), - ('~', '>'), - ], - "com.apple.keylayout.ABC-QWERTZ" => &[ - ('"', '`'), - ('#', '§'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', 'ß'), - (':', 'Ü'), - (';', 'ü'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '´'), - ('\\', '#'), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '\''), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.Albanian" => &[ - ('"', '\''), - (':', 'Ç'), - (';', 'ç'), - ('<', ';'), - ('>', ':'), - ('@', '"'), - ('\'', '@'), - ('\\', 'ë'), - ('`', '<'), - ('|', 'Ë'), - ('~', '>'), - ], - "com.apple.keylayout.Austrian" => &[ - ('"', '`'), - ('#', '§'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', 'ß'), - (':', 'Ü'), - (';', 'ü'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '´'), - ('\\', '#'), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '\''), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.Azeri" => &[ - ('"', 'Ə'), - (',', 'ç'), - ('.', 'ş'), - ('/', '.'), - (':', 'I'), - (';', 'ı'), - ('<', 'Ç'), - ('>', 'Ş'), - ('?', ','), - ('W', 'Ü'), - ('[', 'ö'), - ('\'', 'ə'), - (']', 'ğ'), - ('w', 'ü'), - ('{', 'Ö'), - ('|', '/'), - ('}', 'Ğ'), - ], - "com.apple.keylayout.Belgian" => &[ - ('!', '1'), - ('"', '%'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('.', ';'), - ('/', ':'), - ('0', 'à'), - ('1', '&'), - ('2', 'é'), - ('3', '"'), - ('4', '\''), - ('5', '('), - ('6', '§'), - ('7', 'è'), - ('8', '!'), - ('9', 'ç'), - (':', '°'), - (';', ')'), - ('<', '.'), - ('>', '/'), - ('@', '2'), - ('[', '^'), - ('\'', 'ù'), - ('\\', '`'), - (']', '$'), - ('^', '6'), - ('`', '<'), - ('{', '¨'), - ('|', '£'), - ('}', '*'), - ('~', '>'), - ], - "com.apple.keylayout.Brazilian-ABNT2" => &[ - ('"', '`'), - ('/', 'ç'), - ('?', 'Ç'), - ('\'', '´'), - ('\\', '~'), - ('^', '¨'), - ('`', '\''), - ('|', '^'), - ('~', '"'), - ], - "com.apple.keylayout.Brazilian-Pro" => &[('^', 'ˆ'), ('~', '˜')], - "com.apple.keylayout.British" => &[('#', '£')], - "com.apple.keylayout.Canadian-CSA" => &[ - ('"', 'È'), - ('/', 'é'), - ('<', '\''), - ('>', '"'), - ('?', 'É'), - ('[', '^'), - ('\'', 'è'), - ('\\', 'à'), - (']', 'ç'), - ('`', 'ù'), - ('{', '¨'), - ('|', 'À'), - ('}', 'Ç'), - ('~', 'Ù'), - ], - "com.apple.keylayout.Croatian" => &[ - ('"', 'Ć'), - ('&', '\''), - ('(', ')'), - (')', '='), - ('*', '('), - (':', 'Č'), - (';', 'č'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'š'), - ('\'', 'ć'), - ('\\', 'ž'), - (']', 'đ'), - ('^', '&'), - ('`', '<'), - ('{', 'Š'), - ('|', 'Ž'), - ('}', 'Đ'), - ('~', '>'), - ], - "com.apple.keylayout.Croatian-PC" => &[ - ('"', 'Ć'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '\''), - (':', 'Č'), - (';', 'č'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'š'), - ('\'', 'ć'), - ('\\', 'ž'), - (']', 'đ'), - ('^', '&'), - ('`', '<'), - ('{', 'Š'), - ('|', 'Ž'), - ('}', 'Đ'), - ('~', '>'), - ], - "com.apple.keylayout.Czech" => &[ - ('!', '1'), - ('"', '!'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('+', '%'), - ('/', '\''), - ('0', 'é'), - ('1', '+'), - ('2', 'ě'), - ('3', 'š'), - ('4', 'č'), - ('5', 'ř'), - ('6', 'ž'), - ('7', 'ý'), - ('8', 'á'), - ('9', 'í'), - (':', '"'), - (';', 'ů'), - ('<', '?'), - ('>', ':'), - ('?', 'ˇ'), - ('@', '2'), - ('[', 'ú'), - ('\'', '§'), - (']', ')'), - ('^', '6'), - ('`', '¨'), - ('{', 'Ú'), - ('}', '('), - ('~', '`'), - ], - "com.apple.keylayout.Czech-QWERTY" => &[ - ('!', '1'), - ('"', '!'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('+', '%'), - ('/', '\''), - ('0', 'é'), - ('1', '+'), - ('2', 'ě'), - ('3', 'š'), - ('4', 'č'), - ('5', 'ř'), - ('6', 'ž'), - ('7', 'ý'), - ('8', 'á'), - ('9', 'í'), - (':', '"'), - (';', 'ů'), - ('<', '?'), - ('>', ':'), - ('?', 'ˇ'), - ('@', '2'), - ('[', 'ú'), - ('\'', '§'), - (']', ')'), - ('^', '6'), - ('`', '¨'), - ('{', 'Ú'), - ('}', '('), - ('~', '`'), - ], - "com.apple.keylayout.Danish" => &[ - ('"', '^'), - ('$', '€'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'æ'), - ('\'', '¨'), - ('\\', '\''), - (']', 'ø'), - ('^', '&'), - ('`', '<'), - ('{', 'Æ'), - ('|', '*'), - ('}', 'Ø'), - ('~', '>'), - ], - "com.apple.keylayout.Faroese" => &[ - ('"', 'Ø'), - ('$', '€'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Æ'), - (';', 'æ'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'å'), - ('\'', 'ø'), - ('\\', '\''), - (']', 'ð'), - ('^', '&'), - ('`', '<'), - ('{', 'Å'), - ('|', '*'), - ('}', 'Ð'), - ('~', '>'), - ], - "com.apple.keylayout.Finnish" => &[ - ('"', '^'), - ('$', '€'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '¨'), - ('\\', '\''), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '*'), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.FinnishExtended" => &[ - ('"', 'ˆ'), - ('$', '€'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '¨'), - ('\\', '\''), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '*'), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.FinnishSami-PC" => &[ - ('"', 'ˆ'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '¨'), - ('\\', '@'), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '*'), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.French" => &[ - ('!', '1'), - ('"', '%'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('.', ';'), - ('/', ':'), - ('0', 'à'), - ('1', '&'), - ('2', 'é'), - ('3', '"'), - ('4', '\''), - ('5', '('), - ('6', '§'), - ('7', 'è'), - ('8', '!'), - ('9', 'ç'), - (':', '°'), - (';', ')'), - ('<', '.'), - ('>', '/'), - ('@', '2'), - ('[', '^'), - ('\'', 'ù'), - ('\\', '`'), - (']', '$'), - ('^', '6'), - ('`', '<'), - ('{', '¨'), - ('|', '£'), - ('}', '*'), - ('~', '>'), - ], - "com.apple.keylayout.French-PC" => &[ - ('!', '1'), - ('"', '%'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('-', ')'), - ('.', ';'), - ('/', ':'), - ('0', 'à'), - ('1', '&'), - ('2', 'é'), - ('3', '"'), - ('4', '\''), - ('5', '('), - ('6', '-'), - ('7', 'è'), - ('8', '_'), - ('9', 'ç'), - (':', '§'), - (';', '!'), - ('<', '.'), - ('>', '/'), - ('@', '2'), - ('[', '^'), - ('\'', 'ù'), - ('\\', '*'), - (']', '$'), - ('^', '6'), - ('_', '°'), - ('`', '<'), - ('{', '¨'), - ('|', 'μ'), - ('}', '£'), - ('~', '>'), - ], - "com.apple.keylayout.French-numerical" => &[ - ('!', '1'), - ('"', '%'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('.', ';'), - ('/', ':'), - ('0', 'à'), - ('1', '&'), - ('2', 'é'), - ('3', '"'), - ('4', '\''), - ('5', '('), - ('6', '§'), - ('7', 'è'), - ('8', '!'), - ('9', 'ç'), - (':', '°'), - (';', ')'), - ('<', '.'), - ('>', '/'), - ('@', '2'), - ('[', '^'), - ('\'', 'ù'), - ('\\', '`'), - (']', '$'), - ('^', '6'), - ('`', '<'), - ('{', '¨'), - ('|', '£'), - ('}', '*'), - ('~', '>'), - ], - "com.apple.keylayout.German" => &[ - ('"', '`'), - ('#', '§'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', 'ß'), - (':', 'Ü'), - (';', 'ü'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '´'), - ('\\', '#'), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '\''), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.German-DIN-2137" => &[ - ('"', '`'), - ('#', '§'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', 'ß'), - (':', 'Ü'), - (';', 'ü'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '´'), - ('\\', '#'), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '\''), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.Hawaiian" => &[('\'', 'ʻ')], - "com.apple.keylayout.Hungarian" => &[ - ('!', '\''), - ('"', 'Á'), - ('#', '+'), - ('$', '!'), - ('&', '='), - ('(', ')'), - (')', 'Ö'), - ('*', '('), - ('+', 'Ó'), - ('/', 'ü'), - ('0', 'ö'), - (':', 'É'), - (';', 'é'), - ('<', 'Ü'), - ('=', 'ó'), - ('>', ':'), - ('@', '"'), - ('[', 'ő'), - ('\'', 'á'), - ('\\', 'ű'), - (']', 'ú'), - ('^', '/'), - ('`', 'í'), - ('{', 'Ő'), - ('|', 'Ű'), - ('}', 'Ú'), - ('~', 'Í'), - ], - "com.apple.keylayout.Hungarian-QWERTY" => &[ - ('!', '\''), - ('"', 'Á'), - ('#', '+'), - ('$', '!'), - ('&', '='), - ('(', ')'), - (')', 'Ö'), - ('*', '('), - ('+', 'Ó'), - ('/', 'ü'), - ('0', 'ö'), - (':', 'É'), - (';', 'é'), - ('<', 'Ü'), - ('=', 'ó'), - ('>', ':'), - ('@', '"'), - ('[', 'ő'), - ('\'', 'á'), - ('\\', 'ű'), - (']', 'ú'), - ('^', '/'), - ('`', 'í'), - ('{', 'Ő'), - ('|', 'Ű'), - ('}', 'Ú'), - ('~', 'Í'), - ], - "com.apple.keylayout.Icelandic" => &[ - ('"', 'Ö'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '\''), - (':', 'Ð'), - (';', 'ð'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'æ'), - ('\'', 'ö'), - ('\\', 'þ'), - (']', '´'), - ('^', '&'), - ('`', '<'), - ('{', 'Æ'), - ('|', 'Þ'), - ('}', '´'), - ('~', '>'), - ], - "com.apple.keylayout.Irish" => &[('#', '£')], - "com.apple.keylayout.IrishExtended" => &[('#', '£')], - "com.apple.keylayout.Italian" => &[ - ('!', '1'), - ('"', '%'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - (',', ';'), - ('.', ':'), - ('/', ','), - ('0', 'é'), - ('1', '&'), - ('2', '"'), - ('3', '\''), - ('4', '('), - ('5', 'ç'), - ('6', 'è'), - ('7', ')'), - ('8', '£'), - ('9', 'à'), - (':', '!'), - (';', 'ò'), - ('<', '.'), - ('>', '/'), - ('@', '2'), - ('[', 'ì'), - ('\'', 'ù'), - ('\\', '§'), - (']', '$'), - ('^', '6'), - ('`', '<'), - ('{', '^'), - ('|', '°'), - ('}', '*'), - ('~', '>'), - ], - "com.apple.keylayout.Italian-Pro" => &[ - ('"', '^'), - ('#', '£'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '\''), - (':', 'é'), - (';', 'è'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'ò'), - ('\'', 'ì'), - ('\\', 'ù'), - (']', 'à'), - ('^', '&'), - ('`', '<'), - ('{', 'ç'), - ('|', '§'), - ('}', '°'), - ('~', '>'), - ], - "com.apple.keylayout.LatinAmerican" => &[ - ('"', '¨'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '\''), - (':', 'Ñ'), - (';', 'ñ'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', '{'), - ('\'', '´'), - ('\\', '¿'), - (']', '}'), - ('^', '&'), - ('`', '<'), - ('{', '['), - ('|', '¡'), - ('}', ']'), - ('~', '>'), - ], - "com.apple.keylayout.Lithuanian" => &[ - ('!', 'Ą'), - ('#', 'Ę'), - ('$', 'Ė'), - ('%', 'Į'), - ('&', 'Ų'), - ('*', 'Ū'), - ('+', 'Ž'), - ('1', 'ą'), - ('2', 'č'), - ('3', 'ę'), - ('4', 'ė'), - ('5', 'į'), - ('6', 'š'), - ('7', 'ų'), - ('8', 'ū'), - ('=', 'ž'), - ('@', 'Č'), - ('^', 'Š'), - ], - "com.apple.keylayout.Maltese" => &[ - ('#', '£'), - ('[', 'ġ'), - (']', 'ħ'), - ('`', 'ż'), - ('{', 'Ġ'), - ('}', 'Ħ'), - ('~', 'Ż'), - ], - "com.apple.keylayout.NorthernSami" => &[ - ('"', 'Ŋ'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('Q', 'Á'), - ('W', 'Š'), - ('X', 'Č'), - ('[', 'ø'), - ('\'', 'ŋ'), - ('\\', 'đ'), - (']', 'æ'), - ('^', '&'), - ('`', 'ž'), - ('q', 'á'), - ('w', 'š'), - ('x', 'č'), - ('{', 'Ø'), - ('|', 'Đ'), - ('}', 'Æ'), - ('~', 'Ž'), - ], - "com.apple.keylayout.Norwegian" => &[ - ('"', '^'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ø'), - ('\'', '¨'), - ('\\', '@'), - (']', 'æ'), - ('^', '&'), - ('`', '<'), - ('{', 'Ø'), - ('|', '*'), - ('}', 'Æ'), - ('~', '>'), - ], - "com.apple.keylayout.NorwegianExtended" => &[ - ('"', 'ˆ'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ø'), - ('\\', '@'), - (']', 'æ'), - ('`', '<'), - ('}', 'Æ'), - ('~', '>'), - ], - "com.apple.keylayout.NorwegianSami-PC" => &[ - ('"', 'ˆ'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ø'), - ('\'', '¨'), - ('\\', '@'), - (']', 'æ'), - ('^', '&'), - ('`', '<'), - ('{', 'Ø'), - ('|', '*'), - ('}', 'Æ'), - ('~', '>'), - ], - "com.apple.keylayout.Polish" => &[ - ('!', '§'), - ('"', 'ę'), - ('#', '!'), - ('$', '?'), - ('%', '+'), - ('&', ':'), - ('(', '/'), - (')', '"'), - ('*', '_'), - ('+', ']'), - (',', '.'), - ('.', ','), - ('/', 'ż'), - (':', 'Ł'), - (';', 'ł'), - ('<', 'ś'), - ('=', '['), - ('>', 'ń'), - ('?', 'Ż'), - ('@', '%'), - ('[', 'ó'), - ('\'', 'ą'), - ('\\', ';'), - (']', '('), - ('^', '='), - ('_', 'ć'), - ('`', '<'), - ('{', 'ź'), - ('|', '$'), - ('}', ')'), - ('~', '>'), - ], - "com.apple.keylayout.Portuguese" => &[ - ('"', '`'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '\''), - (':', 'ª'), - (';', 'º'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'ç'), - ('\'', '´'), - (']', '~'), - ('^', '&'), - ('`', '<'), - ('{', 'Ç'), - ('}', '^'), - ('~', '>'), - ], - "com.apple.keylayout.Sami-PC" => &[ - ('"', 'Ŋ'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('Q', 'Á'), - ('W', 'Š'), - ('X', 'Č'), - ('[', 'ø'), - ('\'', 'ŋ'), - ('\\', 'đ'), - (']', 'æ'), - ('^', '&'), - ('`', 'ž'), - ('q', 'á'), - ('w', 'š'), - ('x', 'č'), - ('{', 'Ø'), - ('|', 'Đ'), - ('}', 'Æ'), - ('~', 'Ž'), - ], - "com.apple.keylayout.Serbian-Latin" => &[ - ('"', 'Ć'), - ('&', '\''), - ('(', ')'), - (')', '='), - ('*', '('), - (':', 'Č'), - (';', 'č'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'š'), - ('\'', 'ć'), - ('\\', 'ž'), - (']', 'đ'), - ('^', '&'), - ('`', '<'), - ('{', 'Š'), - ('|', 'Ž'), - ('}', 'Đ'), - ('~', '>'), - ], - "com.apple.keylayout.Slovak" => &[ - ('!', '1'), - ('"', '!'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('+', '%'), - ('/', '\''), - ('0', 'é'), - ('1', '+'), - ('2', 'ľ'), - ('3', 'š'), - ('4', 'č'), - ('5', 'ť'), - ('6', 'ž'), - ('7', 'ý'), - ('8', 'á'), - ('9', 'í'), - (':', '"'), - (';', 'ô'), - ('<', '?'), - ('>', ':'), - ('?', 'ˇ'), - ('@', '2'), - ('[', 'ú'), - ('\'', '§'), - (']', 'ä'), - ('^', '6'), - ('`', 'ň'), - ('{', 'Ú'), - ('}', 'Ä'), - ('~', 'Ň'), - ], - "com.apple.keylayout.Slovak-QWERTY" => &[ - ('!', '1'), - ('"', '!'), - ('#', '3'), - ('$', '4'), - ('%', '5'), - ('&', '7'), - ('(', '9'), - (')', '0'), - ('*', '8'), - ('+', '%'), - ('/', '\''), - ('0', 'é'), - ('1', '+'), - ('2', 'ľ'), - ('3', 'š'), - ('4', 'č'), - ('5', 'ť'), - ('6', 'ž'), - ('7', 'ý'), - ('8', 'á'), - ('9', 'í'), - (':', '"'), - (';', 'ô'), - ('<', '?'), - ('>', ':'), - ('?', 'ˇ'), - ('@', '2'), - ('[', 'ú'), - ('\'', '§'), - (']', 'ä'), - ('^', '6'), - ('`', 'ň'), - ('{', 'Ú'), - ('}', 'Ä'), - ('~', 'Ň'), - ], - "com.apple.keylayout.Slovenian" => &[ - ('"', 'Ć'), - ('&', '\''), - ('(', ')'), - (')', '='), - ('*', '('), - (':', 'Č'), - (';', 'č'), - ('<', ';'), - ('=', '*'), - ('>', ':'), - ('@', '"'), - ('[', 'š'), - ('\'', 'ć'), - ('\\', 'ž'), - (']', 'đ'), - ('^', '&'), - ('`', '<'), - ('{', 'Š'), - ('|', 'Ž'), - ('}', 'Đ'), - ('~', '>'), - ], - "com.apple.keylayout.Spanish" => &[ - ('!', '¡'), - ('"', '¨'), - ('.', 'ç'), - ('/', '.'), - (':', 'º'), - (';', '´'), - ('<', '¿'), - ('>', 'Ç'), - ('@', '!'), - ('[', 'ñ'), - ('\'', '`'), - ('\\', '\''), - (']', ';'), - ('^', '/'), - ('`', '<'), - ('{', 'Ñ'), - ('|', '"'), - ('}', ':'), - ('~', '>'), - ], - "com.apple.keylayout.Spanish-ISO" => &[ - ('"', '¨'), - ('#', '·'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('.', 'ç'), - ('/', '.'), - (':', 'º'), - (';', '´'), - ('<', '¿'), - ('>', 'Ç'), - ('@', '"'), - ('[', 'ñ'), - ('\'', '`'), - ('\\', '\''), - (']', ';'), - ('^', '&'), - ('`', '<'), - ('{', 'Ñ'), - ('|', '"'), - ('}', '`'), - ('~', '>'), - ], - "com.apple.keylayout.Swedish" => &[ - ('"', '^'), - ('$', '€'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '¨'), - ('\\', '\''), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '*'), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.Swedish-Pro" => &[ - ('"', '^'), - ('$', '€'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '¨'), - ('\\', '\''), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '*'), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.SwedishSami-PC" => &[ - ('"', 'ˆ'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('/', '´'), - (':', 'Å'), - (';', 'å'), - ('<', ';'), - ('=', '`'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '¨'), - ('\\', '@'), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'Ö'), - ('|', '*'), - ('}', 'Ä'), - ('~', '>'), - ], - "com.apple.keylayout.SwissFrench" => &[ - ('!', '+'), - ('"', '`'), - ('#', '*'), - ('$', 'ç'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('+', '!'), - ('/', '\''), - (':', 'ü'), - (';', 'è'), - ('<', ';'), - ('=', '¨'), - ('>', ':'), - ('@', '"'), - ('[', 'é'), - ('\'', '^'), - ('\\', '$'), - (']', 'à'), - ('^', '&'), - ('`', '<'), - ('{', 'ö'), - ('|', '£'), - ('}', 'ä'), - ('~', '>'), - ], - "com.apple.keylayout.SwissGerman" => &[ - ('!', '+'), - ('"', '`'), - ('#', '*'), - ('$', 'ç'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('+', '!'), - ('/', '\''), - (':', 'è'), - (';', 'ü'), - ('<', ';'), - ('=', '¨'), - ('>', ':'), - ('@', '"'), - ('[', 'ö'), - ('\'', '^'), - ('\\', '$'), - (']', 'ä'), - ('^', '&'), - ('`', '<'), - ('{', 'é'), - ('|', '£'), - ('}', 'à'), - ('~', '>'), - ], - "com.apple.keylayout.Turkish" => &[ - ('"', '-'), - ('#', '"'), - ('$', '\''), - ('%', '('), - ('&', ')'), - ('(', '%'), - (')', ':'), - ('*', '_'), - (',', 'ö'), - ('-', 'ş'), - ('.', 'ç'), - ('/', '.'), - (':', '$'), - ('<', 'Ö'), - ('>', 'Ç'), - ('@', '*'), - ('[', 'ğ'), - ('\'', ','), - ('\\', 'ü'), - (']', 'ı'), - ('^', '/'), - ('_', 'Ş'), - ('`', '<'), - ('{', 'Ğ'), - ('|', 'Ü'), - ('}', 'I'), - ('~', '>'), - ], - "com.apple.keylayout.Turkish-QWERTY-PC" => &[ - ('"', 'I'), - ('#', '^'), - ('$', '+'), - ('&', '/'), - ('(', ')'), - (')', '='), - ('*', '('), - ('+', ':'), - (',', 'ö'), - ('.', 'ç'), - ('/', '*'), - (':', 'Ş'), - (';', 'ş'), - ('<', 'Ö'), - ('=', '.'), - ('>', 'Ç'), - ('@', '\''), - ('[', 'ğ'), - ('\'', 'ı'), - ('\\', ','), - (']', 'ü'), - ('^', '&'), - ('`', '<'), - ('{', 'Ğ'), - ('|', ';'), - ('}', 'Ü'), - ('~', '>'), - ], - "com.apple.keylayout.Turkish-Standard" => &[ - ('"', 'Ş'), - ('#', '^'), - ('&', '\''), - ('(', ')'), - (')', '='), - ('*', '('), - (',', '.'), - ('.', ','), - (':', 'Ç'), - (';', 'ç'), - ('<', ':'), - ('=', '*'), - ('>', ';'), - ('@', '"'), - ('[', 'ğ'), - ('\'', 'ş'), - ('\\', 'ü'), - (']', 'ı'), - ('^', '&'), - ('`', 'ö'), - ('{', 'Ğ'), - ('|', 'Ü'), - ('}', 'I'), - ('~', 'Ö'), - ], - "com.apple.keylayout.Turkmen" => &[ - ('C', 'Ç'), - ('Q', 'Ä'), - ('V', 'Ý'), - ('X', 'Ü'), - ('[', 'ň'), - ('\\', 'ş'), - (']', 'ö'), - ('^', '№'), - ('`', 'ž'), - ('c', 'ç'), - ('q', 'ä'), - ('v', 'ý'), - ('x', 'ü'), - ('{', 'Ň'), - ('|', 'Ş'), - ('}', 'Ö'), - ('~', 'Ž'), - ], - "com.apple.keylayout.USInternational-PC" => &[('^', 'ˆ'), ('~', '˜')], - "com.apple.keylayout.Welsh" => &[('#', '£')], - - _ => return None, - }; - - Some(HashMap::from_iter(mappings.iter().cloned())) -} - -#[cfg(not(target_os = "macos"))] -pub fn get_key_equivalents(_layout: &str) -> Option> { - None -} diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 91a2d078271e08..7ec2d423c6eb82 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,6 +1,5 @@ mod base_keymap_setting; mod editable_setting_control; -mod key_equivalents; mod keymap_file; mod settings_file; mod settings_json; @@ -14,7 +13,6 @@ use util::asset_str; pub use base_keymap_setting::*; pub use editable_setting_control::*; -pub use key_equivalents::*; pub use keymap_file::{ KeyBindingValidator, KeyBindingValidatorRegistration, KeybindSource, KeybindUpdateOperation, KeybindUpdateTarget, KeymapFile, KeymapFileLoadResult, From 6a3b1925df5ca954825a2bcc754a2c566b1412cc Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 26 Aug 2025 16:42:08 +0800 Subject: [PATCH 29/35] typo --- assets/keymaps/default-windows.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index 74fde38af560ec..fcea4a1e6256e2 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -42,7 +42,7 @@ "shift-f11": "debugger::StepOut", "f11": "zed::ToggleFullScreen", // todo(windows) - // win -> but nealy none app use this + // win -> but nearly none app use this // vscode save all action => ctrl-alt-s ctrl-k s // ctrl-alt-z => shift-alt-z or ctrl-shift-z // "ctrl-alt-z": "edit_prediction::RateCompletions", From 144d1107fdafb3d5bd5cbe9be5226b2c4fd67c1d Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Tue, 26 Aug 2025 16:46:19 +0800 Subject: [PATCH 30/35] fix linux --- crates/gpui/src/platform/linux/platform.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 3fb1ef45729e1e..8bd89fc399cb82 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -25,8 +25,8 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State}; use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions, - Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformTextSystem, PlatformWindow, - Point, Result, Task, WindowAppearance, WindowParams, px, + Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, + PlatformTextSystem, PlatformWindow, Point, Result, Task, WindowAppearance, WindowParams, px, }; #[cfg(any(feature = "wayland", feature = "x11"))] @@ -144,6 +144,10 @@ impl Platform for P { self.keyboard_layout() } + fn keyboard_mapper(&self) -> Rc { + Rc::new(crate::DummyKeyboardMapper) + } + fn on_keyboard_layout_change(&self, callback: Box) { self.with_common(|common| common.callbacks.keyboard_layout_change = Some(callback)); } From 98ea1d838ae7e1c3748ea6c7147fb487310814ae Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 27 Aug 2025 00:04:43 +0800 Subject: [PATCH 31/35] update keymap Co-authored-by: Kate --- assets/keymaps/default-windows.json | 190 ++++------------------------ 1 file changed, 22 insertions(+), 168 deletions(-) diff --git a/assets/keymaps/default-windows.json b/assets/keymaps/default-windows.json index fcea4a1e6256e2..c7a6c3149ccab8 100644 --- a/assets/keymaps/default-windows.json +++ b/assets/keymaps/default-windows.json @@ -41,14 +41,7 @@ "ctrl-f11": "debugger::StepInto", "shift-f11": "debugger::StepOut", "f11": "zed::ToggleFullScreen", - // todo(windows) - // win -> but nearly none app use this - // vscode save all action => ctrl-alt-s ctrl-k s - // ctrl-alt-z => shift-alt-z or ctrl-shift-z - // "ctrl-alt-z": "edit_prediction::RateCompletions", "ctrl-shift-i": "edit_prediction::ToggleMenu", - // todo(windows) (probably remove binding) - // "ctrl-alt-l": "lsp_tool::ToggleMenu" "shift-alt-l": "lsp_tool::ToggleMenu" } }, @@ -150,10 +143,7 @@ "ctrl-h": "buffer_search::DeployReplace", "ctrl-shift-.": "assistant::QuoteSelection", "ctrl-shift-,": "assistant::InsertIntoEditor", - // todo(windows) (if vs code has a similar action, use that, otherwise remove this binding) - // the closest equivalent in vs code is "editor.action.selectToBracket", - // no default shortcut - // "ctrl-alt-e": "editor::SelectEnclosingSymbol", + "shift-alt-e": "editor::SelectEnclosingSymbol", "ctrl-shift-backspace": "editor::GoToPreviousChange", "ctrl-shift-alt-backspace": "editor::GoToNextChange", "alt-enter": "editor::OpenSelectionsInMultibuffer" @@ -204,10 +194,6 @@ "use_key_equivalents": true, "bindings": { "ctrl-k ctrl-r": "git::Restore", - // todo(windows) (find something, look at what vs code does) - // the closest equivalent in vs code is "git.stage", - // no default shortcut - // "ctrl-alt-y": "git::ToggleStaged", "alt-y": "git::StageAndNext", "shift-alt-y": "git::UnstageAndNext" } @@ -256,29 +242,18 @@ "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewThread", - // todo(windows) - // "ctrl-alt-n": "agent::NewTextThread", "shift-alt-n": "agent::NewTextThread", "ctrl-shift-h": "agent::OpenHistory", - // todo(windows) - // "ctrl-alt-c": "agent::OpenSettings", "shift-alt-c": "agent::OpenSettings", - // todo(windows) - // "ctrl-alt-p": "agent::OpenRulesLibrary", "shift-alt-p": "agent::OpenRulesLibrary", "ctrl-i": "agent::ToggleProfileSelector", - // todo(windows) - // "ctrl-alt-/": "agent::ToggleModelSelector", "shift-alt-/": "agent::ToggleModelSelector", "ctrl-shift-a": "agent::ToggleContextPicker", "ctrl-shift-j": "agent::ToggleNavigationMenu", "ctrl-shift-i": "agent::ToggleOptionsMenu", - // todo(windows) // "ctrl-shift-alt-n": "agent::ToggleNewThreadMenu", "shift-alt-escape": "agent::ExpandMessageEditor", "ctrl-shift-.": "assistant::QuoteSelection", - // todo(windows) - // "ctrl-alt-e": "agent::RemoveAllContext", "shift-alt-e": "agent::RemoveAllContext", "ctrl-shift-e": "project_panel::ToggleFocus", "ctrl-shift-enter": "agent::ContinueThread", @@ -306,9 +281,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewTextThread", - // todo(windows) - // "ctrl-alt-t": "agent::NewThread" - "shift-alt-t": "agent::NewThread" + "ctrl-alt-t": "agent::NewThread" } }, { @@ -316,9 +289,7 @@ "use_key_equivalents": true, "bindings": { "ctrl-n": "agent::NewExternalAgentThread", - // todo(windows) - // "ctrl-alt-t": "agent::NewThread" - "shift-alt-t": "agent::NewThread" + "ctrl-alt-t": "agent::NewThread" } }, { @@ -440,11 +411,7 @@ "shift-find": "search::FocusSearch", "ctrl-shift-f": "search::FocusSearch", "ctrl-shift-h": "search::ToggleReplace", - // todo(windows) - // "ctrl-alt-g": "search::ToggleRegex", "alt-r": "search::ToggleRegex" // vscode - // todo(windows) - // "ctrl-alt-x": "search::ToggleRegex" } }, { @@ -469,11 +436,7 @@ "bindings": { "escape": "project_search::ToggleFocus", "ctrl-shift-h": "search::ToggleReplace", - // todo(windows) - // "ctrl-alt-g": "search::ToggleRegex", "alt-r": "search::ToggleRegex" // vscode - // todo(windows) - // "ctrl-alt-x": "search::ToggleRegex" } }, { @@ -496,59 +459,30 @@ "ctrl-shift-pagedown": "pane::SwapItemRight", "ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }], "ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }], - // todo(windows) - // no default for vscode - // "ctrl-alt-t": ["pane::CloseOtherItems", { "close_pinned": false }], - // todo(windows) - // no such action for vscode - // "ctrl-shift-alt-w": "workspace::CloseInactiveTabsAndPanes", + "ctrl-shift-alt-t": ["pane::CloseOtherItems", { "close_pinned": false }], + "ctrl-shift-alt-w": "workspace::CloseInactiveTabsAndPanes", "ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }], "ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }], "ctrl-k u": ["pane::CloseCleanItems", { "close_pinned": false }], "ctrl-k w": ["pane::CloseAllItems", { "close_pinned": false }], "ctrl-k ctrl-w": "workspace::CloseAllItemsAndPanes", "back": "pane::GoBack", - // todo(windows) - // vscode: alt-left - // "ctrl-alt--": "pane::GoBack", "alt--": "pane::GoBack", - // todo(windows) - // vscode: alt-right - // "ctrl-shift-alt--": "pane::GoForward", - "shift-alt--": "pane::GoForward", + "alt-=": "pane::GoForward", "forward": "pane::GoForward", - // todo(windows) - // vscode: Enter - // "ctrl-alt-g": "search::SelectNextMatch", "f3": "search::SelectNextMatch", - // todo(windows) - // vscode: shift-enter - // "ctrl-shift-alt-g": "search::SelectPreviousMatch", "shift-f3": "search::SelectPreviousMatch", "shift-find": "project_search::ToggleFocus", "ctrl-shift-f": "project_search::ToggleFocus", - // todo(windows) - // no default shortcut for vscode - // "ctrl-shift-alt-h": "search::ToggleReplace", "shift-alt-h": "search::ToggleReplace", - // todo(windows) - // vscode: alt-l - // "ctrl-shift-alt-l": "search::ToggleSelection", "alt-l": "search::ToggleSelection", "alt-enter": "search::SelectAllMatches", "alt-c": "search::ToggleCaseSensitive", "alt-w": "search::ToggleWholeWord", "alt-find": "project_search::ToggleFilters", - // todo(windows) - // no default for vscode - // "ctrl-alt-f": "project_search::ToggleFilters", - // todo(windows) - // vscode: alt-r - // "ctrl-shift-alt-r": "search::ToggleRegex", + "alt-f": "project_search::ToggleFilters", "alt-r": "search::ToggleRegex", - // todo(windows) // "ctrl-shift-alt-x": "search::ToggleRegex", - // "alt-r": "search::ToggleRegex", "ctrl-k shift-enter": "pane::TogglePinTab" } }, @@ -612,10 +546,7 @@ "ctrl-k r": "editor::RevealInFileManager", "ctrl-k p": "editor::CopyPath", "ctrl-\\": "pane::SplitRight", - // todo(windows) - // vscode? - // "ctrl-shift-alt-c": "editor::DisplayCursorNames", - "shift-alt-c": "editor::DisplayCursorNames", + "ctrl-shift-alt-c": "editor::DisplayCursorNames", "alt-.": "editor::GoToHunk", "alt-,": "editor::GoToPreviousHunk" } @@ -651,25 +582,17 @@ "alt-open": ["projects::OpenRecent", { "create_new_window": false }], // Change the default action on `menu::Confirm` by setting the parameter // "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": true }], - // todo(windows) - // vscode: ctrl-r - // "ctrl-alt-o": ["projects::OpenRecent", { "create_new_window": false }], "ctrl-r": ["projects::OpenRecent", { "create_new_window": false }], "shift-alt-open": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], // Change to open path modal for existing remote connection by setting the parameter // "ctrl-shift-alt-o": "["projects::OpenRemote", { "from_existing_connection": true }]", - // todo(windows) - // "ctrl-shift-alt-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], - // todo(windows) - // no such for vscode - // "ctrl-shift-alt-b": "branches::OpenRecent", + "ctrl-shift-alt-o": ["projects::OpenRemote", { "from_existing_connection": false, "create_new_window": false }], "shift-alt-b": "branches::OpenRecent", "shift-alt-enter": "toast::RunAction", "ctrl-shift-`": "workspace::NewTerminal", "save": "workspace::Save", "ctrl-s": "workspace::Save", - // vscode? - // "ctrl-k s": "workspace::SaveWithoutFormat", + "ctrl-k ctrl-shift-s": "workspace::SaveWithoutFormat", "shift-save": "workspace::SaveAs", "ctrl-shift-s": "workspace::SaveAs", "new": "workspace::NewFile", @@ -687,33 +610,17 @@ "alt-7": ["workspace::ActivatePane", 6], "alt-8": ["workspace::ActivatePane", 7], "alt-9": ["workspace::ActivatePane", 8], - // todo(windows) - // this is the default for vscode "ctrl-alt-b": "workspace::ToggleRightDock", "ctrl-b": "workspace::ToggleLeftDock", "ctrl-j": "workspace::ToggleBottomDock", - // todo(windows) - // vscode? - // "ctrl-alt-y": "workspace::CloseAllDocks", "ctrl-shift-y": "workspace::CloseAllDocks", - // todo(windows) - // vscode? - // "ctrl-alt-0": "workspace::ResetActiveDockSize", - "shift-alt-0": "workspace::ResetActiveDockSize", + "alt-r": "workspace::ResetActiveDockSize", // For 0px parameter, uses UI font size value. - // todo(windows) - // "ctrl-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], "shift-alt--": ["workspace::DecreaseActiveDockSize", { "px": 0 }], - // todo(windows) - // "ctrl-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], "shift-alt-=": ["workspace::IncreaseActiveDockSize", { "px": 0 }], - // todo(windows) - // "ctrl-shift-alt-0": "workspace::ResetOpenDocksSize", - "ctrl-shift-0": "workspace::ResetOpenDocksSize", - // todo(windows) - // "ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], - // todo(windows) - // "ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], + "shift-alt-0": "workspace::ResetOpenDocksSize", + "ctrl-shift-alt--": ["workspace::DecreaseOpenDocksSize", { "px": 0 }], + "ctrl-shift-alt-=": ["workspace::IncreaseOpenDocksSize", { "px": 0 }], "shift-find": "pane::DeploySearch", "ctrl-shift-f": "pane::DeploySearch", "ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }], @@ -735,9 +642,6 @@ "ctrl-shift-d": "debug_panel::ToggleFocus", "ctrl-shift-/": "agent::ToggleFocus", "alt-save": "workspace::SaveAll", - // todo(windows) - // vscode: ctrl-k s - // "ctrl-alt-s": "workspace::SaveAll", "ctrl-k s": "workspace::SaveAll", "ctrl-k m": "language_selector::Toggle", "escape": "workspace::Unfollow", @@ -751,9 +655,6 @@ "ctrl-k shift-down": "workspace::SwapPaneDown", "ctrl-shift-x": "zed::Extensions", "ctrl-shift-r": "task::Rerun", - // todo(windows) - // no default for vscode - // "ctrl-alt-r": "task::Rerun", "alt-t": "task::Rerun", "shift-alt-t": "task::Spawn", "shift-alt-r": ["task::Spawn", { "reveal_target": "center" }], @@ -798,23 +699,13 @@ "ctrl-shift-u": "editor::RedoSelection", "ctrl-shift-j": "editor::JoinLines", "ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart", - // todo(windows) - // deleteWordLeft - // "ctrl-alt-h": "editor::DeleteToPreviousSubwordStart", + "shift-alt-h": "editor::DeleteToPreviousSubwordStart", "ctrl-alt-delete": "editor::DeleteToNextSubwordEnd", - // todo(windows) - // deleteWordRight - // "ctrl-alt-d": "editor::DeleteToNextSubwordEnd", + "shift-alt-d": "editor::DeleteToNextSubwordEnd", "ctrl-alt-left": "editor::MoveToPreviousSubwordStart", "ctrl-alt-right": "editor::MoveToNextSubwordEnd", "ctrl-shift-alt-left": "editor::SelectToPreviousSubwordStart", - // todo(windows) - // cursorWordLeftSelect - // no default for vscode - // "ctrl-shift-alt-b": "editor::SelectToPreviousSubwordStart", "ctrl-shift-alt-right": "editor::SelectToNextSubwordEnd" - // todo(windows) - // "ctrl-shift-alt-f": "editor::SelectToNextSubwordEnd" } }, // Bindings from Atom @@ -899,11 +790,8 @@ { "use_key_equivalents": true, "bindings": { - // todo(windows) - // "ctrl-shift-alt-f": "workspace::FollowNextCollaborator", + "ctrl-shift-alt-f": "workspace::FollowNextCollaborator", // Only available in debug builds: opens an element inspector for development. - // todo(windows) - // "ctrl-alt-i": "dev::ToggleInspector" "shift-alt-i": "dev::ToggleInspector" } }, @@ -934,8 +822,6 @@ "bindings": { "ctrl-[": "agent::CyclePreviousInlineAssist", "ctrl-]": "agent::CycleNextInlineAssist", - // todo(windows) - // "ctrl-alt-e": "agent::RemoveAllContext" "shift-alt-e": "agent::RemoveAllContext" } }, @@ -960,19 +846,12 @@ "context": "OutlinePanel && not_editing", "use_key_equivalents": true, "bindings": { - "escape": "menu::Cancel", "left": "outline_panel::CollapseSelectedEntry", "right": "outline_panel::ExpandSelectedEntry", "alt-copy": "outline_panel::CopyPath", - // todo(windows) - // should be okay? - "ctrl-alt-c": "outline_panel::CopyPath", + "shift-alt-c": "outline_panel::CopyPath", "shift-alt-copy": "workspace::CopyRelativePath", - // todo(windows) - // should be okay? "ctrl-shift-alt-c": "workspace::CopyRelativePath", - // todo(windows) - // should be okay? "ctrl-alt-r": "outline_panel::RevealInFileManager", "space": "outline_panel::OpenSelectedEntry", "shift-down": "menu::SelectNext", @@ -990,9 +869,6 @@ "new": "project_panel::NewFile", "ctrl-n": "project_panel::NewFile", "alt-new": "project_panel::NewDirectory", - // todo(windows) - // no default for vscode - // "ctrl-alt-n": "project_panel::NewDirectory", "alt-n": "project_panel::NewDirectory", "cut": "project_panel::Cut", "ctrl-x": "project_panel::Cut", @@ -1003,14 +879,8 @@ "shift-insert": "project_panel::Paste", "ctrl-v": "project_panel::Paste", "alt-copy": "project_panel::CopyPath", - // todo(windows) - // vscode: shift-alt-c - // "ctrl-alt-c": "project_panel::CopyPath", "shift-alt-c": "project_panel::CopyPath", "shift-alt-copy": "workspace::CopyRelativePath", - // todo(windows) - // vscode: ctrl-k ctrl-shift-c - // "ctrl-shift-alt-c": "workspace::CopyRelativePath", "ctrl-k ctrl-shift-c": "workspace::CopyRelativePath", "enter": "project_panel::Rename", "f2": "project_panel::Rename", @@ -1019,16 +889,10 @@ "shift-delete": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-backspace": ["project_panel::Delete", { "skip_prompt": false }], "ctrl-delete": ["project_panel::Delete", { "skip_prompt": false }], - // todo(windows) - // vscode: shift-alt-r - // conflict with task::Spawn line 759 - // "ctrl-alt-r": "project_panel::RevealInFileManager", + "ctrl-alt-r": "project_panel::RevealInFileManager", "ctrl-shift-enter": "project_panel::OpenWithSystem", "alt-d": "project_panel::CompareMarkedFiles", "shift-find": "project_panel::NewSearchInDirectory", - // todo(windows) - // no default for vscode - // "ctrl-shift-alt-f": "project_panel::NewSearchInDirectory", "ctrl-k ctrl-shift-f": "project_panel::NewSearchInDirectory", "shift-down": "menu::SelectNext", "shift-up": "menu::SelectPrevious", @@ -1051,9 +915,6 @@ "enter": "menu::Confirm", "alt-y": "git::StageFile", "shift-alt-y": "git::UnstageFile", - // todo(windows) - // vscode? - // "ctrl-alt-y": "git::ToggleStaged", "space": "git::ToggleStaged", "shift-space": "git::StageRange", "tab": "git_panel::FocusEditor", @@ -1150,9 +1011,7 @@ "right": "variable_list::ExpandSelectedEntry", "enter": "variable_list::EditVariable", "ctrl-c": "variable_list::CopyVariableValue", - // todo(windows) - // "ctrl-alt-c": "variable_list::CopyVariableName", - "ctrl-shift-c": "variable_list::CopyVariableName", + "ctrl-alt-c": "variable_list::CopyVariableName", "delete": "variable_list::RemoveWatch", "backspace": "variable_list::RemoveWatch", "alt-enter": "variable_list::AddWatch" @@ -1288,9 +1147,7 @@ "shift-end": "terminal::ScrollToBottom", "ctrl-shift-space": "terminal::ToggleViMode", "ctrl-shift-r": "terminal::RerunTask", - // todo(windows) - // no default for vscode - // "ctrl-alt-r": "terminal::RerunTask", + "ctrl-alt-r": "terminal::RerunTask", "alt-t": "terminal::RerunTask" } }, @@ -1354,10 +1211,7 @@ "bindings": { "ctrl-f": "search::FocusSearch", "alt-find": "keymap_editor::ToggleKeystrokeSearch", - // todo(windows) - // no such action for vscode - // "ctrl-alt-f": "keymap_editor::ToggleKeystrokeSearch", - "ctrl-shift-f": "keymap_editor::ToggleKeystrokeSearch", + "alt-f": "keymap_editor::ToggleKeystrokeSearch", "alt-c": "keymap_editor::ToggleConflictFilter", "enter": "keymap_editor::EditBinding", "alt-enter": "keymap_editor::CreateBinding", From 9615b712ca27de5a64b0dcd97c33cc12a67513c9 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 27 Aug 2025 00:24:16 +0800 Subject: [PATCH 32/35] comments --- crates/gpui/src/platform/keystroke.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index bdb6b29eedf8a5..b3f592a855fde7 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -7,9 +7,9 @@ use std::{ use crate::PlatformKeyboardMapper; -/// TODO: +/// This is a helper trait so that we can simplify the implementation of some functions pub trait AsKeystroke { - /// TODO: + /// Returns the GPUI representation of the keystroke. fn as_keystroke(&self) -> &Keystroke; } @@ -32,14 +32,14 @@ pub struct Keystroke { pub key_char: Option, } -/// TODO: +/// Represents a keystroke that can be used in keybindings and displayed to the user. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct KeybindingKeystroke { - /// TODO: + /// The GPUI representation of the keystroke. pub inner: Keystroke, - /// TODO: + /// The modifiers to display. pub display_modifiers: Modifiers, - /// TODO: + /// The key to display. pub display_key: String, } From 8cac7c62dc8c1013292e622667544993435205f0 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 27 Aug 2025 02:37:30 +0800 Subject: [PATCH 33/35] fix `KeymapEditor` --- crates/gpui/src/app.rs | 4 +- crates/gpui/src/platform/keystroke.rs | 60 +++++++++++-------- crates/settings/src/keymap_file.rs | 37 ++++++++---- crates/settings_ui/src/keybindings.rs | 37 ++++++++---- .../src/ui_components/keystroke_input.rs | 2 +- 5 files changed, 89 insertions(+), 51 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c1abf87d281771..b59d7e717ad9da 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -429,8 +429,8 @@ impl App { } /// Get the current keyboard mapper. - pub fn keyboard_mapper(&self) -> &dyn PlatformKeyboardMapper { - self.keyboard_mapper.as_ref() + pub fn keyboard_mapper(&self) -> &Rc { + &self.keyboard_mapper } /// Invokes a handler when the current keyboard layout changes diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index b3f592a855fde7..6ce17c3a01cd1e 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -219,31 +219,7 @@ impl Keystroke { /// Produces a representation of this key that Parse can understand. pub fn unparse(&self) -> String { - let mut str = String::new(); - if self.modifiers.function { - str.push_str("fn-"); - } - if self.modifiers.control { - str.push_str("ctrl-"); - } - if self.modifiers.alt { - str.push_str("alt-"); - } - if self.modifiers.platform { - #[cfg(target_os = "macos")] - str.push_str("cmd-"); - - #[cfg(any(target_os = "linux", target_os = "freebsd"))] - str.push_str("super-"); - - #[cfg(target_os = "windows")] - str.push_str("win-"); - } - if self.modifiers.shift { - str.push_str("shift-"); - } - str.push_str(&self.key); - str + unparse(&self.modifiers, &self.key) } /// Returns true if this keystroke left @@ -304,6 +280,11 @@ impl KeybindingKeystroke { display_key: key, } } + + /// Produces a representation of this key that Parse can understand. + pub fn unparse(&self) -> String { + unparse(&self.display_modifiers, &self.display_key) + } } fn is_printable_key(key: &str) -> bool { @@ -668,3 +649,32 @@ fn display_key(key: &str, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { }; f.write_char(key) } + +#[inline] +fn unparse(modifiers: &Modifiers, key: &str) -> String { + let mut result = String::new(); + if modifiers.function { + result.push_str("fn-"); + } + if modifiers.control { + result.push_str("ctrl-"); + } + if modifiers.alt { + result.push_str("alt-"); + } + if modifiers.platform { + #[cfg(target_os = "macos")] + result.push_str("cmd-"); + + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + result.push_str("super-"); + + #[cfg(target_os = "windows")] + result.push_str("win-"); + } + if modifiers.shift { + result.push_str("shift-"); + } + result.push_str(&key); + result +} diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 698c9315ce368c..b9d0bad0a26a4d 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -398,7 +398,7 @@ impl KeymapFile { context, use_key_equivalents, action_input_string.map(SharedString::from), - cx.keyboard_mapper(), + cx.keyboard_mapper().as_ref(), ) { Ok(key_binding) => key_binding, Err(InvalidKeystrokeError { keystroke }) => { @@ -600,6 +600,7 @@ impl KeymapFile { mut operation: KeybindUpdateOperation<'a>, mut keymap_contents: String, tab_size: usize, + keyboard_mapper: &dyn gpui::PlatformKeyboardMapper, ) -> Result { match operation { // if trying to replace a keybinding that is not user-defined, treat it as an add operation @@ -639,7 +640,7 @@ impl KeymapFile { .action_value() .context("Failed to generate target action JSON value")?; let Some((index, keystrokes_str)) = - find_binding(&keymap, &target, &target_action_value) + find_binding(&keymap, &target, &target_action_value, keyboard_mapper) else { anyhow::bail!("Failed to find keybinding to remove"); }; @@ -674,7 +675,7 @@ impl KeymapFile { .context("Failed to generate source action JSON value")?; if let Some((index, keystrokes_str)) = - find_binding(&keymap, &target, &target_action_value) + find_binding(&keymap, &target, &target_action_value, keyboard_mapper) { if target.context == source.context { // if we are only changing the keybinding (common case) @@ -774,7 +775,7 @@ impl KeymapFile { } let use_key_equivalents = from.and_then(|from| { let action_value = from.action_value().context("Failed to serialize action value. `use_key_equivalents` on new keybinding may be incorrect.").log_err()?; - let (index, _) = find_binding(&keymap, &from, &action_value)?; + let (index, _) = find_binding(&keymap, &from, &action_value, keyboard_mapper)?; Some(keymap.0[index].use_key_equivalents) }).unwrap_or(false); if use_key_equivalents { @@ -801,6 +802,7 @@ impl KeymapFile { keymap: &'b KeymapFile, target: &KeybindUpdateTarget<'a>, target_action_value: &Value, + keyboard_mapper: &dyn gpui::PlatformKeyboardMapper, ) -> Option<(usize, &'b str)> { let target_context_parsed = KeyBindingContextPredicate::parse(target.context.unwrap_or("")).ok(); @@ -816,8 +818,11 @@ impl KeymapFile { for (keystrokes_str, action) in bindings { let Ok(keystrokes) = keystrokes_str .split_whitespace() - .map(Keystroke::parse) - .collect::, _>>() + .map(|source| { + let keystroke = Keystroke::parse(source)?; + Ok(KeybindingKeystroke::new(keystroke, false, keyboard_mapper)) + }) + .collect::, InvalidKeystrokeError>>() else { continue; }; @@ -825,7 +830,7 @@ impl KeymapFile { || !keystrokes .iter() .zip(target.keystrokes) - .all(|(a, b)| a.should_match(b)) + .all(|(a, b)| a.inner.should_match(b)) { continue; } @@ -840,7 +845,7 @@ impl KeymapFile { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum KeybindUpdateOperation<'a> { Replace { /// Describes the keybind to create @@ -934,7 +939,10 @@ impl<'a> KeybindUpdateTarget<'a> { fn keystrokes_unparsed(&self) -> String { let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8); for keystroke in self.keystrokes { - keystrokes.push_str(&keystroke.inner.unparse()); + // The reason use `keystroke.unparse()` instead of `keystroke.inner.unparse()` + // here is that, we want the user to use `ctrl-shift-4` instread of `ctrl-$` + // by default on Windows. + keystrokes.push_str(&keystroke.unparse()); keystrokes.push(' '); } keystrokes.pop(); @@ -952,7 +960,7 @@ impl<'a> KeybindUpdateTarget<'a> { } } -#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)] pub enum KeybindSource { User, Vim, @@ -1042,8 +1050,13 @@ mod tests { operation: KeybindUpdateOperation, expected: impl ToString, ) { - let result = KeymapFile::update_keybinding(operation, input.to_string(), 4) - .expect("Update succeeded"); + let result = KeymapFile::update_keybinding( + operation, + input.to_string(), + 4, + &gpui::DummyKeyboardMapper, + ) + .expect("Update succeeded"); pretty_assertions::assert_eq!(expected.to_string(), result); } diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 1d719863a11e7e..76c716600768bb 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -14,9 +14,9 @@ use gpui::{ Action, AppContext as _, AsyncApp, Axis, ClickEvent, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global, IsZero, KeyBindingContextPredicate::{And, Descendant, Equal, Identifier, Not, NotEqual, Or}, - KeyContext, KeybindingKeystroke, Keystroke, MouseButton, Point, ScrollStrategy, - ScrollWheelEvent, Stateful, StyledText, Subscription, Task, TextStyleRefinement, WeakEntity, - actions, anchored, deferred, div, + KeyContext, KeybindingKeystroke, Keystroke, MouseButton, PlatformKeyboardMapper, Point, + ScrollStrategy, ScrollWheelEvent, Stateful, StyledText, Subscription, Task, + TextStyleRefinement, WeakEntity, actions, anchored, deferred, div, }; use language::{Language, LanguageConfig, ToOffset as _}; use notifications::status_toast::{StatusToast, ToastIcon}; @@ -1206,8 +1206,11 @@ impl KeymapEditor { .read(cx) .get_scrollbar_offset(Axis::Vertical), )); - cx.spawn(async move |_, _| remove_keybinding(to_remove, &fs, tab_size).await) - .detach_and_notify_err(window, cx); + let keyboard_mapper = cx.keyboard_mapper().clone(); + cx.spawn(async move |_, _| { + remove_keybinding(to_remove, &fs, tab_size, keyboard_mapper.as_ref()).await + }) + .detach_and_notify_err(window, cx); } fn copy_context_to_clipboard( @@ -2320,6 +2323,7 @@ impl KeybindingEditorModal { }).unwrap_or(Ok(()))?; let create = self.creating; + let keyboard_mapper = cx.keyboard_mapper().clone(); cx.spawn(async move |this, cx| { let action_name = existing_keybind.action().name; @@ -2332,6 +2336,7 @@ impl KeybindingEditorModal { new_action_args.as_deref(), &fs, tab_size, + keyboard_mapper.as_ref(), ) .await { @@ -3006,6 +3011,7 @@ async fn save_keybinding_update( new_args: Option<&str>, fs: &Arc, tab_size: usize, + keyboard_mapper: &dyn PlatformKeyboardMapper, ) -> anyhow::Result<()> { let keymap_contents = settings::KeymapFile::load_keymap_file(fs) .await @@ -3048,9 +3054,13 @@ async fn save_keybinding_update( let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry(); - let updated_keymap_contents = - settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size) - .map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?; + let updated_keymap_contents = settings::KeymapFile::update_keybinding( + operation, + keymap_contents, + tab_size, + keyboard_mapper, + ) + .map_err(|err| anyhow::anyhow!("Could not save updated keybinding: {}", err))?; fs.write( paths::keymap_file().as_path(), updated_keymap_contents.as_bytes(), @@ -3071,6 +3081,7 @@ async fn remove_keybinding( existing: ProcessedBinding, fs: &Arc, tab_size: usize, + keyboard_mapper: &dyn PlatformKeyboardMapper, ) -> anyhow::Result<()> { let Some(keystrokes) = existing.keystrokes() else { anyhow::bail!("Cannot remove a keybinding that does not exist"); @@ -3094,9 +3105,13 @@ async fn remove_keybinding( }; let (new_keybinding, removed_keybinding, source) = operation.generate_telemetry(); - let updated_keymap_contents = - settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size) - .context("Failed to update keybinding")?; + let updated_keymap_contents = settings::KeymapFile::update_keybinding( + operation, + keymap_contents, + tab_size, + keyboard_mapper, + ) + .context("Failed to update keybinding")?; fs.write( paths::keymap_file().as_path(), updated_keymap_contents.as_bytes(), diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/settings_ui/src/ui_components/keystroke_input.rs index 391aee3292072b..ca50d5c03dcde3 100644 --- a/crates/settings_ui/src/ui_components/keystroke_input.rs +++ b/crates/settings_ui/src/ui_components/keystroke_input.rs @@ -304,7 +304,7 @@ impl KeystrokeInput { } let mut keystroke = - KeybindingKeystroke::new(keystroke.clone(), false, cx.keyboard_mapper()); + 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()) From a03f7e9b48bbd715f99e496bcccf53b274a8e3e8 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 27 Aug 2025 02:39:20 +0800 Subject: [PATCH 34/35] typo --- crates/settings/src/keymap_file.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index b9d0bad0a26a4d..0e8303c4c17d0b 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -940,7 +940,7 @@ impl<'a> KeybindUpdateTarget<'a> { let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8); for keystroke in self.keystrokes { // The reason use `keystroke.unparse()` instead of `keystroke.inner.unparse()` - // here is that, we want the user to use `ctrl-shift-4` instread of `ctrl-$` + // here is that, we want the user to use `ctrl-shift-4` instead of `ctrl-$` // by default on Windows. keystrokes.push_str(&keystroke.unparse()); keystrokes.push(' '); From 8ea76710db73964c312baca2274c86e370a6f3fa Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 27 Aug 2025 03:03:12 +0800 Subject: [PATCH 35/35] better cfg --- crates/settings/src/settings.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 7ec2d423c6eb82..1966755d626af6 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -87,12 +87,12 @@ pub fn default_settings() -> Cow<'static, str> { #[cfg(target_os = "macos")] pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-macos.json"; -#[cfg(target_os = "linux")] -pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-linux.json"; - #[cfg(target_os = "windows")] pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-windows.json"; +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-linux.json"; + pub fn default_keymap() -> Cow<'static, str> { asset_str::(DEFAULT_KEYMAP_PATH) }