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] 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 a290a132c3b5f9fa42e338c28b86de7ded5b10ac..483f49e8659be2282d2f26d4eea1974f4bcc5b0d 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 b5dbab15c77a0cfff96885e5835f602197e408e6..344334f40a0e2707135c4a7ae4885f2e007c3cff 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 1d3f612c5bef76d75cb1bd8ee9d9c686190c3fd7..12ce69c927752d7caebafc7903af241eee1ad94b 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 44d861b9a5d19f88543e9f1d9aefa58a901727f9..485a5545d48a943a8f37eb39afe002e28c9b439d 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 8c01b8afcfd2b7948cabe925550008590b3c3576..ac5a344242d8e0efcb1290f08c297b8fb1b5d625 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 19bc58ea2342dae25be0bebfcab600771594989c..df8a0b0cefb723578782a7b596b5f3f863d8d15c 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 1d91492f26c7e9e93a761a1d9d46b06300ba3614..8812711cb21fbc1ec8d72afaa4906a6d52ecc8c2 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),