keymap.rs

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