action.rs

  1use crate::SharedString;
  2use anyhow::{anyhow, Context, Result};
  3use collections::HashMap;
  4use lazy_static::lazy_static;
  5use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
  6use serde::Deserialize;
  7use std::any::{type_name, Any, TypeId};
  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    fn is_registered() -> bool
 60    where
 61        Self: Sized;
 62
 63    fn partial_eq(&self, action: &dyn Action) -> bool;
 64    fn boxed_clone(&self) -> Box<dyn Action>;
 65    fn as_any(&self) -> &dyn Any;
 66}
 67
 68// Types become actions by satisfying a list of trait bounds.
 69impl<A> Action for A
 70where
 71    A: for<'a> Deserialize<'a> + PartialEq + Clone + Default + std::fmt::Debug + 'static,
 72{
 73    fn qualified_name() -> SharedString {
 74        let name = type_name::<A>();
 75        let mut separator_matches = name.rmatch_indices("::");
 76        separator_matches.next().unwrap();
 77        let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
 78        // todo!() remove the 2 replacement when migration is done
 79        name[name_start_ix..].replace("2::", "::").into()
 80    }
 81
 82    fn build(params: Option<serde_json::Value>) -> Result<Box<dyn Action>>
 83    where
 84        Self: Sized,
 85    {
 86        let action = if let Some(params) = params {
 87            serde_json::from_value(params).context("failed to deserialize action")?
 88        } else {
 89            Self::default()
 90        };
 91        Ok(Box::new(action))
 92    }
 93
 94    fn is_registered() -> bool {
 95        ACTION_REGISTRY
 96            .read()
 97            .names_by_type_id
 98            .get(&TypeId::of::<A>())
 99            .is_some()
100    }
101
102    fn partial_eq(&self, action: &dyn Action) -> bool {
103        action
104            .as_any()
105            .downcast_ref::<Self>()
106            .map_or(false, |a| self == a)
107    }
108
109    fn boxed_clone(&self) -> Box<dyn Action> {
110        Box::new(self.clone())
111    }
112
113    fn as_any(&self) -> &dyn Any {
114        self
115    }
116}
117
118impl dyn Action {
119    pub fn type_id(&self) -> TypeId {
120        self.as_any().type_id()
121    }
122
123    pub fn name(&self) -> SharedString {
124        ACTION_REGISTRY
125            .read()
126            .names_by_type_id
127            .get(&self.type_id())
128            .expect("type is not a registered action")
129            .clone()
130    }
131}
132
133type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
134
135lazy_static! {
136    static ref ACTION_REGISTRY: RwLock<ActionRegistry> = RwLock::default();
137}
138
139#[derive(Default)]
140struct ActionRegistry {
141    builders_by_name: HashMap<SharedString, ActionBuilder>,
142    names_by_type_id: HashMap<TypeId, SharedString>,
143    all_names: Vec<SharedString>, // So we can return a static slice.
144}
145
146/// Register an action type to allow it to be referenced in keymaps.
147pub fn register_action<A: Action>() {
148    let name = A::qualified_name();
149    let mut lock = ACTION_REGISTRY.write();
150    lock.builders_by_name.insert(name.clone(), A::build);
151    lock.names_by_type_id
152        .insert(TypeId::of::<A>(), name.clone());
153    lock.all_names.push(name);
154}
155
156/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
157pub fn build_action_from_type(type_id: &TypeId) -> Result<Box<dyn Action>> {
158    let lock = ACTION_REGISTRY.read();
159    let name = lock
160        .names_by_type_id
161        .get(type_id)
162        .ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?
163        .clone();
164    drop(lock);
165
166    build_action(&name, None)
167}
168
169/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
170pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
171    let lock = ACTION_REGISTRY.read();
172
173    let build_action = lock
174        .builders_by_name
175        .get(name)
176        .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
177    (build_action)(params)
178}
179
180pub fn all_action_names() -> MappedRwLockReadGuard<'static, [SharedString]> {
181    let lock = ACTION_REGISTRY.read();
182    RwLockReadGuard::map(lock, |registry: &ActionRegistry| {
183        registry.all_names.as_slice()
184    })
185}
186
187/// Defines unit structs that can be used as actions.
188/// To use more complex data types as actions, annotate your type with the #[action] macro.
189#[macro_export]
190macro_rules! actions {
191    () => {};
192
193    ( $name:ident ) => {
194        #[gpui::action]
195        pub struct $name;
196    };
197
198    ( $name:ident, $($rest:tt)* ) => {
199        actions!($name);
200        actions!($($rest)*);
201    };
202}