context.rs

  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    fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
273        type Op = fn(
274            KeyBindingContextPredicate,
275            KeyBindingContextPredicate,
276        ) -> Result<KeyBindingContextPredicate>;
277
278        let (mut predicate, rest) = Self::parse_primary(source)?;
279        source = rest;
280
281        'parse: loop {
282            for (operator, precedence, constructor) in [
283                (">", PRECEDENCE_CHILD, Self::new_child as Op),
284                ("&&", PRECEDENCE_AND, Self::new_and as Op),
285                ("||", PRECEDENCE_OR, Self::new_or as Op),
286                ("==", PRECEDENCE_EQ, Self::new_eq as Op),
287                ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
288            ] {
289                if source.starts_with(operator) && precedence >= min_precedence {
290                    source = skip_whitespace(&source[operator.len()..]);
291                    let (right, rest) = Self::parse_expr(source, precedence + 1)?;
292                    predicate = constructor(predicate, right)?;
293                    source = rest;
294                    continue 'parse;
295                }
296            }
297            break;
298        }
299
300        Ok((predicate, source))
301    }
302
303    fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
304        let next = source
305            .chars()
306            .next()
307            .ok_or_else(|| anyhow!("unexpected eof"))?;
308        match next {
309            '(' => {
310                source = skip_whitespace(&source[1..]);
311                let (predicate, rest) = Self::parse_expr(source, 0)?;
312                if let Some(stripped) = rest.strip_prefix(')') {
313                    source = skip_whitespace(stripped);
314                    Ok((predicate, source))
315                } else {
316                    Err(anyhow!("expected a ')'"))
317                }
318            }
319            '!' => {
320                let source = skip_whitespace(&source[1..]);
321                let (predicate, source) = Self::parse_expr(source, PRECEDENCE_NOT)?;
322                Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
323            }
324            _ if is_identifier_char(next) => {
325                let len = source
326                    .find(|c: char| !is_identifier_char(c) && !is_vim_operator_char(c))
327                    .unwrap_or(source.len());
328                let (identifier, rest) = source.split_at(len);
329                source = skip_whitespace(rest);
330                Ok((
331                    KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
332                    source,
333                ))
334            }
335            _ if is_vim_operator_char(next) => {
336                let (operator, rest) = source.split_at(1);
337                source = skip_whitespace(rest);
338                Ok((
339                    KeyBindingContextPredicate::Identifier(operator.to_string().into()),
340                    source,
341                ))
342            }
343            _ => Err(anyhow!("unexpected character {next:?}")),
344        }
345    }
346
347    fn new_or(self, other: Self) -> Result<Self> {
348        Ok(Self::Or(Box::new(self), Box::new(other)))
349    }
350
351    fn new_and(self, other: Self) -> Result<Self> {
352        Ok(Self::And(Box::new(self), Box::new(other)))
353    }
354
355    fn new_child(self, other: Self) -> Result<Self> {
356        Ok(Self::Child(Box::new(self), Box::new(other)))
357    }
358
359    fn new_eq(self, other: Self) -> Result<Self> {
360        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
361            Ok(Self::Equal(left, right))
362        } else {
363            Err(anyhow!("operands must be identifiers"))
364        }
365    }
366
367    fn new_neq(self, other: Self) -> Result<Self> {
368        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
369            Ok(Self::NotEqual(left, right))
370        } else {
371            Err(anyhow!("operands must be identifiers"))
372        }
373    }
374}
375
376const PRECEDENCE_CHILD: u32 = 1;
377const PRECEDENCE_OR: u32 = 2;
378const PRECEDENCE_AND: u32 = 3;
379const PRECEDENCE_EQ: u32 = 4;
380const PRECEDENCE_NOT: u32 = 5;
381
382fn is_identifier_char(c: char) -> bool {
383    c.is_alphanumeric() || c == '_' || c == '-'
384}
385
386fn is_vim_operator_char(c: char) -> bool {
387    c == '>' || c == '<' || c == '~' || c == '"'
388}
389
390fn skip_whitespace(source: &str) -> &str {
391    let len = source
392        .find(|c: char| !c.is_whitespace())
393        .unwrap_or(source.len());
394    &source[len..]
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400    use crate as gpui;
401    use KeyBindingContextPredicate::*;
402
403    #[test]
404    fn test_actions_definition() {
405        {
406            actions!(test, [A, B, C, D, E, F, G]);
407        }
408
409        {
410            actions!(
411                test,
412                [
413                A,
414                B,
415                C,
416                D,
417                E,
418                F,
419                G, // Don't wrap, test the trailing comma
420            ]
421            );
422        }
423    }
424
425    #[test]
426    fn test_parse_context() {
427        let mut expected = KeyContext::default();
428        expected.add("baz");
429        expected.set("foo", "bar");
430        assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
431        assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
432        assert_eq!(
433            KeyContext::parse("  baz foo   =   bar baz").unwrap(),
434            expected
435        );
436        assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
437    }
438
439    #[test]
440    fn test_parse_identifiers() {
441        // Identifiers
442        assert_eq!(
443            KeyBindingContextPredicate::parse("abc12").unwrap(),
444            Identifier("abc12".into())
445        );
446        assert_eq!(
447            KeyBindingContextPredicate::parse("_1a").unwrap(),
448            Identifier("_1a".into())
449        );
450    }
451
452    #[test]
453    fn test_parse_negations() {
454        assert_eq!(
455            KeyBindingContextPredicate::parse("!abc").unwrap(),
456            Not(Box::new(Identifier("abc".into())))
457        );
458        assert_eq!(
459            KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
460            Not(Box::new(Not(Box::new(Identifier("abc".into())))))
461        );
462    }
463
464    #[test]
465    fn test_parse_equality_operators() {
466        assert_eq!(
467            KeyBindingContextPredicate::parse("a == b").unwrap(),
468            Equal("a".into(), "b".into())
469        );
470        assert_eq!(
471            KeyBindingContextPredicate::parse("c!=d").unwrap(),
472            NotEqual("c".into(), "d".into())
473        );
474        assert_eq!(
475            KeyBindingContextPredicate::parse("c == !d")
476                .unwrap_err()
477                .to_string(),
478            "operands must be identifiers"
479        );
480    }
481
482    #[test]
483    fn test_parse_boolean_operators() {
484        assert_eq!(
485            KeyBindingContextPredicate::parse("a || b").unwrap(),
486            Or(
487                Box::new(Identifier("a".into())),
488                Box::new(Identifier("b".into()))
489            )
490        );
491        assert_eq!(
492            KeyBindingContextPredicate::parse("a || !b && c").unwrap(),
493            Or(
494                Box::new(Identifier("a".into())),
495                Box::new(And(
496                    Box::new(Not(Box::new(Identifier("b".into())))),
497                    Box::new(Identifier("c".into()))
498                ))
499            )
500        );
501        assert_eq!(
502            KeyBindingContextPredicate::parse("a && b || c&&d").unwrap(),
503            Or(
504                Box::new(And(
505                    Box::new(Identifier("a".into())),
506                    Box::new(Identifier("b".into()))
507                )),
508                Box::new(And(
509                    Box::new(Identifier("c".into())),
510                    Box::new(Identifier("d".into()))
511                ))
512            )
513        );
514        assert_eq!(
515            KeyBindingContextPredicate::parse("a == b && c || d == e && f").unwrap(),
516            Or(
517                Box::new(And(
518                    Box::new(Equal("a".into(), "b".into())),
519                    Box::new(Identifier("c".into()))
520                )),
521                Box::new(And(
522                    Box::new(Equal("d".into(), "e".into())),
523                    Box::new(Identifier("f".into()))
524                ))
525            )
526        );
527        assert_eq!(
528            KeyBindingContextPredicate::parse("a && b && c && d").unwrap(),
529            And(
530                Box::new(And(
531                    Box::new(And(
532                        Box::new(Identifier("a".into())),
533                        Box::new(Identifier("b".into()))
534                    )),
535                    Box::new(Identifier("c".into())),
536                )),
537                Box::new(Identifier("d".into()))
538            ),
539        );
540    }
541
542    #[test]
543    fn test_parse_parenthesized_expressions() {
544        assert_eq!(
545            KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(),
546            And(
547                Box::new(Identifier("a".into())),
548                Box::new(Or(
549                    Box::new(Equal("b".into(), "c".into())),
550                    Box::new(NotEqual("d".into(), "e".into())),
551                )),
552            ),
553        );
554        assert_eq!(
555            KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
556            Or(
557                Box::new(Identifier("a".into())),
558                Box::new(Identifier("b".into())),
559            )
560        );
561    }
562}