keymap.rs

  1use anyhow::anyhow;
  2use std::{
  3    any::Any,
  4    collections::{HashMap, HashSet},
  5};
  6use tree_sitter::{Language, Node, Parser};
  7
  8use crate::{Action, AnyAction};
  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
 25pub struct Keymap(Vec<Binding>);
 26
 27pub struct Binding {
 28    keystrokes: Vec<Keystroke>,
 29    action: Box<dyn AnyAction>,
 30    context: Option<ContextPredicate>,
 31}
 32
 33#[derive(Clone, Debug, Eq, PartialEq)]
 34pub struct Keystroke {
 35    pub ctrl: bool,
 36    pub alt: bool,
 37    pub shift: bool,
 38    pub cmd: bool,
 39    pub key: String,
 40}
 41
 42#[derive(Clone, Debug, Default, Eq, PartialEq)]
 43pub struct Context {
 44    pub set: HashSet<String>,
 45    pub map: HashMap<String, String>,
 46}
 47
 48#[derive(Debug, Eq, PartialEq)]
 49enum ContextPredicate {
 50    Identifier(String),
 51    Equal(String, String),
 52    NotEqual(String, String),
 53    Not(Box<ContextPredicate>),
 54    And(Box<ContextPredicate>, Box<ContextPredicate>),
 55    Or(Box<ContextPredicate>, Box<ContextPredicate>),
 56}
 57
 58trait ActionArg {
 59    fn boxed_clone(&self) -> Box<dyn Any>;
 60}
 61
 62impl<T> ActionArg for T
 63where
 64    T: 'static + Any + Clone,
 65{
 66    fn boxed_clone(&self) -> Box<dyn Any> {
 67        Box::new(self.clone())
 68    }
 69}
 70
 71pub enum MatchResult {
 72    None,
 73    Pending,
 74    Action(Box<dyn AnyAction>),
 75}
 76
 77impl Matcher {
 78    pub fn new(keymap: Keymap) -> Self {
 79        Self {
 80            pending: HashMap::new(),
 81            keymap,
 82        }
 83    }
 84
 85    pub fn set_keymap(&mut self, keymap: Keymap) {
 86        self.pending.clear();
 87        self.keymap = keymap;
 88    }
 89
 90    pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
 91        self.pending.clear();
 92        self.keymap.add_bindings(bindings);
 93    }
 94
 95    pub fn push_keystroke(
 96        &mut self,
 97        keystroke: Keystroke,
 98        view_id: usize,
 99        cx: &Context,
100    ) -> MatchResult {
101        let pending = self.pending.entry(view_id).or_default();
102
103        if let Some(pending_ctx) = pending.context.as_ref() {
104            if pending_ctx != cx {
105                pending.keystrokes.clear();
106            }
107        }
108
109        pending.keystrokes.push(keystroke);
110
111        let mut retain_pending = false;
112        for binding in self.keymap.0.iter().rev() {
113            if binding.keystrokes.starts_with(&pending.keystrokes)
114                && binding.context.as_ref().map(|c| c.eval(cx)).unwrap_or(true)
115            {
116                if binding.keystrokes.len() == pending.keystrokes.len() {
117                    self.pending.remove(&view_id);
118                    return MatchResult::Action(binding.action.boxed_clone());
119                } else {
120                    retain_pending = true;
121                    pending.context = Some(cx.clone());
122                }
123            }
124        }
125
126        if retain_pending {
127            MatchResult::Pending
128        } else {
129            self.pending.remove(&view_id);
130            MatchResult::None
131        }
132    }
133}
134
135impl Default for Matcher {
136    fn default() -> Self {
137        Self::new(Keymap::default())
138    }
139}
140
141impl Keymap {
142    pub fn new(bindings: Vec<Binding>) -> Self {
143        Self(bindings)
144    }
145
146    fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
147        self.0.extend(bindings.into_iter());
148    }
149}
150
151mod menu {
152    use crate::action;
153
154    action!(SelectPrev);
155    action!(SelectNext);
156}
157
158impl Default for Keymap {
159    fn default() -> Self {
160        Self(vec![
161            Binding::new("up", menu::SelectPrev, Some("menu")),
162            Binding::new("ctrl-p", menu::SelectPrev, Some("menu")),
163            Binding::new("down", menu::SelectNext, Some("menu")),
164            Binding::new("ctrl-n", menu::SelectNext, Some("menu")),
165        ])
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
228impl Context {
229    pub fn extend(&mut self, other: Context) {
230        for v in other.set {
231            self.set.insert(v);
232        }
233        for (k, v) in other.map {
234            self.map.insert(k, v);
235        }
236    }
237}
238
239impl ContextPredicate {
240    fn parse(source: &str) -> anyhow::Result<Self> {
241        let mut parser = Parser::new();
242        let language = unsafe { tree_sitter_context_predicate() };
243        parser.set_language(language).unwrap();
244        let source = source.as_bytes();
245        let tree = parser.parse(source, None).unwrap();
246        Self::from_node(tree.root_node(), source)
247    }
248
249    fn from_node(node: Node, source: &[u8]) -> anyhow::Result<Self> {
250        let parse_error = "error parsing context predicate";
251        let kind = node.kind();
252
253        match kind {
254            "source" => Self::from_node(node.child(0).ok_or(anyhow!(parse_error))?, source),
255            "identifier" => Ok(Self::Identifier(node.utf8_text(source)?.into())),
256            "not" => {
257                let child = Self::from_node(
258                    node.child_by_field_name("expression")
259                        .ok_or(anyhow!(parse_error))?,
260                    source,
261                )?;
262                Ok(Self::Not(Box::new(child)))
263            }
264            "and" | "or" => {
265                let left = Box::new(Self::from_node(
266                    node.child_by_field_name("left")
267                        .ok_or(anyhow!(parse_error))?,
268                    source,
269                )?);
270                let right = Box::new(Self::from_node(
271                    node.child_by_field_name("right")
272                        .ok_or(anyhow!(parse_error))?,
273                    source,
274                )?);
275                if kind == "and" {
276                    Ok(Self::And(left, right))
277                } else {
278                    Ok(Self::Or(left, right))
279                }
280            }
281            "equal" | "not_equal" => {
282                let left = node
283                    .child_by_field_name("left")
284                    .ok_or(anyhow!(parse_error))?
285                    .utf8_text(source)?
286                    .into();
287                let right = node
288                    .child_by_field_name("right")
289                    .ok_or(anyhow!(parse_error))?
290                    .utf8_text(source)?
291                    .into();
292                if kind == "equal" {
293                    Ok(Self::Equal(left, right))
294                } else {
295                    Ok(Self::NotEqual(left, right))
296                }
297            }
298            "parenthesized" => Self::from_node(
299                node.child_by_field_name("expression")
300                    .ok_or(anyhow!(parse_error))?,
301                source,
302            ),
303            _ => Err(anyhow!(parse_error)),
304        }
305    }
306
307    fn eval(&self, cx: &Context) -> bool {
308        match self {
309            Self::Identifier(name) => cx.set.contains(name.as_str()),
310            Self::Equal(left, right) => cx
311                .map
312                .get(left)
313                .map(|value| value == right)
314                .unwrap_or(false),
315            Self::NotEqual(left, right) => {
316                cx.map.get(left).map(|value| value != right).unwrap_or(true)
317            }
318            Self::Not(pred) => !pred.eval(cx),
319            Self::And(left, right) => left.eval(cx) && right.eval(cx),
320            Self::Or(left, right) => left.eval(cx) || right.eval(cx),
321        }
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use crate::action;
328
329    use super::*;
330
331    #[test]
332    fn test_keystroke_parsing() -> anyhow::Result<()> {
333        assert_eq!(
334            Keystroke::parse("ctrl-p")?,
335            Keystroke {
336                key: "p".into(),
337                ctrl: true,
338                alt: false,
339                shift: false,
340                cmd: false,
341            }
342        );
343
344        assert_eq!(
345            Keystroke::parse("alt-shift-down")?,
346            Keystroke {
347                key: "down".into(),
348                ctrl: false,
349                alt: true,
350                shift: true,
351                cmd: false,
352            }
353        );
354
355        assert_eq!(
356            Keystroke::parse("shift-cmd--")?,
357            Keystroke {
358                key: "-".into(),
359                ctrl: false,
360                alt: false,
361                shift: true,
362                cmd: true,
363            }
364        );
365
366        Ok(())
367    }
368
369    #[test]
370    fn test_context_predicate_parsing() -> anyhow::Result<()> {
371        use ContextPredicate::*;
372
373        assert_eq!(
374            ContextPredicate::parse("a && (b == c || d != e)")?,
375            And(
376                Box::new(Identifier("a".into())),
377                Box::new(Or(
378                    Box::new(Equal("b".into(), "c".into())),
379                    Box::new(NotEqual("d".into(), "e".into())),
380                ))
381            )
382        );
383
384        assert_eq!(
385            ContextPredicate::parse("!a")?,
386            Not(Box::new(Identifier("a".into())),)
387        );
388
389        Ok(())
390    }
391
392    #[test]
393    fn test_context_predicate_eval() -> anyhow::Result<()> {
394        let predicate = ContextPredicate::parse("a && b || c == d")?;
395
396        let mut context = Context::default();
397        context.set.insert("a".into());
398        assert!(!predicate.eval(&context));
399
400        context.set.insert("b".into());
401        assert!(predicate.eval(&context));
402
403        context.set.remove("b");
404        context.map.insert("c".into(), "x".into());
405        assert!(!predicate.eval(&context));
406
407        context.map.insert("c".into(), "d".into());
408        assert!(predicate.eval(&context));
409
410        let predicate = ContextPredicate::parse("!a")?;
411        assert!(predicate.eval(&Context::default()));
412
413        Ok(())
414    }
415
416    #[test]
417    fn test_matcher() -> anyhow::Result<()> {
418        action!(A, &'static str);
419        action!(B);
420        action!(Ab);
421
422        impl PartialEq for A {
423            fn eq(&self, other: &Self) -> bool {
424                self.0 == other.0
425            }
426        }
427        impl Eq for A {}
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"), 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!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
450
451        // Multi-keystroke match
452        assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
453        assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
454
455        // Failed matches don't interfere with matching subsequent keys
456        assert_eq!(matcher.test_keystroke::<()>("x", 1, &ctx_a), None);
457        assert_eq!(matcher.test_keystroke("a", 1, &ctx_a), Some(A("x")));
458
459        // Pending keystrokes are cleared when the context changes
460        assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
461        assert_eq!(matcher.test_keystroke("b", 1, &ctx_a), Some(B));
462
463        let mut ctx_c = Context::default();
464        ctx_c.set.insert("c".into());
465
466        // Pending keystrokes are maintained per-view
467        assert_eq!(matcher.test_keystroke::<()>("a", 1, &ctx_b), None);
468        assert_eq!(matcher.test_keystroke::<()>("a", 2, &ctx_c), None);
469        assert_eq!(matcher.test_keystroke("b", 1, &ctx_b), Some(Ab));
470
471        Ok(())
472    }
473
474    impl Matcher {
475        fn test_keystroke<A: Action + Eq>(
476            &mut self,
477            keystroke: &str,
478            view_id: usize,
479            cx: &Context,
480        ) -> Option<A> {
481            if let MatchResult::Action(action) =
482                self.push_keystroke(Keystroke::parse(keystroke).unwrap(), view_id, cx)
483            {
484                Some(*action.boxed_clone_as_any().downcast().unwrap())
485            } else {
486                None
487            }
488        }
489    }
490}