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
26#[derive(Default)]
27pub struct Keymap(Vec<Binding>);
28
29pub struct Binding {
30 keystrokes: Vec<Keystroke>,
31 action: Box<dyn AnyAction>,
32 context: Option<ContextPredicate>,
33}
34
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct Keystroke {
37 pub ctrl: bool,
38 pub alt: bool,
39 pub shift: bool,
40 pub cmd: bool,
41 pub key: String,
42}
43
44#[derive(Clone, Debug, Default, Eq, PartialEq)]
45pub struct Context {
46 pub set: HashSet<String>,
47 pub map: HashMap<String, String>,
48}
49
50#[derive(Debug, Eq, PartialEq)]
51enum ContextPredicate {
52 Identifier(String),
53 Equal(String, String),
54 NotEqual(String, String),
55 Not(Box<ContextPredicate>),
56 And(Box<ContextPredicate>, Box<ContextPredicate>),
57 Or(Box<ContextPredicate>, Box<ContextPredicate>),
58}
59
60trait ActionArg {
61 fn boxed_clone(&self) -> Box<dyn Any>;
62}
63
64impl<T> ActionArg for T
65where
66 T: 'static + Any + Clone,
67{
68 fn boxed_clone(&self) -> Box<dyn Any> {
69 Box::new(self.clone())
70 }
71}
72
73pub enum MatchResult {
74 None,
75 Pending,
76 Action(Box<dyn AnyAction>),
77}
78
79impl Debug for MatchResult {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 match self {
82 MatchResult::None => f.debug_struct("MatchResult::None").finish(),
83 MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
84 MatchResult::Action(action) => f
85 .debug_tuple("MatchResult::Action")
86 .field(&action.name())
87 .finish(),
88 }
89 }
90}
91
92impl Matcher {
93 pub fn new(keymap: Keymap) -> Self {
94 Self {
95 pending: HashMap::new(),
96 keymap,
97 }
98 }
99
100 pub fn set_keymap(&mut self, keymap: Keymap) {
101 self.pending.clear();
102 self.keymap = keymap;
103 }
104
105 pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
106 self.pending.clear();
107 self.keymap.add_bindings(bindings);
108 }
109
110 pub fn clear_pending(&mut self) {
111 self.pending.clear();
112 }
113
114 pub fn push_keystroke(
115 &mut self,
116 keystroke: Keystroke,
117 view_id: usize,
118 cx: &Context,
119 ) -> MatchResult {
120 let pending = self.pending.entry(view_id).or_default();
121
122 if let Some(pending_ctx) = pending.context.as_ref() {
123 if pending_ctx != cx {
124 pending.keystrokes.clear();
125 }
126 }
127
128 pending.keystrokes.push(keystroke);
129
130 let mut retain_pending = false;
131 for binding in self.keymap.0.iter().rev() {
132 if binding.keystrokes.starts_with(&pending.keystrokes)
133 && binding.context.as_ref().map(|c| c.eval(cx)).unwrap_or(true)
134 {
135 if binding.keystrokes.len() == pending.keystrokes.len() {
136 self.pending.remove(&view_id);
137 return MatchResult::Action(binding.action.boxed_clone());
138 } else {
139 retain_pending = true;
140 pending.context = Some(cx.clone());
141 }
142 }
143 }
144
145 if retain_pending {
146 MatchResult::Pending
147 } else {
148 self.pending.remove(&view_id);
149 MatchResult::None
150 }
151 }
152}
153
154impl Default for Matcher {
155 fn default() -> Self {
156 Self::new(Keymap::default())
157 }
158}
159
160impl Keymap {
161 pub fn new(bindings: Vec<Binding>) -> Self {
162 Self(bindings)
163 }
164
165 fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
166 self.0.extend(bindings.into_iter());
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 pub fn modified(&self) -> bool {
229 self.ctrl || self.alt || self.shift || self.cmd
230 }
231}
232
233impl Context {
234 pub fn extend(&mut self, other: &Context) {
235 for v in &other.set {
236 self.set.insert(v.clone());
237 }
238 for (k, v) in &other.map {
239 self.map.insert(k.clone(), v.clone());
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}