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}