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    let build_action = lock
127        .builders_by_name
128        .get(name)
129        .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
130    (build_action)(params)
131}
132
133pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
134    let lock = ACTION_REGISTRY.read();
135    RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
136        registry.all_names.as_slice()
137    })
138}
139
140/// Defines unit structs that can be used as actions.
141/// To use more complex data types as actions, annotate your type with the #[action] macro.
142#[macro_export]
143macro_rules! actions {
144    () => {};
145
146    ( $name:ident ) => {
147        #[gpui::register_action]
148        #[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
149        pub struct $name;
150    };
151
152    ( $name:ident, $($rest:tt)* ) => {
153        actions!($name);
154        actions!($($rest)*);
155    };
156}
157
158#[derive(Clone, Debug, Default, Eq, PartialEq)]
159pub struct DispatchContext {
160    set: HashSet<SharedString>,
161    map: HashMap<SharedString, SharedString>,
162}
163
164impl<'a> TryFrom<&'a str> for DispatchContext {
165    type Error = anyhow::Error;
166
167    fn try_from(value: &'a str) -> Result<Self> {
168        Self::parse(value)
169    }
170}
171
172impl DispatchContext {
173    pub fn parse(source: &str) -> Result<Self> {
174        let mut context = Self::default();
175        let source = skip_whitespace(source);
176        Self::parse_expr(&source, &mut context)?;
177        Ok(context)
178    }
179
180    fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
181        if source.is_empty() {
182            return Ok(());
183        }
184
185        let key = source
186            .chars()
187            .take_while(|c| is_identifier_char(*c))
188            .collect::<String>();
189        source = skip_whitespace(&source[key.len()..]);
190        if let Some(suffix) = source.strip_prefix('=') {
191            source = skip_whitespace(suffix);
192            let value = source
193                .chars()
194                .take_while(|c| is_identifier_char(*c))
195                .collect::<String>();
196            source = skip_whitespace(&source[value.len()..]);
197            context.set(key, value);
198        } else {
199            context.insert(key);
200        }
201
202        Self::parse_expr(source, context)
203    }
204
205    pub fn is_empty(&self) -> bool {
206        self.set.is_empty() && self.map.is_empty()
207    }
208
209    pub fn clear(&mut self) {
210        self.set.clear();
211        self.map.clear();
212    }
213
214    pub fn extend(&mut self, other: &Self) {
215        for v in &other.set {
216            self.set.insert(v.clone());
217        }
218        for (k, v) in &other.map {
219            self.map.insert(k.clone(), v.clone());
220        }
221    }
222
223    pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
224        self.set.insert(identifier.into());
225    }
226
227    pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
228        self.map.insert(key.into(), value.into());
229    }
230}
231
232#[derive(Clone, Debug, Eq, PartialEq, Hash)]
233pub enum DispatchContextPredicate {
234    Identifier(SharedString),
235    Equal(SharedString, SharedString),
236    NotEqual(SharedString, SharedString),
237    Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
238    Not(Box<DispatchContextPredicate>),
239    And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
240    Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
241}
242
243impl DispatchContextPredicate {
244    pub fn parse(source: &str) -> Result<Self> {
245        let source = skip_whitespace(source);
246        let (predicate, rest) = Self::parse_expr(source, 0)?;
247        if let Some(next) = rest.chars().next() {
248            Err(anyhow!("unexpected character {next:?}"))
249        } else {
250            Ok(predicate)
251        }
252    }
253
254    pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
255        let Some(context) = contexts.last() else {
256            return false;
257        };
258        match self {
259            Self::Identifier(name) => context.set.contains(name),
260            Self::Equal(left, right) => context
261                .map
262                .get(left)
263                .map(|value| value == right)
264                .unwrap_or(false),
265            Self::NotEqual(left, right) => context
266                .map
267                .get(left)
268                .map(|value| value != right)
269                .unwrap_or(true),
270            Self::Not(pred) => !pred.eval(contexts),
271            Self::Child(parent, child) => {
272                parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
273            }
274            Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
275            Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
276        }
277    }
278
279    fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
280        type Op = fn(
281            DispatchContextPredicate,
282            DispatchContextPredicate,
283        ) -> Result<DispatchContextPredicate>;
284
285        let (mut predicate, rest) = Self::parse_primary(source)?;
286        source = rest;
287
288        'parse: loop {
289            for (operator, precedence, constructor) in [
290                (">", PRECEDENCE_CHILD, Self::new_child as Op),
291                ("&&", PRECEDENCE_AND, Self::new_and as Op),
292                ("||", PRECEDENCE_OR, Self::new_or as Op),
293                ("==", PRECEDENCE_EQ, Self::new_eq as Op),
294                ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
295            ] {
296                if source.starts_with(operator) && precedence >= min_precedence {
297                    source = skip_whitespace(&source[operator.len()..]);
298                    let (right, rest) = Self::parse_expr(source, precedence + 1)?;
299                    predicate = constructor(predicate, right)?;
300                    source = rest;
301                    continue 'parse;
302                }
303            }
304            break;
305        }
306
307        Ok((predicate, source))
308    }
309
310    fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
311        let next = source
312            .chars()
313            .next()
314            .ok_or_else(|| anyhow!("unexpected eof"))?;
315        match next {
316            '(' => {
317                source = skip_whitespace(&source[1..]);
318                let (predicate, rest) = Self::parse_expr(source, 0)?;
319                if rest.starts_with(')') {
320                    source = skip_whitespace(&rest[1..]);
321                    Ok((predicate, source))
322                } else {
323                    Err(anyhow!("expected a ')'"))
324                }
325            }
326            '!' => {
327                let source = skip_whitespace(&source[1..]);
328                let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
329                Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
330            }
331            _ if is_identifier_char(next) => {
332                let len = source
333                    .find(|c: char| !is_identifier_char(c))
334                    .unwrap_or(source.len());
335                let (identifier, rest) = source.split_at(len);
336                source = skip_whitespace(rest);
337                Ok((
338                    DispatchContextPredicate::Identifier(identifier.to_string().into()),
339                    source,
340                ))
341            }
342            _ => Err(anyhow!("unexpected character {next:?}")),
343        }
344    }
345
346    fn new_or(self, other: Self) -> Result<Self> {
347        Ok(Self::Or(Box::new(self), Box::new(other)))
348    }
349
350    fn new_and(self, other: Self) -> Result<Self> {
351        Ok(Self::And(Box::new(self), Box::new(other)))
352    }
353
354    fn new_child(self, other: Self) -> Result<Self> {
355        Ok(Self::Child(Box::new(self), Box::new(other)))
356    }
357
358    fn new_eq(self, other: Self) -> Result<Self> {
359        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
360            Ok(Self::Equal(left, right))
361        } else {
362            Err(anyhow!("operands must be identifiers"))
363        }
364    }
365
366    fn new_neq(self, other: Self) -> Result<Self> {
367        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
368            Ok(Self::NotEqual(left, right))
369        } else {
370            Err(anyhow!("operands must be identifiers"))
371        }
372    }
373}
374
375const PRECEDENCE_CHILD: u32 = 1;
376const PRECEDENCE_OR: u32 = 2;
377const PRECEDENCE_AND: u32 = 3;
378const PRECEDENCE_EQ: u32 = 4;
379const PRECEDENCE_NOT: u32 = 5;
380
381fn is_identifier_char(c: char) -> bool {
382    c.is_alphanumeric() || c == '_' || c == '-'
383}
384
385fn skip_whitespace(source: &str) -> &str {
386    let len = source
387        .find(|c: char| !c.is_whitespace())
388        .unwrap_or(source.len());
389    &source[len..]
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395    use crate as gpui;
396    use DispatchContextPredicate::*;
397
398    #[test]
399    fn test_actions_definition() {
400        {
401            actions!(A, B, C, D, E, F, G);
402        }
403
404        {
405            actions!(
406                A,
407                B,
408                C,
409                D,
410                E,
411                F,
412                G, // Don't wrap, test the trailing comma
413            );
414        }
415    }
416
417    #[test]
418    fn test_parse_context() {
419        let mut expected = DispatchContext::default();
420        expected.set("foo", "bar");
421        expected.insert("baz");
422        assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
423        assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
424        assert_eq!(
425            DispatchContext::parse("  baz foo   =   bar baz").unwrap(),
426            expected
427        );
428        assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
429    }
430
431    #[test]
432    fn test_parse_identifiers() {
433        // Identifiers
434        assert_eq!(
435            DispatchContextPredicate::parse("abc12").unwrap(),
436            Identifier("abc12".into())
437        );
438        assert_eq!(
439            DispatchContextPredicate::parse("_1a").unwrap(),
440            Identifier("_1a".into())
441        );
442    }
443
444    #[test]
445    fn test_parse_negations() {
446        assert_eq!(
447            DispatchContextPredicate::parse("!abc").unwrap(),
448            Not(Box::new(Identifier("abc".into())))
449        );
450        assert_eq!(
451            DispatchContextPredicate::parse(" ! ! abc").unwrap(),
452            Not(Box::new(Not(Box::new(Identifier("abc".into())))))
453        );
454    }
455
456    #[test]
457    fn test_parse_equality_operators() {
458        assert_eq!(
459            DispatchContextPredicate::parse("a == b").unwrap(),
460            Equal("a".into(), "b".into())
461        );
462        assert_eq!(
463            DispatchContextPredicate::parse("c!=d").unwrap(),
464            NotEqual("c".into(), "d".into())
465        );
466        assert_eq!(
467            DispatchContextPredicate::parse("c == !d")
468                .unwrap_err()
469                .to_string(),
470            "operands must be identifiers"
471        );
472    }
473
474    #[test]
475    fn test_parse_boolean_operators() {
476        assert_eq!(
477            DispatchContextPredicate::parse("a || b").unwrap(),
478            Or(
479                Box::new(Identifier("a".into())),
480                Box::new(Identifier("b".into()))
481            )
482        );
483        assert_eq!(
484            DispatchContextPredicate::parse("a || !b && c").unwrap(),
485            Or(
486                Box::new(Identifier("a".into())),
487                Box::new(And(
488                    Box::new(Not(Box::new(Identifier("b".into())))),
489                    Box::new(Identifier("c".into()))
490                ))
491            )
492        );
493        assert_eq!(
494            DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
495            Or(
496                Box::new(And(
497                    Box::new(Identifier("a".into())),
498                    Box::new(Identifier("b".into()))
499                )),
500                Box::new(And(
501                    Box::new(Identifier("c".into())),
502                    Box::new(Identifier("d".into()))
503                ))
504            )
505        );
506        assert_eq!(
507            DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
508            Or(
509                Box::new(And(
510                    Box::new(Equal("a".into(), "b".into())),
511                    Box::new(Identifier("c".into()))
512                )),
513                Box::new(And(
514                    Box::new(Equal("d".into(), "e".into())),
515                    Box::new(Identifier("f".into()))
516                ))
517            )
518        );
519        assert_eq!(
520            DispatchContextPredicate::parse("a && b && c && d").unwrap(),
521            And(
522                Box::new(And(
523                    Box::new(And(
524                        Box::new(Identifier("a".into())),
525                        Box::new(Identifier("b".into()))
526                    )),
527                    Box::new(Identifier("c".into())),
528                )),
529                Box::new(Identifier("d".into()))
530            ),
531        );
532    }
533
534    #[test]
535    fn test_parse_parenthesized_expressions() {
536        assert_eq!(
537            DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
538            And(
539                Box::new(Identifier("a".into())),
540                Box::new(Or(
541                    Box::new(Equal("b".into(), "c".into())),
542                    Box::new(NotEqual("d".into(), "e".into())),
543                )),
544            ),
545        );
546        assert_eq!(
547            DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
548            Or(
549                Box::new(Identifier("a".into())),
550                Box::new(Identifier("b".into())),
551            )
552        );
553    }
554}