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}