1use crate::SharedString;
2use anyhow::{Context as _, Result};
3use std::fmt;
4
5#[derive(Clone, Default, Eq, PartialEq, Hash)]
10pub struct KeyContext(Vec<ContextEntry>);
11
12#[derive(Clone, Debug, Eq, PartialEq, Hash)]
13pub struct ContextEntry {
15 pub key: SharedString,
17 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 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 pub fn primary(&self) -> Option<&ContextEntry> {
49 self.0.iter().find(|p| p.value.is_none())
50 }
51
52 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 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 pub fn is_empty(&self) -> bool {
97 self.0.is_empty()
98 }
99
100 pub fn clear(&mut self) {
102 self.0.clear();
103 }
104
105 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 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 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 pub fn contains(&self, key: &str) -> bool {
136 self.0.iter().any(|entry| entry.key.as_ref() == key)
137 }
138
139 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#[derive(Clone, Debug, Eq, PartialEq, Hash)]
170pub enum KeyBindingContextPredicate {
171 Identifier(SharedString),
173 Equal(SharedString, SharedString),
175 NotEqual(SharedString, SharedString),
177 Descendant(
180 Box<KeyBindingContextPredicate>,
181 Box<KeyBindingContextPredicate>,
182 ),
183 Not(Box<KeyBindingContextPredicate>),
185 And(
187 Box<KeyBindingContextPredicate>,
188 Box<KeyBindingContextPredicate>,
189 ),
190 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 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 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 #[allow(unused)]
270 pub fn eval(&self, contexts: &[KeyContext]) -> bool {
271 self.eval_inner(contexts, contexts)
272 }
273
274 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 Self::Descendant(parent, child) => {
304 for i in 0..contexts.len() - 1 {
305 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 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, ]
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 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 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 let workspace_pane_editor = vec![
781 workspace_context.clone(),
782 pane_context.clone(),
783 editor_context.clone(),
784 ];
785
786 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 let pane_pane_then_editor =
796 KeyBindingContextPredicate::parse("(Pane > Pane) > Editor").unwrap();
797 assert!(!pane_pane_then_editor.eval(&workspace_pane_editor));
798
799 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 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 #[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}