wip
This commit is contained in:
parent
49278ceec0
commit
f602c1d69e
7 changed files with 209 additions and 129 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
|
@ -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
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue