Add remaining tests

co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Mikayla 2024-01-10 19:37:53 -08:00
parent a5ca58354d
commit 38396d4281
No known key found for this signature in database
4 changed files with 362 additions and 516 deletions

View file

@ -28,11 +28,11 @@ impl KeystrokeMatcher {
/// Pushes a keystroke onto the matcher.
/// The result of the new keystroke is returned:
/// KeyMatch::None =>
/// - KeyMatch::None =>
/// No match is valid for this key given any pending keystrokes.
/// KeyMatch::Pending =>
/// - KeyMatch::Pending =>
/// There exist bindings which are still waiting for more keys.
/// KeyMatch::Complete(matches) =>
/// - 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(
@ -77,12 +77,10 @@ impl KeystrokeMatcher {
if let Some(pending_key) = pending_key {
self.pending_keystrokes.push(pending_key);
}
if self.pending_keystrokes.is_empty() {
KeyMatch::None
} else {
KeyMatch::Pending
} else {
self.pending_keystrokes.clear();
KeyMatch::None
}
}
}
@ -98,367 +96,374 @@ impl KeyMatch {
pub fn is_some(&self) -> bool {
matches!(self, KeyMatch::Some(_))
}
pub fn matches(self) -> Option<Vec<Box<dyn Action>>> {
match self {
KeyMatch::Some(matches) => Some(matches),
_ => None,
}
}
}
// #[cfg(test)]
// mod tests {
// use anyhow::Result;
// use serde::Deserialize;
impl PartialEq for KeyMatch {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(KeyMatch::None, KeyMatch::None) => true,
(KeyMatch::Pending, KeyMatch::Pending) => true,
(KeyMatch::Some(a), KeyMatch::Some(b)) => {
if a.len() != b.len() {
return false;
}
// use crate::{actions, impl_actions, keymap_matcher::ActionContext};
for (a, b) in a.iter().zip(b.iter()) {
if !a.partial_eq(b.as_ref()) {
return false;
}
}
// use super::*;
true
}
_ => false,
}
}
}
// #[test]
// fn test_keymap_and_view_ordering() -> Result<()> {
// actions!(test, [EditorAction, ProjectPanelAction]);
#[cfg(test)]
mod tests {
// let mut editor = ActionContext::default();
// editor.add_identifier("Editor");
use serde_derive::Deserialize;
// let mut project_panel = ActionContext::default();
// project_panel.add_identifier("ProjectPanel");
use super::*;
use crate::{self as gpui, KeyBindingContextPredicate, Modifiers};
use crate::{actions, KeyBinding};
// // Editor 'deeper' in than project panel
// let dispatch_path = vec![(2, editor), (1, project_panel)];
#[test]
fn test_keymap_and_view_ordering() {
actions!(test, [EditorAction, ProjectPanelAction]);
// // 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 editor = KeyContext::default();
editor.add("Editor");
// let mut matcher = KeymapMatcher::new(keymap);
let mut project_panel = KeyContext::default();
project_panel.add("ProjectPanel");
// assert_eq!(
// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
// KeyMatch::Matches(vec![
// (2, Box::new(EditorAction)),
// (1, Box::new(ProjectPanelAction)),
// ]),
// );
// Editor 'deeper' in than project panel
let dispatch_path = vec![project_panel, editor];
// Ok(())
// }
// But editor actions 'higher' up in keymap
let keymap = Keymap::new(vec![
KeyBinding::new("left", EditorAction, Some("Editor")),
KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")),
]);
// #[test]
// fn test_push_keystroke() -> Result<()> {
// actions!(test, [B, AB, C, D, DA, E, EF]);
let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
// let mut context1 = ActionContext::default();
// context1.add_identifier("1");
let matches = matcher
.match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path)
.matches()
.unwrap();
// let mut context2 = ActionContext::default();
// context2.add_identifier("2");
assert!(matches[0].partial_eq(&EditorAction));
assert!(matches.get(1).is_none());
}
// let dispatch_path = vec![(2, context2), (1, context1)];
#[test]
fn test_multi_keystroke_match() {
actions!(test, [B, AB, C, D, DA, E, EF]);
// 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 context1 = KeyContext::default();
context1.add("1");
// let mut matcher = KeymapMatcher::new(keymap);
let mut context2 = KeyContext::default();
context2.add("2");
// // 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());
let dispatch_path = vec![context2, context1];
// // 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());
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")),
]);
// // 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());
let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
// // 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))]),
// );
// 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());
// // 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());
// 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());
// Ok(())
// }
eprintln!("PROBLEM AREA");
// 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());
// #[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,
// }
// );
// 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)]),
);
}
// assert_eq!(
// Keystroke::parse("alt-shift-down")?,
// Keystroke {
// key: "down".into(),
// ctrl: false,
// alt: true,
// shift: true,
// cmd: false,
// function: false,
// ime_key: None,
// }
// );
#[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("shift-cmd--")?,
// Keystroke {
// key: "-".into(),
// ctrl: false,
// alt: false,
// shift: true,
// cmd: true,
// 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,
}
);
// Ok(())
// }
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() -> Result<()> {
// use KeymapContextPredicate::*;
#[test]
fn test_context_predicate_parsing() {
use KeyBindingContextPredicate::*;
// 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!(
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!(
// KeymapContextPredicate::parse("!a")?,
// Not(Box::new(Identifier("a".into())),)
// );
assert_eq!(
KeyBindingContextPredicate::parse("!a").unwrap(),
Not(Box::new(Identifier("a".into())),)
);
}
// Ok(())
// }
#[test]
fn test_context_predicate_eval() {
let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap();
// #[test]
// fn test_context_predicate_eval() {
// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
let mut context = KeyContext::default();
context.add("a");
assert!(!predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// assert!(!predicate.eval(&[context]));
let mut context = KeyContext::default();
context.add("a");
context.add("b");
assert!(predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_identifier("b");
// assert!(predicate.eval(&[context]));
let mut context = KeyContext::default();
context.add("a");
context.set("c", "x");
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 = KeyContext::default();
context.add("a");
context.set("c", "d");
assert!(predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_key("c", "d");
// assert!(predicate.eval(&[context]));
let predicate = KeyBindingContextPredicate::parse("!a").unwrap();
assert!(predicate.eval(&[KeyContext::default()]));
}
// let predicate = KeymapContextPredicate::parse("!a").unwrap();
// assert!(predicate.eval(&[ActionContext::default()]));
// }
#[test]
fn test_context_child_predicate_eval() {
let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap();
let contexts = [
context_set(&["e", "f"]),
context_set(&["c", "d"]), // match this context
context_set(&["a", "b"]),
];
// #[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..]));
// 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(&["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"]),
];
// 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..]));
// 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
}
}
// 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() {
#[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
pub struct A(pub String);
impl_actions!(test, [A]);
actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
// #[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,
}
// #[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 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 = KeyContext::default();
context_a.add("a");
// let mut context_a = ActionContext::default();
// context_a.add_identifier("a");
let mut context_b = KeyContext::default();
context_b.add("b");
// let mut context_b = ActionContext::default();
// context_b.add_identifier("b");
let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap)));
// let mut matcher = KeymapMatcher::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();
// // 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").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();
// // 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").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();
// // 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();
let mut context_c = KeyContext::default();
context_c.add("c");
// // 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();
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)])
);
// let mut context_c = ActionContext::default();
// context_c.add_identifier("c");
// handle Czech $ (option + 4 key)
assert_eq!(
matcher.match_keystroke(&Keystroke::parse("alt-ç->$").unwrap(), &[context_a.clone()]),
KeyMatch::Some(vec![Box::new(Dollar)])
);
// // 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 Brazillian 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 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 ctrl+` on a brazillian keyboard
assert_eq!(
matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]),
KeyMatch::Some(vec![Box::new(Backtick)])
);
// // 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(())
// }
// }
// 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)])
);
}
}