1use crate::register_action::generate_register_action;
2use proc_macro::TokenStream;
3use proc_macro2::Ident;
4use quote::quote;
5use syn::{Data, DeriveInput, LitStr, Token, parse::ParseStream};
6
7pub(crate) fn derive_action(input: TokenStream) -> TokenStream {
8 let input = syn::parse_macro_input!(input as DeriveInput);
9
10 let struct_name = &input.ident;
11 let mut name_argument = None;
12 let mut deprecated_aliases = Vec::new();
13 let mut no_json = false;
14 let mut no_register = false;
15 let mut namespace = None;
16 let mut deprecated = None;
17
18 for attr in &input.attrs {
19 if attr.path().is_ident("action") {
20 attr.parse_nested_meta(|meta| {
21 if meta.path.is_ident("name") {
22 if name_argument.is_some() {
23 return Err(meta.error("'name' argument specified multiple times"));
24 }
25 meta.input.parse::<Token![=]>()?;
26 let lit: LitStr = meta.input.parse()?;
27 name_argument = Some(lit.value());
28 } else if meta.path.is_ident("namespace") {
29 if namespace.is_some() {
30 return Err(meta.error("'namespace' argument specified multiple times"));
31 }
32 meta.input.parse::<Token![=]>()?;
33 let ident: Ident = meta.input.parse()?;
34 namespace = Some(ident.to_string());
35 } else if meta.path.is_ident("no_json") {
36 if no_json {
37 return Err(meta.error("'no_json' argument specified multiple times"));
38 }
39 no_json = true;
40 } else if meta.path.is_ident("no_register") {
41 if no_register {
42 return Err(meta.error("'no_register' argument specified multiple times"));
43 }
44 no_register = true;
45 } else if meta.path.is_ident("deprecated_aliases") {
46 if !deprecated_aliases.is_empty() {
47 return Err(
48 meta.error("'deprecated_aliases' argument specified multiple times")
49 );
50 }
51 meta.input.parse::<Token![=]>()?;
52 // Parse array of string literals
53 let content;
54 syn::bracketed!(content in meta.input);
55 let aliases = content.parse_terminated(
56 |input: ParseStream| input.parse::<LitStr>(),
57 Token![,],
58 )?;
59 deprecated_aliases.extend(aliases.into_iter().map(|lit| lit.value()));
60 } else if meta.path.is_ident("deprecated") {
61 if deprecated.is_some() {
62 return Err(meta.error("'deprecated' argument specified multiple times"));
63 }
64 meta.input.parse::<Token![=]>()?;
65 let lit: LitStr = meta.input.parse()?;
66 deprecated = Some(lit.value());
67 } else {
68 return Err(meta.error(format!(
69 "'{:?}' argument not recognized, expected \
70 'namespace', 'no_json', 'no_register, 'deprecated_aliases', or 'deprecated'",
71 meta.path
72 )));
73 }
74 Ok(())
75 })
76 .unwrap_or_else(|e| panic!("in #[action] attribute: {}", e));
77 }
78 }
79
80 let name = name_argument.unwrap_or_else(|| struct_name.to_string());
81
82 if name.contains("::") {
83 panic!(
84 "in #[action] attribute: `name = \"{name}\"` must not contain `::`, \
85 also specify `namespace` instead"
86 );
87 }
88
89 let full_name = if let Some(namespace) = namespace {
90 format!("{namespace}::{name}")
91 } else {
92 name
93 };
94
95 let is_unit_struct = matches!(&input.data, Data::Struct(data) if data.fields.is_empty());
96
97 let build_fn_body = if no_json {
98 let error_msg = format!("{} cannot be built from JSON", full_name);
99 quote! { Err(gpui::private::anyhow::anyhow!(#error_msg)) }
100 } else if is_unit_struct {
101 quote! { Ok(Box::new(Self)) }
102 } else {
103 quote! { Ok(Box::new(gpui::private::serde_json::from_value::<Self>(_value)?)) }
104 };
105
106 let json_schema_fn_body = if no_json || is_unit_struct {
107 quote! { None }
108 } else {
109 quote! { Some(<Self as gpui::private::schemars::JsonSchema>::json_schema(_generator)) }
110 };
111
112 let deprecated_aliases_fn_body = if deprecated_aliases.is_empty() {
113 quote! { &[] }
114 } else {
115 let aliases = deprecated_aliases.iter();
116 quote! { &[#(#aliases),*] }
117 };
118
119 let deprecation_fn_body = if let Some(message) = deprecated {
120 quote! { Some(#message) }
121 } else {
122 quote! { None }
123 };
124
125 let registration = if no_register {
126 quote! {}
127 } else {
128 generate_register_action(struct_name)
129 };
130
131 TokenStream::from(quote! {
132 #registration
133
134 impl gpui::Action for #struct_name {
135 fn name(&self) -> &'static str {
136 #full_name
137 }
138
139 fn name_for_type() -> &'static str
140 where
141 Self: Sized
142 {
143 #full_name
144 }
145
146 fn partial_eq(&self, action: &dyn gpui::Action) -> bool {
147 action
148 .as_any()
149 .downcast_ref::<Self>()
150 .map_or(false, |a| self == a)
151 }
152
153 fn boxed_clone(&self) -> Box<dyn gpui::Action> {
154 Box::new(self.clone())
155 }
156
157 fn build(_value: gpui::private::serde_json::Value) -> gpui::Result<Box<dyn gpui::Action>> {
158 #build_fn_body
159 }
160
161 fn action_json_schema(
162 _generator: &mut gpui::private::schemars::SchemaGenerator,
163 ) -> Option<gpui::private::schemars::Schema> {
164 #json_schema_fn_body
165 }
166
167 fn deprecated_aliases() -> &'static [&'static str] {
168 #deprecated_aliases_fn_body
169 }
170
171 fn deprecation_message() -> Option<&'static str> {
172 #deprecation_fn_body
173 }
174 }
175 })
176}