first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -120,5 +120,5 @@ impl ContextPredicate {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "context_test.rs"]
|
||||
#[path = "context_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::{macros::*, *};
|
||||
use super::macros::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_context_predicate_eval() -> anyhow::Result<()> {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use super::macros::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_context_predicate_eval() -> anyhow::Result<()> {
|
||||
let predicate = id!("a") & id!("b") | eq!("c", "d");
|
||||
|
||||
let mut context = Context::default();
|
||||
context.set.insert("a");
|
||||
assert!(!predicate.eval(&context));
|
||||
|
||||
context.set.insert("b");
|
||||
assert!(predicate.eval(&context));
|
||||
|
||||
context.set.remove("b");
|
||||
context.map.insert("c", "x");
|
||||
assert!(!predicate.eval(&context));
|
||||
|
||||
context.map.insert("c", "d");
|
||||
assert!(predicate.eval(&context));
|
||||
|
||||
let predicate = !id!("a");
|
||||
assert!(predicate.eval(&Context::default()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Either;
|
||||
|
||||
use super::{
|
||||
BindingLens, Context, CustomTag, EditableBinding, EditableBindingLens, FixedBinding, Keymap,
|
||||
Keystroke, Trigger,
|
||||
};
|
||||
use crate::{actions::StandardAction, Action, EntityId};
|
||||
use itertools::Either;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use crate::actions::StandardAction;
|
||||
use crate::{Action, EntityId, EntityIdMap};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Matcher {
|
||||
pending: HashMap<EntityId, Pending>,
|
||||
pending: EntityIdMap<Pending>,
|
||||
keymap: Keymap,
|
||||
/// Default binding validator that should run on every binding (irrespective of the [`Context`]
|
||||
/// the binding was registered against).
|
||||
@@ -53,7 +56,7 @@ pub enum MatchResult {
|
||||
impl Matcher {
|
||||
pub fn new(keymap: Keymap) -> Self {
|
||||
Self {
|
||||
pending: HashMap::new(),
|
||||
pending: EntityIdMap::default(),
|
||||
keymap,
|
||||
default_binding_validator: None,
|
||||
binding_validators: vec![],
|
||||
@@ -376,5 +379,5 @@ impl Matcher {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "matcher_test.rs"]
|
||||
#[path = "matcher_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::keymap::macros::*;
|
||||
|
||||
use super::*;
|
||||
use crate::keymap::macros::*;
|
||||
|
||||
#[test]
|
||||
fn test_matcher() -> anyhow::Result<()> {
|
||||
@@ -96,7 +95,6 @@ fn test_editable_binding_matching() {
|
||||
}
|
||||
|
||||
let mut keymap = Keymap::default();
|
||||
use crate::keymap::macros::*;
|
||||
keymap.register_editable_bindings([
|
||||
EditableBinding::new("a", "Action for A", Action::A("b"))
|
||||
.with_key_binding("a")
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
use super::*;
|
||||
use crate::keymap::macros::*;
|
||||
|
||||
#[test]
|
||||
fn test_matcher() -> anyhow::Result<()> {
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Action {
|
||||
A(String),
|
||||
B,
|
||||
AB,
|
||||
}
|
||||
|
||||
let keymap = Keymap::new(vec![
|
||||
FixedBinding::new("a", Action::A("b".into()), id!("a")),
|
||||
FixedBinding::new("b", Action::B, id!("a")),
|
||||
FixedBinding::new("a b", Action::AB, id!("a") | id!("b")),
|
||||
]);
|
||||
|
||||
let mut ctx_a = Context::default();
|
||||
ctx_a.set.insert("a");
|
||||
|
||||
let mut ctx_b = Context::default();
|
||||
ctx_b.set.insert("b");
|
||||
|
||||
let mut matcher = Matcher::new(keymap);
|
||||
|
||||
let view_id = EntityId::new();
|
||||
|
||||
// Basic match
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("a", view_id, &ctx_a)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::A("b".into())
|
||||
);
|
||||
|
||||
// Multi-keystroke match
|
||||
assert!(matcher.test_keystroke("a", view_id, &ctx_b).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("b", view_id, &ctx_b)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::AB
|
||||
);
|
||||
|
||||
// Failed matches don't interfere with matching subsequent keys
|
||||
assert!(matcher.test_keystroke("x", view_id, &ctx_a).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("a", view_id, &ctx_a)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::A("b".into())
|
||||
);
|
||||
|
||||
// Pending keystrokes are cleared when the context changes
|
||||
assert!(matcher.test_keystroke("a", view_id, &ctx_b).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("b", view_id, &ctx_a)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::B
|
||||
);
|
||||
|
||||
let mut ctx_c = Context::default();
|
||||
ctx_c.set.insert("c");
|
||||
|
||||
// Pending keystrokes are maintained per-view
|
||||
let view_id1 = EntityId::new();
|
||||
let view_id2 = EntityId::new();
|
||||
assert_ne!(view_id1, view_id2);
|
||||
assert!(matcher.test_keystroke("a", view_id1, &ctx_b).is_none());
|
||||
assert!(matcher.test_keystroke("a", view_id2, &ctx_c).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("b", view_id1, &ctx_b)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::AB
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_editable_binding_matching() {
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Action {
|
||||
A(&'static str),
|
||||
B,
|
||||
AOrB,
|
||||
}
|
||||
|
||||
let mut keymap = Keymap::default();
|
||||
keymap.register_editable_bindings([
|
||||
EditableBinding::new("a", "Action for A", Action::A("b"))
|
||||
.with_key_binding("a")
|
||||
.with_context_predicate(id!("a")),
|
||||
EditableBinding::new("b", "Action for B", Action::B)
|
||||
.with_key_binding("b")
|
||||
.with_context_predicate(id!("a")),
|
||||
EditableBinding::new("a_or_b", "Action for A or B", Action::AOrB)
|
||||
.with_key_binding("a b")
|
||||
.with_context_predicate(id!("a") | id!("b")),
|
||||
]);
|
||||
|
||||
let mut ctx_a = Context::default();
|
||||
ctx_a.set.insert("a");
|
||||
|
||||
let mut ctx_b = Context::default();
|
||||
ctx_b.set.insert("b");
|
||||
|
||||
let mut matcher = Matcher::new(keymap);
|
||||
|
||||
let view_id = EntityId::new();
|
||||
|
||||
// Basic match
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("a", view_id, &ctx_a)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::A("b"),
|
||||
);
|
||||
|
||||
// Multi-keystroke match
|
||||
assert!(matcher.test_keystroke("a", view_id, &ctx_b).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("b", view_id, &ctx_b)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::AOrB
|
||||
);
|
||||
|
||||
// Failed matches don't interfere with matching subsequent keys
|
||||
assert!(matcher.test_keystroke("x", view_id, &ctx_a).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("a", view_id, &ctx_a)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::A("b")
|
||||
);
|
||||
|
||||
// Pending keystrokes are cleared when the context changes
|
||||
assert!(matcher.test_keystroke("a", view_id, &ctx_b).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("b", view_id, &ctx_a)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::B
|
||||
);
|
||||
|
||||
let mut ctx_c = Context::default();
|
||||
ctx_c.set.insert("c");
|
||||
|
||||
// Pending keystrokes are maintained per-view
|
||||
let view_id1 = EntityId::new();
|
||||
let view_id2 = EntityId::new();
|
||||
assert_ne!(view_id1, view_id2);
|
||||
assert!(matcher.test_keystroke("a", view_id1, &ctx_b).is_none());
|
||||
assert!(matcher.test_keystroke("a", view_id2, &ctx_c).is_none());
|
||||
assert_eq!(
|
||||
matcher
|
||||
.test_keystroke("b", view_id1, &ctx_b)
|
||||
.unwrap()
|
||||
.as_action::<Action>(),
|
||||
&Action::AOrB
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bindings_for_context() {
|
||||
#[derive(Debug)]
|
||||
enum Action {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
}
|
||||
let keymap = Keymap::new(vec![
|
||||
FixedBinding::new("a", Action::A, id!("a")),
|
||||
FixedBinding::new("b", Action::B, id!("b")),
|
||||
FixedBinding::new("c", Action::C, id!("b")),
|
||||
]);
|
||||
let matcher = Matcher::new(keymap);
|
||||
|
||||
let mut ctx_a = Context::default();
|
||||
ctx_a.set.insert("a");
|
||||
|
||||
let mut ctx_b = Context::default();
|
||||
ctx_b.set.insert("b");
|
||||
|
||||
// Getting bindings for the 'a' context returns a single result
|
||||
let ctx_a_bindings = matcher
|
||||
.bindings_for_context(ctx_a)
|
||||
.filter_map(|bind| match bind.trigger {
|
||||
Trigger::Keystrokes(keys) => {
|
||||
assert_eq!(keys.len(), 1);
|
||||
Some(keys[0].normalized())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(ctx_a_bindings.len(), 1);
|
||||
assert_eq!(ctx_a_bindings, vec!["a"]);
|
||||
|
||||
// Getting bindings for the 'b' context returns two results, in the reverse order they
|
||||
// added, so the "c" binding first followed by the "b" binding
|
||||
let ctx_b_bindings = matcher
|
||||
.bindings_for_context(ctx_b)
|
||||
.filter_map(|bind| match bind.trigger {
|
||||
Trigger::Keystrokes(keys) => {
|
||||
assert_eq!(keys.len(), 1);
|
||||
Some(keys[0].normalized())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(ctx_b_bindings, vec!["c", "b"]);
|
||||
}
|
||||
|
||||
impl Matcher {
|
||||
fn test_keystroke(
|
||||
&mut self,
|
||||
keystroke: &str,
|
||||
view_id: EntityId,
|
||||
ctx: &Context,
|
||||
) -> Option<Arc<dyn Action>> {
|
||||
match self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, ctx) {
|
||||
MatchResult::Action(action) => Some(action),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait AsAction {
|
||||
fn as_action<A: Action>(&self) -> &A;
|
||||
}
|
||||
|
||||
impl AsAction for Arc<dyn Action> {
|
||||
fn as_action<A: Action>(&self) -> &A {
|
||||
self.as_ref().as_any().downcast_ref::<A>().unwrap()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user