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::Not(pred) => write!(f, "!{}", pred),
206 Self::Descendant(parent, child) => write!(f, "{} > {}", parent, child),
207 Self::And(left, right) => write!(f, "({} && {})", left, right),
208 Self::Or(left, right) => write!(f, "({} || {})", left, right),
209 }
210 }
211}
212
213impl KeyBindingContextPredicate {
214 /// Parse a string in the same format as the keymap's context field.
215 ///
216 /// A basic equivalence check against a set of identifiers can performed by
217 /// simply writing a string:
218 ///
219 /// `StatusBar` -> A predicate that will match a context with the identifier `StatusBar`
220 ///
221 /// You can also specify a key-value pair:
222 ///
223 /// `mode == visible` -> A predicate that will match a context with the key `mode`
224 /// with the value `visible`
225 ///
226 /// And a logical operations combining these two checks:
227 ///
228 /// `StatusBar && mode == visible` -> A predicate that will match a context with the
229 /// identifier `StatusBar` and the key `mode`
230 /// with the value `visible`
231 ///
232 ///
233 /// There is also a special child `>` operator that will match a predicate that is
234 /// below another predicate:
235 ///
236 /// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
237 /// and a child context that has the key `mode` with the
238 /// value `visible`
239 ///
240 /// This syntax supports `!=`, `||` and `&&` as logical operators.
241 /// You can also preface an operation or check with a `!` to negate it.
242 pub fn parse(source: &str) -> Result<Self> {
243 let source = skip_whitespace(source);
244 let (predicate, rest) = Self::parse_expr(source, 0)?;
245 if let Some(next) = rest.chars().next() {
246 anyhow::bail!("unexpected character '{next:?}'");
247 } else {
248 Ok(predicate)
249 }
250 }
251
252 /// Find the deepest depth at which the predicate matches.
253 pub fn depth_of(&self, contexts: &[KeyContext]) -> Option<usize> {
254 for depth in (0..=contexts.len()).rev() {
255 let context_slice = &contexts[0..depth];
256 if self.eval_inner(context_slice, contexts) {
257 return Some(depth);
258 }
259 }
260 None
261 }
262
263 /// Eval a predicate against a set of contexts, arranged from lowest to highest.
264 #[allow(unused)]
265 pub(crate) fn eval(&self, contexts: &[KeyContext]) -> bool {
266 self.eval_inner(contexts, contexts)
267 }
268
269 /// Eval a predicate against a set of contexts, arranged from lowest to highest.
270 pub fn eval_inner(&self, contexts: &[KeyContext], all_contexts: &[KeyContext]) -> bool {
271 let Some(context) = contexts.last() else {
272 return false;
273 };
274 match self {
275 Self::Identifier(name) => context.contains(name),
276 Self::Equal(left, right) => context
277 .get(left)
278 .map(|value| value == right)
279 .unwrap_or(false),
280 Self::NotEqual(left, right) => context
281 .get(left)
282 .map(|value| value != right)
283 .unwrap_or(true),
284 Self::Not(pred) => {
285 for i in 0..all_contexts.len() {
286 if pred.eval_inner(&all_contexts[..=i], all_contexts) {
287 return false;
288 }
289 }
290 true
291 }
292 // Workspace > Pane > Editor
293 //
294 // Pane > (Pane > Editor) // should match?
295 // (Pane > Pane) > Editor // should not match?
296 // Pane > !Workspace <-- should match?
297 // !Workspace <-- shouldn't match?
298 Self::Descendant(parent, child) => {
299 for i in 0..contexts.len() - 1 {
300 // [Workspace > Pane], [Editor]
301 if parent.eval_inner(&contexts[..=i], all_contexts) {
302 if !child.eval_inner(&contexts[i + 1..], &contexts[i + 1..]) {
303 return false;
304 }
305 return true;
306 }
307 }
308 false
309 }
310 Self::And(left, right) => {
311 left.eval_inner(contexts, all_contexts) && right.eval_inner(contexts, all_contexts)
312 }
313 Self::Or(left, right) => {
314 left.eval_inner(contexts, all_contexts) || right.eval_inner(contexts, all_contexts)
315 }
316 }
317 }
318
319 /// Returns whether or not this predicate matches all possible contexts matched by
320 /// the other predicate.
321 pub fn is_superset(&self, other: &Self) -> bool {
322 if self == other {
323 return true;
324 }
325
326 if let KeyBindingContextPredicate::Or(left, right) = self {
327 return left.is_superset(other) || right.is_superset(other);
328 }
329
330 match other {
331 KeyBindingContextPredicate::Descendant(_, child) => self.is_superset(child),
332 KeyBindingContextPredicate::And(left, right) => {
333 self.is_superset(left) || self.is_superset(right)
334 }
335 KeyBindingContextPredicate::Identifier(_) => false,
336 KeyBindingContextPredicate::Equal(_, _) => false,
337 KeyBindingContextPredicate::NotEqual(_, _) => false,
338 KeyBindingContextPredicate::Not(_) => false,
339 KeyBindingContextPredicate::Or(_, _) => false,
340 }
341 }
342
343 fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
344 type Op = fn(
345 KeyBindingContextPredicate,
346 KeyBindingContextPredicate,
347 ) -> Result<KeyBindingContextPredicate>;
348
349 let (mut predicate, rest) = Self::parse_primary(source)?;
350 source = rest;
351
352 'parse: loop {
353 for (operator, precedence, constructor) in [
354 (">", PRECEDENCE_CHILD, Self::new_child as Op),
355 ("&&", PRECEDENCE_AND, Self::new_and as Op),
356 ("||", PRECEDENCE_OR, Self::new_or as Op),
357 ("==", PRECEDENCE_EQ, Self::new_eq as Op),
358 ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
359 ] {
360 if source.starts_with(operator) && precedence >= min_precedence {
361 source = skip_whitespace(&source[operator.len()..]);
362 let (right, rest) = Self::parse_expr(source, precedence + 1)?;
363 predicate = constructor(predicate, right)?;
364 source = rest;
365 continue 'parse;
366 }
367 }
368 break;
369 }
370
371 Ok((predicate, source))
372 }
373
374 fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
375 let next = source.chars().next().context("unexpected end")?;
376 match next {
377 '(' => {
378 source = skip_whitespace(&source[1..]);
379 let (predicate, rest) = Self::parse_expr(source, 0)?;
380 let stripped = rest.strip_prefix(')').context("expected a ')'")?;
381 source = skip_whitespace(stripped);
382 Ok((predicate, source))
383 }
384 '!' => {
385 let source = skip_whitespace(&source[1..]);
386 let (predicate, source) = Self::parse_expr(source, PRECEDENCE_NOT)?;
387 Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
388 }
389 _ if is_identifier_char(next) => {
390 let len = source
391 .find(|c: char| !is_identifier_char(c) && !is_vim_operator_char(c))
392 .unwrap_or(source.len());
393 let (identifier, rest) = source.split_at(len);
394 source = skip_whitespace(rest);
395 Ok((
396 KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
397 source,
398 ))
399 }
400 _ if is_vim_operator_char(next) => {
401 let (operator, rest) = source.split_at(1);
402 source = skip_whitespace(rest);
403 Ok((
404 KeyBindingContextPredicate::Identifier(operator.to_string().into()),
405 source,
406 ))
407 }
408 _ => anyhow::bail!("unexpected character '{next:?}'"),
409 }
410 }
411
412 fn new_or(self, other: Self) -> Result<Self> {
413 Ok(Self::Or(Box::new(self), Box::new(other)))
414 }
415
416 fn new_and(self, other: Self) -> Result<Self> {
417 Ok(Self::And(Box::new(self), Box::new(other)))
418 }
419
420 fn new_child(self, other: Self) -> Result<Self> {
421 Ok(Self::Descendant(Box::new(self), Box::new(other)))
422 }
423
424 fn new_eq(self, other: Self) -> Result<Self> {
425 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
426 Ok(Self::Equal(left, right))
427 } else {
428 anyhow::bail!("operands of == must be identifiers");
429 }
430 }
431
432 fn new_neq(self, other: Self) -> Result<Self> {
433 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
434 Ok(Self::NotEqual(left, right))
435 } else {
436 anyhow::bail!("operands of != must be identifiers");
437 }
438 }
439}
440
441const PRECEDENCE_CHILD: u32 = 1;
442const PRECEDENCE_OR: u32 = 2;
443const PRECEDENCE_AND: u32 = 3;
444const PRECEDENCE_EQ: u32 = 4;
445const PRECEDENCE_NOT: u32 = 5;
446
447fn is_identifier_char(c: char) -> bool {
448 c.is_alphanumeric() || c == '_' || c == '-'
449}
450
451fn is_vim_operator_char(c: char) -> bool {
452 c == '>' || c == '<' || c == '~' || c == '"' || c == '?'
453}
454
455fn skip_whitespace(source: &str) -> &str {
456 let len = source
457 .find(|c: char| !c.is_whitespace())
458 .unwrap_or(source.len());
459 &source[len..]
460}
461
462#[cfg(test)]
463mod tests {
464 use core::slice;
465
466 use super::*;
467 use crate as gpui;
468 use KeyBindingContextPredicate::*;
469
470 #[test]
471 fn test_actions_definition() {
472 {
473 actions!(test_only, [A, B, C, D, E, F, G]);
474 }
475
476 {
477 actions!(
478 test_only,
479 [
480 H, I, J, K, L, M, N, // Don't wrap, test the trailing comma
481 ]
482 );
483 }
484 }
485
486 #[test]
487 fn test_parse_context() {
488 let mut expected = KeyContext::default();
489 expected.add("baz");
490 expected.set("foo", "bar");
491 assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
492 assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
493 assert_eq!(
494 KeyContext::parse(" baz foo = bar baz").unwrap(),
495 expected
496 );
497 assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
498 }
499
500 #[test]
501 fn test_parse_identifiers() {
502 // Identifiers
503 assert_eq!(
504 KeyBindingContextPredicate::parse("abc12").unwrap(),
505 Identifier("abc12".into())
506 );
507 assert_eq!(
508 KeyBindingContextPredicate::parse("_1a").unwrap(),
509 Identifier("_1a".into())
510 );
511 }
512
513 #[test]
514 fn test_parse_negations() {
515 assert_eq!(
516 KeyBindingContextPredicate::parse("!abc").unwrap(),
517 Not(Box::new(Identifier("abc".into())))
518 );
519 assert_eq!(
520 KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
521 Not(Box::new(Not(Box::new(Identifier("abc".into())))))
522 );
523 }
524
525 #[test]
526 fn test_parse_equality_operators() {
527 assert_eq!(
528 KeyBindingContextPredicate::parse("a == b").unwrap(),
529 Equal("a".into(), "b".into())
530 );
531 assert_eq!(
532 KeyBindingContextPredicate::parse("c!=d").unwrap(),
533 NotEqual("c".into(), "d".into())
534 );
535 assert_eq!(
536 KeyBindingContextPredicate::parse("c == !d")
537 .unwrap_err()
538 .to_string(),
539 "operands of == must be identifiers"
540 );
541 }
542
543 #[test]
544 fn test_parse_boolean_operators() {
545 assert_eq!(
546 KeyBindingContextPredicate::parse("a || b").unwrap(),
547 Or(
548 Box::new(Identifier("a".into())),
549 Box::new(Identifier("b".into()))
550 )
551 );
552 assert_eq!(
553 KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
554 Or(
555 Box::new(Identifier("a".into())),
556 Box::new(And(
557 Box::new(Not(Box::new(Identifier("b".into())))),
558 Box::new(Identifier("c".into()))
559 ))
560 )
561 );
562 assert_eq!(
563 KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
564 Or(
565 Box::new(And(
566 Box::new(Identifier("a".into())),
567 Box::new(Identifier("b".into()))
568 )),
569 Box::new(And(
570 Box::new(Identifier("c".into())),
571 Box::new(Identifier("d".into()))
572 ))
573 )
574 );
575 assert_eq!(
576 KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
577 Or(
578 Box::new(And(
579 Box::new(Equal("a".into(), "b".into())),
580 Box::new(Identifier("c".into()))
581 )),
582 Box::new(And(
583 Box::new(Equal("d".into(), "e".into())),
584 Box::new(Identifier("f".into()))
585 ))
586 )
587 );
588 assert_eq!(
589 KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
590 And(
591 Box::new(And(
592 Box::new(And(
593 Box::new(Identifier("a".into())),
594 Box::new(Identifier("b".into()))
595 )),
596 Box::new(Identifier("c".into())),
597 )),
598 Box::new(Identifier("d".into()))
599 ),
600 );
601 }
602
603 #[test]
604 fn test_parse_parenthesized_expressions() {
605 assert_eq!(
606 KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
607 And(
608 Box::new(Identifier("a".into())),
609 Box::new(Or(
610 Box::new(Equal("b".into(), "c".into())),
611 Box::new(NotEqual("d".into(), "e".into())),
612 )),
613 ),
614 );
615 assert_eq!(
616 KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
617 Or(
618 Box::new(Identifier("a".into())),
619 Box::new(Identifier("b".into())),
620 )
621 );
622 }
623
624 #[test]
625 fn test_is_superset() {
626 assert_is_superset("editor", "editor", true);
627 assert_is_superset("editor", "workspace", false);
628
629 assert_is_superset("editor", "editor && vim_mode", true);
630 assert_is_superset("editor", "mode == full && editor", true);
631 assert_is_superset("editor && mode == full", "editor", false);
632
633 assert_is_superset("editor", "something > editor", true);
634 assert_is_superset("editor", "editor > menu", false);
635
636 assert_is_superset("foo || bar || baz", "bar", true);
637 assert_is_superset("foo || bar || baz", "quux", false);
638
639 #[track_caller]
640 fn assert_is_superset(a: &str, b: &str, result: bool) {
641 let a = KeyBindingContextPredicate::parse(a).unwrap();
642 let b = KeyBindingContextPredicate::parse(b).unwrap();
643 assert_eq!(a.is_superset(&b), result, "({a:?}).is_superset({b:?})");
644 }
645 }
646
647 #[test]
648 fn test_child_operator() {
649 let predicate = KeyBindingContextPredicate::parse("parent > child").unwrap();
650
651 let parent_context = KeyContext::try_from("parent").unwrap();
652 let child_context = KeyContext::try_from("child").unwrap();
653
654 let contexts = vec![parent_context.clone(), child_context.clone()];
655 assert!(predicate.eval(&contexts));
656
657 let grandparent_context = KeyContext::try_from("grandparent").unwrap();
658
659 let contexts = vec![
660 grandparent_context,
661 parent_context.clone(),
662 child_context.clone(),
663 ];
664 assert!(predicate.eval(&contexts));
665
666 let other_context = KeyContext::try_from("other").unwrap();
667
668 let contexts = vec![other_context.clone(), child_context.clone()];
669 assert!(!predicate.eval(&contexts));
670
671 let contexts = vec![parent_context.clone(), other_context, child_context.clone()];
672 assert!(predicate.eval(&contexts));
673
674 assert!(!predicate.eval(&[]));
675 assert!(!predicate.eval(slice::from_ref(&child_context)));
676 assert!(!predicate.eval(&[parent_context]));
677
678 let zany_predicate = KeyBindingContextPredicate::parse("child > child").unwrap();
679 assert!(!zany_predicate.eval(slice::from_ref(&child_context)));
680 assert!(zany_predicate.eval(&[child_context.clone(), child_context]));
681 }
682
683 #[test]
684 fn test_not_operator() {
685 let not_predicate = KeyBindingContextPredicate::parse("!editor").unwrap();
686 let editor_context = KeyContext::try_from("editor").unwrap();
687 let workspace_context = KeyContext::try_from("workspace").unwrap();
688 let parent_context = KeyContext::try_from("parent").unwrap();
689 let child_context = KeyContext::try_from("child").unwrap();
690
691 assert!(not_predicate.eval(slice::from_ref(&workspace_context)));
692 assert!(!not_predicate.eval(slice::from_ref(&editor_context)));
693 assert!(!not_predicate.eval(&[editor_context.clone(), workspace_context.clone()]));
694 assert!(!not_predicate.eval(&[workspace_context.clone(), editor_context.clone()]));
695
696 let complex_not = KeyBindingContextPredicate::parse("!editor && workspace").unwrap();
697 assert!(complex_not.eval(slice::from_ref(&workspace_context)));
698 assert!(!complex_not.eval(&[editor_context.clone(), workspace_context.clone()]));
699
700 let not_mode_predicate = KeyBindingContextPredicate::parse("!(mode == full)").unwrap();
701 let mut mode_context = KeyContext::default();
702 mode_context.set("mode", "full");
703 assert!(!not_mode_predicate.eval(&[mode_context.clone()]));
704
705 let mut other_mode_context = KeyContext::default();
706 other_mode_context.set("mode", "partial");
707 assert!(not_mode_predicate.eval(&[other_mode_context]));
708
709 let not_descendant = KeyBindingContextPredicate::parse("!(parent > child)").unwrap();
710 assert!(not_descendant.eval(slice::from_ref(&parent_context)));
711 assert!(not_descendant.eval(slice::from_ref(&child_context)));
712 assert!(!not_descendant.eval(&[parent_context.clone(), child_context.clone()]));
713
714 let not_descendant = KeyBindingContextPredicate::parse("parent > !child").unwrap();
715 assert!(!not_descendant.eval(slice::from_ref(&parent_context)));
716 assert!(!not_descendant.eval(slice::from_ref(&child_context)));
717 assert!(!not_descendant.eval(&[parent_context, child_context]));
718
719 let double_not = KeyBindingContextPredicate::parse("!!editor").unwrap();
720 assert!(double_not.eval(slice::from_ref(&editor_context)));
721 assert!(!double_not.eval(slice::from_ref(&workspace_context)));
722
723 // Test complex descendant cases
724 let workspace_context = KeyContext::try_from("Workspace").unwrap();
725 let pane_context = KeyContext::try_from("Pane").unwrap();
726 let editor_context = KeyContext::try_from("Editor").unwrap();
727
728 // Workspace > Pane > Editor
729 let workspace_pane_editor = vec![
730 workspace_context.clone(),
731 pane_context.clone(),
732 editor_context.clone(),
733 ];
734
735 // Pane > (Pane > Editor) - should not match
736 let pane_pane_editor = KeyBindingContextPredicate::parse("Pane > (Pane > Editor)").unwrap();
737 assert!(!pane_pane_editor.eval(&workspace_pane_editor));
738
739 let workspace_pane_editor_predicate =
740 KeyBindingContextPredicate::parse("Workspace > Pane > Editor").unwrap();
741 assert!(workspace_pane_editor_predicate.eval(&workspace_pane_editor));
742
743 // (Pane > Pane) > Editor - should not match
744 let pane_pane_then_editor =
745 KeyBindingContextPredicate::parse("(Pane > Pane) > Editor").unwrap();
746 assert!(!pane_pane_then_editor.eval(&workspace_pane_editor));
747
748 // Pane > !Workspace - should match
749 let pane_not_workspace = KeyBindingContextPredicate::parse("Pane > !Workspace").unwrap();
750 assert!(pane_not_workspace.eval(&[pane_context.clone(), editor_context.clone()]));
751 assert!(!pane_not_workspace.eval(&[pane_context.clone(), workspace_context.clone()]));
752
753 // !Workspace - shouldn't match when Workspace is in the context
754 let not_workspace = KeyBindingContextPredicate::parse("!Workspace").unwrap();
755 assert!(!not_workspace.eval(slice::from_ref(&workspace_context)));
756 assert!(not_workspace.eval(slice::from_ref(&pane_context)));
757 assert!(not_workspace.eval(slice::from_ref(&editor_context)));
758 assert!(!not_workspace.eval(&workspace_pane_editor));
759 }
760}