gpui_ce_macros 0.3.0

Macros used by gpui
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Fields, Member, parse_macro_input};

fn marked_field(input: &DeriveInput, attribute: &str) -> syn::Result<Member> {
    let Data::Struct(data) = &input.data else {
        return Err(syn::Error::new_spanned(
            &input.ident,
            "element trait derives require a struct",
        ));
    };

    let mut found = None;

    for (idx, field) in data.fields.iter().enumerate() {
        if field
            .attrs
            .iter()
            .any(|attr| attr.path().is_ident(attribute))
        {
            let member = match &data.fields {
                Fields::Named(..) => Member::Named(field.ident.clone().expect("named field")),
                Fields::Unnamed(..) => Member::Unnamed(idx.into()),
                Fields::Unit => unreachable!(),
            };

            if found.replace(member).is_some() {
                return Err(syn::Error::new_spanned(
                    field,
                    format!("only one #[{attribute}] field is allowed"),
                ));
            }
        }
    }

    found.ok_or_else(|| {
        syn::Error::new_spanned(
            &input.ident,
            format!("derive requires a #[{attribute}] field"),
        )
    })
}

pub fn derive_styled(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let field = match marked_field(&input, "style") {
        Ok(field) => field,
        Err(error) => {
            return error.to_compile_error().into();
        }
    };

    let name = &input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

    quote! {
        impl #impl_generics gpui::Styled for #name #type_generics #where_clause {
            fn style(&mut self) -> &mut gpui::StyleRefinement {
                &mut self.#field
            }
        }
    }
    .into()
}

pub fn derive_parent_element(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let field = match marked_field(&input, "children") {
        Ok(field) => field,
        Err(error) => {
            return error.to_compile_error().into();
        }
    };

    let name = &input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

    quote! {
        impl #impl_generics gpui::ParentElement for #name #type_generics #where_clause {
            fn extend(&mut self, elements: impl IntoIterator<Item = gpui::AnyElement>) {
                self.#field.extend(elements);
            }
        }
    }
    .into()
}

pub fn derive_interactive_element(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let field = match marked_field(&input, "interactivity") {
        Ok(field) => field,
        Err(error) => {
            return error.to_compile_error().into();
        }
    };

    let name = &input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

    quote! {
        impl #impl_generics gpui::InteractiveElement for #name #type_generics #where_clause {
            fn interactivity(&mut self) -> &mut gpui::Interactivity {
                &mut self.#field
            }
        }
    }
    .into()
}

pub fn derive_stateful_interactive_element(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

    quote! {
        impl #impl_generics gpui::StatefulInteractiveElement for #name #type_generics #where_clause {}
    }
    .into()
}