mod binding; mod keymap; mod keymap_context; mod keystroke; use std::{any::TypeId, fmt::Debug}; use collections::HashMap; use smallvec::SmallVec; use crate::Action; pub use binding::{Binding, BindingMatchResult}; pub use keymap::Keymap; pub use keymap_context::{KeymapContext, KeymapContextPredicate}; pub use keystroke::Keystroke; pub struct KeymapMatcher { pub contexts: Vec, pending_views: HashMap, pending_keystrokes: Vec, keymap: Keymap, } impl KeymapMatcher { pub fn new(keymap: Keymap) -> Self { Self { contexts: Vec::new(), pending_views: Default::default(), pending_keystrokes: Vec::new(), keymap, } } pub fn set_keymap(&mut self, keymap: Keymap) { self.clear_pending(); self.keymap = keymap; } pub fn add_bindings>(&mut self, bindings: T) { self.clear_pending(); self.keymap.add_bindings(bindings); } pub fn clear_bindings(&mut self) { self.clear_pending(); self.keymap.clear(); } pub fn bindings_for_action_type(&self, action_type: TypeId) -> impl Iterator { self.keymap.bindings_for_action_type(action_type) } pub fn clear_pending(&mut self) { self.pending_keystrokes.clear(); self.pending_views.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: /// MatchResult::None => /// No match is valid for this key given any pending keystrokes. /// MatchResult::Pending => /// There exist bindings which are still waiting for more keys. /// MatchResult::Complete(matches) => /// 1 or more bindings have received the necessary key presses. /// The order of the matched actions is by position of the matching first, // and order in the keymap second. pub fn push_keystroke( &mut self, keystroke: Keystroke, mut dispatch_path: Vec<(usize, KeymapContext)>, ) -> MatchResult { let mut any_pending = false; // Collect matched bindings into an ordered list using the position in the matching binding first, // and then the order the binding matched in the view tree second. // The key is the reverse position of the binding in the bindings list so that later bindings // match before earlier ones in the user's config let mut matched_bindings: Vec<(usize, Box)> = Default::default(); let first_keystroke = self.pending_keystrokes.is_empty(); self.pending_keystrokes.push(keystroke.clone()); self.contexts.clear(); self.contexts .extend(dispatch_path.iter_mut().map(|e| std::mem::take(&mut e.1))); // Find the bindings which map the pending keystrokes and current context for (i, (view_id, _)) in dispatch_path.iter().enumerate() { // Don't require pending view entry if there are no pending keystrokes if !first_keystroke && !self.pending_views.contains_key(view_id) { continue; } // If there is a previous view context, invalidate that view if it // has changed if let Some(previous_view_context) = self.pending_views.remove(view_id) { if previous_view_context != self.contexts[i] { continue; } } for binding in self.keymap.bindings().iter().rev() { match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..]) { BindingMatchResult::Complete(action) => { matched_bindings.push((*view_id, action)); } BindingMatchResult::Partial => { self.pending_views .insert(*view_id, self.contexts[i].clone()); any_pending = true; } _ => {} } } } if !any_pending { self.clear_pending(); } if !matched_bindings.is_empty() { // Collect the sorted matched bindings into the final vec for ease of use // Matched bindings are in order by precedence MatchResult::Matches(matched_bindings) } else if any_pending { MatchResult::Pending } else { MatchResult::None } } pub fn keystrokes_for_action( &self, action: &dyn Action, contexts: &[KeymapContext], ) -> Option> { self.keymap .bindings() .iter() .rev() .find_map(|binding| binding.keystrokes_for_action(action, contexts)) } } impl Default for KeymapMatcher { fn default() -> Self { Self::new(Keymap::default()) } } pub enum MatchResult { None, Pending, Matches(Vec<(usize, Box)>), } impl Debug for MatchResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { MatchResult::None => f.debug_struct("MatchResult::None").finish(), MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(), MatchResult::Matches(matches) => f .debug_list() .entries( matches .iter() .map(|(view_id, action)| format!("{view_id}, {}", action.name())), ) .finish(), } } } impl PartialEq for MatchResult { fn eq(&self, other: &Self) -> bool { match (self, other) { (MatchResult::None, MatchResult::None) => true, (MatchResult::Pending, MatchResult::Pending) => true, (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => { matches.len() == other_matches.len() && matches.iter().zip(other_matches.iter()).all( |((view_id, action), (other_view_id, other_action))| { view_id == other_view_id && action.eq(other_action.as_ref()) }, ) } _ => false, } } } impl Eq for MatchResult {} impl Clone for MatchResult { fn clone(&self) -> Self { match self { MatchResult::None => MatchResult::None, MatchResult::Pending => MatchResult::Pending, MatchResult::Matches(matches) => MatchResult::Matches( matches .iter() .map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref()))) .collect(), ), } } } #[cfg(test)] mod tests { use anyhow::Result; use serde::Deserialize; use crate::{actions, impl_actions, keymap_matcher::KeymapContext}; use super::*; #[test] fn test_keymap_and_view_ordering() -> Result<()> { actions!(test, [EditorAction, ProjectPanelAction]); let mut editor = KeymapContext::default(); editor.add_identifier("Editor"); let mut project_panel = KeymapContext::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.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()), MatchResult::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 = KeymapContext::default(); context1.add_identifier("1"); let mut context2 = KeymapContext::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.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), MatchResult::Pending, ); // B alone doesn't match because a was pending, so AB is returned instead assert_eq!( matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), MatchResult::Matches(vec![(1, Box::new(AB))]), ); assert!(!matcher.has_pending_keystrokes()); // Without an a prefix, B is dispatched like expected assert_eq!( matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), MatchResult::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.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), MatchResult::Pending, ); assert_eq!( matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()), MatchResult::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.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()), MatchResult::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.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), MatchResult::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, } ); assert_eq!( Keystroke::parse("alt-shift-down")?, Keystroke { key: "down".into(), ctrl: false, alt: true, shift: true, cmd: false, function: false, } ); assert_eq!( Keystroke::parse("shift-cmd--")?, Keystroke { key: "-".into(), ctrl: false, alt: false, shift: true, cmd: true, function: false, } ); 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 = KeymapContext::default(); context.add_identifier("a"); assert!(!predicate.eval(&[context])); let mut context = KeymapContext::default(); context.add_identifier("a"); context.add_identifier("b"); assert!(predicate.eval(&[context])); let mut context = KeymapContext::default(); context.add_identifier("a"); context.add_key("c", "x"); assert!(!predicate.eval(&[context])); let mut context = KeymapContext::default(); context.add_identifier("a"); context.add_key("c", "d"); assert!(predicate.eval(&[context])); let predicate = KeymapContextPredicate::parse("!a").unwrap(); assert!(predicate.eval(&[KeymapContext::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]) -> KeymapContext { let mut keymap = KeymapContext::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]); #[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")), ]); let mut context_a = KeymapContext::default(); context_a.add_identifier("a"); let mut context_b = KeymapContext::default(); context_b.add_identifier("b"); let mut matcher = KeymapMatcher::new(keymap); // Basic match assert_eq!( matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))]) ); matcher.clear_pending(); // Multi-keystroke match assert_eq!( matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), MatchResult::Pending ); assert_eq!( matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), MatchResult::Matches(vec![(1, Box::new(Ab))]) ); matcher.clear_pending(); // Failed matches don't interfere with matching subsequent keys assert_eq!( matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]), MatchResult::None ); assert_eq!( matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))]) ); matcher.clear_pending(); // Pending keystrokes are cleared when the context changes assert_eq!( matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), MatchResult::Pending ); assert_eq!( matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]), MatchResult::None ); matcher.clear_pending(); let mut context_c = KeymapContext::default(); context_c.add_identifier("c"); // Pending keystrokes are maintained per-view assert_eq!( matcher.push_keystroke( Keystroke::parse("a")?, vec![(1, context_b.clone()), (2, context_c.clone())] ), MatchResult::Pending ); assert_eq!( matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), MatchResult::Matches(vec![(1, Box::new(Ab))]) ); Ok(()) } }