1use anyhow::Result;
2use smallvec::SmallVec;
3
4use crate::Action;
5
6use super::{KeymapContext, KeymapContextPredicate, Keystroke};
7
8pub struct Binding {
9 action: Box<dyn Action>,
10 keystrokes: SmallVec<[Keystroke; 2]>,
11 context_predicate: Option<KeymapContextPredicate>,
12}
13
14impl Binding {
15 pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
16 Self::load(keystrokes, Box::new(action), context).unwrap()
17 }
18
19 pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
20 let context = if let Some(context) = context {
21 Some(KeymapContextPredicate::parse(context)?)
22 } else {
23 None
24 };
25
26 let keystrokes = keystrokes
27 .split_whitespace()
28 .map(Keystroke::parse)
29 .collect::<Result<_>>()?;
30
31 Ok(Self {
32 keystrokes,
33 action,
34 context_predicate: context,
35 })
36 }
37
38 pub fn match_context(&self, contexts: &[KeymapContext]) -> bool {
39 self.context_predicate
40 .as_ref()
41 .map(|predicate| predicate.eval(contexts))
42 .unwrap_or(true)
43 }
44
45 pub fn match_keys_and_context(
46 &self,
47 pending_keystrokes: &Vec<Keystroke>,
48 contexts: &[KeymapContext],
49 ) -> BindingMatchResult {
50 if self.keystrokes.as_ref().starts_with(&pending_keystrokes) && self.match_context(contexts)
51 {
52 // If the binding is completed, push it onto the matches list
53 if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
54 BindingMatchResult::Complete(self.action.boxed_clone())
55 } else {
56 BindingMatchResult::Partial
57 }
58 } else {
59 BindingMatchResult::Fail
60 }
61 }
62
63 pub fn keystrokes_for_action(
64 &self,
65 action: &dyn Action,
66 contexts: &[KeymapContext],
67 ) -> Option<SmallVec<[Keystroke; 2]>> {
68 if self.action.eq(action) && self.match_context(contexts) {
69 Some(self.keystrokes.clone())
70 } else {
71 None
72 }
73 }
74
75 pub fn keystrokes(&self) -> &[Keystroke] {
76 self.keystrokes.as_slice()
77 }
78
79 pub fn action(&self) -> &dyn Action {
80 self.action.as_ref()
81 }
82}
83
84pub enum BindingMatchResult {
85 Complete(Box<dyn Action>),
86 Partial,
87 Fail,
88}