Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Junkui Zhang
0c96bcd572 fix 2025-07-09 19:37:46 +08:00
Junkui Zhang
1610d05526 checkpoint 2025-07-09 19:29:32 +08:00
Junkui Zhang
fa1632bc09 wip 2025-07-09 19:03:17 +08:00
Junkui Zhang
1e4d953fa3 parse KeybindKeystroke 2025-07-09 17:14:39 +08:00
Junkui Zhang
ae625acad6 init 2025-07-09 17:08:16 +08:00
11 changed files with 305 additions and 68 deletions

View file

@ -9000,7 +9000,7 @@ impl Editor {
max_width: Pixels, max_width: Pixels,
cursor_point: Point, cursor_point: Point,
style: &EditorStyle, style: &EditorStyle,
accept_keystroke: Option<&gpui::Keystroke>, accept_keystroke: Option<&gpui::KeybindingKeystroke>,
_window: &Window, _window: &Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) -> Option<AnyElement> { ) -> Option<AnyElement> {

View file

@ -42,7 +42,7 @@ use gpui::{
Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle, Action, Along, AnyElement, App, AppContext, AvailableSpace, Axis as ScrollbarAxis, BorderStyle,
Bounds, ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges, Bounds, ClickEvent, ContentMask, Context, Corner, Corners, CursorStyle, DispatchPhase, Edges,
Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox, Element, ElementInputHandler, Entity, Focusable as _, FontId, GlobalElementId, Hitbox,
HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, Keystroke, Length, HitboxBehavior, Hsla, InteractiveElement, IntoElement, IsZero, KeybindingKeystroke, Length,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad,
ParentElement, Pixels, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString, ParentElement, Pixels, ScrollDelta, ScrollHandle, ScrollWheelEvent, ShapedLine, SharedString,
Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, WeakEntity, Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, WeakEntity,
@ -6994,7 +6994,7 @@ fn header_jump_data(
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>); pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
impl AcceptEditPredictionBinding { impl AcceptEditPredictionBinding {
pub fn keystroke(&self) -> Option<&Keystroke> { pub fn keystroke(&self) -> Option<&KeybindingKeystroke> {
if let Some(binding) = self.0.as_ref() { if let Some(binding) = self.0.as_ref() {
match &binding.keystrokes() { match &binding.keystrokes() {
[keystroke, ..] => Some(keystroke), [keystroke, ..] => Some(keystroke),

View file

@ -51,7 +51,7 @@
/// ///
use crate::{ use crate::{
Action, ActionRegistry, App, BindingIndex, DispatchPhase, EntityId, FocusId, KeyBinding, Action, ActionRegistry, App, BindingIndex, DispatchPhase, EntityId, FocusId, KeyBinding,
KeyContext, Keymap, Keystroke, ModifiersChangedEvent, Window, KeyContext, KeybindingKeystroke, Keymap, Keystroke, ModifiersChangedEvent, Window,
}; };
use collections::FxHashMap; use collections::FxHashMap;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -444,10 +444,11 @@ impl DispatchTree {
fn binding_matches_predicate_and_not_shadowed( fn binding_matches_predicate_and_not_shadowed(
keymap: &Keymap, keymap: &Keymap,
binding_index: BindingIndex, binding_index: BindingIndex,
keystrokes: &[Keystroke], keystrokes: &[KeybindingKeystroke],
context_stack: &[KeyContext], context_stack: &[KeyContext],
) -> bool { ) -> 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() { if let Some((highest_precedence_index, _)) = bindings.iter().next() {
binding_index == *highest_precedence_index binding_index == *highest_precedence_index
} else { } else {

View file

@ -4,7 +4,7 @@ mod context;
pub use binding::*; pub use binding::*;
pub use context::*; pub use context::*;
use crate::{Action, Keystroke, is_no_action}; use crate::{Action, KeybindingKeystroke, Keystroke, is_no_action};
use collections::HashMap; use collections::HashMap;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::any::TypeId; use std::any::TypeId;
@ -177,10 +177,37 @@ impl Keymap {
.map(|pending| (BindingIndex(ix), binding, pending)) .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<Item = (BindingIndex, &'a KeyBinding, bool)>,
context_stack: &[KeyContext],
) -> (SmallVec<[(BindingIndex, KeyBinding); 1]>, bool) {
let mut bindings: SmallVec<[(BindingIndex, KeyBinding, usize); 1]> = SmallVec::new(); let mut bindings: SmallVec<[(BindingIndex, KeyBinding, usize); 1]> = SmallVec::new();
// (pending, is_no_action, depth, keystrokes) // (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 { 'outer: for (binding_index, binding, pending) in possibilities {
for depth in (0..=context_stack.len()).rev() { for depth in (0..=context_stack.len()).rev() {

View file

@ -2,13 +2,16 @@ use std::rc::Rc;
use collections::HashMap; use collections::HashMap;
use crate::{Action, InvalidKeystrokeError, KeyBindingContextPredicate, Keystroke, SharedString}; use crate::{
Action, InvalidKeystrokeError, KeyBindingContextPredicate, KeybindingKeystroke, Keystroke,
SharedString,
};
use smallvec::SmallVec; use smallvec::SmallVec;
/// A keybinding and its associated metadata, from the keymap. /// A keybinding and its associated metadata, from the keymap.
pub struct KeyBinding { pub struct KeyBinding {
pub(crate) action: Box<dyn Action>, pub(crate) action: Box<dyn Action>,
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>, pub(crate) keystrokes: SmallVec<[KeybindingKeystroke; 2]>,
pub(crate) context_predicate: Option<Rc<KeyBindingContextPredicate>>, pub(crate) context_predicate: Option<Rc<KeyBindingContextPredicate>>,
pub(crate) meta: Option<KeyBindingMetaIndex>, pub(crate) meta: Option<KeyBindingMetaIndex>,
/// The json input string used when building the keybinding, if any /// The json input string used when building the keybinding, if any
@ -46,16 +49,17 @@ impl KeyBinding {
key_equivalents: Option<&HashMap<char, char>>, key_equivalents: Option<&HashMap<char, char>>,
action_input: Option<SharedString>, action_input: Option<SharedString>,
) -> std::result::Result<Self, InvalidKeystrokeError> { ) -> std::result::Result<Self, InvalidKeystrokeError> {
let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes let mut keystrokes: SmallVec<[KeybindingKeystroke; 2]> = keystrokes
.split_whitespace() .split_whitespace()
.map(Keystroke::parse) .map(KeybindingKeystroke::parse)
.collect::<std::result::Result<_, _>>()?; .collect::<std::result::Result<_, _>>()?;
if let Some(equivalents) = key_equivalents { if let Some(equivalents) = key_equivalents {
for keystroke in keystrokes.iter_mut() { for keystroke in keystrokes.iter_mut() {
if keystroke.key.chars().count() == 1 { if keystroke.inner.key.chars().count() == 1 {
if let Some(key) = equivalents.get(&keystroke.key.chars().next().unwrap()) { if let Some(key) = equivalents.get(&keystroke.inner.key.chars().next().unwrap())
keystroke.key = key.to_string(); {
keystroke.inner.key = key.to_string();
} }
} }
} }
@ -96,8 +100,23 @@ impl KeyBinding {
Some(self.keystrokes.len() > typed.len()) Some(self.keystrokes.len() > typed.len())
} }
/// TODO:
pub fn match_keybinding_keystrokes(&self, typed: &[KeybindingKeystroke]) -> Option<bool> {
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 /// Get the keystrokes associated with this binding
pub fn keystrokes(&self) -> &[Keystroke] { pub fn keystrokes(&self) -> &[KeybindingKeystroke] {
self.keystrokes.as_slice() self.keystrokes.as_slice()
} }

View file

@ -21,6 +21,17 @@ pub struct Keystroke {
pub key_char: Option<String>, pub key_char: Option<String>,
} }
/// TODO:
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct KeybindingKeystroke {
/// 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 /// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use
/// markdown to display it. /// markdown to display it.
#[derive(Debug)] #[derive(Debug)]
@ -55,7 +66,7 @@ impl Keystroke {
/// ///
/// This method assumes that `self` was typed and `target' is in the keymap, and checks /// This method assumes that `self` was typed and `target' is in the keymap, and checks
/// both possibilities for self against the target. /// 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"))] #[cfg(not(target_os = "windows"))]
if let Some(key_char) = self if let Some(key_char) = self
.key_char .key_char
@ -68,7 +79,7 @@ impl Keystroke {
..Default::default() ..Default::default()
}; };
if &target.key == key_char && target.modifiers == ime_modifiers { if &target.inner.key == key_char && target.inner.modifiers == ime_modifiers {
return true; return true;
} }
} }
@ -80,12 +91,12 @@ impl Keystroke {
.filter(|key_char| key_char != &&self.key) .filter(|key_char| key_char != &&self.key)
{ {
// On Windows, if key_char is set, then the typed keystroke produced the key_char // 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; return true;
} }
} }
target.modifiers == self.modifiers && target.key == self.key target.inner.modifiers == self.modifiers && target.inner.key == self.key
} }
/// key syntax is: /// key syntax is:
@ -261,6 +272,132 @@ impl Keystroke {
} }
self 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 {
/// TODO:
pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> {
let keystroke = Keystroke::parse(source)?;
let Keystroke {
mut modifiers, key, ..
} = keystroke.clone();
let (key, modifiers) = temp_keyboard_mapper(key, modifiers);
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) {
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 { fn is_printable_key(key: &str) -> bool {

View file

@ -3316,7 +3316,7 @@ impl Window {
binding binding
.keystrokes() .keystrokes()
.iter() .iter()
.map(ToString::to_string) .map(|ks| ks.to_string())
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(" ") .join(" ")
}) })

View file

@ -3,7 +3,8 @@ use collections::{BTreeMap, HashMap, IndexMap};
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
Action, ActionBuildError, App, InvalidKeystrokeError, KEYSTROKE_PARSE_EXPECTED_MESSAGE, 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 schemars::{JsonSchema, json_schema};
use serde::Deserialize; use serde::Deserialize;
@ -787,7 +788,7 @@ pub enum KeybindUpdateOperation<'a> {
pub struct KeybindUpdateTarget<'a> { pub struct KeybindUpdateTarget<'a> {
pub context: Option<&'a str>, pub context: Option<&'a str>,
pub keystrokes: &'a [Keystroke], pub keystrokes: &'a [KeybindingKeystroke],
pub action_name: &'a str, pub action_name: &'a str,
pub use_key_equivalents: bool, pub use_key_equivalents: bool,
pub input: Option<&'a str>, pub input: Option<&'a str>,
@ -810,7 +811,7 @@ impl<'a> KeybindUpdateTarget<'a> {
fn keystrokes_unparsed(&self) -> String { fn keystrokes_unparsed(&self) -> String {
let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8); let mut keystrokes = String::with_capacity(self.keystrokes.len() * 8);
for keystroke in self.keystrokes { for keystroke in self.keystrokes {
keystrokes.push_str(&keystroke.unparse()); keystrokes.push_str(&keystroke.inner.unparse());
keystrokes.push(' '); keystrokes.push(' ');
} }
keystrokes.pop(); keystrokes.pop();

View file

@ -11,8 +11,8 @@ use fs::Fs;
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, AppContext as _, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, StyledText, Subscription, Global, KeyContext, KeybindingKeystroke, Keystroke, ModifiersChangedEvent, ScrollStrategy,
WeakEntity, actions, div, transparent_black, StyledText, Subscription, WeakEntity, actions, div, transparent_black,
}; };
use language::{Language, LanguageConfig, ToOffset as _}; use language::{Language, LanguageConfig, ToOffset as _};
use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets}; use settings::{BaseKeymap, KeybindSource, KeymapFile, SettingsAssets};
@ -274,7 +274,7 @@ impl KeymapEditor {
for key_binding in key_bindings { for key_binding in key_bindings {
let source = key_binding.meta().map(settings::KeybindSource::from_meta); 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( let ui_key_binding = Some(
ui::KeyBinding::new_from_gpui(key_binding.clone(), cx) ui::KeyBinding::new_from_gpui(key_binding.clone(), cx)
.vim_mode(source == Some(settings::KeybindSource::Vim)), .vim_mode(source == Some(settings::KeybindSource::Vim)),
@ -1143,7 +1143,7 @@ async fn load_rust_language(workspace: WeakEntity<Workspace>, cx: &mut AsyncApp)
async fn save_keybinding_update( async fn save_keybinding_update(
existing: ProcessedKeybinding, existing: ProcessedKeybinding,
new_keystrokes: &[Keystroke], new_keystrokes: &[KeybindingKeystroke],
new_context: Option<&str>, new_context: Option<&str>,
fs: &Arc<dyn Fs>, fs: &Arc<dyn Fs>,
tab_size: usize, tab_size: usize,
@ -1202,7 +1202,7 @@ async fn save_keybinding_update(
} }
struct KeystrokeInput { struct KeystrokeInput {
keystrokes: Vec<Keystroke>, keystrokes: Vec<KeybindingKeystroke>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }
@ -1230,10 +1230,14 @@ impl KeystrokeInput {
last.modifiers = event.modifiers; last.modifiers = event.modifiers;
} }
} else { } else {
self.keystrokes.push(Keystroke { self.keystrokes.push(KeybindingKeystroke {
inner: Keystroke {
modifiers: event.modifiers,
key: "".to_string(),
key_char: None,
},
modifiers: event.modifiers, modifiers: event.modifiers,
key: "".to_string(), key: "".to_string(),
key_char: None,
}); });
} }
cx.stop_propagation(); cx.stop_propagation();
@ -1249,12 +1253,13 @@ impl KeystrokeInput {
if event.is_held { if event.is_held {
return; return;
} }
let keystroke = event.keystroke.clone().into_keybinding_keystroke();
if let Some(last) = self.keystrokes.last_mut() if let Some(last) = self.keystrokes.last_mut()
&& last.key.is_empty() && last.key.is_empty()
{ {
*last = event.keystroke.clone(); *last = keystroke;
} else { } else {
self.keystrokes.push(event.keystroke.clone()); self.keystrokes.push(keystroke);
} }
cx.stop_propagation(); cx.stop_propagation();
cx.notify(); cx.notify();
@ -1266,29 +1271,35 @@ impl KeystrokeInput {
_window: &mut Window, _window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let keystroke = event.keystroke.clone().into_keybinding_keystroke();
if let Some(last) = self.keystrokes.last_mut() if let Some(last) = self.keystrokes.last_mut()
&& !last.key.is_empty() && !last.key.is_empty()
&& last.modifiers == event.keystroke.modifiers && last.modifiers == keystroke.modifiers
{ {
self.keystrokes.push(Keystroke { self.keystrokes.push(KeybindingKeystroke {
modifiers: event.keystroke.modifiers, inner: Keystroke {
modifiers: event.keystroke.modifiers,
key: "".to_string(),
key_char: None,
},
modifiers: keystroke.modifiers,
key: "".to_string(), key: "".to_string(),
key_char: None,
}); });
} }
cx.stop_propagation(); cx.stop_propagation();
cx.notify(); cx.notify();
} }
fn keystrokes(&self) -> &[Keystroke] { fn keystrokes(&self) -> &[KeybindingKeystroke] {
if self if self
.keystrokes .keystrokes
.last() .last()
.map_or(false, |last| last.key.is_empty()) .map_or(false, |last| last.key.is_empty())
{ {
return &self.keystrokes[..self.keystrokes.len() - 1]; &self.keystrokes[..self.keystrokes.len() - 1]
} else {
&self.keystrokes
} }
return &self.keystrokes;
} }
} }
@ -1332,7 +1343,8 @@ impl Render for KeystrokeInput {
.gap(ui::DynamicSpacing::Base04.rems(cx)) .gap(ui::DynamicSpacing::Base04.rems(cx))
.children(self.keystrokes.iter().map(|keystroke| { .children(self.keystrokes.iter().map(|keystroke| {
h_flex().children(ui::render_keystroke( h_flex().children(ui::render_keystroke(
keystroke, &keystroke.modifiers,
&keystroke.key,
None, None,
Some(rems(0.875).into()), Some(rems(0.875).into()),
ui::PlatformStyle::platform(), ui::PlatformStyle::platform(),

View file

@ -1,8 +1,8 @@
use crate::PlatformStyle; use crate::PlatformStyle;
use crate::{Icon, IconName, IconSize, h_flex, prelude::*}; use crate::{Icon, IconName, IconSize, h_flex, prelude::*};
use gpui::{ use gpui::{
Action, AnyElement, App, FocusHandle, Global, IntoElement, Keystroke, Modifiers, Window, Action, AnyElement, App, FocusHandle, Global, IntoElement, KeybindingKeystroke, Keystroke,
relative, Modifiers, Window, relative,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -13,7 +13,7 @@ pub struct KeyBinding {
/// More than one keystroke produces a chord. /// More than one keystroke produces a chord.
/// ///
/// This should always contain at least one keystroke. /// This should always contain at least one keystroke.
pub keystrokes: Vec<Keystroke>, pub keystrokes: Vec<KeybindingKeystroke>,
/// The [`PlatformStyle`] to use when displaying this keybinding. /// The [`PlatformStyle`] to use when displaying this keybinding.
platform_style: PlatformStyle, platform_style: PlatformStyle,
@ -59,7 +59,7 @@ impl KeyBinding {
cx.try_global::<VimStyle>().is_some_and(|g| g.0) cx.try_global::<VimStyle>().is_some_and(|g| g.0)
} }
pub fn new(keystrokes: Vec<Keystroke>, cx: &App) -> Self { pub fn new(keystrokes: Vec<KeybindingKeystroke>, cx: &App) -> Self {
Self { Self {
keystrokes, keystrokes,
platform_style: PlatformStyle::platform(), platform_style: PlatformStyle::platform(),
@ -99,16 +99,16 @@ impl KeyBinding {
} }
fn render_key( fn render_key(
keystroke: &Keystroke, key: &str,
color: Option<Color>, color: Option<Color>,
platform_style: PlatformStyle, platform_style: PlatformStyle,
size: impl Into<Option<AbsoluteLength>>, size: impl Into<Option<AbsoluteLength>>,
) -> AnyElement { ) -> AnyElement {
let key_icon = icon_for_key(keystroke, platform_style); let key_icon = icon_for_key(key, platform_style);
match key_icon { match key_icon {
Some(icon) => KeyIcon::new(icon, color).size(size).into_any_element(), Some(icon) => KeyIcon::new(icon, color).size(size).into_any_element(),
None => { None => {
let key = util::capitalize(&keystroke.key); let key = util::capitalize(key);
Key::new(&key, color).size(size).into_any_element() Key::new(&key, color).size(size).into_any_element()
} }
} }
@ -138,7 +138,8 @@ impl RenderOnce for KeyBinding {
.rounded_xs() .rounded_xs()
.text_color(cx.theme().colors().text_muted) .text_color(cx.theme().colors().text_muted)
.children(render_keystroke( .children(render_keystroke(
keystroke, &keystroke.modifiers,
&keystroke.key,
color, color,
self.size, self.size,
self.platform_style, self.platform_style,
@ -149,7 +150,8 @@ impl RenderOnce for KeyBinding {
} }
pub fn render_keystroke( pub fn render_keystroke(
keystroke: &Keystroke, modifiers: &Modifiers,
key: &str,
color: Option<Color>, color: Option<Color>,
size: impl Into<Option<AbsoluteLength>>, size: impl Into<Option<AbsoluteLength>>,
platform_style: PlatformStyle, platform_style: PlatformStyle,
@ -163,26 +165,29 @@ pub fn render_keystroke(
let size = size.into(); let size = size.into();
if use_text { if use_text {
let element = Key::new(keystroke_text(&keystroke, platform_style, vim_mode), color) let element = Key::new(
.size(size) keystroke_text(modifiers, key, platform_style, vim_mode),
.into_any_element(); color,
)
.size(size)
.into_any_element();
vec![element] vec![element]
} else { } else {
let mut elements = Vec::new(); let mut elements = Vec::new();
elements.extend(render_modifiers( elements.extend(render_modifiers(
&keystroke.modifiers, modifiers,
platform_style, platform_style,
color, color,
size, size,
true, true,
)); ));
elements.push(render_key(&keystroke, color, platform_style, size)); elements.push(render_key(key, color, platform_style, size));
elements elements
} }
} }
fn icon_for_key(keystroke: &Keystroke, platform_style: PlatformStyle) -> Option<IconName> { fn icon_for_key(key: &str, platform_style: PlatformStyle) -> Option<IconName> {
match keystroke.key.as_str() { match key {
"left" => Some(IconName::ArrowLeft), "left" => Some(IconName::ArrowLeft),
"right" => Some(IconName::ArrowRight), "right" => Some(IconName::ArrowRight),
"up" => Some(IconName::ArrowUp), "up" => Some(IconName::ArrowUp),
@ -379,7 +384,23 @@ impl KeyIcon {
/// Returns a textual representation of the key binding for the given [`Action`]. /// 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<String> { pub fn text_for_action(action: &dyn Action, window: &Window, cx: &App) -> Option<String> {
let key_binding = window.highest_precedence_binding_for_action(action)?; 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_keybinding_keystrokes(keystrokes: &[KeybindingKeystroke], cx: &App) -> String {
let platform_style = PlatformStyle::platform();
let vim_enabled = cx.try_global::<VimStyle>().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 { pub fn text_for_keystrokes(keystrokes: &[Keystroke], cx: &App) -> String {
@ -387,22 +408,39 @@ pub fn text_for_keystrokes(keystrokes: &[Keystroke], cx: &App) -> String {
let vim_enabled = cx.try_global::<VimStyle>().is_some(); let vim_enabled = cx.try_global::<VimStyle>().is_some();
keystrokes keystrokes
.iter() .iter()
.map(|keystroke| keystroke_text(keystroke, platform_style, vim_enabled)) .map(|keystroke| {
keystroke_text(
&keystroke.modifiers,
&keystroke.key,
platform_style,
vim_enabled,
)
})
.join(" ") .join(" ")
} }
pub fn text_for_keystroke(keystroke: &Keystroke, cx: &App) -> String { pub fn text_for_keystroke(keystroke: &Keystroke, cx: &App) -> String {
let platform_style = PlatformStyle::platform(); let platform_style = PlatformStyle::platform();
let vim_enabled = cx.try_global::<VimStyle>().is_some(); let vim_enabled = cx.try_global::<VimStyle>().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`]. /// 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 mut text = String::new();
let delimiter = '-'; let delimiter = '-';
if keystroke.modifiers.function { if modifiers.function {
match vim_mode { match vim_mode {
false => text.push_str("Fn"), false => text.push_str("Fn"),
true => text.push_str("fn"), true => text.push_str("fn"),
@ -411,7 +449,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
text.push(delimiter); text.push(delimiter);
} }
if keystroke.modifiers.control { if modifiers.control {
match (platform_style, vim_mode) { match (platform_style, vim_mode) {
(PlatformStyle::Mac, false) => text.push_str("Control"), (PlatformStyle::Mac, false) => text.push_str("Control"),
(PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Ctrl"), (PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Ctrl"),
@ -421,7 +459,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
text.push(delimiter); text.push(delimiter);
} }
if keystroke.modifiers.platform { if modifiers.platform {
match (platform_style, vim_mode) { match (platform_style, vim_mode) {
(PlatformStyle::Mac, false) => text.push_str("Command"), (PlatformStyle::Mac, false) => text.push_str("Command"),
(PlatformStyle::Mac, true) => text.push_str("cmd"), (PlatformStyle::Mac, true) => text.push_str("cmd"),
@ -434,7 +472,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
text.push(delimiter); text.push(delimiter);
} }
if keystroke.modifiers.alt { if modifiers.alt {
match (platform_style, vim_mode) { match (platform_style, vim_mode) {
(PlatformStyle::Mac, false) => text.push_str("Option"), (PlatformStyle::Mac, false) => text.push_str("Option"),
(PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Alt"), (PlatformStyle::Linux | PlatformStyle::Windows, false) => text.push_str("Alt"),
@ -444,7 +482,7 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
text.push(delimiter); text.push(delimiter);
} }
if keystroke.modifiers.shift { if modifiers.shift {
match (platform_style, vim_mode) { match (platform_style, vim_mode) {
(_, false) => text.push_str("Shift"), (_, false) => text.push_str("Shift"),
(_, true) => text.push_str("shift"), (_, true) => text.push_str("shift"),
@ -453,9 +491,9 @@ fn keystroke_text(keystroke: &Keystroke, platform_style: PlatformStyle, vim_mode
} }
if vim_mode { if vim_mode {
text.push_str(&keystroke.key) text.push_str(key)
} else { } else {
let key = match keystroke.key.as_str() { let key = match key {
"pageup" => "PageUp", "pageup" => "PageUp",
"pagedown" => "PageDown", "pagedown" => "PageDown",
key => &util::capitalize(key), key => &util::capitalize(key),

View file

@ -50,6 +50,8 @@ impl ModeIndicator {
} }
} }
// TODO:
// what's this?
fn update_pending_keys(&mut self, window: &mut Window, cx: &App) { fn update_pending_keys(&mut self, window: &mut Window, cx: &App) {
self.pending_keys = window self.pending_keys = window
.pending_input_keystrokes() .pending_input_keystrokes()