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