From ae625acad60262a7cd4192d7bf4a92e0a4704b97 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 9 Jul 2025 17:08:16 +0800 Subject: [PATCH 1/5] init --- crates/gpui/src/platform/keystroke.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 40387a8202..34fda3a859 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -5,6 +5,16 @@ use std::{ fmt::{Display, Write}, }; +/// TODO: +pub struct KeybindKeystroke { + /// 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 1e4d953fa312eeb9346eb1e28b942185ad632f53 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 9 Jul 2025 17:14:39 +0800 Subject: [PATCH 2/5] parse `KeybindKeystroke` --- crates/gpui/src/platform/keystroke.rs | 126 ++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 10 deletions(-) diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 34fda3a859..44d861b9a5 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -5,16 +5,6 @@ use std::{ fmt::{Display, Write}, }; -/// TODO: -pub struct KeybindKeystroke { - /// 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 { @@ -31,6 +21,16 @@ pub struct Keystroke { pub key_char: Option, } +/// TODO: +pub struct KeybindKeystroke { + /// 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)] @@ -273,6 +273,112 @@ impl Keystroke { } } +impl KeybindKeystroke { + /// TODO: + pub fn parse(source: &str) -> std::result::Result { + let keystroke = Keystroke::parse(source)?; + let Keystroke { + mut modifiers, key, .. + } = keystroke.clone(); + let (key, modifiers) = temp_keyboard_mapper(key, modifiers); + Ok(KeybindKeystroke { + inner: keystroke, + modifiers, + key, + }) + } +} + +fn temp_keyboard_mapper(key: String, mut modifiers: Modifiers) -> (String, Modifiers) { + match key.as_str() { + "~" => { + 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, modifiers), + } +} + fn is_printable_key(key: &str) -> bool { !matches!( key, From fa1632bc09a7914c5885f967c0b347215091fe5c Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 9 Jul 2025 19:03:17 +0800 Subject: [PATCH 3/5] wip --- crates/gpui/src/key_dispatch.rs | 7 +-- crates/gpui/src/keymap.rs | 31 ++++++++++- crates/gpui/src/keymap/binding.rs | 35 +++++++++--- crates/gpui/src/platform/keystroke.rs | 25 ++++++--- crates/gpui/src/window.rs | 2 +- crates/settings/src/keymap_file.rs | 7 +-- crates/ui/src/components/keybinding.rs | 75 +++++++++++++++++--------- 7 files changed, 133 insertions(+), 49 deletions(-) diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index a290a132c3..483f49e865 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -51,7 +51,7 @@ /// use crate::{ Action, ActionRegistry, App, BindingIndex, DispatchPhase, EntityId, FocusId, KeyBinding, - KeyContext, Keymap, Keystroke, ModifiersChangedEvent, Window, + KeyContext, KeybindingKeystroke, Keymap, Keystroke, ModifiersChangedEvent, Window, }; use collections::FxHashMap; use smallvec::SmallVec; @@ -444,10 +444,11 @@ impl DispatchTree { fn binding_matches_predicate_and_not_shadowed( keymap: &Keymap, binding_index: BindingIndex, - keystrokes: &[Keystroke], + keystrokes: &[KeybindingKeystroke], context_stack: &[KeyContext], ) -> bool { - let (bindings, _) = keymap.bindings_for_input_with_indices(&keystrokes, context_stack); + let (bindings, _) = + keymap.bindings_for_keybinding_keystroke_with_indices(&keystrokes, context_stack); if let Some((highest_precedence_index, _)) = bindings.iter().next() { binding_index == *highest_precedence_index } else { diff --git a/crates/gpui/src/keymap.rs b/crates/gpui/src/keymap.rs index b5dbab15c7..344334f40a 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, KeybindingKeystroke, Keystroke, is_no_action}; use collections::HashMap; use smallvec::SmallVec; use std::any::TypeId; @@ -177,10 +177,37 @@ impl Keymap { .map(|pending| (BindingIndex(ix), binding, pending)) }); + self.bindings_for_keystrokes_with_indices_inner(possibilities, context_stack) + } + + /// TODO: + pub fn bindings_for_keybinding_keystroke_with_indices( + &self, + input: &[KeybindingKeystroke], + context_stack: &[KeyContext], + ) -> (SmallVec<[(BindingIndex, KeyBinding); 1]>, bool) { + let possibilities = self + .bindings() + .enumerate() + .rev() + .filter_map(|(ix, binding)| { + binding + .match_keybinding_keystrokes(input) + .map(|pending| (BindingIndex(ix), binding, pending)) + }); + + self.bindings_for_keystrokes_with_indices_inner(possibilities, context_stack) + } + + fn bindings_for_keystrokes_with_indices_inner<'a>( + &'a self, + possibilities: impl Iterator, + context_stack: &[KeyContext], + ) -> (SmallVec<[(BindingIndex, KeyBinding); 1]>, bool) { let mut bindings: SmallVec<[(BindingIndex, KeyBinding, usize); 1]> = SmallVec::new(); // (pending, is_no_action, depth, keystrokes) - let mut pending_info_opt: Option<(bool, bool, usize, &[Keystroke])> = None; + let mut pending_info_opt: Option<(bool, bool, usize, &[KeybindingKeystroke])> = None; 'outer: for (binding_index, binding, pending) in possibilities { for depth in (0..=context_stack.len()).rev() { diff --git a/crates/gpui/src/keymap/binding.rs b/crates/gpui/src/keymap/binding.rs index 1d3f612c5b..12ce69c927 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, 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 @@ -46,16 +49,17 @@ impl KeyBinding { key_equivalents: Option<&HashMap>, action_input: Option, ) -> std::result::Result { - let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes + let mut keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes .split_whitespace() - .map(Keystroke::parse) + .map(KeybindingKeystroke::parse) .collect::>()?; if let Some(equivalents) = key_equivalents { for keystroke in keystrokes.iter_mut() { - if keystroke.key.chars().count() == 1 { - if let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap()) { - keystroke.key = key.to_string(); + if keystroke.inner.key.chars().count() == 1 { + if let Some(key) = equivalents.get(&keystroke.inner.key.chars().next().unwrap()) + { + keystroke.inner.key = key.to_string(); } } } @@ -96,8 +100,23 @@ impl KeyBinding { Some(self.keystrokes.len() > typed.len()) } + /// TODO: + pub fn match_keybinding_keystrokes(&self, typed: &[KeybindingKeystroke]) -> Option { + if self.keystrokes.len() < typed.len() { + return None; + } + + for (target, typed) in self.keystrokes.iter().zip(typed.iter()) { + if !typed.inner.should_match(target) { + return None; + } + } + + Some(self.keystrokes.len() > typed.len()) + } + /// 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 44d861b9a5..485a5545d4 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -22,7 +22,8 @@ pub struct Keystroke { } /// TODO: -pub struct KeybindKeystroke { +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct KeybindingKeystroke { /// TODO: pub inner: Keystroke, /// TODO: @@ -65,7 +66,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 @@ -78,7 +79,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; } } @@ -90,12 +91,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: @@ -273,7 +274,7 @@ impl Keystroke { } } -impl KeybindKeystroke { +impl KeybindingKeystroke { /// TODO: pub fn parse(source: &str) -> std::result::Result { let keystroke = Keystroke::parse(source)?; @@ -281,12 +282,22 @@ impl KeybindKeystroke { mut modifiers, key, .. } = keystroke.clone(); let (key, modifiers) = temp_keyboard_mapper(key, modifiers); - Ok(KeybindKeystroke { + Ok(KeybindingKeystroke { inner: keystroke, modifiers, key, }) } + + /// TODO: + pub fn to_string(&self) -> String { + let keystroke = Keystroke { + modifiers: self.modifiers, + key: self.key.clone(), + key_char: None, + }; + keystroke.to_string() + } } fn temp_keyboard_mapper(key: String, mut modifiers: Modifiers) -> (String, Modifiers) { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 8c01b8afcf..ac5a344242 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3316,7 +3316,7 @@ impl Window { binding .keystrokes() .iter() - .map(ToString::to_string) + .map(|ks| ks.to_string()) .collect::>() .join(" ") }) diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index 19bc58ea23..df8a0b0cef 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; @@ -787,7 +788,7 @@ pub enum KeybindUpdateOperation<'a> { pub struct KeybindUpdateTarget<'a> { pub context: Option<&'a str>, - pub keystrokes: &'a [Keystroke], + pub keystrokes: &'a [KeybindingKeystroke], pub action_name: &'a str, pub use_key_equivalents: bool, pub input: Option<&'a str>, @@ -810,7 +811,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(); diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 1d91492f26..8812711cb2 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, Keystroke, + 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() } } @@ -149,7 +149,7 @@ impl RenderOnce for KeyBinding { } pub fn render_keystroke( - keystroke: &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,44 @@ 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 { let platform_style = PlatformStyle::platform(); let vim_enabled = cx.try_global::().is_some(); - keystroke_text(keystroke, platform_style, vim_enabled) + keystroke_text( + &keystroke.modifiers, + &keystroke.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 +436,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 +446,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 +459,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 +469,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 +478,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), From 1610d05526bb57bc44835a0aa370cee8ebe7b687 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 9 Jul 2025 19:29:32 +0800 Subject: [PATCH 4/5] checkpoint --- crates/editor/src/editor.rs | 2 +- crates/editor/src/element.rs | 14 +++++----- crates/gpui/src/platform/keystroke.rs | 10 +++++++ crates/settings_ui/src/keybindings.rs | 27 ++++++++++++------- crates/ui/src/components/keybinding.rs | 37 +++++++++++++++++--------- crates/vim/src/mode_indicator.rs | 2 ++ 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index c5fe0db74c..7ffd0af761 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9000,7 +9000,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 8a5bfb3bab..4e19d8b11c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -42,12 +42,12 @@ use gpui::{ Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle, Bounds, ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, - HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, Keystroke, Length, - ModifiersChangedEvent, MouseButton, 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, + HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, KeybindingKeystroke, Keystroke, + Length, ModifiersChangedEvent, MouseButton, 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, }; use itertools::Itertools; use language::language_settings::{ @@ -6994,7 +6994,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/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 485a5545d4..30e065bb0c 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -272,6 +272,16 @@ impl Keystroke { } self } + + /// TODO: + pub fn into_keybinding_keystroke(self) -> KeybindingKeystroke { + let (key, modifiers) = temp_keyboard_mapper(self.key.clone(), self.modifiers); + KeybindingKeystroke { + inner: self, + modifiers, + key, + } + } } impl KeybindingKeystroke { diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 951dd77541..253f97d53e 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -11,8 +11,8 @@ use fs::Fs; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, - Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText, Subscription, - WeakEntity, actions, div, transparent_black, + Global, KeyContext, KeybindingKeystroke, Keystroke, ModifiersChangedEvent, ScrollStrategy, + StyledText, Subscription, WeakEntity, actions, div, transparent_black, }; use language::{Language, LanguageConfig, ToOffset as _}; use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets}; @@ -274,7 +274,7 @@ impl KeymapEditor { for key_binding in key_bindings { let source = key_binding.meta().map(settings::KeybindSource::from_meta); - 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 = Some( ui::KeyBinding::new_from_gpui(key_binding.clone(), cx) .vim_mode(source == Some(settings::KeybindSource::Vim)), @@ -1143,7 +1143,7 @@ async fn load_rust_language(workspace: WeakEntity, cx: &mut AsyncApp) async fn save_keybinding_update( existing: ProcessedKeybinding, - new_keystrokes: &[Keystroke], + new_keystrokes: &[KeybindingKeystroke], new_context: Option<&str>, fs: &Arc, tab_size: usize, @@ -1280,15 +1280,21 @@ impl KeystrokeInput { cx.notify(); } - fn keystrokes(&self) -> &[Keystroke] { - if self + fn keystrokes(&self) -> Vec { + let keystrokes = if self .keystrokes .last() .map_or(false, |last| last.key.is_empty()) { - return &self.keystrokes[..self.keystrokes.len() - 1]; - } - return &self.keystrokes; + &self.keystrokes[..self.keystrokes.len() - 1] + } else { + &self.keystrokes + }; + keystrokes + .to_vec() + .into_iter() + .map(|keystroke| keystroke.into_keybinding_keystroke()) + .collect() } } @@ -1332,7 +1338,8 @@ impl Render for KeystrokeInput { .gap(ui::DynamicSpacing::Base04.rems(cx)) .children(self.keystrokes.iter().map(|keystroke| { h_flex().children(ui::render_keystroke( - keystroke, + &keystroke.modifiers, + &keystroke.key, None, Some(rems(0.875).into()), ui::PlatformStyle::platform(), diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 8812711cb2..898c7e5cbb 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -138,7 +138,8 @@ impl RenderOnce for KeyBinding { .rounded_xs() .text_color(cx.theme().colors().text_muted) .children(render_keystroke( - keystroke, + &keystroke.modifiers, + &keystroke.key, color, self.size, self.platform_style, @@ -149,7 +150,8 @@ impl RenderOnce for KeyBinding { } pub fn render_keystroke( - keystroke: &KeybindingKeystroke, + modifiers: &Modifiers, + key: &str, color: Option, size: impl Into>, platform_style: PlatformStyle, @@ -164,12 +166,7 @@ pub fn render_keystroke( if use_text { let element = Key::new( - keystroke_text( - &keystroke.modifiers, - &keystroke.key, - platform_style, - vim_mode, - ), + keystroke_text(modifiers, key, platform_style, vim_mode), color, ) .size(size) @@ -178,13 +175,13 @@ pub fn render_keystroke( } else { let mut elements = Vec::new(); elements.extend(render_modifiers( - &keystroke.modifiers, + modifiers, platform_style, color, size, true, )); - elements.push(render_key(&keystroke.key, color, platform_style, size)); + elements.push(render_key(key, color, platform_style, size)); elements } } @@ -387,10 +384,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_keybinding_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.modifiers, + &keystroke.key, + platform_style, + vim_enabled, + ) + }) + .join(" ") +} + +pub fn text_for_keystrokes(keystrokes: &[Keystroke], cx: &App) -> String { let platform_style = PlatformStyle::platform(); let vim_enabled = cx.try_global::().is_some(); keystrokes diff --git a/crates/vim/src/mode_indicator.rs b/crates/vim/src/mode_indicator.rs index d54b270074..d12b4a71bb 100644 --- a/crates/vim/src/mode_indicator.rs +++ b/crates/vim/src/mode_indicator.rs @@ -50,6 +50,8 @@ impl ModeIndicator { } } + // TODO: + // what's this? fn update_pending_keys(&mut self, window: &mut Window, cx: &App) { self.pending_keys = window .pending_input_keystrokes() From 0c96bcd57236b08035de9d735bcdfac8a25a33ba Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 9 Jul 2025 19:37:46 +0800 Subject: [PATCH 5/5] fix --- crates/editor/src/element.rs | 12 ++++----- crates/settings_ui/src/keybindings.rs | 39 +++++++++++++++------------ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4e19d8b11c..6581c3bc01 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -42,12 +42,12 @@ use gpui::{ Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle, Bounds, ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, - HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, KeybindingKeystroke, Keystroke, - Length, ModifiersChangedEvent, MouseButton, 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, + HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, KeybindingKeystroke, Length, + ModifiersChangedEvent, MouseButton, 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, }; use itertools::Itertools; use language::language_settings::{ diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 253f97d53e..56b55b9416 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -1202,7 +1202,7 @@ async fn save_keybinding_update( } struct KeystrokeInput { - keystrokes: Vec, + keystrokes: Vec, focus_handle: FocusHandle, } @@ -1230,10 +1230,14 @@ impl KeystrokeInput { last.modifiers = event.modifiers; } } else { - self.keystrokes.push(Keystroke { + self.keystrokes.push(KeybindingKeystroke { + inner: Keystroke { + modifiers: event.modifiers, + key: "".to_string(), + key_char: None, + }, modifiers: event.modifiers, key: "".to_string(), - key_char: None, }); } cx.stop_propagation(); @@ -1249,12 +1253,13 @@ impl KeystrokeInput { if event.is_held { return; } + let keystroke = event.keystroke.clone().into_keybinding_keystroke(); if let Some(last) = self.keystrokes.last_mut() && last.key.is_empty() { - *last = event.keystroke.clone(); + *last = keystroke; } else { - self.keystrokes.push(event.keystroke.clone()); + self.keystrokes.push(keystroke); } cx.stop_propagation(); cx.notify(); @@ -1266,22 +1271,27 @@ impl KeystrokeInput { _window: &mut Window, cx: &mut Context, ) { + let keystroke = event.keystroke.clone().into_keybinding_keystroke(); if let Some(last) = self.keystrokes.last_mut() && !last.key.is_empty() - && last.modifiers == event.keystroke.modifiers + && last.modifiers == keystroke.modifiers { - self.keystrokes.push(Keystroke { - modifiers: event.keystroke.modifiers, + self.keystrokes.push(KeybindingKeystroke { + inner: Keystroke { + modifiers: event.keystroke.modifiers, + key: "".to_string(), + key_char: None, + }, + modifiers: keystroke.modifiers, key: "".to_string(), - key_char: None, }); } cx.stop_propagation(); cx.notify(); } - fn keystrokes(&self) -> Vec { - let keystrokes = if self + fn keystrokes(&self) -> &[KeybindingKeystroke] { + if self .keystrokes .last() .map_or(false, |last| last.key.is_empty()) @@ -1289,12 +1299,7 @@ impl KeystrokeInput { &self.keystrokes[..self.keystrokes.len() - 1] } else { &self.keystrokes - }; - keystrokes - .to_vec() - .into_iter() - .map(|keystroke| keystroke.into_keybinding_keystroke()) - .collect() + } } }