Skip to main content

gpui_core/keymap/
context.rs

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    #[doc(hidden)]
266    pub fn eval(&self, contexts: &[KeyContext]) -> bool {
267        self.eval_inner(contexts, contexts)
268    }
269
270    /// Eval a predicate against a set of contexts, arranged from lowest to highest.
271    pub fn eval_inner(&self, contexts: &[KeyContext], all_contexts: &[KeyContext]) -> bool {
272        let Some(context) = contexts.last() else {
273            return false;
274        };
275        match self {
276            Self::Identifier(name) => context.contains(name),
277            Self::Equal(left, right) => context
278                .get(left)
279                .map(|value| value == right)
280                .unwrap_or(false),
281            Self::NotEqual(left, right) => context
282                .get(left)
283                .map(|value| value != right)
284                .unwrap_or(true),
285            Self::Not(pred) => {
286                for i in 0..all_contexts.len() {
287                    if pred.eval_inner(&all_contexts[..=i], all_contexts) {
288                        return false;
289                    }
290                }
291                true
292            }
293            // Workspace > Pane > Editor
294            //
295            // Pane > (Pane > Editor) // should match?
296            // (Pane > Pane) > Editor // should not match?
297            // Pane > !Workspace <-- should match?
298            // !Workspace        <-- shouldn't match?
299            Self::Descendant(parent, child) => {
300                for i in 0..contexts.len() - 1 {
301                    // [Workspace >  Pane], [Editor]
302                    if parent.eval_inner(&contexts[..=i], all_contexts) {
303                        if !child.eval_inner(&contexts[i + 1..], &contexts[i + 1..]) {
304                            return false;
305                        }
306                        return true;
307                    }
308                }
309                false
310            }
311            Self::And(left, right) => {
312                left.eval_inner(contexts, all_contexts) && right.eval_inner(contexts, all_contexts)
313            }
314            Self::Or(left, right) => {
315                left.eval_inner(contexts, all_contexts) || right.eval_inner(contexts, all_contexts)
316            }
317        }
318    }
319
320    /// Returns whether or not this predicate matches all possible contexts matched by
321    /// the other predicate.
322    pub fn is_superset(&self, other: &Self) -> bool {
323        if self == other {
324            return true;
325        }
326
327        if let KeyBindingContextPredicate::Or(left, right) = self {
328            return left.is_superset(other) || right.is_superset(other);
329        }
330
331        match other {
332            KeyBindingContextPredicate::Descendant(_, child) => self.is_superset(child),
333            KeyBindingContextPredicate::And(left, right) => {
334                self.is_superset(left) || self.is_superset(right)
335            }
336            KeyBindingContextPredicate::Identifier(_) => false,
337            KeyBindingContextPredicate::Equal(_, _) => false,
338            KeyBindingContextPredicate::NotEqual(_, _) => false,
339            KeyBindingContextPredicate::Not(_) => false,
340            KeyBindingContextPredicate::Or(_, _) => false,
341        }
342    }
343
344    fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
345        type Op = fn(
346            KeyBindingContextPredicate,
347            KeyBindingContextPredicate,
348        ) -> Result<KeyBindingContextPredicate>;
349
350        let (mut predicate, rest) = Self::parse_primary(source)?;
351        source = rest;
352
353        'parse: loop {
354            for (operator, precedence, constructor) in [
355                (">", PRECEDENCE_CHILD, Self::new_child as Op),
356                ("&&", PRECEDENCE_AND, Self::new_and as Op),
357                ("||", PRECEDENCE_OR, Self::new_or as Op),
358                ("==", PRECEDENCE_EQ, Self::new_eq as Op),
359                ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
360            ] {
361                if source.starts_with(operator) && precedence >= min_precedence {
362                    source = skip_whitespace(&source[operator.len()..]);
363                    let (right, rest) = Self::parse_expr(source, precedence + 1)?;
364                    predicate = constructor(predicate, right)?;
365                    source = rest;
366                    continue 'parse;
367                }
368            }
369            break;
370        }
371
372        Ok((predicate, source))
373    }
374
375    fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
376        let next = source.chars().next().context("unexpected end")?;
377        match next {
378            '(' => {
379                source = skip_whitespace(&source[1..]);
380                let (predicate, rest) = Self::parse_expr(source, 0)?;
381                let stripped = rest.strip_prefix(')').context("expected a ')'")?;
382                source = skip_whitespace(stripped);
383                Ok((predicate, source))
384            }
385            '!' => {
386                let source = skip_whitespace(&source[1..]);
387                let (predicate, source) = Self::parse_expr(source, PRECEDENCE_NOT)?;
388                Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
389            }
390            _ if is_identifier_char(next) => {
391                let len = source
392                    .find(|c: char| !is_identifier_char(c) && !is_vim_operator_char(c))
393                    .unwrap_or(source.len());
394                let (identifier, rest) = source.split_at(len);
395                source = skip_whitespace(rest);
396                Ok((
397                    KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
398                    source,
399                ))
400            }
401            _ if is_vim_operator_char(next) => {
402                let (operator, rest) = source.split_at(1);
403                source = skip_whitespace(rest);
404                Ok((
405                    KeyBindingContextPredicate::Identifier(operator.to_string().into()),
406                    source,
407                ))
408            }
409            _ => anyhow::bail!("unexpected character '{next:?}'"),
410        }
411    }
412
413    fn new_or(self, other: Self) -> Result<Self> {
414        Ok(Self::Or(Box::new(self), Box::new(other)))
415    }
416
417    fn new_and(self, other: Self) -> Result<Self> {
418        Ok(Self::And(Box::new(self), Box::new(other)))
419    }
420
421    fn new_child(self, other: Self) -> Result<Self> {
422        Ok(Self::Descendant(Box::new(self), Box::new(other)))
423    }
424
425    fn new_eq(self, other: Self) -> Result<Self> {
426        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
427            Ok(Self::Equal(left, right))
428        } else {
429            anyhow::bail!("operands of == must be identifiers");
430        }
431    }
432
433    fn new_neq(self, other: Self) -> Result<Self> {
434        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
435            Ok(Self::NotEqual(left, right))
436        } else {
437            anyhow::bail!("operands of != must be identifiers");
438        }
439    }
440}
441
442const PRECEDENCE_CHILD: u32 = 1;
443const PRECEDENCE_OR: u32 = 2;
444const PRECEDENCE_AND: u32 = 3;
445const PRECEDENCE_EQ: u32 = 4;
446const PRECEDENCE_NOT: u32 = 5;
447
448fn is_identifier_char(c: char) -> bool {
449    c.is_alphanumeric() || c == '_' || c == '-'
450}
451
452fn is_vim_operator_char(c: char) -> bool {
453    c == '>' || c == '<' || c == '~' || c == '"' || c == '?'
454}
455
456fn skip_whitespace(source: &str) -> &str {
457    let len = source
458        .find(|c: char| !c.is_whitespace())
459        .unwrap_or(source.len());
460    &source[len..]
461}
462
463#[cfg(test)]
464mod tests {
465    use core::slice;
466
467    use super::*;
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}