action.rs

  1use crate::SharedString;
  2use anyhow::{anyhow, Context, Result};
  3use collections::{HashMap, HashSet};
  4use lazy_static::lazy_static;
  5use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
  6use serde::Deserialize;
  7use std::any::{type_name, Any};
  8
  9/// Actions are used to implement keyboard-driven UI.
 10/// When you declare an action, you can bind keys to the action in the keymap and
 11/// listeners for that action in the element tree.
 12///
 13/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
 14/// action for each listed action name.
 15/// ```rust
 16/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
 17/// ```
 18/// More complex data types can also be actions. If you annotate your type with the `#[action]` proc macro,
 19/// it will automatically
 20/// ```
 21/// #[action]
 22/// pub struct SelectNext {
 23///     pub replace_newest: bool,
 24/// }
 25///
 26/// Any type A that satisfies the following bounds is automatically an action:
 27///
 28/// ```
 29/// A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
 30/// ```
 31///
 32/// The `#[action]` annotation will derive these implementations for your struct automatically. If you
 33/// want to control them manually, you can use the lower-level `#[register_action]` macro, which only
 34/// generates the code needed to register your action before `main`. Then you'll need to implement all
 35/// the traits manually.
 36///
 37/// ```
 38/// #[gpui::register_action]
 39/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
 40/// pub struct Paste {
 41///     pub content: SharedString,
 42/// }
 43///
 44/// impl std::default::Default for Paste {
 45///     fn default() -> Self {
 46///         Self {
 47///             content: SharedString::from("🍝"),
 48///         }
 49///     }
 50/// }
 51/// ```
 52pub trait Action: std::fmt::Debug + 'static {
 53    fn qualified_name() -> SharedString
 54    where
 55        Self: Sized;
 56    fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>>
 57    where
 58        Self: Sized;
 59
 60    fn partial_eq(&self, action: &dyn Action) -> bool;
 61    fn boxed_clone(&self) -> Box<dyn Action>;
 62    fn as_any(&self) -> &dyn Any;
 63}
 64
 65// Types become actions by satisfying a list of trait bounds.
 66impl<A> Action for A
 67where
 68    A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
 69{
 70    fn qualified_name() -> SharedString {
 71        // todo!() remove the 2 replacement when migration is done
 72        type_name::<A>().replace("2::", "::").into()
 73    }
 74
 75    fn build(params: Option<serde_json::Value>) -> Result<Box<dyn Action>>
 76    where
 77        Self: Sized,
 78    {
 79        let action = if let Some(params) = params {
 80            serde_json::from_value(params).context("failed to deserialize action")?
 81        } else {
 82            Self::default()
 83        };
 84        Ok(Box::new(action))
 85    }
 86
 87    fn partial_eq(&self, action: &dyn Action) -> bool {
 88        action
 89            .as_any()
 90            .downcast_ref::<Self>()
 91            .map_or(false, |a| self == a)
 92    }
 93
 94    fn boxed_clone(&self) -> Box<dyn Action> {
 95        Box::new(self.clone())
 96    }
 97
 98    fn as_any(&self) -> &dyn Any {
 99        self
100    }
101}
102
103type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
104
105lazy_static! {
106    static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
107}
108
109#[derive(Default)]
110struct ActionRegistry {
111    builders_by_name: HashMap<SharedString, ActionBuilder>,
112    all_names: Vec<SharedString>, // So we can return a static slice.
113}
114
115/// Register an action type to allow it to be referenced in keymaps.
116pub fn register_action<A: Action>() {
117    let name = A::qualified_name();
118    let mut lock = ACTION_REGISTRY.write();
119    lock.builders_by_name.insert(name.clone(), A::build);
120    lock.all_names.push(name);
121}
122
123/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
124pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
125    let lock = ACTION_REGISTRY.read();
126
127    let build_action = lock
128        .builders_by_name
129        .get(name)
130        .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
131    (build_action)(params)
132}
133
134pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
135    let lock = ACTION_REGISTRY.read();
136    RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
137        registry.all_names.as_slice()
138    })
139}
140
141/// Defines unit structs that can be used as actions.
142/// To use more complex data types as actions, annotate your type with the #[action] macro.
143#[macro_export]
144macro_rules! actions {
145    () => {};
146
147    ( $name:ident ) => {
148        #[gpui::register_action]
149        #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
150        pub struct $name;
151    };
152
153    ( $name:ident, $($rest:tt)* ) => {
154        actions!($name);
155        actions!($($rest)*);
156    };
157}
158
159#[derive(Clone, Debug, Default, Eq, PartialEq)]
160pub struct DispatchContext {
161    set: HashSet<SharedString>,
162    map: HashMap<SharedString, SharedString>,
163}
164
165impl<'a> TryFrom<&'a str> for DispatchContext {
166    type Error = anyhow::Error;
167
168    fn try_from(value: &'a str) -> Result<Self> {
169        Self::parse(value)
170    }
171}
172
173impl DispatchContext {
174    pub fn parse(source: &str) -> Result<Self> {
175        let mut context = Self::default();
176        let source = skip_whitespace(source);
177        Self::parse_expr(&source, &mut context)?;
178        Ok(context)
179    }
180
181    fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
182        if source.is_empty() {
183            return Ok(());
184        }
185
186        let key = source
187            .chars()
188            .take_while(|c| is_identifier_char(*c))
189            .collect::<String>();
190        source = skip_whitespace(&source[key.len()..]);
191        if let Some(suffix) = source.strip_prefix('=') {
192            source = skip_whitespace(suffix);
193            let value = source
194                .chars()
195                .take_while(|c| is_identifier_char(*c))
196                .collect::<String>();
197            source = skip_whitespace(&source[value.len()..]);
198            context.set(key, value);
199        } else {
200            context.insert(key);
201        }
202
203        Self::parse_expr(source, context)
204    }
205
206    pub fn is_empty(&self) -> bool {
207        self.set.is_empty() && self.map.is_empty()
208    }
209
210    pub fn clear(&mut self) {
211        self.set.clear();
212        self.map.clear();
213    }
214
215    pub fn extend(&mut self, other: &Self) {
216        for v in &other.set {
217            self.set.insert(v.clone());
218        }
219        for (k, v) in &other.map {
220            self.map.insert(k.clone(), v.clone());
221        }
222    }
223
224    pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
225        self.set.insert(identifier.into());
226    }
227
228    pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
229        self.map.insert(key.into(), value.into());
230    }
231}
232
233#[derive(Clone, Debug, Eq, PartialEq, Hash)]
234pub enum DispatchContextPredicate {
235    Identifier(SharedString),
236    Equal(SharedString, SharedString),
237    NotEqual(SharedString, SharedString),
238    Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
239    Not(Box<DispatchContextPredicate>),
240    And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
241    Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
242}
243
244impl DispatchContextPredicate {
245    pub fn parse(source: &str) -> Result<Self> {
246        let source = skip_whitespace(source);
247        let (predicate, rest) = Self::parse_expr(source, 0)?;
248        if let Some(next) = rest.chars().next() {
249            Err(anyhow!("unexpected character {next:?}"))
250        } else {
251            Ok(predicate)
252        }
253    }
254
255    pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
256        let Some(context) = contexts.last() else {
257            return false;
258        };
259        match self {
260            Self::Identifier(name) => context.set.contains(name),
261            Self::Equal(left, right) => context
262                .map
263                .get(left)
264                .map(|value| value == right)
265                .unwrap_or(false),
266            Self::NotEqual(left, right) => context
267                .map
268                .get(left)
269                .map(|value| value != right)
270                .unwrap_or(true),
271            Self::Not(pred) => !pred.eval(contexts),
272            Self::Child(parent, child) => {
273                parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
274            }
275            Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
276            Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
277        }
278    }
279
280    fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
281        type Op = fn(
282            DispatchContextPredicate,
283            DispatchContextPredicate,
284        ) -> Result<DispatchContextPredicate>;
285
286        let (mut predicate, rest) = Self::parse_primary(source)?;
287        source = rest;
288
289        'parse: loop {
290            for (operator, precedence, constructor) in [
291                (">", PRECEDENCE_CHILD, Self::new_child as Op),
292                ("&&", PRECEDENCE_AND, Self::new_and as Op),
293                ("||", PRECEDENCE_OR, Self::new_or as Op),
294                ("==", PRECEDENCE_EQ, Self::new_eq as Op),
295                ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
296            ] {
297                if source.starts_with(operator) && precedence >= min_precedence {
298                    source = skip_whitespace(&source[operator.len()..]);
299                    let (right, rest) = Self::parse_expr(source, precedence + 1)?;
300                    predicate = constructor(predicate, right)?;
301                    source = rest;
302                    continue 'parse;
303                }
304            }
305            break;
306        }
307
308        Ok((predicate, source))
309    }
310
311    fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
312        let next = source
313            .chars()
314            .next()
315            .ok_or_else(|| anyhow!("unexpected eof"))?;
316        match next {
317            '(' => {
318                source = skip_whitespace(&source[1..]);
319                let (predicate, rest) = Self::parse_expr(source, 0)?;
320                if rest.starts_with(')') {
321                    source = skip_whitespace(&rest[1..]);
322                    Ok((predicate, source))
323                } else {
324                    Err(anyhow!("expected a ')'"))
325                }
326            }
327            '!' => {
328                let source = skip_whitespace(&source[1..]);
329                let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
330                Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
331            }
332            _ if is_identifier_char(next) => {
333                let len = source
334                    .find(|c: char| !is_identifier_char(c))
335                    .unwrap_or(source.len());
336                let (identifier, rest) = source.split_at(len);
337                source = skip_whitespace(rest);
338                Ok((
339                    DispatchContextPredicate::Identifier(identifier.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 skip_whitespace(source: &str) -> &str {
387    let len = source
388        .find(|c: char| !c.is_whitespace())
389        .unwrap_or(source.len());
390    &source[len..]
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use crate as gpui;
397    use DispatchContextPredicate::*;
398
399    #[test]
400    fn test_actions_definition() {
401        {
402            actions!(A, B, C, D, E, F, G);
403        }
404
405        {
406            actions!(
407                A,
408                B,
409                C,
410                D,
411                E,
412                F,
413                G, // Don't wrap, test the trailing comma
414            );
415        }
416    }
417
418    #[test]
419    fn test_parse_context() {
420        let mut expected = DispatchContext::default();
421        expected.set("foo", "bar");
422        expected.insert("baz");
423        assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
424        assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
425        assert_eq!(
426            DispatchContext::parse("  baz foo   =   bar baz").unwrap(),
427            expected
428        );
429        assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
430    }
431
432    #[test]
433    fn test_parse_identifiers() {
434        // Identifiers
435        assert_eq!(
436            DispatchContextPredicate::parse("abc12").unwrap(),
437            Identifier("abc12".into())
438        );
439        assert_eq!(
440            DispatchContextPredicate::parse("_1a").unwrap(),
441            Identifier("_1a".into())
442        );
443    }
444
445    #[test]
446    fn test_parse_negations() {
447        assert_eq!(
448            DispatchContextPredicate::parse("!abc").unwrap(),
449            Not(Box::new(Identifier("abc".into())))
450        );
451        assert_eq!(
452            DispatchContextPredicate::parse(" ! ! abc").unwrap(),
453            Not(Box::new(Not(Box::new(Identifier("abc".into())))))
454        );
455    }
456
457    #[test]
458    fn test_parse_equality_operators() {
459        assert_eq!(
460            DispatchContextPredicate::parse("a == b").unwrap(),
461            Equal("a".into(), "b".into())
462        );
463        assert_eq!(
464            DispatchContextPredicate::parse("c!=d").unwrap(),
465            NotEqual("c".into(), "d".into())
466        );
467        assert_eq!(
468            DispatchContextPredicate::parse("c == !d")
469                .unwrap_err()
470                .to_string(),
471            "operands must be identifiers"
472        );
473    }
474
475    #[test]
476    fn test_parse_boolean_operators() {
477        assert_eq!(
478            DispatchContextPredicate::parse("a || b").unwrap(),
479            Or(
480                Box::new(Identifier("a".into())),
481                Box::new(Identifier("b".into()))
482            )
483        );
484        assert_eq!(
485            DispatchContextPredicate::parse("a || !b && c").unwrap(),
486            Or(
487                Box::new(Identifier("a".into())),
488                Box::new(And(
489                    Box::new(Not(Box::new(Identifier("b".into())))),
490                    Box::new(Identifier("c".into()))
491                ))
492            )
493        );
494        assert_eq!(
495            DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
496            Or(
497                Box::new(And(
498                    Box::new(Identifier("a".into())),
499                    Box::new(Identifier("b".into()))
500                )),
501                Box::new(And(
502                    Box::new(Identifier("c".into())),
503                    Box::new(Identifier("d".into()))
504                ))
505            )
506        );
507        assert_eq!(
508            DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
509            Or(
510                Box::new(And(
511                    Box::new(Equal("a".into(), "b".into())),
512                    Box::new(Identifier("c".into()))
513                )),
514                Box::new(And(
515                    Box::new(Equal("d".into(), "e".into())),
516                    Box::new(Identifier("f".into()))
517                ))
518            )
519        );
520        assert_eq!(
521            DispatchContextPredicate::parse("a && b && c && d").unwrap(),
522            And(
523                Box::new(And(
524                    Box::new(And(
525                        Box::new(Identifier("a".into())),
526                        Box::new(Identifier("b".into()))
527                    )),
528                    Box::new(Identifier("c".into())),
529                )),
530                Box::new(Identifier("d".into()))
531            ),
532        );
533    }
534
535    #[test]
536    fn test_parse_parenthesized_expressions() {
537        assert_eq!(
538            DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
539            And(
540                Box::new(Identifier("a".into())),
541                Box::new(Or(
542                    Box::new(Equal("b".into(), "c".into())),
543                    Box::new(NotEqual("d".into(), "e".into())),
544                )),
545            ),
546        );
547        assert_eq!(
548            DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
549            Or(
550                Box::new(Identifier("a".into())),
551                Box::new(Identifier("b".into())),
552            )
553        );
554    }
555}