Checkpoint
This commit is contained in:
parent
f3979a9f28
commit
e4fe9538d7
107 changed files with 81 additions and 81 deletions
80
crates/gpui2/src/keymap/binding.rs
Normal file
80
crates/gpui2/src/keymap/binding.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use crate::{Action, DispatchContext, DispatchContextPredicate, KeyMatch, Keystroke};
|
||||
use anyhow::Result;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
pub struct KeyBinding {
|
||||
action: Box<dyn Action>,
|
||||
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
pub(super) context_predicate: Option<DispatchContextPredicate>,
|
||||
}
|
||||
|
||||
impl KeyBinding {
|
||||
pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
|
||||
Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
|
||||
}
|
||||
|
||||
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
||||
let context = if let Some(context) = context {
|
||||
Some(DispatchContextPredicate::parse(context)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let keystrokes = keystrokes
|
||||
.split_whitespace()
|
||||
.map(Keystroke::parse)
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
Ok(Self {
|
||||
keystrokes,
|
||||
action,
|
||||
context_predicate: context,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn matches_context(&self, contexts: &[&DispatchContext]) -> bool {
|
||||
self.context_predicate
|
||||
.as_ref()
|
||||
.map(|predicate| predicate.eval(contexts))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
|
||||
pub fn match_keystrokes(
|
||||
&self,
|
||||
pending_keystrokes: &[Keystroke],
|
||||
contexts: &[&DispatchContext],
|
||||
) -> KeyMatch {
|
||||
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
|
||||
&& self.matches_context(contexts)
|
||||
{
|
||||
// If the binding is completed, push it onto the matches list
|
||||
if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
|
||||
KeyMatch::Some(self.action.boxed_clone())
|
||||
} else {
|
||||
KeyMatch::Pending
|
||||
}
|
||||
} else {
|
||||
KeyMatch::None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keystrokes_for_action(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
contexts: &[&DispatchContext],
|
||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||
if self.action.partial_eq(action) && self.matches_context(contexts) {
|
||||
Some(self.keystrokes.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||
self.keystrokes.as_slice()
|
||||
}
|
||||
|
||||
pub fn action(&self) -> &dyn Action {
|
||||
self.action.as_ref()
|
||||
}
|
||||
}
|
398
crates/gpui2/src/keymap/keymap.rs
Normal file
398
crates/gpui2/src/keymap/keymap.rs
Normal file
|
@ -0,0 +1,398 @@
|
|||
use crate::{DispatchContextPredicate, KeyBinding, Keystroke};
|
||||
use collections::HashSet;
|
||||
use smallvec::SmallVec;
|
||||
use std::{any::TypeId, collections::HashMap};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub struct KeymapVersion(usize);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Keymap {
|
||||
bindings: Vec<KeyBinding>,
|
||||
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
|
||||
disabled_keystrokes:
|
||||
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<DispatchContextPredicate>>>,
|
||||
version: KeymapVersion,
|
||||
}
|
||||
|
||||
impl Keymap {
|
||||
pub fn new(bindings: Vec<KeyBinding>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.add_bindings(bindings);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn version(&self) -> KeymapVersion {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ KeyBinding> {
|
||||
self.binding_indices_by_action_id
|
||||
.get(&action_id)
|
||||
.map(SmallVec::as_slice)
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.map(|ix| &self.bindings[*ix])
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
}
|
||||
|
||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||
// todo!("no action")
|
||||
// let no_action_id = (NoAction {}).id();
|
||||
let mut new_bindings = Vec::new();
|
||||
let has_new_disabled_keystrokes = false;
|
||||
for binding in bindings {
|
||||
// if binding.action().id() == no_action_id {
|
||||
// has_new_disabled_keystrokes |= self
|
||||
// .disabled_keystrokes
|
||||
// .entry(binding.keystrokes)
|
||||
// .or_default()
|
||||
// .insert(binding.context_predicate);
|
||||
// } else {
|
||||
new_bindings.push(binding);
|
||||
// }
|
||||
}
|
||||
|
||||
if has_new_disabled_keystrokes {
|
||||
self.binding_indices_by_action_id.retain(|_, indices| {
|
||||
indices.retain(|ix| {
|
||||
let binding = &self.bindings[*ix];
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => {
|
||||
!disabled_predicates.contains(&binding.context_predicate)
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
});
|
||||
!indices.is_empty()
|
||||
});
|
||||
}
|
||||
|
||||
for new_binding in new_bindings {
|
||||
if !self.binding_disabled(&new_binding) {
|
||||
self.binding_indices_by_action_id
|
||||
.entry(new_binding.action().as_any().type_id())
|
||||
.or_default()
|
||||
.push(self.bindings.len());
|
||||
self.bindings.push(new_binding);
|
||||
}
|
||||
}
|
||||
|
||||
self.version.0 += 1;
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.binding_indices_by_action_id.clear();
|
||||
self.disabled_keystrokes.clear();
|
||||
self.version.0 += 1;
|
||||
}
|
||||
|
||||
pub fn bindings(&self) -> Vec<&KeyBinding> {
|
||||
self.bindings
|
||||
.iter()
|
||||
.filter(|binding| !self.binding_disabled(binding))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn binding_disabled(&self, binding: &KeyBinding) -> bool {
|
||||
match self.disabled_keystrokes.get(&binding.keystrokes) {
|
||||
Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::actions;
|
||||
|
||||
// use super::*;
|
||||
|
||||
// actions!(
|
||||
// keymap_test,
|
||||
// [Present1, Present2, Present3, Duplicate, Missing]
|
||||
// );
|
||||
|
||||
// #[test]
|
||||
// fn regular_keymap() {
|
||||
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let missing = Binding::new("ctrl-r", Missing {}, None);
|
||||
// let all_bindings = [
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &present_3,
|
||||
// &keystroke_duplicate_to_1,
|
||||
// &full_duplicate_to_2,
|
||||
// &missing,
|
||||
// ];
|
||||
|
||||
// let mut keymap = Keymap::default();
|
||||
// assert_absent(&keymap, &all_bindings);
|
||||
// assert!(keymap.bindings().is_empty());
|
||||
|
||||
// keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
|
||||
// assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
|
||||
// assert_present(
|
||||
// &keymap,
|
||||
// &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
|
||||
// );
|
||||
|
||||
// keymap.add_bindings([
|
||||
// keystroke_duplicate_to_1.clone(),
|
||||
// full_duplicate_to_2.clone(),
|
||||
// ]);
|
||||
// assert_absent(&keymap, &[&missing]);
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(&keystroke_duplicate_to_1),
|
||||
// "Duplicate binding 1 was added and should not be disabled"
|
||||
// );
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(&full_duplicate_to_2),
|
||||
// "Duplicate binding 2 was added and should not be disabled"
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// keymap
|
||||
// .bindings_for_action(keystroke_duplicate_to_1.action().id())
|
||||
// .map(|binding| &binding.keystrokes)
|
||||
// .flatten()
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![&Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: "q".to_string(),
|
||||
// ime_key: None,
|
||||
// }],
|
||||
// "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap
|
||||
// .bindings_for_action(full_duplicate_to_2.action().id())
|
||||
// .map(|binding| &binding.keystrokes)
|
||||
// .flatten()
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![
|
||||
// &Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: "w".to_string(),
|
||||
// ime_key: None,
|
||||
// },
|
||||
// &Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: "w".to_string(),
|
||||
// ime_key: None,
|
||||
// }
|
||||
// ],
|
||||
// "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
|
||||
// );
|
||||
|
||||
// let updated_bindings = keymap.bindings();
|
||||
// let expected_updated_bindings = vec![
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &present_3,
|
||||
// &keystroke_duplicate_to_1,
|
||||
// &full_duplicate_to_2,
|
||||
// ];
|
||||
// assert_eq!(
|
||||
// updated_bindings.len(),
|
||||
// expected_updated_bindings.len(),
|
||||
// "Unexpected updated keymap bindings {updated_bindings:?}"
|
||||
// );
|
||||
// for (i, expected) in expected_updated_bindings.iter().enumerate() {
|
||||
// let keymap_binding = &updated_bindings[i];
|
||||
// assert_eq!(
|
||||
// keymap_binding.context_predicate, expected.context_predicate,
|
||||
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap_binding.keystrokes, expected.keystrokes,
|
||||
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// }
|
||||
|
||||
// keymap.clear();
|
||||
// assert_absent(&keymap, &all_bindings);
|
||||
// assert!(keymap.bindings().is_empty());
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn keymap_with_ignored() {
|
||||
// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
|
||||
// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
|
||||
// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
|
||||
// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
|
||||
// let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
|
||||
// let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
|
||||
// let ignored_3_with_other_context =
|
||||
// Binding::new("ctrl-e", NoAction {}, Some("other_context"));
|
||||
|
||||
// let mut keymap = Keymap::default();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// ignored_1.clone(),
|
||||
// ignored_2.clone(),
|
||||
// ignored_3_with_other_context.clone(),
|
||||
// ]);
|
||||
// assert_absent(&keymap, &[&present_3]);
|
||||
// assert_disabled(
|
||||
// &keymap,
|
||||
// &[
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &ignored_1,
|
||||
// &ignored_2,
|
||||
// &ignored_3_with_other_context,
|
||||
// ],
|
||||
// );
|
||||
// assert!(keymap.bindings().is_empty());
|
||||
// keymap.clear();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// present_1.clone(),
|
||||
// present_2.clone(),
|
||||
// present_3.clone(),
|
||||
// ignored_1.clone(),
|
||||
// ignored_2.clone(),
|
||||
// ignored_3_with_other_context.clone(),
|
||||
// ]);
|
||||
// assert_present(&keymap, &[(&present_3, "e")]);
|
||||
// assert_disabled(
|
||||
// &keymap,
|
||||
// &[
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &ignored_1,
|
||||
// &ignored_2,
|
||||
// &ignored_3_with_other_context,
|
||||
// ],
|
||||
// );
|
||||
// keymap.clear();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// present_1.clone(),
|
||||
// present_2.clone(),
|
||||
// present_3.clone(),
|
||||
// ignored_1.clone(),
|
||||
// ]);
|
||||
// assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
|
||||
// assert_disabled(&keymap, &[&present_1, &ignored_1]);
|
||||
// assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
|
||||
// keymap.clear();
|
||||
|
||||
// keymap.add_bindings([
|
||||
// present_1.clone(),
|
||||
// present_2.clone(),
|
||||
// present_3.clone(),
|
||||
// keystroke_duplicate_to_1.clone(),
|
||||
// full_duplicate_to_2.clone(),
|
||||
// ignored_1.clone(),
|
||||
// ignored_2.clone(),
|
||||
// ignored_3_with_other_context.clone(),
|
||||
// ]);
|
||||
// assert_present(&keymap, &[(&present_3, "e")]);
|
||||
// assert_disabled(
|
||||
// &keymap,
|
||||
// &[
|
||||
// &present_1,
|
||||
// &present_2,
|
||||
// &keystroke_duplicate_to_1,
|
||||
// &full_duplicate_to_2,
|
||||
// &ignored_1,
|
||||
// &ignored_2,
|
||||
// &ignored_3_with_other_context,
|
||||
// ],
|
||||
// );
|
||||
// keymap.clear();
|
||||
// }
|
||||
|
||||
// #[track_caller]
|
||||
// fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
|
||||
// let keymap_bindings = keymap.bindings();
|
||||
// assert_eq!(
|
||||
// expected_bindings.len(),
|
||||
// keymap_bindings.len(),
|
||||
// "Unexpected keymap bindings {keymap_bindings:?}"
|
||||
// );
|
||||
// for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(expected),
|
||||
// "{expected:?} should not be disabled as it was added into keymap for element {i}"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap
|
||||
// .bindings_for_action(expected.action().id())
|
||||
// .map(|binding| &binding.keystrokes)
|
||||
// .flatten()
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![&Keystroke {
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// key: expected_key.to_string(),
|
||||
// ime_key: None,
|
||||
// }],
|
||||
// "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
|
||||
// );
|
||||
|
||||
// let keymap_binding = &keymap_bindings[i];
|
||||
// assert_eq!(
|
||||
// keymap_binding.context_predicate, expected.context_predicate,
|
||||
// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap_binding.keystrokes, expected.keystrokes,
|
||||
// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[track_caller]
|
||||
// fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
// for binding in bindings.iter() {
|
||||
// assert!(
|
||||
// !keymap.binding_disabled(binding),
|
||||
// "{binding:?} should not be disabled in the keymap where was not added"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap.bindings_for_action(binding.action().id()).count(),
|
||||
// 0,
|
||||
// "{binding:?} should have no actions in the keymap where was not added"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[track_caller]
|
||||
// fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
|
||||
// for binding in bindings.iter() {
|
||||
// assert!(
|
||||
// keymap.binding_disabled(binding),
|
||||
// "{binding:?} should be disabled in the keymap"
|
||||
// );
|
||||
// assert_eq!(
|
||||
// keymap.bindings_for_action(binding.action().id()).count(),
|
||||
// 0,
|
||||
// "{binding:?} should have no actions in the keymap where it was disabled"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
473
crates/gpui2/src/keymap/matcher.rs
Normal file
473
crates/gpui2/src/keymap/matcher.rs
Normal file
|
@ -0,0 +1,473 @@
|
|||
use crate::{Action, DispatchContext, Keymap, KeymapVersion, Keystroke};
|
||||
use parking_lot::RwLock;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct KeyMatcher {
|
||||
pending_keystrokes: Vec<Keystroke>,
|
||||
keymap: Arc<RwLock<Keymap>>,
|
||||
keymap_version: KeymapVersion,
|
||||
}
|
||||
|
||||
impl KeyMatcher {
|
||||
pub fn new(keymap: Arc<RwLock<Keymap>>) -> Self {
|
||||
let keymap_version = keymap.read().version();
|
||||
Self {
|
||||
pending_keystrokes: Vec::new(),
|
||||
keymap_version,
|
||||
keymap,
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("replace with a function that calls an FnMut for every binding matching the action")
|
||||
// pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
|
||||
// self.keymap.read().bindings_for_action(action_id)
|
||||
// }
|
||||
|
||||
pub fn clear_pending(&mut self) {
|
||||
self.pending_keystrokes.clear();
|
||||
}
|
||||
|
||||
pub fn has_pending_keystrokes(&self) -> bool {
|
||||
!self.pending_keystrokes.is_empty()
|
||||
}
|
||||
|
||||
/// Pushes a keystroke onto the matcher.
|
||||
/// The result of the new keystroke is returned:
|
||||
/// KeyMatch::None =>
|
||||
/// No match is valid for this key given any pending keystrokes.
|
||||
/// KeyMatch::Pending =>
|
||||
/// There exist bindings which are still waiting for more keys.
|
||||
/// KeyMatch::Complete(matches) =>
|
||||
/// One or more bindings have received the necessary key presses.
|
||||
/// Bindings added later will take precedence over earlier bindings.
|
||||
pub fn match_keystroke(
|
||||
&mut self,
|
||||
keystroke: &Keystroke,
|
||||
context_stack: &[&DispatchContext],
|
||||
) -> KeyMatch {
|
||||
let keymap = self.keymap.read();
|
||||
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
|
||||
if keymap.version() != self.keymap_version {
|
||||
self.keymap_version = keymap.version();
|
||||
self.pending_keystrokes.clear();
|
||||
}
|
||||
|
||||
let mut pending_key = None;
|
||||
|
||||
for binding in keymap.bindings().iter().rev() {
|
||||
for candidate in keystroke.match_candidates() {
|
||||
self.pending_keystrokes.push(candidate.clone());
|
||||
match binding.match_keystrokes(&self.pending_keystrokes, context_stack) {
|
||||
KeyMatch::Some(action) => {
|
||||
self.pending_keystrokes.clear();
|
||||
return KeyMatch::Some(action);
|
||||
}
|
||||
KeyMatch::Pending => {
|
||||
pending_key.get_or_insert(candidate);
|
||||
}
|
||||
KeyMatch::None => {}
|
||||
}
|
||||
self.pending_keystrokes.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pending_key) = pending_key {
|
||||
self.pending_keystrokes.push(pending_key);
|
||||
}
|
||||
|
||||
if self.pending_keystrokes.is_empty() {
|
||||
KeyMatch::None
|
||||
} else {
|
||||
KeyMatch::Pending
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keystrokes_for_action(
|
||||
&self,
|
||||
action: &dyn Action,
|
||||
contexts: &[&DispatchContext],
|
||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||
self.keymap
|
||||
.read()
|
||||
.bindings()
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|binding| binding.keystrokes_for_action(action, contexts))
|
||||
}
|
||||
}
|
||||
|
||||
pub enum KeyMatch {
|
||||
None,
|
||||
Pending,
|
||||
Some(Box<dyn Action>),
|
||||
}
|
||||
|
||||
impl KeyMatch {
|
||||
pub fn is_some(&self) -> bool {
|
||||
matches!(self, KeyMatch::Some(_))
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use anyhow::Result;
|
||||
// use serde::Deserialize;
|
||||
|
||||
// use crate::{actions, impl_actions, keymap_matcher::ActionContext};
|
||||
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn test_keymap_and_view_ordering() -> Result<()> {
|
||||
// actions!(test, [EditorAction, ProjectPanelAction]);
|
||||
|
||||
// let mut editor = ActionContext::default();
|
||||
// editor.add_identifier("Editor");
|
||||
|
||||
// let mut project_panel = ActionContext::default();
|
||||
// project_panel.add_identifier("ProjectPanel");
|
||||
|
||||
// // Editor 'deeper' in than project panel
|
||||
// let dispatch_path = vec![(2, editor), (1, project_panel)];
|
||||
|
||||
// // But editor actions 'higher' up in keymap
|
||||
// let keymap = Keymap::new(vec![
|
||||
// Binding::new("left", EditorAction, Some("Editor")),
|
||||
// Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
|
||||
// ]);
|
||||
|
||||
// let mut matcher = KeymapMatcher::new(keymap);
|
||||
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
|
||||
// KeyMatch::Matches(vec![
|
||||
// (2, Box::new(EditorAction)),
|
||||
// (1, Box::new(ProjectPanelAction)),
|
||||
// ]),
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_push_keystroke() -> Result<()> {
|
||||
// actions!(test, [B, AB, C, D, DA, E, EF]);
|
||||
|
||||
// let mut context1 = ActionContext::default();
|
||||
// context1.add_identifier("1");
|
||||
|
||||
// let mut context2 = ActionContext::default();
|
||||
// context2.add_identifier("2");
|
||||
|
||||
// let dispatch_path = vec![(2, context2), (1, context1)];
|
||||
|
||||
// let keymap = Keymap::new(vec![
|
||||
// Binding::new("a b", AB, Some("1")),
|
||||
// Binding::new("b", B, Some("2")),
|
||||
// Binding::new("c", C, Some("2")),
|
||||
// Binding::new("d", D, Some("1")),
|
||||
// Binding::new("d", D, Some("2")),
|
||||
// Binding::new("d a", DA, Some("2")),
|
||||
// ]);
|
||||
|
||||
// let mut matcher = KeymapMatcher::new(keymap);
|
||||
|
||||
// // Binding with pending prefix always takes precedence
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
|
||||
// KeyMatch::Pending,
|
||||
// );
|
||||
// // B alone doesn't match because a was pending, so AB is returned instead
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(AB))]),
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// // Without an a prefix, B is dispatched like expected
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
|
||||
// KeyMatch::Matches(vec![(2, Box::new(B))]),
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// // If a is prefixed, C will not be dispatched because there
|
||||
// // was a pending binding for it
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
|
||||
// KeyMatch::Pending,
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
|
||||
// KeyMatch::None,
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// // If a single keystroke matches multiple bindings in the tree
|
||||
// // all of them are returned so that we can fallback if the action
|
||||
// // handler decides to propagate the action
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
|
||||
// KeyMatch::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
|
||||
// );
|
||||
|
||||
// // If none of the d action handlers consume the binding, a pending
|
||||
// // binding may then be used
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
|
||||
// KeyMatch::Matches(vec![(2, Box::new(DA))]),
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_keystroke_parsing() -> Result<()> {
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("ctrl-p")?,
|
||||
// Keystroke {
|
||||
// key: "p".into(),
|
||||
// ctrl: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("alt-shift-down")?,
|
||||
// Keystroke {
|
||||
// key: "down".into(),
|
||||
// ctrl: false,
|
||||
// alt: true,
|
||||
// shift: true,
|
||||
// cmd: false,
|
||||
// function: false,
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("shift-cmd--")?,
|
||||
// Keystroke {
|
||||
// key: "-".into(),
|
||||
// ctrl: false,
|
||||
// alt: false,
|
||||
// shift: true,
|
||||
// cmd: true,
|
||||
// function: false,
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_predicate_parsing() -> Result<()> {
|
||||
// use KeymapContextPredicate::*;
|
||||
|
||||
// assert_eq!(
|
||||
// KeymapContextPredicate::parse("a && (b == c || d != e)")?,
|
||||
// And(
|
||||
// Box::new(Identifier("a".into())),
|
||||
// Box::new(Or(
|
||||
// Box::new(Equal("b".into(), "c".into())),
|
||||
// Box::new(NotEqual("d".into(), "e".into())),
|
||||
// ))
|
||||
// )
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// KeymapContextPredicate::parse("!a")?,
|
||||
// Not(Box::new(Identifier("a".into())),)
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_predicate_eval() {
|
||||
// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
|
||||
|
||||
// let mut context = ActionContext::default();
|
||||
// context.add_identifier("a");
|
||||
// assert!(!predicate.eval(&[context]));
|
||||
|
||||
// let mut context = ActionContext::default();
|
||||
// context.add_identifier("a");
|
||||
// context.add_identifier("b");
|
||||
// assert!(predicate.eval(&[context]));
|
||||
|
||||
// let mut context = ActionContext::default();
|
||||
// context.add_identifier("a");
|
||||
// context.add_key("c", "x");
|
||||
// assert!(!predicate.eval(&[context]));
|
||||
|
||||
// let mut context = ActionContext::default();
|
||||
// context.add_identifier("a");
|
||||
// context.add_key("c", "d");
|
||||
// assert!(predicate.eval(&[context]));
|
||||
|
||||
// let predicate = KeymapContextPredicate::parse("!a").unwrap();
|
||||
// assert!(predicate.eval(&[ActionContext::default()]));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_child_predicate_eval() {
|
||||
// let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
|
||||
// let contexts = [
|
||||
// context_set(&["e", "f"]),
|
||||
// context_set(&["c", "d"]), // match this context
|
||||
// context_set(&["a", "b"]),
|
||||
// ];
|
||||
|
||||
// assert!(!predicate.eval(&contexts[0..]));
|
||||
// assert!(predicate.eval(&contexts[1..]));
|
||||
// assert!(!predicate.eval(&contexts[2..]));
|
||||
|
||||
// let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
|
||||
// let contexts = [
|
||||
// context_set(&["f"]),
|
||||
// context_set(&["e"]), // only match this context
|
||||
// context_set(&["c"]),
|
||||
// context_set(&["a", "b"]),
|
||||
// context_set(&["e"]),
|
||||
// context_set(&["c", "d"]),
|
||||
// context_set(&["a", "b"]),
|
||||
// ];
|
||||
|
||||
// assert!(!predicate.eval(&contexts[0..]));
|
||||
// assert!(predicate.eval(&contexts[1..]));
|
||||
// assert!(!predicate.eval(&contexts[2..]));
|
||||
// assert!(!predicate.eval(&contexts[3..]));
|
||||
// assert!(!predicate.eval(&contexts[4..]));
|
||||
// assert!(!predicate.eval(&contexts[5..]));
|
||||
// assert!(!predicate.eval(&contexts[6..]));
|
||||
|
||||
// fn context_set(names: &[&str]) -> ActionContext {
|
||||
// let mut keymap = ActionContext::new();
|
||||
// names
|
||||
// .iter()
|
||||
// .for_each(|name| keymap.add_identifier(name.to_string()));
|
||||
// keymap
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_matcher() -> Result<()> {
|
||||
// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
|
||||
// pub struct A(pub String);
|
||||
// impl_actions!(test, [A]);
|
||||
// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
|
||||
|
||||
// #[derive(Clone, Debug, Eq, PartialEq)]
|
||||
// struct ActionArg {
|
||||
// a: &'static str,
|
||||
// }
|
||||
|
||||
// let keymap = Keymap::new(vec![
|
||||
// Binding::new("a", A("x".to_string()), Some("a")),
|
||||
// Binding::new("b", B, Some("a")),
|
||||
// Binding::new("a b", Ab, Some("a || b")),
|
||||
// Binding::new("$", Dollar, Some("a")),
|
||||
// Binding::new("\"", Quote, Some("a")),
|
||||
// Binding::new("alt-s", Ess, Some("a")),
|
||||
// Binding::new("ctrl-`", Backtick, Some("a")),
|
||||
// ]);
|
||||
|
||||
// let mut context_a = ActionContext::default();
|
||||
// context_a.add_identifier("a");
|
||||
|
||||
// let mut context_b = ActionContext::default();
|
||||
// context_b.add_identifier("b");
|
||||
|
||||
// let mut matcher = KeymapMatcher::new(keymap);
|
||||
|
||||
// // Basic match
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// // Multi-keystroke match
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
|
||||
// KeyMatch::Pending
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(Ab))])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// // Failed matches don't interfere with matching subsequent keys
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::None
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// // Pending keystrokes are cleared when the context changes
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
|
||||
// KeyMatch::Pending
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::None
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// let mut context_c = ActionContext::default();
|
||||
// context_c.add_identifier("c");
|
||||
|
||||
// // Pending keystrokes are maintained per-view
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(
|
||||
// Keystroke::parse("a")?,
|
||||
// vec![(1, context_b.clone()), (2, context_c.clone())]
|
||||
// ),
|
||||
// KeyMatch::Pending
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(Ab))])
|
||||
// );
|
||||
|
||||
// // handle Czech $ (option + 4 key)
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(Dollar))])
|
||||
// );
|
||||
|
||||
// // handle Brazillian quote (quote key then space key)
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(Quote))])
|
||||
// );
|
||||
|
||||
// // handle ctrl+` on a brazillian keyboard
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(Backtick))])
|
||||
// );
|
||||
|
||||
// // handle alt-s on a US keyboard
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]),
|
||||
// KeyMatch::Matches(vec![(1, Box::new(Ess))])
|
||||
// );
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
7
crates/gpui2/src/keymap/mod.rs
Normal file
7
crates/gpui2/src/keymap/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod binding;
|
||||
mod keymap;
|
||||
mod matcher;
|
||||
|
||||
pub use binding::*;
|
||||
pub use keymap::*;
|
||||
pub use matcher::*;
|
Loading…
Add table
Add a link
Reference in a new issue