This commit is contained in:
Junkui Zhang 2025-08-19 19:11:58 +08:00
parent 49278ceec0
commit f602c1d69e
7 changed files with 209 additions and 129 deletions

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, AsKeystroke, Keystroke, is_no_action};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::any::TypeId; use std::any::TypeId;
@ -141,7 +141,7 @@ impl Keymap {
/// only. /// only.
pub fn bindings_for_input( pub fn bindings_for_input(
&self, &self,
input: &[Keystroke], input: &[impl AsKeystroke],
context_stack: &[KeyContext], context_stack: &[KeyContext],
) -> (SmallVec<[KeyBinding; 1]>, bool) { ) -> (SmallVec<[KeyBinding; 1]>, bool) {
let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new(); let mut matched_bindings = SmallVec::<[(usize, BindingIndex, &KeyBinding); 1]>::new();
@ -192,7 +192,6 @@ impl Keymap {
(bindings, !pending.is_empty()) (bindings, !pending.is_empty())
} }
/// Check if the given binding is enabled, given a certain key context. /// 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. /// 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<usize> { fn binding_enabled(&self, binding: &KeyBinding, contexts: &[KeyContext]) -> Option<usize> {
@ -639,7 +638,7 @@ mod tests {
fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) { fn assert_bindings(keymap: &Keymap, action: &dyn Action, expected: &[&str]) {
let actual = keymap let actual = keymap
.bindings_for_action(action) .bindings_for_action(action)
.map(|binding| binding.keystrokes[0].unparse()) .map(|binding| binding.keystrokes[0].inner.unparse())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(actual, expected, "{:?}", action); assert_eq!(actual, expected, "{:?}", action);
} }

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, AsKeystroke, 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
@ -45,7 +48,7 @@ impl KeyBinding {
) -> std::result::Result<Self, InvalidKeystrokeError> { ) -> std::result::Result<Self, InvalidKeystrokeError> {
let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes let mut keystrokes: SmallVec<[Keystroke; 2]> = keystrokes
.split_whitespace() .split_whitespace()
.map(Keystroke::parse) .map(|source| Keystroke::parse(source).map(|keystroke| keystroke.into_shifted()))
.collect::<std::result::Result<_, _>>()?; .collect::<std::result::Result<_, _>>()?;
if let Some(equivalents) = key_equivalents { if let Some(equivalents) = key_equivalents {
@ -58,6 +61,11 @@ impl KeyBinding {
} }
} }
let keystrokes = keystrokes
.into_iter()
.map(KeybindingKeystroke::new)
.collect();
Ok(Self { Ok(Self {
keystrokes, keystrokes,
action, action,
@ -79,13 +87,13 @@ impl KeyBinding {
} }
/// Check if the given keystrokes match this binding. /// Check if the given keystrokes match this binding.
pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option<bool> { pub fn match_keystrokes(&self, typed: &[impl AsKeystroke]) -> Option<bool> {
if self.keystrokes.len() < typed.len() { if self.keystrokes.len() < typed.len() {
return None; return None;
} }
for (target, typed) in self.keystrokes.iter().zip(typed.iter()) { for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
if !typed.should_match(target) { if !typed.as_keystroke().should_match(target) {
return None; return None;
} }
} }
@ -94,7 +102,7 @@ impl KeyBinding {
} }
/// 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

@ -5,6 +5,12 @@ use std::{
fmt::{Display, Write}, fmt::{Display, Write},
}; };
/// TODO:
pub trait AsKeystroke {
/// TODO:
fn as_keystroke(&self) -> &Keystroke;
}
/// A keystroke and associated metadata generated by the platform /// A keystroke and associated metadata generated by the platform
#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] #[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Keystroke { pub struct Keystroke {
@ -25,8 +31,8 @@ pub struct Keystroke {
} }
/// TODO: /// TODO:
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct KeystrokeDisplay { pub struct KeybindingKeystroke {
/// TODO: /// TODO:
pub inner: Keystroke, pub inner: Keystroke,
/// TODO: /// TODO:
@ -69,7 +75,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
@ -82,7 +88,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;
} }
} }
@ -94,12 +100,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:
@ -276,7 +282,8 @@ impl Keystroke {
self self
} }
fn into_shifted(self) -> Self { /// TODO:
pub fn into_shifted(self) -> Self {
let Keystroke { let Keystroke {
modifiers, modifiers,
key, key,
@ -291,17 +298,16 @@ impl Keystroke {
} }
} }
impl KeystrokeDisplay { impl KeybindingKeystroke {
/// TODO: /// Create a new keybinding keystroke from the given keystroke
pub fn parse(source: &str) -> std::result::Result<Self, InvalidKeystrokeError> { pub fn new(keystroke: Keystroke) -> Self {
let keystroke = Keystroke::parse(source)?;
let (key, modifiers) = to_unshifted_key(&keystroke.key, &keystroke.modifiers); let (key, modifiers) = to_unshifted_key(&keystroke.key, &keystroke.modifiers);
let inner = keystroke.into_shifted(); let inner = keystroke.into_shifted();
Ok(KeystrokeDisplay { KeybindingKeystroke {
inner, inner,
key, key,
modifiers, modifiers,
}) }
} }
} }
@ -361,65 +367,15 @@ fn is_printable_key(key: &str) -> bool {
impl std::fmt::Display for Keystroke { impl std::fmt::Display for Keystroke {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.modifiers.control { display_modifiers(&self.modifiers, f)?;
#[cfg(target_os = "macos")] display_key(&self.key, f)
f.write_char('^')?; }
}
#[cfg(not(target_os = "macos"))] impl std::fmt::Display for KeybindingKeystroke {
write!(f, "ctrl-")?; fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
} display_modifiers(&self.modifiers, f)?;
if self.modifiers.alt { display_key(&self.key, f)
#[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" => '⌘',
key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(),
key => return f.write_str(key),
};
f.write_char(key)
} }
} }
@ -640,6 +596,18 @@ pub struct Capslock {
pub on: bool, 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) { fn to_unshifted_key(key: &str, modifiers: &Modifiers) -> (String, Modifiers) {
let mut modifiers = modifiers.clone(); let mut modifiers = modifiers.clone();
match key { 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)] #[cfg(test)]
mod tests { mod tests {
use crate::{Keystroke, KeystrokeDisplay, Modifiers}; use crate::{KeybindingKeystroke, Keystroke, Modifiers};
#[test] #[test]
fn test_parsing_keystroke_on_windows() { fn test_parsing_keystroke_on_windows() {
@ -839,7 +873,7 @@ mod tests {
assert_eq!(keystroke.key, "$"); assert_eq!(keystroke.key, "$");
assert_eq!(keystroke.key_char, None); 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.inner, keystroke);
assert_eq!(keystroke_display.key, "4"); assert_eq!(keystroke_display.key, "4");
assert_eq!(keystroke_display.modifiers, Modifiers::control_shift()); assert_eq!(keystroke_display.modifiers, Modifiers::control_shift());
@ -850,7 +884,10 @@ mod tests {
assert_eq!(keystroke.key, "4"); assert_eq!(keystroke.key, "4");
assert_eq!(keystroke.key_char, None); 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!( assert_eq!(
keystroke_display.inner, keystroke_display.inner,
Keystroke { Keystroke {

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;
@ -916,7 +917,7 @@ impl<'a> KeybindUpdateOperation<'a> {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
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 action_arguments: Option<&'a str>, pub action_arguments: Option<&'a str>,
} }
@ -941,7 +942,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();
@ -1020,7 +1021,7 @@ impl From<KeybindSource> for KeyBindingMetaIndex {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use gpui::Keystroke; use gpui::{KeybindingKeystroke, Keystroke};
use unindent::Unindent; use unindent::Unindent;
use crate::{ use crate::{
@ -1055,10 +1056,10 @@ mod tests {
} }
#[track_caller] #[track_caller]
fn parse_keystrokes(keystrokes: &str) -> Vec<Keystroke> { fn parse_keystrokes(keystrokes: &str) -> Vec<KeybindingKeystroke> {
keystrokes keystrokes
.split(' ') .split(' ')
.map(|s| Keystroke::parse(s).expect("Keystrokes valid")) .map(|s| KeybindingKeystroke::new(Keystroke::parse(s).expect("Keystrokes valid")))
.collect() .collect()
} }

View file

@ -364,7 +364,7 @@ impl KeystrokeInput {
&self.keystrokes &self.keystrokes
}; };
keystrokes.iter().map(move |keystroke| { keystrokes.iter().map(move |keystroke| {
h_flex().children(ui::render_keystroke( h_flex().children(ui::render_keybinding_keystroke(
keystroke, keystroke,
Some(Color::Default), Some(Color::Default),
Some(rems(0.875).into()), Some(rems(0.875).into()),

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, Modifiers,
relative, 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()
} }
} }
@ -137,7 +137,7 @@ impl RenderOnce for KeyBinding {
.py_0p5() .py_0p5()
.rounded_xs() .rounded_xs()
.text_color(cx.theme().colors().text_muted) .text_color(cx.theme().colors().text_muted)
.children(render_keystroke( .children(render_keybinding_keystroke(
keystroke, keystroke,
color, color,
self.size, self.size,
@ -148,8 +148,8 @@ impl RenderOnce for KeyBinding {
} }
} }
pub fn render_keystroke( pub fn render_keybinding_keystroke(
keystroke: &Keystroke, keystroke: &KeybindingKeystroke,
color: Option<Color>, color: Option<Color>,
size: impl Into<Option<AbsoluteLength>>, size: impl Into<Option<AbsoluteLength>>,
platform_style: PlatformStyle, platform_style: PlatformStyle,
@ -163,9 +163,17 @@ 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(
.into_any_element(); &keystroke.modifiers,
&keystroke.key,
platform_style,
vim_mode,
),
color,
)
.size(size)
.into_any_element();
vec![element] vec![element]
} else { } else {
let mut elements = Vec::new(); let mut elements = Vec::new();
@ -176,13 +184,13 @@ pub fn render_keystroke(
size, size,
true, true,
)); ));
elements.push(render_key(keystroke, color, platform_style, size)); elements.push(render_key(&keystroke.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),
@ -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)) 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 platform_style = PlatformStyle::platform();
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(modifiers: &Modifiers, key: &str, 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(modifiers, 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 +431,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 +441,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 +454,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 +464,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 +473,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),
@ -562,9 +582,11 @@ mod tests {
#[test] #[test]
fn test_text_for_keystroke() { fn test_text_for_keystroke() {
let keystroke = Keystroke::parse("cmd-c").unwrap();
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("cmd-c").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Mac, PlatformStyle::Mac,
false false
), ),
@ -572,7 +594,8 @@ mod tests {
); );
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("cmd-c").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Linux, PlatformStyle::Linux,
false false
), ),
@ -580,16 +603,19 @@ mod tests {
); );
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("cmd-c").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Windows, PlatformStyle::Windows,
false false
), ),
"Win-C".to_string() "Win-C".to_string()
); );
let keystroke = Keystroke::parse("ctrl-alt-delete").unwrap();
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("ctrl-alt-delete").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Mac, PlatformStyle::Mac,
false false
), ),
@ -597,7 +623,8 @@ mod tests {
); );
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("ctrl-alt-delete").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Linux, PlatformStyle::Linux,
false false
), ),
@ -605,16 +632,19 @@ mod tests {
); );
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("ctrl-alt-delete").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Windows, PlatformStyle::Windows,
false false
), ),
"Ctrl-Alt-Delete".to_string() "Ctrl-Alt-Delete".to_string()
); );
let keystroke = Keystroke::parse("shift-pageup").unwrap();
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("shift-pageup").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Mac, PlatformStyle::Mac,
false false
), ),
@ -622,7 +652,8 @@ mod tests {
); );
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("shift-pageup").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Linux, PlatformStyle::Linux,
false, false,
), ),
@ -630,7 +661,8 @@ mod tests {
); );
assert_eq!( assert_eq!(
keystroke_text( keystroke_text(
&Keystroke::parse("shift-pageup").unwrap(), &keystroke.modifiers,
&keystroke.key,
PlatformStyle::Windows, PlatformStyle::Windows,
false false
), ),

View file

@ -72,7 +72,10 @@ impl QuickActionBar {
Tooltip::with_meta( Tooltip::with_meta(
tooltip_text, tooltip_text,
Some(open_action_for_tooltip), 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, window,
cx, cx,
) )