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}; #[derive(Default)] pub struct Matcher { pending: HashMap, keymap: Keymap, /// Default binding validator that should run on every binding (irrespective of the [`Context`] /// the binding was registered against). default_binding_validator: Option, /// List of validators to be used during binding validation. Each binding validator validates /// all of the bindings that match the [`Context`] it is paired with. binding_validators: Vec<(Context, BindingValidatorFn)>, /// Function to convert bindings that have a [`CustomTag`] trigger to one that has a /// [`Keystroke`]-based trigger instead. If `None`, bindings are not converted. custom_trigger_to_keystroke_fn: Option Option + 'static>>, /// Function to lookup the default keystroke for a given custom action. Used when converting /// custom actions to key events during keybinding editing. default_keystroke_trigger_for_custom_action: Option Option + 'static>>, } #[derive(Default)] struct Pending { keystrokes: Vec, context: Option, } type BindingValidatorFn = Box IsBindingValid>; /// Enum indicating the results of validating a binding. #[derive(Debug, PartialEq)] pub enum IsBindingValid { /// The binding is valid. Yes, /// The binding is invalid. No, } pub enum MatchResult { None, Pending, Action(Arc), } impl Matcher { pub fn new(keymap: Keymap) -> Self { Self { pending: HashMap::new(), keymap, default_binding_validator: None, binding_validators: vec![], custom_trigger_to_keystroke_fn: None, default_keystroke_trigger_for_custom_action: None, } } pub fn set_keymap(&mut self, keymap: Keymap) { self.pending.clear(); self.keymap = keymap; } /// Helper function to that returns [`Trigger`] with any [`Trigger::Custom`]s replaced by a /// [`Trigger::Keystrokes`]. fn convert_custom_trigger_to_keystroke_trigger( trigger: Trigger, custom_tag_to_keystroke: &dyn Fn(CustomTag) -> Option, ) -> Trigger { let Trigger::Custom(custom_tag) = trigger else { return trigger; }; let Some(new_keystroke) = custom_tag_to_keystroke(custom_tag) else { return trigger; }; Trigger::Keystrokes(vec![new_keystroke]) } pub fn register_fixed_bindings>(&mut self, bindings: T) { self.pending.clear(); let bindings = match &self.custom_trigger_to_keystroke_fn { None => Either::Left(bindings), Some(custom_tag_to_keystroke) => { let bindings = bindings.into_iter().map(|mut fixed_binding| { fixed_binding.trigger = Self::convert_custom_trigger_to_keystroke_trigger( fixed_binding.trigger, custom_tag_to_keystroke, ); fixed_binding }); Either::Right(bindings) } }; self.keymap.register_fixed_bindings(bindings.into_iter()); } /// Register new actions with the key matcher /// /// Editable Bindings have a name identifier which can be used to override their key bindings /// via the `set_custom_trigger` method. pub fn register_editable_bindings>( &mut self, actions: A, ) { self.pending.clear(); let actions = match &self.custom_trigger_to_keystroke_fn { None => Either::Left(actions), Some(custom_tag_to_keystroke) => { let bindings = actions.into_iter().map(|mut editable_binding| { editable_binding.trigger = Self::convert_custom_trigger_to_keystroke_trigger( editable_binding.trigger, custom_tag_to_keystroke, ); editable_binding }); Either::Right(bindings) } }; self.keymap.register_editable_bindings(actions.into_iter()); } /// Set a custom trigger for a given editable binding name. /// /// This will override the default trigger for that action. pub fn set_custom_trigger(&mut self, name: String, trigger: Trigger) { self.pending.clear(); self.keymap .update_custom_trigger(name.as_str(), Some(trigger)); } /// Remove any custom trigger associated with a given action. /// /// This will return the trigger to its default state. pub fn remove_custom_trigger(&mut self, name: N) where N: AsRef, { self.pending.clear(); self.keymap.update_custom_trigger(name.as_ref(), None); } /// Registers a validator that validates every binding that matches the given view's default /// [`Context`]. /// After the app is initialized, the provided `binding_validator` function is called for every /// binding that matches the View's default context. If the binding is invalid (indicated by /// [`IsBindingValid::No`]), the app will panic if `debug_assertions` are enabled. #[cfg(debug_assertions)] pub(crate) fn register_binding_validator IsBindingValid + 'static>( &mut self, context: Context, binding_validator: F, ) { self.binding_validators .push((context, Box::new(binding_validator))); } /// Sets a default binding validator that runs on _every_ binding that is registered by the /// application. #[cfg(debug_assertions)] pub(crate) fn set_default_binding_validator IsBindingValid + 'static>( &mut self, binding_validator: F, ) { self.default_binding_validator = Some(Box::new(binding_validator)); } /// Runs through each registered binding validator, asserting that each matching binding is /// valid. #[cfg(debug_assertions)] pub(crate) fn validate_bindings(&mut self) { let mut all_failed_bindings = vec![]; for (context, validator) in &self.binding_validators { for binding in self.bindings_for_context(context.clone()) { if let IsBindingValid::No = validator(binding) { all_failed_bindings.push(binding); } } } if let Some(default_validator) = &self.default_binding_validator { for binding in self.get_bindings() { if let IsBindingValid::No = default_validator(binding) { all_failed_bindings.push(binding); } } } if !all_failed_bindings.is_empty() { panic!("Bindings failed validation {all_failed_bindings:#?}"); } } /// Overrides any registered binding that has a [`Trigger::Custom`] to one that is keystroke /// based ([`Trigger::Keystrokes`]) using the provided `custom_to_keystroke` fn. pub(crate) fn convert_custom_triggers_to_keystroke_triggers( &mut self, custom_to_keystroke: impl Fn(CustomTag) -> Option + 'static, ) { self.custom_trigger_to_keystroke_fn = Some(Box::new(custom_to_keystroke)); } /// Registers a lookup function that returns the default keystroke for a given custom action. /// Used when converting custom actions to key events during keybinding editing. pub(crate) fn register_default_keystroke_triggers_for_custom_actions( &mut self, custom_to_keystroke: impl Fn(CustomTag) -> Option + 'static, ) { self.default_keystroke_trigger_for_custom_action = Some(Box::new(custom_to_keystroke)); } pub(crate) fn custom_action_bindings(&self) -> impl Iterator> { self.keymap.custom_action_bindings() } /// Returns the first matching binding for the given custom action (not taking) /// into account the current context pub fn default_binding_for_custom_action( &self, custom_tag: CustomTag, ) -> Option> { self.keymap .bindings() // Filter out just the matching custom binding or action. // We look for matches against either the current or original trigger. .find(|binding| { matches!( (binding.trigger, binding.original_trigger), (Trigger::Custom(tag), _) | (_, Some(Trigger::Custom(tag))) if *tag == custom_tag ) }) } /// Returns any matching binding for the given custom tag and context pub fn binding_for_custom_action_in_context( &self, custom_tag: CustomTag, context: &Context, ) -> Option> { self.keymap .custom_action_bindings() // First filter out just the matching custom binding or action // We look for matches against either the current or original trigger. .filter(|binding| { matches!( (binding.trigger, binding.original_trigger), (Trigger::Custom(tag), _) | (_, Some(Trigger::Custom(tag))) if *tag == custom_tag ) }) // And then filter against the current context and return the first match .find(move |binding| binding.context_predicate.eval(context)) } pub fn default_keystroke_trigger_for_custom_action( &self, custom_tag: CustomTag, ) -> Option { self.default_keystroke_trigger_for_custom_action .as_ref() .and_then(|f| f(custom_tag)) } pub fn get_binding_by_name(&self, name: &str) -> Option> { self.keymap.get_binding_by_name(name) } /// Returns an iterator of lenses to key bindings that apply to the given context. /// /// Key bindings are returned in precedence order, so the highest precedence key binding is /// returned first. pub fn bindings_for_context(&self, context: Context) -> impl Iterator> { self.keymap .bindings() .filter(move |binding| binding.context_predicate.eval(&context)) } /// Fetch an iterator of editable bindings /// /// The triggers for those actions will be overwritten by any custom triggers /// /// Items will be returned in the reverse order they were registered, the most recently /// registered editable binding will have the highest precedence pub fn editable_bindings(&self) -> impl Iterator> { self.keymap.editable_bindings() } /// Fetch an iterator of `BindingLens` objects, with the editable key bindings /// modified by the custom bindings, where appropriate. /// /// Editable bindings will be returned first, followed by any fixed bindings in the reverse /// order they were added. pub fn get_bindings(&self) -> impl Iterator> { self.keymap.bindings() } pub fn push_keystroke( &mut self, keystroke: Keystroke, view_id: EntityId, ctx: &Context, ) -> MatchResult { let pending = self.pending.entry(view_id).or_default(); if let Some(pending_ctx) = pending.context.as_ref() { if pending_ctx != ctx { pending.keystrokes.clear(); } } pending.keystrokes.push(keystroke); let mut retain_pending = false; for binding in self.keymap.bindings() { if let Trigger::Keystrokes(keystrokes) = &binding.trigger { if keystrokes.starts_with(&pending.keystrokes) && binding.context_predicate.eval(ctx) { if keystrokes.len() == pending.keystrokes.len() { self.pending.remove(&view_id); return MatchResult::Action(binding.action.clone()); } else { retain_pending = true; pending.context = Some(ctx.clone()); } } } } if retain_pending { MatchResult::Pending } else { self.pending.remove(&view_id); MatchResult::None } } // Attempt to match with a StandardAction. // This returns None or Action, never Pending. pub fn match_standard(&self, action: StandardAction, ctx: &Context) -> MatchResult { for binding in self.keymap.bindings() { if let Trigger::Standard(triggeract) = binding.trigger { if *triggeract == action && binding.context_predicate.eval(ctx) { return MatchResult::Action(binding.action.clone()); } } } MatchResult::None } // Attempt to match with a CustomAction. // This returns None or Action, never Pending. pub fn match_custom(&self, action: CustomTag, ctx: &Context) -> MatchResult { for binding in self.keymap.bindings() { if let Trigger::Custom(tag) = binding.trigger { if *tag == action && binding.context_predicate.eval(ctx) { return MatchResult::Action(binding.action.clone()); } } if let Some(Trigger::Custom(tag)) = binding.original_trigger { if *tag == action && binding.context_predicate.eval(ctx) { return MatchResult::Action(binding.action.clone()); } } } MatchResult::None } } #[cfg(test)] #[path = "matcher_test.rs"] mod tests;