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() -> 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 pub fn primary(&self) -> Option<&ContextEntry> {
51 self.0.iter().find(|p| p.value.is_none())
52 }
53
54 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 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 pub fn is_empty(&self) -> bool {
99 self.0.is_empty()
100 }
101
102 pub fn clear(&mut self) {
104 self.0.clear();
105 }
106
107 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 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 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 pub fn contains(&self, key: &str) -> bool {
138 self.0.iter().any(|entry| entry.key.as_ref() == key)
139 }
140
141 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#[derive(Clone, Debug, Eq, PartialEq, Hash)]
172pub enum KeyBindingContextPredicate {
173 Identifier(SharedString),
175 Equal(SharedString, SharedString),
177 NotEqual(SharedString, SharedString),
179 Descendant(
182 Box<KeyBindingContextPredicate>,
183 Box<KeyBindingContextPredicate>,
184 ),
185 Not(Box<KeyBindingContextPredicate>),
187 And(
189 Box<KeyBindingContextPredicate>,
190 Box<KeyBindingContextPredicate>,
191 ),
192 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 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 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 #[allow(unused)]
272 pub fn eval(&self, contexts: &[KeyContext]) -> bool {
273 self.eval_inner(contexts, contexts)
274 }
275
276 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 Self::Descendant(parent, child) => {
306 for i in 0..contexts.len() - 1 {
307 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 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, ]
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 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 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 let workspace_pane_editor = vec![
782 workspace_context.clone(),
783 pane_context.clone(),
784 editor_context.clone(),
785 ];
786
787 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 let pane_pane_then_editor =
797 KeyBindingContextPredicate::parse("(Pane > Pane) > Editor").unwrap();
798 assert!(!pane_pane_then_editor.eval(&workspace_pane_editor));
799
800 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 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 #[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}