derive_action.rs

  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}