Skip to main content

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