Merge branch 'main' into jk
This commit is contained in:
commit
9d261cf859
30 changed files with 536 additions and 426 deletions
|
@ -2,6 +2,7 @@ use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
|
|||
use anyhow::Result;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A keybinding and it's associated metadata, from the keymap.
|
||||
pub struct KeyBinding {
|
||||
pub(crate) action: Box<dyn Action>,
|
||||
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
|
||||
|
@ -19,10 +20,12 @@ impl Clone for KeyBinding {
|
|||
}
|
||||
|
||||
impl KeyBinding {
|
||||
/// Construct a new keybinding from the given data.
|
||||
pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
|
||||
Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
|
||||
}
|
||||
|
||||
/// Load a keybinding from the given raw data.
|
||||
pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
|
||||
let context = if let Some(context) = context {
|
||||
Some(KeyBindingContextPredicate::parse(context)?)
|
||||
|
@ -42,6 +45,7 @@ impl KeyBinding {
|
|||
})
|
||||
}
|
||||
|
||||
/// Check if the given keystrokes match this binding.
|
||||
pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
|
||||
if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
|
||||
// If the binding is completed, push it onto the matches list
|
||||
|
@ -55,10 +59,12 @@ impl KeyBinding {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the keystrokes associated with this binding
|
||||
pub fn keystrokes(&self) -> &[Keystroke] {
|
||||
self.keystrokes.as_slice()
|
||||
}
|
||||
|
||||
/// Get the action associated with this binding
|
||||
pub fn action(&self) -> &dyn Action {
|
||||
self.action.as_ref()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ use anyhow::{anyhow, Result};
|
|||
use smallvec::SmallVec;
|
||||
use std::fmt;
|
||||
|
||||
/// A datastructure for resolving whether an action should be dispatched
|
||||
/// at this point in the element tree. Contains a set of identifiers
|
||||
/// and/or key value pairs representing the current context for the
|
||||
/// keymap.
|
||||
#[derive(Clone, Default, Eq, PartialEq, Hash)]
|
||||
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
|
||||
|
||||
|
@ -21,6 +25,11 @@ impl<'a> TryFrom<&'a str> for KeyContext {
|
|||
}
|
||||
|
||||
impl KeyContext {
|
||||
/// Parse a key context from a string.
|
||||
/// The key context format is very simple:
|
||||
/// - either a single identifier, such as `StatusBar`
|
||||
/// - or a key value pair, such as `mode = visible`
|
||||
/// - separated by whitespace, such as `StatusBar mode = visible`
|
||||
pub fn parse(source: &str) -> Result<Self> {
|
||||
let mut context = Self::default();
|
||||
let source = skip_whitespace(source);
|
||||
|
@ -53,14 +62,17 @@ impl KeyContext {
|
|||
Self::parse_expr(source, context)
|
||||
}
|
||||
|
||||
/// Check if this context is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Clear this context.
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
|
||||
/// Extend this context with another context.
|
||||
pub fn extend(&mut self, other: &Self) {
|
||||
for entry in &other.0 {
|
||||
if !self.contains(&entry.key) {
|
||||
|
@ -69,6 +81,7 @@ impl KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Add an identifier to this context, if it's not already in this context.
|
||||
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
|
||||
let key = identifier.into();
|
||||
|
||||
|
@ -77,6 +90,7 @@ impl KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set a key value pair in this context, if it's not already set.
|
||||
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
|
||||
let key = key.into();
|
||||
if !self.contains(&key) {
|
||||
|
@ -87,10 +101,12 @@ impl KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if this context contains a given identifier or key.
|
||||
pub fn contains(&self, key: &str) -> bool {
|
||||
self.0.iter().any(|entry| entry.key.as_ref() == key)
|
||||
}
|
||||
|
||||
/// Get the associated value for a given identifier or key.
|
||||
pub fn get(&self, key: &str) -> Option<&SharedString> {
|
||||
self.0
|
||||
.iter()
|
||||
|
@ -117,20 +133,31 @@ impl fmt::Debug for KeyContext {
|
|||
}
|
||||
}
|
||||
|
||||
/// A datastructure for resolving whether an action should be dispatched
|
||||
/// Representing a small language for describing which contexts correspond
|
||||
/// to which actions.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum KeyBindingContextPredicate {
|
||||
/// A predicate that will match a given identifier.
|
||||
Identifier(SharedString),
|
||||
/// A predicate that will match a given key-value pair.
|
||||
Equal(SharedString, SharedString),
|
||||
/// A predicate that will match a given key-value pair not being present.
|
||||
NotEqual(SharedString, SharedString),
|
||||
/// A predicate that will match a given predicate appearing below another predicate.
|
||||
/// in the element tree
|
||||
Child(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
),
|
||||
/// Predicate that will invert another predicate.
|
||||
Not(Box<KeyBindingContextPredicate>),
|
||||
/// A predicate that will match if both of its children match.
|
||||
And(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
),
|
||||
/// A predicate that will match if either of its children match.
|
||||
Or(
|
||||
Box<KeyBindingContextPredicate>,
|
||||
Box<KeyBindingContextPredicate>,
|
||||
|
@ -138,6 +165,34 @@ pub enum KeyBindingContextPredicate {
|
|||
}
|
||||
|
||||
impl KeyBindingContextPredicate {
|
||||
/// Parse a string in the same format as the keymap's context field.
|
||||
///
|
||||
/// A basic equivalence check against a set of identifiers can performed by
|
||||
/// simply writing a string:
|
||||
///
|
||||
/// `StatusBar` -> A predicate that will match a context with the identifier `StatusBar`
|
||||
///
|
||||
/// You can also specify a key-value pair:
|
||||
///
|
||||
/// `mode == visible` -> A predicate that will match a context with the key `mode`
|
||||
/// with the value `visible`
|
||||
///
|
||||
/// And a logical operations combining these two checks:
|
||||
///
|
||||
/// `StatusBar && mode == visible` -> A predicate that will match a context with the
|
||||
/// identifier `StatusBar` and the key `mode`
|
||||
/// with the value `visible`
|
||||
///
|
||||
///
|
||||
/// There is also a special child `>` operator that will match a predicate that is
|
||||
/// below another predicate:
|
||||
///
|
||||
/// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
|
||||
/// and a child context that has the key `mode` with the
|
||||
/// value `visible`
|
||||
///
|
||||
/// This syntax supports `!=`, `||` and `&&` as logical operators.
|
||||
/// You can also preface an operation or check with a `!` to negate it.
|
||||
pub fn parse(source: &str) -> Result<Self> {
|
||||
let source = skip_whitespace(source);
|
||||
let (predicate, rest) = Self::parse_expr(source, 0)?;
|
||||
|
@ -148,6 +203,7 @@ impl KeyBindingContextPredicate {
|
|||
}
|
||||
}
|
||||
|
||||
/// Eval a predicate against a set of contexts, arranged from lowest to highest.
|
||||
pub fn eval(&self, contexts: &[KeyContext]) -> bool {
|
||||
let Some(context) = contexts.last() else {
|
||||
return false;
|
||||
|
|
|
@ -6,9 +6,12 @@ use std::{
|
|||
collections::HashMap,
|
||||
};
|
||||
|
||||
/// An opaque identifier of which version of the keymap is currently active.
|
||||
/// The keymap's version is changed whenever bindings are added or removed.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub struct KeymapVersion(usize);
|
||||
|
||||
/// A collection of key bindings for the user's application.
|
||||
#[derive(Default)]
|
||||
pub struct Keymap {
|
||||
bindings: Vec<KeyBinding>,
|
||||
|
@ -19,16 +22,19 @@ pub struct Keymap {
|
|||
}
|
||||
|
||||
impl Keymap {
|
||||
/// Create a new keymap with the given bindings.
|
||||
pub fn new(bindings: Vec<KeyBinding>) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.add_bindings(bindings);
|
||||
this
|
||||
}
|
||||
|
||||
/// Get the current version of the keymap.
|
||||
pub fn version(&self) -> KeymapVersion {
|
||||
self.version
|
||||
}
|
||||
|
||||
/// Add more bindings to the keymap.
|
||||
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
|
||||
let no_action_id = (NoAction {}).type_id();
|
||||
|
||||
|
@ -51,6 +57,7 @@ impl Keymap {
|
|||
self.version.0 += 1;
|
||||
}
|
||||
|
||||
/// Reset this keymap to its initial state.
|
||||
pub fn clear(&mut self) {
|
||||
self.bindings.clear();
|
||||
self.binding_indices_by_action_id.clear();
|
||||
|
@ -77,6 +84,7 @@ impl Keymap {
|
|||
.filter(move |binding| binding.action().partial_eq(action))
|
||||
}
|
||||
|
||||
/// Check if the given binding is enabled, given a certain key context.
|
||||
pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
|
||||
// If binding has a context predicate, it must match the current context,
|
||||
if let Some(predicate) = &binding.context_predicate {
|
||||
|
|
|
@ -3,7 +3,7 @@ use parking_lot::Mutex;
|
|||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct KeystrokeMatcher {
|
||||
pub(crate) struct KeystrokeMatcher {
|
||||
pending_keystrokes: Vec<Keystroke>,
|
||||
keymap: Arc<Mutex<Keymap>>,
|
||||
keymap_version: KeymapVersion,
|
||||
|
@ -41,7 +41,7 @@ impl KeystrokeMatcher {
|
|||
/// - 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(
|
||||
pub(crate) fn match_keystroke(
|
||||
&mut self,
|
||||
keystroke: &Keystroke,
|
||||
context_stack: &[KeyContext],
|
||||
|
@ -88,6 +88,10 @@ impl KeystrokeMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
/// The result of matching a keystroke against a given keybinding.
|
||||
/// - KeyMatch::None => No match is valid for this key given any pending keystrokes.
|
||||
/// - KeyMatch::Pending => There exist bindings that is still waiting for more keys.
|
||||
/// - KeyMatch::Some(matches) => One or more bindings have received the necessary key presses.
|
||||
#[derive(Debug)]
|
||||
pub enum KeyMatch {
|
||||
None,
|
||||
|
@ -95,341 +99,3 @@ pub enum KeyMatch {
|
|||
Matched,
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
|
||||
// use serde_derive::Deserialize;
|
||||
|
||||
// use super::*;
|
||||
// use crate::{self as gpui, KeyBindingContextPredicate, Modifiers};
|
||||
// use crate::{actions, KeyBinding};
|
||||
|
||||
// #[test]
|
||||
// fn test_keymap_and_view_ordering() {
|
||||
// actions!(test, [EditorAction, ProjectPanelAction]);
|
||||
|
||||
// let mut editor = KeyContext::default();
|
||||
// editor.add("Editor");
|
||||
|
||||
// let mut project_panel = KeyContext::default();
|
||||
// project_panel.add("ProjectPanel");
|
||||
|
||||
// // Editor 'deeper' in than project panel
|
||||
// let dispatch_path = vec![project_panel, editor];
|
||||
|
||||
// // But editor actions 'higher' up in keymap
|
||||
// let keymap = Keymap::new(vec![
|
||||
// KeyBinding::new("left", EditorAction, Some("Editor")),
|
||||
// KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")),
|
||||
// ]);
|
||||
|
||||
// let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
|
||||
|
||||
// let matches = matcher
|
||||
// .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path)
|
||||
// .matches()
|
||||
// .unwrap();
|
||||
|
||||
// assert!(matches[0].partial_eq(&EditorAction));
|
||||
// assert!(matches.get(1).is_none());
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_multi_keystroke_match() {
|
||||
// actions!(test, [B, AB, C, D, DA, E, EF]);
|
||||
|
||||
// let mut context1 = KeyContext::default();
|
||||
// context1.add("1");
|
||||
|
||||
// let mut context2 = KeyContext::default();
|
||||
// context2.add("2");
|
||||
|
||||
// let dispatch_path = vec![context2, context1];
|
||||
|
||||
// let keymap = Keymap::new(vec![
|
||||
// KeyBinding::new("a b", AB, Some("1")),
|
||||
// KeyBinding::new("b", B, Some("2")),
|
||||
// KeyBinding::new("c", C, Some("2")),
|
||||
// KeyBinding::new("d", D, Some("1")),
|
||||
// KeyBinding::new("d", D, Some("2")),
|
||||
// KeyBinding::new("d a", DA, Some("2")),
|
||||
// ]);
|
||||
|
||||
// let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
|
||||
|
||||
// // Binding with pending prefix always takes precedence
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Pending,
|
||||
// );
|
||||
// // B alone doesn't match because a was pending, so AB is returned instead
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Some(vec![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").unwrap(), &dispatch_path[0..1]),
|
||||
// KeyMatch::Some(vec![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").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Pending,
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path),
|
||||
// KeyMatch::None,
|
||||
// );
|
||||
// assert!(!matcher.has_pending_keystrokes());
|
||||
|
||||
// // If a single keystroke matches multiple bindings in the tree
|
||||
// // only one of them is returned.
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path),
|
||||
// KeyMatch::Some(vec![Box::new(D)]),
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_keystroke_parsing() {
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("ctrl-p").unwrap(),
|
||||
// Keystroke {
|
||||
// key: "p".into(),
|
||||
// modifiers: Modifiers {
|
||||
// control: true,
|
||||
// alt: false,
|
||||
// shift: false,
|
||||
// command: false,
|
||||
// function: false,
|
||||
// },
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("alt-shift-down").unwrap(),
|
||||
// Keystroke {
|
||||
// key: "down".into(),
|
||||
// modifiers: Modifiers {
|
||||
// control: false,
|
||||
// alt: true,
|
||||
// shift: true,
|
||||
// command: false,
|
||||
// function: false,
|
||||
// },
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// Keystroke::parse("shift-cmd--").unwrap(),
|
||||
// Keystroke {
|
||||
// key: "-".into(),
|
||||
// modifiers: Modifiers {
|
||||
// control: false,
|
||||
// alt: false,
|
||||
// shift: true,
|
||||
// command: true,
|
||||
// function: false,
|
||||
// },
|
||||
// ime_key: None,
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_predicate_parsing() {
|
||||
// use KeyBindingContextPredicate::*;
|
||||
|
||||
// assert_eq!(
|
||||
// KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
|
||||
// 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!(
|
||||
// KeyBindingContextPredicate::parse("!a").unwrap(),
|
||||
// Not(Box::new(Identifier("a".into())),)
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_predicate_eval() {
|
||||
// let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap();
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// assert!(!predicate.eval(&[context]));
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// context.add("b");
|
||||
// assert!(predicate.eval(&[context]));
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// context.set("c", "x");
|
||||
// assert!(!predicate.eval(&[context]));
|
||||
|
||||
// let mut context = KeyContext::default();
|
||||
// context.add("a");
|
||||
// context.set("c", "d");
|
||||
// assert!(predicate.eval(&[context]));
|
||||
|
||||
// let predicate = KeyBindingContextPredicate::parse("!a").unwrap();
|
||||
// assert!(predicate.eval(&[KeyContext::default()]));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_context_child_predicate_eval() {
|
||||
// let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap();
|
||||
// let contexts = [
|
||||
// context_set(&["a", "b"]),
|
||||
// context_set(&["c", "d"]), // match this context
|
||||
// context_set(&["e", "f"]),
|
||||
// ];
|
||||
|
||||
// assert!(!predicate.eval(&contexts[..=0]));
|
||||
// assert!(predicate.eval(&contexts[..=1]));
|
||||
// assert!(!predicate.eval(&contexts[..=2]));
|
||||
|
||||
// let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap();
|
||||
// let contexts = [
|
||||
// context_set(&["a", "b"]),
|
||||
// context_set(&["c", "d"]),
|
||||
// context_set(&["e"]),
|
||||
// context_set(&["a", "b"]),
|
||||
// context_set(&["c"]),
|
||||
// context_set(&["e"]), // only match this context
|
||||
// context_set(&["f"]),
|
||||
// ];
|
||||
|
||||
// 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]) -> KeyContext {
|
||||
// let mut keymap = KeyContext::default();
|
||||
// names.iter().for_each(|name| keymap.add(name.to_string()));
|
||||
// keymap
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_matcher() {
|
||||
// #[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![
|
||||
// KeyBinding::new("a", A("x".to_string()), Some("a")),
|
||||
// KeyBinding::new("b", B, Some("a")),
|
||||
// KeyBinding::new("a b", Ab, Some("a || b")),
|
||||
// KeyBinding::new("$", Dollar, Some("a")),
|
||||
// KeyBinding::new("\"", Quote, Some("a")),
|
||||
// KeyBinding::new("alt-s", Ess, Some("a")),
|
||||
// KeyBinding::new("ctrl-`", Backtick, Some("a")),
|
||||
// ]);
|
||||
|
||||
// let mut context_a = KeyContext::default();
|
||||
// context_a.add("a");
|
||||
|
||||
// let mut context_b = KeyContext::default();
|
||||
// context_b.add("b");
|
||||
|
||||
// let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
|
||||
|
||||
// // Basic match
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(A("x".to_string()))])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// // Multi-keystroke match
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]),
|
||||
// KeyMatch::Pending
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Ab)])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// // Failed matches don't interfere with matching subsequent keys
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::None
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(A("x".to_string()))])
|
||||
// );
|
||||
// matcher.clear_pending();
|
||||
|
||||
// let mut context_c = KeyContext::default();
|
||||
// context_c.add("c");
|
||||
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(
|
||||
// &Keystroke::parse("a").unwrap(),
|
||||
// &[context_c.clone(), context_b.clone()]
|
||||
// ),
|
||||
// KeyMatch::Pending
|
||||
// );
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Ab)])
|
||||
// );
|
||||
|
||||
// // handle Czech $ (option + 4 key)
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("alt-ç->$").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Dollar)])
|
||||
// );
|
||||
|
||||
// // handle Brazilian quote (quote key then space key)
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(
|
||||
// &Keystroke::parse("space->\"").unwrap(),
|
||||
// &[context_a.clone()]
|
||||
// ),
|
||||
// KeyMatch::Some(vec![Box::new(Quote)])
|
||||
// );
|
||||
|
||||
// // handle ctrl+` on a brazilian keyboard
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Backtick)])
|
||||
// );
|
||||
|
||||
// // handle alt-s on a US keyboard
|
||||
// assert_eq!(
|
||||
// matcher.match_keystroke(&Keystroke::parse("alt-s->ß").unwrap(), &[context_a.clone()]),
|
||||
// KeyMatch::Some(vec![Box::new(Ess)])
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -6,4 +6,4 @@ mod matcher;
|
|||
pub use binding::*;
|
||||
pub use context::*;
|
||||
pub use keymap::*;
|
||||
pub use matcher::*;
|
||||
pub(crate) use matcher::*;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue