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