1use crate::SharedString;
2use anyhow::{Context as _, Result};
3use std::fmt;
4
5/// A datastructure for resolving whether an action should be dispatched
6/// at this point in the element tree. Contains a set of identifiers
7/// and/or key value pairs representing the current context for the
8/// keymap.
9#[derive(Clone, Default, Eq, PartialEq, Hash)]
10pub struct KeyContext(Vec<ContextEntry>);
11
12#[derive(Clone, Debug, Eq, PartialEq, Hash)]
13/// An entry in a KeyContext
14pub struct ContextEntry {
15 /// The key (or name if no value)
16 pub key: SharedString,
17 /// The value
18 pub value: Option<SharedString>,
19}
20
21impl<'a> TryFrom<&'a str> for KeyContext {
22 type Error = anyhow::Error;
23
24 fn try_from(value: &'a str) -> Result<Self> {
25 Self::parse(value)
26 }
27}
28
29impl KeyContext {
30 /// Initialize a new [`KeyContext`] that contains an `os` key set to either `macos`, `linux`, `windows` or `unknown`.
31 pub fn new_with_defaults() -> Self {
32 let mut context = Self::default();
33 #[cfg(target_os = "macos")]
34 context.set("os", "macos");
35 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
36 context.set("os", "linux");
37 #[cfg(target_os = "windows")]
38 context.set("os", "windows");
39 #[cfg(not(any(
40 target_os = "macos",
41 target_os = "linux",
42 target_os = "freebsd",
43 target_os = "windows"
44 )))]
45 context.set("os", "unknown");
46 context
47 }
48
49 /// Returns the primary context entry (usually the name of the component)
50 pub fn primary(&self) -> Option<&ContextEntry> {
51 self.0.iter().find(|p| p.value.is_none())
52 }
53
54 /// Returns everything except the primary context entry.
55 pub fn secondary(&self) -> impl Iterator<Item = &ContextEntry> {
56 let primary = self.primary();
57 self.0.iter().filter(move |&p| Some(p) != primary)
58 }
59
60 /// Parse a key context from a string.
61 /// The key context format is very simple:
62 /// - either a single identifier, such as `StatusBar`
63 /// - or a key value pair, such as `mode = visible`
64 /// - separated by whitespace, such as `StatusBar mode = visible`
65 pub fn parse(source: &str) -> Result<Self> {
66 let mut context = Self::default();
67 let source = skip_whitespace(source);
68 Self::parse_expr(source, &mut context)?;
69 Ok(context)
70 }
71
72 fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
73 if source.is_empty() {
74 return Ok(());
75 }
76
77 let key = source
78 .chars()
79 .take_while(|c| is_identifier_char(*c))
80 .collect::<String>();
81 source = skip_whitespace(&source[key.len()..]);
82 if let Some(suffix) = source.strip_prefix('=') {
83 source = skip_whitespace(suffix);
84 let value = source
85 .chars()
86 .take_while(|c| is_identifier_char(*c))
87 .collect::<String>();
88 source = skip_whitespace(&source[value.len()..]);
89 context.set(key, value);
90 } else {
91 context.add(key);
92 }
93
94 Self::parse_expr(source, context)
95 }
96
97 /// Check if this context is empty.
98 pub fn is_empty(&self) -> bool {
99 self.0.is_empty()
100 }
101
102 /// Clear this context.
103 pub fn clear(&mut self) {
104 self.0.clear();
105 }
106
107 /// Extend this context with another context.
108 pub fn extend(&mut self, other: &Self) {
109 for entry in &other.0 {
110 if !self.contains(&entry.key) {
111 self.0.push(entry.clone());
112 }
113 }
114 }
115
116 /// Add an identifier to this context, if it's not already in this context.
117 pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
118 let key = identifier.into();
119
120 if !self.contains(&key) {
121 self.0.push(ContextEntry { key, value: None })
122 }
123 }
124
125 /// Set a key value pair in this context, if it's not already set.
126 pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
127 let key = key.into();
128 if !self.contains(&key) {
129 self.0.push(ContextEntry {
130 key,
131 value: Some(value.into()),
132 })
133 }
134 }
135
136 /// Check if this context contains a given identifier or key.
137 pub fn contains(&self, key: &str) -> bool {
138 self.0.iter().any(|entry| entry.key.as_ref() == key)
139 }
140
141 /// Get the associated value for a given identifier or key.
142 pub fn get(&self, key: &str) -> Option<&SharedString> {
143 self.0
144 .iter()
145 .find(|entry| entry.key.as_ref() == key)?
146 .value
147 .as_ref()
148 }
149}
150
151impl fmt::Debug for KeyContext {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 let mut entries = self.0.iter().peekable();
154 while let Some(entry) = entries.next() {
155 if let Some(ref value) = entry.value {
156 write!(f, "{}={}", entry.key, value)?;
157 } else {
158 write!(f, "{}", entry.key)?;
159 }
160 if entries.peek().is_some() {
161 write!(f, " ")?;
162 }
163 }
164 Ok(())
165 }
166}
167
168/// A datastructure for resolving whether an action should be dispatched
169/// Representing a small language for describing which contexts correspond
170/// to which actions.
171#[derive(Clone, Debug, Eq, PartialEq, Hash)]
172pub enum KeyBindingContextPredicate {
173 /// A predicate that will match a given identifier.
174 Identifier(SharedString),
175 /// A predicate that will match a given key-value pair.
176 Equal(SharedString, SharedString),
177 /// A predicate that will match a given key-value pair not being present.
178 NotEqual(SharedString, SharedString),
179 /// A predicate that will match a given predicate appearing below another predicate.
180 /// in the element tree
181 Descendant(
182 Box<KeyBindingContextPredicate>,
183 Box<KeyBindingContextPredicate>,
184 ),
185 /// Predicate that will invert another predicate.
186 Not(Box<KeyBindingContextPredicate>),
187 /// A predicate that will match if both of its children match.
188 And(
189 Box<KeyBindingContextPredicate>,
190 Box<KeyBindingContextPredicate>,
191 ),
192 /// A predicate that will match if either of its children match.
193 Or(
194 Box<KeyBindingContextPredicate>,
195 Box<KeyBindingContextPredicate>,
196 ),
197}
198
199impl fmt::Display for KeyBindingContextPredicate {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 match self {
202 Self::Identifier(name) => write!(f, "{name}"),
203 Self::Equal(left, right) => write!(f, "{left} == {right}"),
204 Self::NotEqual(left, right) => write!(f, "{left} != {right}"),
205 Self::Descendant(parent, child) => write!(f, "{parent} > {child}"),
206 Self::Not(pred) => match pred.as_ref() {
207 Self::Identifier(name) => write!(f, "!{name}"),
208 _ => write!(f, "!({pred})"),
209 },
210 Self::And(..) => self.fmt_joined(f, " && ", LogicalOperator::And, |node| {
211 matches!(node, Self::Or(..))
212 }),
213 Self::Or(..) => self.fmt_joined(f, " || ", LogicalOperator::Or, |node| {
214 matches!(node, Self::And(..))
215 }),
216 }
217 }
218}
219
220impl KeyBindingContextPredicate {
221 /// Parse a string in the same format as the keymap's context field.
222 ///
223 /// A basic equivalence check against a set of identifiers can performed by
224 /// simply writing a string:
225 ///
226 /// `StatusBar` -> A predicate that will match a context with the identifier `StatusBar`
227 ///
228 /// You can also specify a key-value pair:
229 ///
230 /// `mode == visible` -> A predicate that will match a context with the key `mode`
231 /// with the value `visible`
232 ///
233 /// And a logical operations combining these two checks:
234 ///
235 /// `StatusBar && mode == visible` -> A predicate that will match a context with the
236 /// identifier `StatusBar` and the key `mode`
237 /// with the value `visible`
238 ///
239 ///
240 /// There is also a special child `>` operator that will match a predicate that is
241 /// below another predicate:
242 ///
243 /// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
244 /// and a child context that has the key `mode` with the
245 /// value `visible`
246 ///
247 /// This syntax supports `!=`, `||` and `&&` as logical operators.
248 /// You can also preface an operation or check with a `!` to negate it.
249 pub fn parse(source: &str) -> Result<Self> {
250 let source = skip_whitespace(source);
251 let (predicate, rest) = Self::parse_expr(source, 0)?;
252 if let Some(next) = rest.chars().next() {
253 anyhow::bail!("unexpected character '{next:?}'");
254 } else {
255 Ok(predicate)
256 }
257 }
258
259 /// Find the deepest depth at which the predicate matches.
260 pub fn depth_of(&self, contexts: &[KeyContext]) -> Option<usize> {
261 for depth in (0..=contexts.len()).rev() {
262 let context_slice = &contexts[0..depth];
263 if self.eval_inner(context_slice, contexts) {
264 return Some(depth);
265 }
266 }
267 None
268 }
269
270 /// Eval a predicate against a set of contexts, arranged from lowest to highest.
271 #[allow(unused)]
272 pub fn eval(&self, contexts: &[KeyContext]) -> bool {
273 self.eval_inner(contexts, contexts)
274 }
275
276 /// Eval a predicate against a set of contexts, arranged from lowest to highest.
277 pub fn eval_inner(&self, contexts: &[KeyContext], all_contexts: &[KeyContext]) -> bool {
278 let Some(context) = contexts.last() else {
279 return false;
280 };
281 match self {
282 Self::Identifier(name) => context.contains(name),
283 Self::Equal(left, right) => context
284 .get(left)
285 .map(|value| value == right)
286 .unwrap_or(false),
287 Self::NotEqual(left, right) => context
288 .get(left)
289 .map(|value| value != right)
290 .unwrap_or(true),
291 Self::Not(pred) => {
292 for i in 0..all_contexts.len() {
293 if pred.eval_inner(&all_contexts[..=i], all_contexts) {
294 return false;
295 }
296 }
297 true
298 }
299 // Workspace > Pane > Editor
300 //
301 // Pane > (Pane > Editor) // should match?
302 // (Pane > Pane) > Editor // should not match?
303 // Pane > !Workspace <-- should match?
304 // !Workspace <-- shouldn't match?
305 Self::Descendant(parent, child) => {
306 for i in 0..contexts.len() - 1 {
307 // [Workspace > Pane], [Editor]
308 if parent.eval_inner(&contexts[..=i], all_contexts) {
309 if !child.eval_inner(&contexts[i + 1..], &contexts[i + 1..]) {
310 return false;
311 }
312 return true;
313 }
314 }
315 false
316 }
317 Self::And(left, right) => {
318 left.eval_inner(contexts, all_contexts) && right.eval_inner(contexts, all_contexts)
319 }
320 Self::Or(left, right) => {
321 left.eval_inner(contexts, all_contexts) || right.eval_inner(contexts, all_contexts)
322 }
323 }
324 }
325
326 /// Returns whether or not this predicate matches all possible contexts matched by
327 /// the other predicate.
328 pub fn is_superset(&self, other: &Self) -> bool {
329 if self == other {
330 return true;
331 }
332
333 if let KeyBindingContextPredicate::Or(left, right) = self {
334 return left.is_superset(other) || right.is_superset(other);
335 }
336
337 match other {
338 KeyBindingContextPredicate::Descendant(_, child) => self.is_superset(child),
339 KeyBindingContextPredicate::And(left, right) => {
340 self.is_superset(left) || self.is_superset(right)
341 }
342 KeyBindingContextPredicate::Identifier(_) => false,
343 KeyBindingContextPredicate::Equal(_, _) => false,
344 KeyBindingContextPredicate::NotEqual(_, _) => false,
345 KeyBindingContextPredicate::Not(_) => false,
346 KeyBindingContextPredicate::Or(_, _) => false,
347 }
348 }
349
350 fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
351 type Op = fn(
352 KeyBindingContextPredicate,
353 KeyBindingContextPredicate,
354 ) -> Result<KeyBindingContextPredicate>;
355
356 let (mut predicate, rest) = Self::parse_primary(source)?;
357 source = rest;
358
359 'parse: loop {
360 for (operator, precedence, constructor) in [
361 (">", PRECEDENCE_CHILD, Self::new_child as Op),
362 ("&&", PRECEDENCE_AND, Self::new_and as Op),
363 ("||", PRECEDENCE_OR, Self::new_or as Op),
364 ("==", PRECEDENCE_EQ, Self::new_eq as Op),
365 ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
366 ] {
367 if source.starts_with(operator) && precedence >= min_precedence {
368 source = skip_whitespace(&source[operator.len()..]);
369 let (right, rest) = Self::parse_expr(source, precedence + 1)?;
370 predicate = constructor(predicate, right)?;
371 source = rest;
372 continue 'parse;
373 }
374 }
375 break;
376 }
377
378 Ok((predicate, source))
379 }
380
381 fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
382 let next = source.chars().next().context("unexpected end")?;
383 match next {
384 '(' => {
385 source = skip_whitespace(&source[1..]);
386 let (predicate, rest) = Self::parse_expr(source, 0)?;
387 let stripped = rest.strip_prefix(')').context("expected a ')'")?;
388 source = skip_whitespace(stripped);
389 Ok((predicate, source))
390 }
391 '!' => {
392 let source = skip_whitespace(&source[1..]);
393 let (predicate, source) = Self::parse_expr(source, PRECEDENCE_NOT)?;
394 Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
395 }
396 _ if is_identifier_char(next) => {
397 let len = source
398 .find(|c: char| !is_identifier_char(c) && !is_vim_operator_char(c))
399 .unwrap_or(source.len());
400 let (identifier, rest) = source.split_at(len);
401 source = skip_whitespace(rest);
402 Ok((
403 KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
404 source,
405 ))
406 }
407 _ if is_vim_operator_char(next) => {
408 let (operator, rest) = source.split_at(1);
409 source = skip_whitespace(rest);
410 Ok((
411 KeyBindingContextPredicate::Identifier(operator.to_string().into()),
412 source,
413 ))
414 }
415 _ => anyhow::bail!("unexpected character '{next:?}'"),
416 }
417 }
418
419 fn new_or(self, other: Self) -> Result<Self> {
420 Ok(Self::Or(Box::new(self), Box::new(other)))
421 }
422
423 fn new_and(self, other: Self) -> Result<Self> {
424 Ok(Self::And(Box::new(self), Box::new(other)))
425 }
426
427 fn new_child(self, other: Self) -> Result<Self> {
428 Ok(Self::Descendant(Box::new(self), Box::new(other)))
429 }
430
431 fn new_eq(self, other: Self) -> Result<Self> {
432 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
433 Ok(Self::Equal(left, right))
434 } else {
435 anyhow::bail!("operands of == must be identifiers");
436 }
437 }
438
439 fn new_neq(self, other: Self) -> Result<Self> {
440 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
441 Ok(Self::NotEqual(left, right))
442 } else {
443 anyhow::bail!("operands of != must be identifiers");
444 }
445 }
446
447 fn fmt_joined(
448 &self,
449 f: &mut fmt::Formatter<'_>,
450 separator: &str,
451 operator: LogicalOperator,
452 needs_parens: impl Fn(&Self) -> bool + Copy,
453 ) -> fmt::Result {
454 let mut first = true;
455 self.fmt_joined_inner(f, separator, operator, needs_parens, &mut first)
456 }
457
458 fn fmt_joined_inner(
459 &self,
460 f: &mut fmt::Formatter<'_>,
461 separator: &str,
462 operator: LogicalOperator,
463 needs_parens: impl Fn(&Self) -> bool + Copy,
464 first: &mut bool,
465 ) -> fmt::Result {
466 match (operator, self) {
467 (LogicalOperator::And, Self::And(left, right))
468 | (LogicalOperator::Or, Self::Or(left, right)) => {
469 left.fmt_joined_inner(f, separator, operator, needs_parens, first)?;
470 right.fmt_joined_inner(f, separator, operator, needs_parens, first)
471 }
472 (_, node) => {
473 if !*first {
474 f.write_str(separator)?;
475 }
476 *first = false;
477
478 if needs_parens(node) {
479 write!(f, "({node})")
480 } else {
481 write!(f, "{node}")
482 }
483 }
484 }
485 }
486}
487
488#[derive(Clone, Copy)]
489enum LogicalOperator {
490 And,
491 Or,
492}
493
494const PRECEDENCE_CHILD: u32 = 1;
495const PRECEDENCE_OR: u32 = 2;
496const PRECEDENCE_AND: u32 = 3;
497const PRECEDENCE_EQ: u32 = 4;
498const PRECEDENCE_NOT: u32 = 5;
499
500fn is_identifier_char(c: char) -> bool {
501 c.is_alphanumeric() || c == '_' || c == '-'
502}
503
504fn is_vim_operator_char(c: char) -> bool {
505 c == '>' || c == '<' || c == '~' || c == '"' || c == '?'
506}
507
508fn skip_whitespace(source: &str) -> &str {
509 let len = source
510 .find(|c: char| !c.is_whitespace())
511 .unwrap_or(source.len());
512 &source[len..]
513}
514
515#[cfg(test)]
516mod tests {
517 use core::slice;
518
519 use super::*;
520 use crate as gpui;
521 use KeyBindingContextPredicate::*;
522
523 #[test]
524 fn test_actions_definition() {
525 {
526 actions!(test_only, [A, B, C, D, E, F, G]);
527 }
528
529 {
530 actions!(
531 test_only,
532 [
533 H, I, J, K, L, M, N, // Don't wrap, test the trailing comma
534 ]
535 );
536 }
537 }
538
539 #[test]
540 fn test_parse_context() {
541 let mut expected = KeyContext::default();
542 expected.add("baz");
543 expected.set("foo", "bar");
544 assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
545 assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
546 assert_eq!(
547 KeyContext::parse(" baz foo = bar baz").unwrap(),
548 expected
549 );
550 assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
551 }
552
553 #[test]
554 fn test_parse_identifiers() {
555 // Identifiers
556 assert_eq!(
557 KeyBindingContextPredicate::parse("abc12").unwrap(),
558 Identifier("abc12".into())
559 );
560 assert_eq!(
561 KeyBindingContextPredicate::parse("_1a").unwrap(),
562 Identifier("_1a".into())
563 );
564 }
565
566 #[test]
567 fn test_parse_negations() {
568 assert_eq!(
569 KeyBindingContextPredicate::parse("!abc").unwrap(),
570 Not(Box::new(Identifier("abc".into())))
571 );
572 assert_eq!(
573 KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
574 Not(Box::new(Not(Box::new(Identifier("abc".into())))))
575 );
576 }
577
578 #[test]
579 fn test_parse_equality_operators() {
580 assert_eq!(
581 KeyBindingContextPredicate::parse("a == b").unwrap(),
582 Equal("a".into(), "b".into())
583 );
584 assert_eq!(
585 KeyBindingContextPredicate::parse("c!=d").unwrap(),
586 NotEqual("c".into(), "d".into())
587 );
588 assert_eq!(
589 KeyBindingContextPredicate::parse("c == !d")
590 .unwrap_err()
591 .to_string(),
592 "operands of == must be identifiers"
593 );
594 }
595
596 #[test]
597 fn test_parse_boolean_operators() {
598 assert_eq!(
599 KeyBindingContextPredicate::parse("a || b").unwrap(),
600 Or(
601 Box::new(Identifier("a".into())),
602 Box::new(Identifier("b".into()))
603 )
604 );
605 assert_eq!(
606 KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
607 Or(
608 Box::new(Identifier("a".into())),
609 Box::new(And(
610 Box::new(Not(Box::new(Identifier("b".into())))),
611 Box::new(Identifier("c".into()))
612 ))
613 )
614 );
615 assert_eq!(
616 KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
617 Or(
618 Box::new(And(
619 Box::new(Identifier("a".into())),
620 Box::new(Identifier("b".into()))
621 )),
622 Box::new(And(
623 Box::new(Identifier("c".into())),
624 Box::new(Identifier("d".into()))
625 ))
626 )
627 );
628 assert_eq!(
629 KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
630 Or(
631 Box::new(And(
632 Box::new(Equal("a".into(), "b".into())),
633 Box::new(Identifier("c".into()))
634 )),
635 Box::new(And(
636 Box::new(Equal("d".into(), "e".into())),
637 Box::new(Identifier("f".into()))
638 ))
639 )
640 );
641 assert_eq!(
642 KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
643 And(
644 Box::new(And(
645 Box::new(And(
646 Box::new(Identifier("a".into())),
647 Box::new(Identifier("b".into()))
648 )),
649 Box::new(Identifier("c".into())),
650 )),
651 Box::new(Identifier("d".into()))
652 ),
653 );
654 }
655
656 #[test]
657 fn test_parse_parenthesized_expressions() {
658 assert_eq!(
659 KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
660 And(
661 Box::new(Identifier("a".into())),
662 Box::new(Or(
663 Box::new(Equal("b".into(), "c".into())),
664 Box::new(NotEqual("d".into(), "e".into())),
665 )),
666 ),
667 );
668 assert_eq!(
669 KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
670 Or(
671 Box::new(Identifier("a".into())),
672 Box::new(Identifier("b".into())),
673 )
674 );
675 }
676
677 #[test]
678 fn test_is_superset() {
679 assert_is_superset("editor", "editor", true);
680 assert_is_superset("editor", "workspace", false);
681
682 assert_is_superset("editor", "editor && vim_mode", true);
683 assert_is_superset("editor", "mode == full && editor", true);
684 assert_is_superset("editor && mode == full", "editor", false);
685
686 assert_is_superset("editor", "something > editor", true);
687 assert_is_superset("editor", "editor > menu", false);
688
689 assert_is_superset("foo || bar || baz", "bar", true);
690 assert_is_superset("foo || bar || baz", "quux", false);
691
692 #[track_caller]
693 fn assert_is_superset(a: &str, b: &str, result: bool) {
694 let a = KeyBindingContextPredicate::parse(a).unwrap();
695 let b = KeyBindingContextPredicate::parse(b).unwrap();
696 assert_eq!(a.is_superset(&b), result, "({a:?}).is_superset({b:?})");
697 }
698 }
699
700 #[test]
701 fn test_child_operator() {
702 let predicate = KeyBindingContextPredicate::parse("parent > child").unwrap();
703
704 let parent_context = KeyContext::try_from("parent").unwrap();
705 let child_context = KeyContext::try_from("child").unwrap();
706
707 let contexts = vec![parent_context.clone(), child_context.clone()];
708 assert!(predicate.eval(&contexts));
709
710 let grandparent_context = KeyContext::try_from("grandparent").unwrap();
711
712 let contexts = vec![
713 grandparent_context,
714 parent_context.clone(),
715 child_context.clone(),
716 ];
717 assert!(predicate.eval(&contexts));
718
719 let other_context = KeyContext::try_from("other").unwrap();
720
721 let contexts = vec![other_context.clone(), child_context.clone()];
722 assert!(!predicate.eval(&contexts));
723
724 let contexts = vec![parent_context.clone(), other_context, child_context.clone()];
725 assert!(predicate.eval(&contexts));
726
727 assert!(!predicate.eval(&[]));
728 assert!(!predicate.eval(slice::from_ref(&child_context)));
729 assert!(!predicate.eval(&[parent_context]));
730
731 let zany_predicate = KeyBindingContextPredicate::parse("child > child").unwrap();
732 assert!(!zany_predicate.eval(slice::from_ref(&child_context)));
733 assert!(zany_predicate.eval(&[child_context.clone(), child_context]));
734 }
735
736 #[test]
737 fn test_not_operator() {
738 let not_predicate = KeyBindingContextPredicate::parse("!editor").unwrap();
739 let editor_context = KeyContext::try_from("editor").unwrap();
740 let workspace_context = KeyContext::try_from("workspace").unwrap();
741 let parent_context = KeyContext::try_from("parent").unwrap();
742 let child_context = KeyContext::try_from("child").unwrap();
743
744 assert!(not_predicate.eval(slice::from_ref(&workspace_context)));
745 assert!(!not_predicate.eval(slice::from_ref(&editor_context)));
746 assert!(!not_predicate.eval(&[editor_context.clone(), workspace_context.clone()]));
747 assert!(!not_predicate.eval(&[workspace_context.clone(), editor_context.clone()]));
748
749 let complex_not = KeyBindingContextPredicate::parse("!editor && workspace").unwrap();
750 assert!(complex_not.eval(slice::from_ref(&workspace_context)));
751 assert!(!complex_not.eval(&[editor_context.clone(), workspace_context.clone()]));
752
753 let not_mode_predicate = KeyBindingContextPredicate::parse("!(mode == full)").unwrap();
754 let mut mode_context = KeyContext::default();
755 mode_context.set("mode", "full");
756 assert!(!not_mode_predicate.eval(&[mode_context.clone()]));
757
758 let mut other_mode_context = KeyContext::default();
759 other_mode_context.set("mode", "partial");
760 assert!(not_mode_predicate.eval(&[other_mode_context]));
761
762 let not_descendant = KeyBindingContextPredicate::parse("!(parent > child)").unwrap();
763 assert!(not_descendant.eval(slice::from_ref(&parent_context)));
764 assert!(not_descendant.eval(slice::from_ref(&child_context)));
765 assert!(!not_descendant.eval(&[parent_context.clone(), child_context.clone()]));
766
767 let not_descendant = KeyBindingContextPredicate::parse("parent > !child").unwrap();
768 assert!(!not_descendant.eval(slice::from_ref(&parent_context)));
769 assert!(!not_descendant.eval(slice::from_ref(&child_context)));
770 assert!(!not_descendant.eval(&[parent_context, child_context]));
771
772 let double_not = KeyBindingContextPredicate::parse("!!editor").unwrap();
773 assert!(double_not.eval(slice::from_ref(&editor_context)));
774 assert!(!double_not.eval(slice::from_ref(&workspace_context)));
775
776 // Test complex descendant cases
777 let workspace_context = KeyContext::try_from("Workspace").unwrap();
778 let pane_context = KeyContext::try_from("Pane").unwrap();
779 let editor_context = KeyContext::try_from("Editor").unwrap();
780
781 // Workspace > Pane > Editor
782 let workspace_pane_editor = vec![
783 workspace_context.clone(),
784 pane_context.clone(),
785 editor_context.clone(),
786 ];
787
788 // Pane > (Pane > Editor) - should not match
789 let pane_pane_editor = KeyBindingContextPredicate::parse("Pane > (Pane > Editor)").unwrap();
790 assert!(!pane_pane_editor.eval(&workspace_pane_editor));
791
792 let workspace_pane_editor_predicate =
793 KeyBindingContextPredicate::parse("Workspace > Pane > Editor").unwrap();
794 assert!(workspace_pane_editor_predicate.eval(&workspace_pane_editor));
795
796 // (Pane > Pane) > Editor - should not match
797 let pane_pane_then_editor =
798 KeyBindingContextPredicate::parse("(Pane > Pane) > Editor").unwrap();
799 assert!(!pane_pane_then_editor.eval(&workspace_pane_editor));
800
801 // Pane > !Workspace - should match
802 let pane_not_workspace = KeyBindingContextPredicate::parse("Pane > !Workspace").unwrap();
803 assert!(pane_not_workspace.eval(&[pane_context.clone(), editor_context.clone()]));
804 assert!(!pane_not_workspace.eval(&[pane_context.clone(), workspace_context.clone()]));
805
806 // !Workspace - shouldn't match when Workspace is in the context
807 let not_workspace = KeyBindingContextPredicate::parse("!Workspace").unwrap();
808 assert!(!not_workspace.eval(slice::from_ref(&workspace_context)));
809 assert!(not_workspace.eval(slice::from_ref(&pane_context)));
810 assert!(not_workspace.eval(slice::from_ref(&editor_context)));
811 assert!(!not_workspace.eval(&workspace_pane_editor));
812 }
813
814 // MARK: - Display
815
816 #[test]
817 fn test_context_display() {
818 fn ident(s: &str) -> Box<KeyBindingContextPredicate> {
819 Box::new(Identifier(SharedString::new(s)))
820 }
821 fn eq(a: &str, b: &str) -> Box<KeyBindingContextPredicate> {
822 Box::new(Equal(SharedString::new(a), SharedString::new(b)))
823 }
824 fn not_eq(a: &str, b: &str) -> Box<KeyBindingContextPredicate> {
825 Box::new(NotEqual(SharedString::new(a), SharedString::new(b)))
826 }
827 fn and(
828 a: Box<KeyBindingContextPredicate>,
829 b: Box<KeyBindingContextPredicate>,
830 ) -> Box<KeyBindingContextPredicate> {
831 Box::new(And(a, b))
832 }
833 fn or(
834 a: Box<KeyBindingContextPredicate>,
835 b: Box<KeyBindingContextPredicate>,
836 ) -> Box<KeyBindingContextPredicate> {
837 Box::new(Or(a, b))
838 }
839 fn descendant(
840 a: Box<KeyBindingContextPredicate>,
841 b: Box<KeyBindingContextPredicate>,
842 ) -> Box<KeyBindingContextPredicate> {
843 Box::new(Descendant(a, b))
844 }
845 fn not(a: Box<KeyBindingContextPredicate>) -> Box<KeyBindingContextPredicate> {
846 Box::new(Not(a))
847 }
848
849 let test_cases = [
850 (ident("a"), "a"),
851 (eq("a", "b"), "a == b"),
852 (not_eq("a", "b"), "a != b"),
853 (descendant(ident("a"), ident("b")), "a > b"),
854 (not(ident("a")), "!a"),
855 (not_eq("a", "b"), "a != b"),
856 (descendant(ident("a"), ident("b")), "a > b"),
857 (not(and(ident("a"), ident("b"))), "!(a && b)"),
858 (not(or(ident("a"), ident("b"))), "!(a || b)"),
859 (and(ident("a"), ident("b")), "a && b"),
860 (and(and(ident("a"), ident("b")), ident("c")), "a && b && c"),
861 (or(ident("a"), ident("b")), "a || b"),
862 (or(or(ident("a"), ident("b")), ident("c")), "a || b || c"),
863 (or(ident("a"), and(ident("b"), ident("c"))), "a || (b && c)"),
864 (
865 and(
866 and(
867 and(ident("a"), eq("b", "c")),
868 not(descendant(ident("d"), ident("e"))),
869 ),
870 eq("f", "g"),
871 ),
872 "a && b == c && !(d > e) && f == g",
873 ),
874 (
875 and(and(ident("a"), or(ident("b"), ident("c"))), ident("d")),
876 "a && (b || c) && d",
877 ),
878 (
879 or(or(ident("a"), and(ident("b"), ident("c"))), ident("d")),
880 "a || (b && c) || d",
881 ),
882 ];
883
884 for (predicate, expected) in test_cases {
885 let actual = predicate.to_string();
886 assert_eq!(actual, expected);
887 let parsed = KeyBindingContextPredicate::parse(&actual).unwrap();
888 assert_eq!(parsed, *predicate);
889 }
890 }
891}