keymap.rs

  1use crate::Action;
  2use anyhow::anyhow;
  3use std::{
  4    any::Any,
  5    collections::{HashMap, HashSet},
  6    fmt::Debug,
  7};
  8use tree_sitter::{Language, Node, Parser};
  9
 10extern "C" {
 11    fn tree_sitter_context_predicate() -> Language;
 12}
 13
 14pub struct Matcher {
 15    pending: HashMap<usize, Pending>,
 16    keymap: Keymap,
 17}
 18
 19#[derive(Default)]
 20struct Pending {
 21    keystrokes: Vec<Keystroke>,
 22    context: Option<Context>,
 23}
 24
 25#[derive(Default)]
 26pub struct Keymap(Vec<Binding>);
 27
 28pub struct Binding {
 29    keystrokes: Vec<Keystroke>,
 30    action: Box<dyn Action>,
 31    context: Option<ContextPredicate>,
 32}
 33
 34#[derive(Clone, Debug, Eq, PartialEq)]
 35pub struct Keystroke {
 36    pub ctrl: bool,
 37    pub alt: bool,
 38    pub shift: bool,
 39    pub cmd: bool,
 40    pub key: String,
 41}
 42
 43#[derive(Clone, Debug, Default, Eq, PartialEq)]
 44pub struct Context {
 45    pub set: HashSet<String>,
 46    pub map: HashMap<String, String>,
 47}
 48
 49#[derive(Debug, Eq, PartialEq)]
 50enum ContextPredicate {
 51    Identifier(String),
 52    Equal(String, String),
 53    NotEqual(String, String),
 54    Not(Box<ContextPredicate>),
 55    And(Box<ContextPredicate>, Box<ContextPredicate>),
 56    Or(Box<ContextPredicate>, Box<ContextPredicate>),
 57}
 58
 59trait ActionArg {
 60    fn boxed_clone(&self) -> Box<dyn Any>;
 61}
 62
 63impl<T> ActionArg for T
 64where
 65    T: 'static + Any + Clone,
 66{
 67    fn boxed_clone(&self) -> Box<dyn Any> {
 68        Box::new(self.clone())
 69    }
 70}
 71
 72pub enum MatchResult {
 73    None,
 74    Pending,
 75    Action(Box<dyn Action>),
 76}
 77
 78impl Debug for MatchResult {
 79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 80        match self {
 81            MatchResult::None => f.debug_struct("MatchResult::None").finish(),
 82            MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
 83            MatchResult::Action(action) => f
 84                .debug_tuple("MatchResult::Action")
 85                .field(&action.name())
 86                .finish(),
 87        }
 88    }
 89}
 90
 91impl Matcher {
 92    pub fn new(keymap: Keymap) -> Self {
 93        Self {
 94            pending: HashMap::new(),
 95            keymap,
 96        }
 97    }
 98
 99    pub fn set_keymap(&mut self, keymap: Keymap) {
100        self.pending.clear();
101        self.keymap = keymap;
102    }
103
104    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
105        self.pending.clear();
106        self.keymap.add_bindings(bindings);
107    }
108
109    pub fn clear_pending(&mut self) {
110        self.pending.clear();
111    }
112
113    pub fn push_keystroke(
114        &mut self,
115        keystroke: Keystroke,
116        view_id: usize,
117        cx: &Context,
118    ) -> MatchResult {
119        let pending = self.pending.entry(view_id).or_default();
120
121        if let Some(pending_ctx) = pending.context.as_ref() {
122            if pending_ctx != cx {
123                pending.keystrokes.clear();
124            }
125        }
126
127        pending.keystrokes.push(keystroke);
128
129        let mut retain_pending = false;
130        for binding in self.keymap.0.iter().rev() {
131            if binding.keystrokes.starts_with(&pending.keystrokes)
132                && binding.context.as_ref().map(|c| c.eval(cx)).unwrap_or(true)
133            {
134                if binding.keystrokes.len() == pending.keystrokes.len() {
135                    self.pending.remove(&view_id);
136                    return MatchResult::Action(binding.action.boxed_clone());
137                } else {
138                    retain_pending = true;
139                    pending.context = Some(cx.clone());
140                }
141            }
142        }
143
144        if retain_pending {
145            MatchResult::Pending
146        } else {
147            self.pending.remove(&view_id);
148            MatchResult::None
149        }
150    }
151}
152
153impl Default for Matcher {
154    fn default() -> Self {
155        Self::new(Keymap::default())
156    }
157}
158
159impl Keymap {
160    pub fn new(bindings: Vec<Binding>) -> Self {
161        Self(bindings)
162    }
163
164    fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
165        self.0.extend(bindings.into_iter());
166    }
167}
168
169impl Binding {
170    pub fn new<A: Action>(keystrokes: &str, action: A, context: Option<&str>) -> Self {
171        let context = if let Some(context) = context {
172            Some(ContextPredicate::parse(context).unwrap())
173        } else {
174            None
175        };
176
177        Self {
178            keystrokes: keystrokes
179                .split_whitespace()
180                .map(|key| Keystroke::parse(key).unwrap())
181                .collect(),
182            action: Box::new(action),
183            context,
184        }
185    }
186}
187
188impl Keystroke {
189    pub fn parse(source: &str) -> anyhow::Result<Self> {
190        let mut ctrl = false;
191        let mut alt = false;
192        let mut shift = false;
193        let mut cmd = false;
194        let mut key = None;
195
196        let mut components = source.split("-").peekable();
197        while let Some(component) = components.next() {
198            match component {
199                "ctrl" => ctrl = true,
200                "alt" => alt = true,
201                "shift" => shift = true,
202                "cmd" => cmd = true,
203                _ => {
204                    if let Some(component) = components.peek() {
205                        if component.is_empty() && source.ends_with('-') {
206                            key = Some(String::from("-"));
207                            break;
208                        } else {
209                            return Err(anyhow!("Invalid keystroke `{}`", source));
210                        }
211                    } else {
212                        key = Some(String::from(component));
213                    }
214                }
215            }
216        }
217
218        Ok(Keystroke {
219            ctrl,
220            alt,
221            shift,
222            cmd,
223            key: key.unwrap(),
224        })
225    }
226
227    pub fn modified(&self) -> bool {
228        self.ctrl || self.alt || self.shift || self.cmd
229    }
230}
231
232impl Context {
233    pub fn extend(&mut self, other: &Context) {
234        for v in &other.set {
235            self.set.insert(v.clone());
236        }
237        for (k, v) in &other.map {
238            self.map.insert(k.clone(), v.clone());
239        }
240    }
241}
242
243impl ContextPredicate {
244    fn parse(source: &str) -> anyhow::Result<Self> {
245        let mut parser = Parser::new();
246        let language = unsafe { tree_sitter_context_predicate() };
247        parser.set_language(language).unwrap();
248        let source = source.as_bytes();
249        let tree = parser.parse(source, None).unwrap();
250        Self::from_node(tree.root_node(), source)
251    }
252
253    fn from_node(node: Node, source: &[u8]) -> anyhow::Result<Self> {
254        let parse_error = "error parsing context predicate";
255        let kind = node.kind();
256
257        match kind {
258            "source" => Self::from_node(node.child(0).ok_or(anyhow!(parse_error))?, source),
259            "identifier" => Ok(Self::Identifier(node.utf8_text(source)?.into())),
260            "not" => {
261                let child = Self::from_node(
262                    node.child_by_field_name("expression")
263                        .ok_or(anyhow!(parse_error))?,
264                    source,
265                )?;
266                Ok(Self::Not(Box::new(child)))
267            }
268            "and" | "or" => {
269                let left = Box::new(Self::from_node(
270                    node.child_by_field_name("left")
271                        .ok_or(anyhow!(parse_error))?,
272                    source,
273                )?);
274                let right = Box::new(Self::from_node(
275                    node.child_by_field_name("right")
276                        .ok_or(anyhow!(parse_error))?,
277                    source,
278                )?);
279                if kind == "and" {
280                    Ok(Self::And(left, right))
281                } else {
282                    Ok(Self::Or(left, right))
283                }
284            }
285            "equal" | "not_equal" => {
286                let left = node
287                    .child_by_field_name("left")
288                    .ok_or(anyhow!(parse_error))?
289                    .utf8_text(source)?
290                    .into();
291                let right = node
292                    .child_by_field_name("right")
293                    .ok_or(anyhow!(parse_error))?
294                    .utf8_text(source)?
295                    .into();
296                if kind == "equal" {
297                    Ok(Self::Equal(left, right))
298                } else {
299                    Ok(Self::NotEqual(left, right))
300                }
301            }
302            "parenthesized" => Self::from_node(
303                node.child_by_field_name("expression")
304                    .ok_or(anyhow!(parse_error))?,
305                source,
306            ),
307            _ => Err(anyhow!(parse_error)),
308        }
309    }
310
311    fn eval(&self, cx: &Context) -> bool {
312        match self {
313            Self::Identifier(name) => cx.set.contains(name.as_str()),
314            Self::Equal(left, right) => cx
315                .map
316                .get(left)
317                .map(|value| value == right)
318                .unwrap_or(false),
319            Self::NotEqual(left, right) => {
320                cx.map.get(left).map(|value| value != right).unwrap_or(true)
321            }
322            Self::Not(pred) => !pred.eval(cx),
323            Self::And(left, right) => left.eval(cx) && right.eval(cx),
324            Self::Or(left, right) => left.eval(cx) || right.eval(cx),
325        }
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use serde::Deserialize;
332
333    use crate::{actions, impl_actions};
334
335    use super::*;
336
337    #[test]
338    fn test_keystroke_parsing() -> anyhow::Result<()> {
339        assert_eq!(
340            Keystroke::parse("ctrl-p")?,
341            Keystroke {
342                key: "p".into(),
343                ctrl: true,
344                alt: false,
345                shift: false,
346                cmd: false,
347            }
348        );
349
350        assert_eq!(
351            Keystroke::parse("alt-shift-down")?,
352            Keystroke {
353                key: "down".into(),
354                ctrl: false,
355                alt: true,
356                shift: true,
357                cmd: false,
358            }
359        );
360
361        assert_eq!(
362            Keystroke::parse("shift-cmd--")?,
363            Keystroke {
364                key: "-".into(),
365                ctrl: false,
366                alt: false,
367                shift: true,
368                cmd: true,
369            }
370        );
371
372        Ok(())
373    }
374
375    #[test]
376    fn test_context_predicate_parsing() -> anyhow::Result<()> {
377        use ContextPredicate::*;
378
379        assert_eq!(
380            ContextPredicate::parse("a && (b == c || d != e)")?,
381            And(
382                Box::new(Identifier("a".into())),
383                Box::new(Or(
384                    Box::new(Equal("b".into(), "c".into())),
385                    Box::new(NotEqual("d".into(), "e".into())),
386                ))
387            )
388        );
389
390        assert_eq!(
391            ContextPredicate::parse("!a")?,
392            Not(Box::new(Identifier("a".into())),)
393        );
394
395        Ok(())
396    }
397
398    #[test]
399    fn test_context_predicate_eval() -> anyhow::Result<()> {
400        let predicate = ContextPredicate::parse("a && b || c == d")?;
401
402        let mut context = Context::default();
403        context.set.insert("a".into());
404        assert!(!predicate.eval(&context));
405
406        context.set.insert("b".into());
407        assert!(predicate.eval(&context));
408
409        context.set.remove("b");
410        context.map.insert("c".into(), "x".into());
411        assert!(!predicate.eval(&context));
412
413        context.map.insert("c".into(), "d".into());
414        assert!(predicate.eval(&context));
415
416        let predicate = ContextPredicate::parse("!a")?;
417        assert!(predicate.eval(&Context::default()));
418
419        Ok(())
420    }
421
422    #[test]
423    fn test_matcher() -> anyhow::Result<()> {
424        #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
425        pub struct A(pub String);
426        impl_actions!(test, [A]);
427        actions!(test, [B, Ab]);
428
429        #[derive(Clone, Debug, Eq, PartialEq)]
430        struct ActionArg {
431            a: &'static str,
432        }
433
434        let keymap = Keymap(vec![
435            Binding::new("a", A("x".to_string()), Some("a")),
436            Binding::new("b", B, Some("a")),
437            Binding::new("a b", Ab, Some("a || b")),
438        ]);
439
440        let mut ctx_a = Context::default();
441        ctx_a.set.insert("a".into());
442
443        let mut ctx_b = Context::default();
444        ctx_b.set.insert("b".into());
445
446        let mut matcher = Matcher::new(keymap);
447
448        // Basic match
449        assert_eq!(
450            downcast(&matcher.test_keystroke("a", 1, &ctx_a)),
451            Some(&A("x".to_string()))
452        );
453
454        // Multi-keystroke match
455        assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none());
456        assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab));
457
458        // Failed matches don't interfere with matching subsequent keys
459        assert!(matcher.test_keystroke("x", 1, &ctx_a).is_none());
460        assert_eq!(
461            downcast(&matcher.test_keystroke("a", 1, &ctx_a)),
462            Some(&A("x".to_string()))
463        );
464
465        // Pending keystrokes are cleared when the context changes
466        assert!(&matcher.test_keystroke("a", 1, &ctx_b).is_none());
467        assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_a)), Some(&B));
468
469        let mut ctx_c = Context::default();
470        ctx_c.set.insert("c".into());
471
472        // Pending keystrokes are maintained per-view
473        assert!(matcher.test_keystroke("a", 1, &ctx_b).is_none());
474        assert!(matcher.test_keystroke("a", 2, &ctx_c).is_none());
475        assert_eq!(downcast(&matcher.test_keystroke("b", 1, &ctx_b)), Some(&Ab));
476
477        Ok(())
478    }
479
480    fn downcast<'a, A: Action>(action: &'a Option<Box<dyn Action>>) -> Option<&'a A> {
481        action
482            .as_ref()
483            .and_then(|action| action.as_any().downcast_ref())
484    }
485
486    impl Matcher {
487        fn test_keystroke(
488            &mut self,
489            keystroke: &str,
490            view_id: usize,
491            cx: &Context,
492        ) -> Option<Box<dyn Action>> {
493            if let MatchResult::Action(action) =
494                self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx)
495            {
496                Some(action.boxed_clone())
497            } else {
498                None
499            }
500        }
501    }
502}