#![recursion_limit = "512"]
mod derive_action;
mod derive_app_context;
mod derive_into_element;
mod derive_render;
mod derive_visual_context;
mod register_action;
mod styles;
mod test;
#[cfg(any(feature = "inspector", debug_assertions))]
mod derive_inspector_reflection;
use proc_macro::TokenStream;
use proc_macro_crate::{FoundCrate, crate_name};
use quote::quote;
use std::{env, fs, path::PathBuf, sync::OnceLock};
use syn::{DeriveInput, Ident};
use toml_edit::DocumentMut;
#[proc_macro_derive(Action, attributes(action))]
pub fn derive_action(input: TokenStream) -> TokenStream {
derive_action::derive_action(input)
}
#[proc_macro]
pub fn register_action(ident: TokenStream) -> TokenStream {
register_action::register_action(ident)
}
#[proc_macro_derive(IntoElement)]
pub fn derive_into_element(input: TokenStream) -> TokenStream {
derive_into_element::derive_into_element(input)
}
#[proc_macro_derive(Render)]
#[doc(hidden)]
pub fn derive_render(input: TokenStream) -> TokenStream {
derive_render::derive_render(input)
}
#[proc_macro_derive(AppContext, attributes(app))]
pub fn derive_app_context(input: TokenStream) -> TokenStream {
derive_app_context::derive_app_context(input)
}
#[proc_macro_derive(VisualContext, attributes(window, app))]
pub fn derive_visual_context(input: TokenStream) -> TokenStream {
derive_visual_context::derive_visual_context(input)
}
#[proc_macro]
#[doc(hidden)]
pub fn style_helpers(input: TokenStream) -> TokenStream {
styles::style_helpers(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn visibility_style_methods(input: TokenStream) -> TokenStream {
styles::visibility_style_methods(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn margin_style_methods(input: TokenStream) -> TokenStream {
styles::margin_style_methods(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn padding_style_methods(input: TokenStream) -> TokenStream {
styles::padding_style_methods(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn position_style_methods(input: TokenStream) -> TokenStream {
styles::position_style_methods(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn overflow_style_methods(input: TokenStream) -> TokenStream {
styles::overflow_style_methods(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
styles::cursor_style_methods(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn border_style_methods(input: TokenStream) -> TokenStream {
styles::border_style_methods(input)
}
#[doc(hidden)]
#[proc_macro]
pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
styles::box_shadow_style_methods(input)
}
#[proc_macro_attribute]
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
test::test(args, function)
}
#[cfg(any(feature = "inspector", debug_assertions))]
#[proc_macro_attribute]
pub fn derive_inspector_reflection(_args: TokenStream, input: TokenStream) -> TokenStream {
derive_inspector_reflection::derive_inspector_reflection(_args, input)
}
pub(crate) fn gpui_crate_path() -> proc_macro2::TokenStream {
if let Some(path) = configured_gpui_crate_path() {
return path;
}
let facade = crate_name("fc-gpui").ok();
let core = crate_name("fc-gpui-core").ok();
select_gpui_crate_path(facade, core)
}
fn select_gpui_crate_path(
facade: Option<FoundCrate>,
core: Option<FoundCrate>,
) -> proc_macro2::TokenStream {
match (facade, core) {
(Some(_), Some(_)) => panic!(
"both fc-gpui and fc-gpui-core are direct dependencies; \
set [package.metadata.gpui-macros] crate = \"<dependency identifier>\" \
in Cargo.toml to select the crate used by GPUI macros"
),
(Some(found), None) => crate_path(found, "fc_gpui", "gpui"),
(None, Some(found)) => crate_path(found, "fc_gpui_core", "gpui_core"),
(None, None) => quote!(::gpui),
}
}
fn crate_path(
found: FoundCrate,
package_identifier: &str,
library_identifier: &str,
) -> proc_macro2::TokenStream {
match found {
FoundCrate::Itself => quote!(::gpui),
FoundCrate::Name(name) => {
let name = if name == package_identifier {
library_identifier
} else {
&name
};
let ident = Ident::new(&name.replace('-', "_"), proc_macro2::Span::call_site());
quote!(::#ident)
}
}
}
fn configured_gpui_crate_path() -> Option<proc_macro2::TokenStream> {
static CONFIGURED_CRATE: OnceLock<Option<String>> = OnceLock::new();
let crate_name = CONFIGURED_CRATE.get_or_init(|| {
let manifest_path = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR")?).join("Cargo.toml");
let manifest = fs::read_to_string(manifest_path).ok()?;
let document = manifest.parse::<DocumentMut>().ok()?;
document
.get("package")?
.get("metadata")?
.get("gpui-macros")?
.get("crate")?
.as_str()
.map(str::to_owned)
});
let ident = syn::parse_str::<Ident>(crate_name.as_deref()?).unwrap_or_else(|_| {
panic!(
"package.metadata.gpui-macros.crate must be a Rust crate identifier, got {crate_name:?}"
)
});
Some(quote!(::#ident))
}
pub(crate) fn get_simple_attribute_field(ast: &DeriveInput, name: &'static str) -> Option<Ident> {
match &ast.data {
syn::Data::Struct(data_struct) => data_struct
.fields
.iter()
.find(|field| field.attrs.iter().any(|attr| attr.path().is_ident(name)))
.map(|field| field.ident.clone().unwrap()),
syn::Data::Enum(_) => None,
syn::Data::Union(_) => None,
}
}
#[cfg(test)]
mod tests {
use super::{FoundCrate, select_gpui_crate_path};
#[test]
#[should_panic(expected = "both fc-gpui and fc-gpui-core are direct dependencies")]
fn ambiguous_direct_dependencies_require_an_explicit_crate() {
select_gpui_crate_path(
Some(FoundCrate::Name("facade_ui".into())),
Some(FoundCrate::Name("core_ui".into())),
);
}
}