Compare commits
5 commits
main
...
keybind-ke
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0c96bcd572 | ||
![]() |
1610d05526 | ||
![]() |
fa1632bc09 | ||
![]() |
1e4d953fa3 | ||
![]() |
ae625acad6 |
11 changed files with 305 additions and 68 deletions
|
@ -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> {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(" ")
|
||||||
})
|
})
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue