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}