Allow actions to be deserialized from JSON

Introduce separate macro for implementing 'internal' actions which
are not intended to be loaded from keymaps.
This commit is contained in:
Max Brunsfeld 2022-04-08 15:32:56 -07:00
parent 1778622960
commit fd4b81c8fc
26 changed files with 559 additions and 335 deletions

View file

@ -328,6 +328,8 @@ impl ContextPredicate {
#[cfg(test)]
mod tests {
use serde::Deserialize;
use crate::{actions, impl_actions};
use super::*;
@ -419,30 +421,18 @@ mod tests {
#[test]
fn test_matcher() -> anyhow::Result<()> {
#[derive(Clone)]
pub struct A(pub &'static str);
#[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
pub struct A(pub String);
impl_actions!(test, [A]);
actions!(test, [B, Ab]);
impl PartialEq for A {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for A {}
impl Debug for A {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "A({:?})", &self.0)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct ActionArg {
a: &'static str,
}
let keymap = Keymap(vec![
Binding::new("a", A("x"), Some("a")),
Binding::new("a", A("x".to_string()), Some("a")),
Binding::new("b", B, Some("a")),
Binding::new("a b", Ab, Some("a || b")),
]);
@ -456,40 +446,54 @@ mod tests {
let mut matcher = Matcher::new(keymap);
// Basic match
assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
assert_eq!(
downcast(&matcher.test_keystroke("a", 1, &ctx_a)),
Some(&A("x".to_string()))
);
// Multi-keystroke match
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none());
assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab));
// Failed matches don't interfere with matching subsequent keys
assert_eq!(matcher.test_keystroke::<A>("x", 1, &ctx_a), None);
assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
assert!(matcher.test_keystroke("x", 1, &ctx_a).is_none());
assert_eq!(
downcast(&matcher.test_keystroke("a", 1, &ctx_a)),
Some(&A("x".to_string()))
);
// Pending keystrokes are cleared when the context changes
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke("b", 1, &ctx_a), Some(B));
assert!(&matcher.test_keystroke("a", 1, &ctx_b).is_none());
assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_a)), Some(&B));
let mut ctx_c = Context::default();
ctx_c.set.insert("c".into());
// Pending keystrokes are maintained per-view
assert_eq!(matcher.test_keystroke::<A>("a", 1, &ctx_b), None);
assert_eq!(matcher.test_keystroke::<A>("a", 2, &ctx_c), None);
assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none());
assert!(matcher.test_keystroke("a", 2, &ctx_c).is_none());
assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab));
Ok(())
}
fn downcast<'a, A: Action>(action: &'a Option<Box<dyn Action>>) -> Option<&'a A> {
action
.as_ref()
.and_then(|action| action.as_any().downcast_ref())
}
impl Matcher {
fn test_keystroke<A>(&mut self, keystroke: &str, view_id: usize, cx: &Context) -> Option<A>
where
A: Action + Debug + Eq,
{
fn test_keystroke(
&mut self,
keystroke: &str,
view_id: usize,
cx: &Context,
) -> Option<Box<dyn Action>> {
if let MatchResult::Action(action) =
self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx)
{
Some(*action.boxed_clone_as_any().downcast().unwrap())
Some(action.boxed_clone())
} else {
None
}