Skip to main content

open_gpui/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::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 KeyBindingContextPredicate::*;
521
522    #[test]
523    fn test_actions_definition() {
524        {
525            actions!(test_only, [A, B, C, D, E, F, G]);
526        }
527
528        {
529            actions!(
530                test_only,
531                [
532                    H, I, J, K, L, M, N, // Don't wrap, test the trailing comma
533                ]
534            );
535        }
536    }
537
538    #[test]
539    fn test_parse_context() {
540        let mut expected = KeyContext::default();
541        expected.add("baz");
542        expected.set("foo", "bar");
543        assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
544        assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
545        assert_eq!(
546            KeyContext::parse("  baz foo   =   bar baz").unwrap(),
547            expected
548        );
549        assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
550    }
551
552    #[test]
553    fn test_parse_identifiers() {
554        // Identifiers
555        assert_eq!(
556            KeyBindingContextPredicate::parse("abc12").unwrap(),
557            Identifier("abc12".into())
558        );
559        assert_eq!(
560            KeyBindingContextPredicate::parse("_1a").unwrap(),
561            Identifier("_1a".into())
562        );
563    }
564
565    #[test]
566    fn test_parse_negations() {
567        assert_eq!(
568            KeyBindingContextPredicate::parse("!abc").unwrap(),
569            Not(Box::new(Identifier("abc".into())))
570        );
571        assert_eq!(
572            KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
573            Not(Box::new(Not(Box::new(Identifier("abc".into())))))
574        );
575    }
576
577    #[test]
578    fn test_parse_equality_operators() {
579        assert_eq!(
580            KeyBindingContextPredicate::parse("a == b").unwrap(),
581            Equal("a".into(), "b".into())
582        );
583        assert_eq!(
584            KeyBindingContextPredicate::parse("c!=d").unwrap(),
585            NotEqual("c".into(), "d".into())
586        );
587        assert_eq!(
588            KeyBindingContextPredicate::parse("c == !d")
589                .unwrap_err()
590                .to_string(),
591            "operands of == must be identifiers"
592        );
593    }
594
595    #[test]
596    fn test_parse_boolean_operators() {
597        assert_eq!(
598            KeyBindingContextPredicate::parse("a || b").unwrap(),
599            Or(
600                Box::new(Identifier("a".into())),
601                Box::new(Identifier("b".into()))
602            )
603        );
604        assert_eq!(
605            KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
606            Or(
607                Box::new(Identifier("a".into())),
608                Box::new(And(
609                    Box::new(Not(Box::new(Identifier("b".into())))),
610                    Box::new(Identifier("c".into()))
611                ))
612            )
613        );
614        assert_eq!(
615            KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
616            Or(
617                Box::new(And(
618                    Box::new(Identifier("a".into())),
619                    Box::new(Identifier("b".into()))
620                )),
621                Box::new(And(
622                    Box::new(Identifier("c".into())),
623                    Box::new(Identifier("d".into()))
624                ))
625            )
626        );
627        assert_eq!(
628            KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
629            Or(
630                Box::new(And(
631                    Box::new(Equal("a".into(), "b".into())),
632                    Box::new(Identifier("c".into()))
633                )),
634                Box::new(And(
635                    Box::new(Equal("d".into(), "e".into())),
636                    Box::new(Identifier("f".into()))
637                ))
638            )
639        );
640        assert_eq!(
641            KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
642            And(
643                Box::new(And(
644                    Box::new(And(
645                        Box::new(Identifier("a".into())),
646                        Box::new(Identifier("b".into()))
647                    )),
648                    Box::new(Identifier("c".into())),
649                )),
650                Box::new(Identifier("d".into()))
651            ),
652        );
653    }
654
655    #[test]
656    fn test_parse_parenthesized_expressions() {
657        assert_eq!(
658            KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
659            And(
660                Box::new(Identifier("a".into())),
661                Box::new(Or(
662                    Box::new(Equal("b".into(), "c".into())),
663                    Box::new(NotEqual("d".into(), "e".into())),
664                )),
665            ),
666        );
667        assert_eq!(
668            KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
669            Or(
670                Box::new(Identifier("a".into())),
671                Box::new(Identifier("b".into())),
672            )
673        );
674    }
675
676    #[test]
677    fn test_is_superset() {
678        assert_is_superset("editor", "editor", true);
679        assert_is_superset("editor", "workspace", false);
680
681        assert_is_superset("editor", "editor && vim_mode", true);
682        assert_is_superset("editor", "mode == full && editor", true);
683        assert_is_superset("editor && mode == full", "editor", false);
684
685        assert_is_superset("editor", "something > editor", true);
686        assert_is_superset("editor", "editor > menu", false);
687
688        assert_is_superset("foo || bar || baz", "bar", true);
689        assert_is_superset("foo || bar || baz", "quux", false);
690
691        #[track_caller]
692        fn assert_is_superset(a: &str, b: &str, result: bool) {
693            let a = KeyBindingContextPredicate::parse(a).unwrap();
694            let b = KeyBindingContextPredicate::parse(b).unwrap();
695            assert_eq!(a.is_superset(&b), result, "({a:?}).is_superset({b:?})");
696        }
697    }
698
699    #[test]
700    fn test_child_operator() {
701        let predicate = KeyBindingContextPredicate::parse("parent > child").unwrap();
702
703        let parent_context = KeyContext::try_from("parent").unwrap();
704        let child_context = KeyContext::try_from("child").unwrap();
705
706        let contexts = vec![parent_context.clone(), child_context.clone()];
707        assert!(predicate.eval(&contexts));
708
709        let grandparent_context = KeyContext::try_from("grandparent").unwrap();
710
711        let contexts = vec![
712            grandparent_context,
713            parent_context.clone(),
714            child_context.clone(),
715        ];
716        assert!(predicate.eval(&contexts));
717
718        let other_context = KeyContext::try_from("other").unwrap();
719
720        let contexts = vec![other_context.clone(), child_context.clone()];
721        assert!(!predicate.eval(&contexts));
722
723        let contexts = vec![parent_context.clone(), other_context, child_context.clone()];
724        assert!(predicate.eval(&contexts));
725
726        assert!(!predicate.eval(&[]));
727        assert!(!predicate.eval(slice::from_ref(&child_context)));
728        assert!(!predicate.eval(&[parent_context]));
729
730        let zany_predicate = KeyBindingContextPredicate::parse("child > child").unwrap();
731        assert!(!zany_predicate.eval(slice::from_ref(&child_context)));
732        assert!(zany_predicate.eval(&[child_context.clone(), child_context]));
733    }
734
735    #[test]
736    fn test_not_operator() {
737        let not_predicate = KeyBindingContextPredicate::parse("!editor").unwrap();
738        let editor_context = KeyContext::try_from("editor").unwrap();
739        let workspace_context = KeyContext::try_from("workspace").unwrap();
740        let parent_context = KeyContext::try_from("parent").unwrap();
741        let child_context = KeyContext::try_from("child").unwrap();
742
743        assert!(not_predicate.eval(slice::from_ref(&workspace_context)));
744        assert!(!not_predicate.eval(slice::from_ref(&editor_context)));
745        assert!(!not_predicate.eval(&[editor_context.clone(), workspace_context.clone()]));
746        assert!(!not_predicate.eval(&[workspace_context.clone(), editor_context.clone()]));
747
748        let complex_not = KeyBindingContextPredicate::parse("!editor && workspace").unwrap();
749        assert!(complex_not.eval(slice::from_ref(&workspace_context)));
750        assert!(!complex_not.eval(&[editor_context.clone(), workspace_context.clone()]));
751
752        let not_mode_predicate = KeyBindingContextPredicate::parse("!(mode == full)").unwrap();
753        let mut mode_context = KeyContext::default();
754        mode_context.set("mode", "full");
755        assert!(!not_mode_predicate.eval(&[mode_context.clone()]));
756
757        let mut other_mode_context = KeyContext::default();
758        other_mode_context.set("mode", "partial");
759        assert!(not_mode_predicate.eval(&[other_mode_context]));
760
761        let not_descendant = KeyBindingContextPredicate::parse("!(parent > child)").unwrap();
762        assert!(not_descendant.eval(slice::from_ref(&parent_context)));
763        assert!(not_descendant.eval(slice::from_ref(&child_context)));
764        assert!(!not_descendant.eval(&[parent_context.clone(), child_context.clone()]));
765
766        let not_descendant = KeyBindingContextPredicate::parse("parent > !child").unwrap();
767        assert!(!not_descendant.eval(slice::from_ref(&parent_context)));
768        assert!(!not_descendant.eval(slice::from_ref(&child_context)));
769        assert!(!not_descendant.eval(&[parent_context, child_context]));
770
771        let double_not = KeyBindingContextPredicate::parse("!!editor").unwrap();
772        assert!(double_not.eval(slice::from_ref(&editor_context)));
773        assert!(!double_not.eval(slice::from_ref(&workspace_context)));
774
775        // Test complex descendant cases
776        let workspace_context = KeyContext::try_from("Workspace").unwrap();
777        let pane_context = KeyContext::try_from("Pane").unwrap();
778        let editor_context = KeyContext::try_from("Editor").unwrap();
779
780        // Workspace > Pane > Editor
781        let workspace_pane_editor = vec![
782            workspace_context.clone(),
783            pane_context.clone(),
784            editor_context.clone(),
785        ];
786
787        // Pane > (Pane > Editor) - should not match
788        let pane_pane_editor = KeyBindingContextPredicate::parse("Pane > (Pane > Editor)").unwrap();
789        assert!(!pane_pane_editor.eval(&workspace_pane_editor));
790
791        let workspace_pane_editor_predicate =
792            KeyBindingContextPredicate::parse("Workspace > Pane > Editor").unwrap();
793        assert!(workspace_pane_editor_predicate.eval(&workspace_pane_editor));
794
795        // (Pane > Pane) > Editor - should not match
796        let pane_pane_then_editor =
797            KeyBindingContextPredicate::parse("(Pane > Pane) > Editor").unwrap();
798        assert!(!pane_pane_then_editor.eval(&workspace_pane_editor));
799
800        // Pane > !Workspace - should match
801        let pane_not_workspace = KeyBindingContextPredicate::parse("Pane > !Workspace").unwrap();
802        assert!(pane_not_workspace.eval(&[pane_context.clone(), editor_context.clone()]));
803        assert!(!pane_not_workspace.eval(&[pane_context.clone(), workspace_context.clone()]));
804
805        // !Workspace - shouldn't match when Workspace is in the context
806        let not_workspace = KeyBindingContextPredicate::parse("!Workspace").unwrap();
807        assert!(!not_workspace.eval(slice::from_ref(&workspace_context)));
808        assert!(not_workspace.eval(slice::from_ref(&pane_context)));
809        assert!(not_workspace.eval(slice::from_ref(&editor_context)));
810        assert!(!not_workspace.eval(&workspace_pane_editor));
811    }
812
813    // MARK: - Display
814
815    #[test]
816    fn test_context_display() {
817        fn ident(s: &str) -> Box<KeyBindingContextPredicate> {
818            Box::new(Identifier(SharedString::new(s)))
819        }
820        fn eq(a: &str, b: &str) -> Box<KeyBindingContextPredicate> {
821            Box::new(Equal(SharedString::new(a), SharedString::new(b)))
822        }
823        fn not_eq(a: &str, b: &str) -> Box<KeyBindingContextPredicate> {
824            Box::new(NotEqual(SharedString::new(a), SharedString::new(b)))
825        }
826        fn and(
827            a: Box<KeyBindingContextPredicate>,
828            b: Box<KeyBindingContextPredicate>,
829        ) -> Box<KeyBindingContextPredicate> {
830            Box::new(And(a, b))
831        }
832        fn or(
833            a: Box<KeyBindingContextPredicate>,
834            b: Box<KeyBindingContextPredicate>,
835        ) -> Box<KeyBindingContextPredicate> {
836            Box::new(Or(a, b))
837        }
838        fn descendant(
839            a: Box<KeyBindingContextPredicate>,
840            b: Box<KeyBindingContextPredicate>,
841        ) -> Box<KeyBindingContextPredicate> {
842            Box::new(Descendant(a, b))
843        }
844        fn not(a: Box<KeyBindingContextPredicate>) -> Box<KeyBindingContextPredicate> {
845            Box::new(Not(a))
846        }
847
848        let test_cases = [
849            (ident("a"), "a"),
850            (eq("a", "b"), "a == b"),
851            (not_eq("a", "b"), "a != b"),
852            (descendant(ident("a"), ident("b")), "a > b"),
853            (not(ident("a")), "!a"),
854            (not_eq("a", "b"), "a != b"),
855            (descendant(ident("a"), ident("b")), "a > b"),
856            (not(and(ident("a"), ident("b"))), "!(a && b)"),
857            (not(or(ident("a"), ident("b"))), "!(a || b)"),
858            (and(ident("a"), ident("b")), "a && b"),
859            (and(and(ident("a"), ident("b")), ident("c")), "a && b && c"),
860            (or(ident("a"), ident("b")), "a || b"),
861            (or(or(ident("a"), ident("b")), ident("c")), "a || b || c"),
862            (or(ident("a"), and(ident("b"), ident("c"))), "a || (b && c)"),
863            (
864                and(
865                    and(
866                        and(ident("a"), eq("b", "c")),
867                        not(descendant(ident("d"), ident("e"))),
868                    ),
869                    eq("f", "g"),
870                ),
871                "a && b == c && !(d > e) && f == g",
872            ),
873            (
874                and(and(ident("a"), or(ident("b"), ident("c"))), ident("d")),
875                "a && (b || c) && d",
876            ),
877            (
878                or(or(ident("a"), and(ident("b"), ident("c"))), ident("d")),
879                "a || (b && c) || d",
880            ),
881        ];
882
883        for (predicate, expected) in test_cases {
884            let actual = predicate.to_string();
885            assert_eq!(actual, expected);
886            let parsed = KeyBindingContextPredicate::parse(&actual).unwrap();
887            assert_eq!(parsed, *predicate);
888        }
889    }
890}