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}