action.rs

  1use crate::SharedString;
  2use anyhow::{anyhow, Context, Result};
  3use collections::HashMap;
  4pub use no_action::NoAction;
  5use serde_json::json;
  6use std::any::{Any, TypeId};
  7
  8/// Actions are used to implement keyboard-driven UI.
  9/// When you declare an action, you can bind keys to the action in the keymap and
 10/// listeners for that action in the element tree.
 11///
 12/// To declare a list of simple actions, you can use the actions! macro, which defines a simple unit struct
 13/// action for each listed action name.
 14/// ```rust
 15/// actions!(MoveUp, MoveDown, MoveLeft, MoveRight, Newline);
 16/// ```
 17/// More complex data types can also be actions. If you annotate your type with the action derive macro
 18/// it will be implemented and registered automatically.
 19/// ```
 20/// #[derive(Clone, PartialEq, serde_derive::Deserialize, Action)]
 21/// pub struct SelectNext {
 22///     pub replace_newest: bool,
 23/// }
 24///
 25/// If you want to control the behavior of the action trait manually, you can use the lower-level `#[register_action]`
 26/// macro, which only generates the code needed to register your action before `main`.
 27///
 28/// ```
 29/// #[gpui::register_action]
 30/// #[derive(gpui::serde::Deserialize, std::cmp::PartialEq, std::clone::Clone, std::fmt::Debug)]
 31/// pub struct Paste {
 32///     pub content: SharedString,
 33/// }
 34///
 35/// impl gpui::Action for Paste {
 36///      ///...
 37/// }
 38/// ```
 39pub trait Action: 'static {
 40    fn boxed_clone(&self) -> Box<dyn Action>;
 41    fn as_any(&self) -> &dyn Any;
 42    fn partial_eq(&self, action: &dyn Action) -> bool;
 43    fn name(&self) -> &str;
 44
 45    fn debug_name() -> &'static str
 46    where
 47        Self: Sized;
 48    fn build(value: serde_json::Value) -> Result<Box<dyn Action>>
 49    where
 50        Self: Sized;
 51}
 52
 53impl std::fmt::Debug for dyn Action {
 54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 55        f.debug_struct("dyn Action")
 56            .field("type_name", &self.name())
 57            .finish()
 58    }
 59}
 60
 61impl dyn Action {
 62    pub fn type_id(&self) -> TypeId {
 63        self.as_any().type_id()
 64    }
 65}
 66
 67type ActionBuilder = fn(json: serde_json::Value) -> anyhow::Result<Box<dyn Action>>;
 68
 69pub(crate) struct ActionRegistry {
 70    builders_by_name: HashMap<SharedString, ActionBuilder>,
 71    names_by_type_id: HashMap<TypeId, SharedString>,
 72    all_names: Vec<SharedString>, // So we can return a static slice.
 73}
 74
 75impl Default for ActionRegistry {
 76    fn default() -> Self {
 77        let mut this = ActionRegistry {
 78            builders_by_name: Default::default(),
 79            names_by_type_id: Default::default(),
 80            all_names: Default::default(),
 81        };
 82
 83        this.load_actions();
 84
 85        this
 86    }
 87}
 88
 89/// This type must be public so that our macros can build it in other crates.
 90/// But this is an implementation detail and should not be used directly.
 91#[doc(hidden)]
 92pub type MacroActionBuilder = fn() -> ActionData;
 93
 94/// This type must be public so that our macros can build it in other crates.
 95/// But this is an implementation detail and should not be used directly.
 96#[doc(hidden)]
 97pub struct ActionData {
 98    pub name: &'static str,
 99    pub type_id: TypeId,
100    pub build: ActionBuilder,
101}
102
103/// This constant must be public to be accessible from other crates.
104/// But it's existence is an implementation detail and should not be used directly.
105#[doc(hidden)]
106#[linkme::distributed_slice]
107pub static __GPUI_ACTIONS: [MacroActionBuilder];
108
109impl ActionRegistry {
110    /// Load all registered actions into the registry.
111    pub(crate) fn load_actions(&mut self) {
112        for builder in __GPUI_ACTIONS {
113            let action = builder();
114            let name: SharedString = qualify_action(action.name).into();
115            self.builders_by_name.insert(name.clone(), action.build);
116            self.names_by_type_id.insert(action.type_id, name.clone());
117            self.all_names.push(name);
118        }
119    }
120
121    /// Construct an action based on its name and optional JSON parameters sourced from the keymap.
122    pub fn build_action_type(&self, type_id: &TypeId) -> Result<Box<dyn Action>> {
123        let name = self
124            .names_by_type_id
125            .get(type_id)
126            .ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?
127            .clone();
128
129        self.build_action(&name, None)
130    }
131
132    /// Construct an action based on its name and optional JSON parameters sourced from the keymap.
133    pub fn build_action(
134        &self,
135        name: &str,
136        params: Option<serde_json::Value>,
137    ) -> Result<Box<dyn Action>> {
138        let build_action = self
139            .builders_by_name
140            .get(name)
141            .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
142        (build_action)(params.unwrap_or_else(|| json!({})))
143            .with_context(|| format!("Attempting to build action {}", name))
144    }
145
146    pub fn all_action_names(&self) -> &[SharedString] {
147        self.all_names.as_slice()
148    }
149}
150
151/// Defines unit structs that can be used as actions.
152/// To use more complex data types as actions, annotate your type with the #[action] macro.
153#[macro_export]
154macro_rules! actions {
155    () => {};
156
157    ( $name:ident ) => {
158        #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::serde_derive::Deserialize, gpui::Action)]
159        pub struct $name;
160    };
161
162    ( $name:ident, $($rest:tt)* ) => {
163        actions!($name);
164        actions!($($rest)*);
165    };
166}
167
168/// This used by our macros to pre-process the action name deterministically
169#[doc(hidden)]
170pub fn qualify_action(action_name: &'static str) -> String {
171    let mut separator_matches = action_name.rmatch_indices("::");
172    separator_matches.next().unwrap();
173    let name_start_ix = separator_matches.next().map_or(0, |(ix, _)| ix + 2);
174    // todo!() remove the 2 replacement when migration is done
175    action_name[name_start_ix..]
176        .replace("2::", "::")
177        .to_string()
178}
179
180mod no_action {
181    use crate as gpui;
182
183    actions!(NoAction);
184}