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