1use crate::SharedString;
2use anyhow::{anyhow, Result};
3use smallvec::SmallVec;
4use std::fmt;
5
6/// A datastructure for resolving whether an action should be dispatched
7/// at this point in the element tree. Contains a set of identifiers
8/// and/or key value pairs representing the current context for the
9/// keymap.
10#[derive(Clone, Default, Eq, PartialEq, Hash)]
11pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
12
13#[derive(Clone, Debug, Eq, PartialEq, Hash)]
14/// An entry in a KeyContext
15pub struct ContextEntry {
16 /// The key (or name if no value)
17 pub key: SharedString,
18 /// The value
19 pub value: Option<SharedString>,
20}
21
22impl<'a> TryFrom<&'a str> for KeyContext {
23 type Error = anyhow::Error;
24
25 fn try_from(value: &'a str) -> Result<Self> {
26 Self::parse(value)
27 }
28}
29
30impl KeyContext {
31 /// Initialize a new [`KeyContext`] that contains an `os` key set to either `macos`, `linux`, `windows` or `unknown`.
32 pub fn new_with_defaults() -> Self {
33 let mut context = Self::default();
34 #[cfg(target_os = "macos")]
35 context.set("os", "macos");
36 #[cfg(target_os = "linux")]
37 context.set("os", "linux");
38 #[cfg(target_os = "windows")]
39 context.set("os", "windows");
40 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
41 context.set("os", "unknown");
42 context
43 }
44
45 /// Returns the primary context entry (usually the name of the component)
46 pub fn primary(&self) -> Option<&ContextEntry> {
47 self.0.iter().find(|p| p.value.is_none())
48 }
49
50 /// Returns everything except the primary context entry.
51 pub fn secondary(&self) -> impl Iterator<Item = &ContextEntry> {
52 let primary = self.primary();
53 self.0.iter().filter(move |&p| Some(p) != primary)
54 }
55
56 /// Parse a key context from a string.
57 /// The key context format is very simple:
58 /// - either a single identifier, such as `StatusBar`
59 /// - or a key value pair, such as `mode = visible`
60 /// - separated by whitespace, such as `StatusBar mode = visible`
61 pub fn parse(source: &str) -> Result<Self> {
62 let mut context = Self::default();
63 let source = skip_whitespace(source);
64 Self::parse_expr(source, &mut context)?;
65 Ok(context)
66 }
67
68 fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
69 if source.is_empty() {
70 return Ok(());
71 }
72
73 let key = source
74 .chars()
75 .take_while(|c| is_identifier_char(*c))
76 .collect::<String>();
77 source = skip_whitespace(&source[key.len()..]);
78 if let Some(suffix) = source.strip_prefix('=') {
79 source = skip_whitespace(suffix);
80 let value = source
81 .chars()
82 .take_while(|c| is_identifier_char(*c))
83 .collect::<String>();
84 source = skip_whitespace(&source[value.len()..]);
85 context.set(key, value);
86 } else {
87 context.add(key);
88 }
89
90 Self::parse_expr(source, context)
91 }
92
93 /// Check if this context is empty.
94 pub fn is_empty(&self) -> bool {
95 self.0.is_empty()
96 }
97
98 /// Clear this context.
99 pub fn clear(&mut self) {
100 self.0.clear();
101 }
102
103 /// Extend this context with another context.
104 pub fn extend(&mut self, other: &Self) {
105 for entry in &other.0 {
106 if !self.contains(&entry.key) {
107 self.0.push(entry.clone());
108 }
109 }
110 }
111
112 /// Add an identifier to this context, if it's not already in this context.
113 pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
114 let key = identifier.into();
115
116 if !self.contains(&key) {
117 self.0.push(ContextEntry { key, value: None })
118 }
119 }
120
121 /// Set a key value pair in this context, if it's not already set.
122 pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
123 let key = key.into();
124 if !self.contains(&key) {
125 self.0.push(ContextEntry {
126 key,
127 value: Some(value.into()),
128 })
129 }
130 }
131
132 /// Check if this context contains a given identifier or key.
133 pub fn contains(&self, key: &str) -> bool {
134 self.0.iter().any(|entry| entry.key.as_ref() == key)
135 }
136
137 /// Get the associated value for a given identifier or key.
138 pub fn get(&self, key: &str) -> Option<&SharedString> {
139 self.0
140 .iter()
141 .find(|entry| entry.key.as_ref() == key)?
142 .value
143 .as_ref()
144 }
145}
146
147impl fmt::Debug for KeyContext {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 let mut entries = self.0.iter().peekable();
150 while let Some(entry) = entries.next() {
151 if let Some(ref value) = entry.value {
152 write!(f, "{}={}", entry.key, value)?;
153 } else {
154 write!(f, "{}", entry.key)?;
155 }
156 if entries.peek().is_some() {
157 write!(f, " ")?;
158 }
159 }
160 Ok(())
161 }
162}
163
164/// A datastructure for resolving whether an action should be dispatched
165/// Representing a small language for describing which contexts correspond
166/// to which actions.
167#[derive(Clone, Debug, Eq, PartialEq, Hash)]
168pub enum KeyBindingContextPredicate {
169 /// A predicate that will match a given identifier.
170 Identifier(SharedString),
171 /// A predicate that will match a given key-value pair.
172 Equal(SharedString, SharedString),
173 /// A predicate that will match a given key-value pair not being present.
174 NotEqual(SharedString, SharedString),
175 /// A predicate that will match a given predicate appearing below another predicate.
176 /// in the element tree
177 Child(
178 Box<KeyBindingContextPredicate>,
179 Box<KeyBindingContextPredicate>,
180 ),
181 /// Predicate that will invert another predicate.
182 Not(Box<KeyBindingContextPredicate>),
183 /// A predicate that will match if both of its children match.
184 And(
185 Box<KeyBindingContextPredicate>,
186 Box<KeyBindingContextPredicate>,
187 ),
188 /// A predicate that will match if either of its children match.
189 Or(
190 Box<KeyBindingContextPredicate>,
191 Box<KeyBindingContextPredicate>,
192 ),
193}
194
195impl fmt::Display for KeyBindingContextPredicate {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 match self {
198 Self::Identifier(name) => write!(f, "{}", name),
199 Self::Equal(left, right) => write!(f, "{} == {}", left, right),
200 Self::NotEqual(left, right) => write!(f, "{} != {}", left, right),
201 Self::Not(pred) => write!(f, "!{}", pred),
202 Self::Child(parent, child) => write!(f, "{} > {}", parent, child),
203 Self::And(left, right) => write!(f, "({} && {})", left, right),
204 Self::Or(left, right) => write!(f, "({} || {})", left, right),
205 }
206 }
207}
208
209impl KeyBindingContextPredicate {
210 /// Parse a string in the same format as the keymap's context field.
211 ///
212 /// A basic equivalence check against a set of identifiers can performed by
213 /// simply writing a string:
214 ///
215 /// `StatusBar` -> A predicate that will match a context with the identifier `StatusBar`
216 ///
217 /// You can also specify a key-value pair:
218 ///
219 /// `mode == visible` -> A predicate that will match a context with the key `mode`
220 /// with the value `visible`
221 ///
222 /// And a logical operations combining these two checks:
223 ///
224 /// `StatusBar && mode == visible` -> A predicate that will match a context with the
225 /// identifier `StatusBar` and the key `mode`
226 /// with the value `visible`
227 ///
228 ///
229 /// There is also a special child `>` operator that will match a predicate that is
230 /// below another predicate:
231 ///
232 /// `StatusBar > mode == visible` -> A predicate that will match a context identifier `StatusBar`
233 /// and a child context that has the key `mode` with the
234 /// value `visible`
235 ///
236 /// This syntax supports `!=`, `||` and `&&` as logical operators.
237 /// You can also preface an operation or check with a `!` to negate it.
238 pub fn parse(source: &str) -> Result<Self> {
239 let source = skip_whitespace(source);
240 let (predicate, rest) = Self::parse_expr(source, 0)?;
241 if let Some(next) = rest.chars().next() {
242 Err(anyhow!("unexpected character {next:?}"))
243 } else {
244 Ok(predicate)
245 }
246 }
247
248 /// Eval a predicate against a set of contexts, arranged from lowest to highest.
249 pub fn eval(&self, contexts: &[KeyContext]) -> bool {
250 let Some(context) = contexts.last() else {
251 return false;
252 };
253 match self {
254 Self::Identifier(name) => context.contains(name),
255 Self::Equal(left, right) => context
256 .get(left)
257 .map(|value| value == right)
258 .unwrap_or(false),
259 Self::NotEqual(left, right) => context
260 .get(left)
261 .map(|value| value != right)
262 .unwrap_or(true),
263 Self::Not(pred) => !pred.eval(contexts),
264 Self::Child(parent, child) => {
265 parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
266 }
267 Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
268 Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
269 }
270 }
271
272 /// Returns whether or not this predicate matches all possible contexts matched by
273 /// the other predicate.
274 pub fn is_superset(&self, other: &Self) -> bool {
275 if self == other {
276 return true;
277 }
278
279 if let KeyBindingContextPredicate::Or(left, right) = self {
280 return left.is_superset(other) || right.is_superset(other);
281 }
282
283 match other {
284 KeyBindingContextPredicate::Child(_, child) => self.is_superset(child),
285 KeyBindingContextPredicate::And(left, right) => {
286 self.is_superset(left) || self.is_superset(right)
287 }
288 KeyBindingContextPredicate::Identifier(_) => false,
289 KeyBindingContextPredicate::Equal(_, _) => false,
290 KeyBindingContextPredicate::NotEqual(_, _) => false,
291 KeyBindingContextPredicate::Not(_) => false,
292 KeyBindingContextPredicate::Or(_, _) => false,
293 }
294 }
295
296 fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
297 type Op = fn(
298 KeyBindingContextPredicate,
299 KeyBindingContextPredicate,
300 ) -> Result<KeyBindingContextPredicate>;
301
302 let (mut predicate, rest) = Self::parse_primary(source)?;
303 source = rest;
304
305 'parse: loop {
306 for (operator, precedence, constructor) in [
307 (">", PRECEDENCE_CHILD, Self::new_child as Op),
308 ("&&", PRECEDENCE_AND, Self::new_and as Op),
309 ("||", PRECEDENCE_OR, Self::new_or as Op),
310 ("==", PRECEDENCE_EQ, Self::new_eq as Op),
311 ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
312 ] {
313 if source.starts_with(operator) && precedence >= min_precedence {
314 source = skip_whitespace(&source[operator.len()..]);
315 let (right, rest) = Self::parse_expr(source, precedence + 1)?;
316 predicate = constructor(predicate, right)?;
317 source = rest;
318 continue 'parse;
319 }
320 }
321 break;
322 }
323
324 Ok((predicate, source))
325 }
326
327 fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
328 let next = source
329 .chars()
330 .next()
331 .ok_or_else(|| anyhow!("unexpected eof"))?;
332 match next {
333 '(' => {
334 source = skip_whitespace(&source[1..]);
335 let (predicate, rest) = Self::parse_expr(source, 0)?;
336 if let Some(stripped) = rest.strip_prefix(')') {
337 source = skip_whitespace(stripped);
338 Ok((predicate, source))
339 } else {
340 Err(anyhow!("expected a ')'"))
341 }
342 }
343 '!' => {
344 let source = skip_whitespace(&source[1..]);
345 let (predicate, source) = Self::parse_expr(source, PRECEDENCE_NOT)?;
346 Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
347 }
348 _ if is_identifier_char(next) => {
349 let len = source
350 .find(|c: char| !is_identifier_char(c) && !is_vim_operator_char(c))
351 .unwrap_or(source.len());
352 let (identifier, rest) = source.split_at(len);
353 source = skip_whitespace(rest);
354 Ok((
355 KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
356 source,
357 ))
358 }
359 _ if is_vim_operator_char(next) => {
360 let (operator, rest) = source.split_at(1);
361 source = skip_whitespace(rest);
362 Ok((
363 KeyBindingContextPredicate::Identifier(operator.to_string().into()),
364 source,
365 ))
366 }
367 _ => Err(anyhow!("unexpected character {next:?}")),
368 }
369 }
370
371 fn new_or(self, other: Self) -> Result<Self> {
372 Ok(Self::Or(Box::new(self), Box::new(other)))
373 }
374
375 fn new_and(self, other: Self) -> Result<Self> {
376 Ok(Self::And(Box::new(self), Box::new(other)))
377 }
378
379 fn new_child(self, other: Self) -> Result<Self> {
380 Ok(Self::Child(Box::new(self), Box::new(other)))
381 }
382
383 fn new_eq(self, other: Self) -> Result<Self> {
384 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
385 Ok(Self::Equal(left, right))
386 } else {
387 Err(anyhow!("operands must be identifiers"))
388 }
389 }
390
391 fn new_neq(self, other: Self) -> Result<Self> {
392 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
393 Ok(Self::NotEqual(left, right))
394 } else {
395 Err(anyhow!("operands must be identifiers"))
396 }
397 }
398}
399
400const PRECEDENCE_CHILD: u32 = 1;
401const PRECEDENCE_OR: u32 = 2;
402const PRECEDENCE_AND: u32 = 3;
403const PRECEDENCE_EQ: u32 = 4;
404const PRECEDENCE_NOT: u32 = 5;
405
406fn is_identifier_char(c: char) -> bool {
407 c.is_alphanumeric() || c == '_' || c == '-'
408}
409
410fn is_vim_operator_char(c: char) -> bool {
411 c == '>' || c == '<' || c == '~' || c == '"'
412}
413
414fn skip_whitespace(source: &str) -> &str {
415 let len = source
416 .find(|c: char| !c.is_whitespace())
417 .unwrap_or(source.len());
418 &source[len..]
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use crate as gpui;
425 use KeyBindingContextPredicate::*;
426
427 #[test]
428 fn test_actions_definition() {
429 {
430 actions!(test, [A, B, C, D, E, F, G]);
431 }
432
433 {
434 actions!(
435 test,
436 [
437 A,
438 B,
439 C,
440 D,
441 E,
442 F,
443 G, // Don't wrap, test the trailing comma
444 ]
445 );
446 }
447 }
448
449 #[test]
450 fn test_parse_context() {
451 let mut expected = KeyContext::default();
452 expected.add("baz");
453 expected.set("foo", "bar");
454 assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
455 assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
456 assert_eq!(
457 KeyContext::parse(" baz foo = bar baz").unwrap(),
458 expected
459 );
460 assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
461 }
462
463 #[test]
464 fn test_parse_identifiers() {
465 // Identifiers
466 assert_eq!(
467 KeyBindingContextPredicate::parse("abc12").unwrap(),
468 Identifier("abc12".into())
469 );
470 assert_eq!(
471 KeyBindingContextPredicate::parse("_1a").unwrap(),
472 Identifier("_1a".into())
473 );
474 }
475
476 #[test]
477 fn test_parse_negations() {
478 assert_eq!(
479 KeyBindingContextPredicate::parse("!abc").unwrap(),
480 Not(Box::new(Identifier("abc".into())))
481 );
482 assert_eq!(
483 KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
484 Not(Box::new(Not(Box::new(Identifier("abc".into())))))
485 );
486 }
487
488 #[test]
489 fn test_parse_equality_operators() {
490 assert_eq!(
491 KeyBindingContextPredicate::parse("a == b").unwrap(),
492 Equal("a".into(), "b".into())
493 );
494 assert_eq!(
495 KeyBindingContextPredicate::parse("c!=d").unwrap(),
496 NotEqual("c".into(), "d".into())
497 );
498 assert_eq!(
499 KeyBindingContextPredicate::parse("c == !d")
500 .unwrap_err()
501 .to_string(),
502 "operands must be identifiers"
503 );
504 }
505
506 #[test]
507 fn test_parse_boolean_operators() {
508 assert_eq!(
509 KeyBindingContextPredicate::parse("a || b").unwrap(),
510 Or(
511 Box::new(Identifier("a".into())),
512 Box::new(Identifier("b".into()))
513 )
514 );
515 assert_eq!(
516 KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
517 Or(
518 Box::new(Identifier("a".into())),
519 Box::new(And(
520 Box::new(Not(Box::new(Identifier("b".into())))),
521 Box::new(Identifier("c".into()))
522 ))
523 )
524 );
525 assert_eq!(
526 KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
527 Or(
528 Box::new(And(
529 Box::new(Identifier("a".into())),
530 Box::new(Identifier("b".into()))
531 )),
532 Box::new(And(
533 Box::new(Identifier("c".into())),
534 Box::new(Identifier("d".into()))
535 ))
536 )
537 );
538 assert_eq!(
539 KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
540 Or(
541 Box::new(And(
542 Box::new(Equal("a".into(), "b".into())),
543 Box::new(Identifier("c".into()))
544 )),
545 Box::new(And(
546 Box::new(Equal("d".into(), "e".into())),
547 Box::new(Identifier("f".into()))
548 ))
549 )
550 );
551 assert_eq!(
552 KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
553 And(
554 Box::new(And(
555 Box::new(And(
556 Box::new(Identifier("a".into())),
557 Box::new(Identifier("b".into()))
558 )),
559 Box::new(Identifier("c".into())),
560 )),
561 Box::new(Identifier("d".into()))
562 ),
563 );
564 }
565
566 #[test]
567 fn test_parse_parenthesized_expressions() {
568 assert_eq!(
569 KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
570 And(
571 Box::new(Identifier("a".into())),
572 Box::new(Or(
573 Box::new(Equal("b".into(), "c".into())),
574 Box::new(NotEqual("d".into(), "e".into())),
575 )),
576 ),
577 );
578 assert_eq!(
579 KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
580 Or(
581 Box::new(Identifier("a".into())),
582 Box::new(Identifier("b".into())),
583 )
584 );
585 }
586
587 #[test]
588 fn test_is_superset() {
589 assert_is_superset("editor", "editor", true);
590 assert_is_superset("editor", "workspace", false);
591
592 assert_is_superset("editor", "editor && vim_mode", true);
593 assert_is_superset("editor", "mode == full && editor", true);
594 assert_is_superset("editor && mode == full", "editor", false);
595
596 assert_is_superset("editor", "something > editor", true);
597 assert_is_superset("editor", "editor > menu", false);
598
599 assert_is_superset("foo || bar || baz", "bar", true);
600 assert_is_superset("foo || bar || baz", "quux", false);
601
602 #[track_caller]
603 fn assert_is_superset(a: &str, b: &str, result: bool) {
604 let a = KeyBindingContextPredicate::parse(a).unwrap();
605 let b = KeyBindingContextPredicate::parse(b).unwrap();
606 assert_eq!(a.is_superset(&b), result, "({a:?}).is_superset({b:?})");
607 }
608 }
609}