Skip to main content

fc_gpui_macros/
gpui_macros.rs

1#![recursion_limit = "512"]
2
3mod derive_action;
4mod derive_app_context;
5mod derive_into_element;
6mod derive_render;
7mod derive_visual_context;
8mod register_action;
9mod styles;
10mod test;
11
12#[cfg(any(feature = "inspector", debug_assertions))]
13mod derive_inspector_reflection;
14
15use proc_macro::TokenStream;
16use proc_macro_crate::{FoundCrate, crate_name};
17use quote::quote;
18use std::{env, fs, path::PathBuf, sync::OnceLock};
19use syn::{DeriveInput, Ident};
20use toml_edit::DocumentMut;
21
22/// `Action` derive macro - see the trait documentation for details.
23#[proc_macro_derive(Action, attributes(action))]
24pub fn derive_action(input: TokenStream) -> TokenStream {
25    derive_action::derive_action(input)
26}
27
28/// This can be used to register an action with the GPUI runtime when you want to manually implement
29/// the `Action` trait. Typically you should use the `Action` derive macro or `actions!` macro
30/// instead.
31#[proc_macro]
32pub fn register_action(ident: TokenStream) -> TokenStream {
33    register_action::register_action(ident)
34}
35
36/// #[derive(IntoElement)] is used to create a Component out of anything that implements
37/// the `RenderOnce` trait.
38#[proc_macro_derive(IntoElement)]
39pub fn derive_into_element(input: TokenStream) -> TokenStream {
40    derive_into_element::derive_into_element(input)
41}
42
43#[proc_macro_derive(Render)]
44#[doc(hidden)]
45pub fn derive_render(input: TokenStream) -> TokenStream {
46    derive_render::derive_render(input)
47}
48
49/// #[derive(AppContext)] is used to create a context out of anything that holds a `&mut App`
50/// Note that a `#[app]` attribute is required to identify the variable holding the &mut App.
51///
52/// Failure to add the attribute causes a compile error:
53///
54/// ```compile_fail
55/// # #[macro_use] extern crate gpui_macros;
56/// # #[macro_use] extern crate gpui;
57/// #[derive(AppContext)]
58/// struct MyContext<'a> {
59///     app: &'a mut gpui::App
60/// }
61/// ```
62#[proc_macro_derive(AppContext, attributes(app))]
63pub fn derive_app_context(input: TokenStream) -> TokenStream {
64    derive_app_context::derive_app_context(input)
65}
66
67/// #[derive(VisualContext)] is used to create a visual context out of anything that holds a `&mut Window` and
68/// implements `AppContext`
69/// Note that a `#[app]` and a `#[window]` attribute are required to identify the variables holding the &mut App,
70/// and &mut Window respectively.
71///
72/// Failure to add both attributes causes a compile error:
73///
74/// ```compile_fail
75/// # #[macro_use] extern crate gpui_macros;
76/// # #[macro_use] extern crate gpui;
77/// #[derive(VisualContext)]
78/// struct MyContext<'a, 'b> {
79///     #[app]
80///     app: &'a mut gpui::App,
81///     window: &'b mut gpui::Window
82/// }
83/// ```
84///
85/// ```compile_fail
86/// # #[macro_use] extern crate gpui_macros;
87/// # #[macro_use] extern crate gpui;
88/// #[derive(VisualContext)]
89/// struct MyContext<'a, 'b> {
90///     app: &'a mut gpui::App,
91///     #[window]
92///     window: &'b mut gpui::Window
93/// }
94/// ```
95#[proc_macro_derive(VisualContext, attributes(window, app))]
96pub fn derive_visual_context(input: TokenStream) -> TokenStream {
97    derive_visual_context::derive_visual_context(input)
98}
99
100/// Used by GPUI to generate the style helpers.
101#[proc_macro]
102#[doc(hidden)]
103pub fn style_helpers(input: TokenStream) -> TokenStream {
104    styles::style_helpers(input)
105}
106
107/// Generates methods for visibility styles.
108#[doc(hidden)]
109#[proc_macro]
110pub fn visibility_style_methods(input: TokenStream) -> TokenStream {
111    styles::visibility_style_methods(input)
112}
113
114/// Generates methods for margin styles.
115#[doc(hidden)]
116#[proc_macro]
117pub fn margin_style_methods(input: TokenStream) -> TokenStream {
118    styles::margin_style_methods(input)
119}
120
121/// Generates methods for padding styles.
122#[doc(hidden)]
123#[proc_macro]
124pub fn padding_style_methods(input: TokenStream) -> TokenStream {
125    styles::padding_style_methods(input)
126}
127
128/// Generates methods for position styles.
129#[doc(hidden)]
130#[proc_macro]
131pub fn position_style_methods(input: TokenStream) -> TokenStream {
132    styles::position_style_methods(input)
133}
134
135/// Generates methods for overflow styles.
136#[doc(hidden)]
137#[proc_macro]
138pub fn overflow_style_methods(input: TokenStream) -> TokenStream {
139    styles::overflow_style_methods(input)
140}
141
142/// Generates methods for cursor styles.
143#[doc(hidden)]
144#[proc_macro]
145pub fn cursor_style_methods(input: TokenStream) -> TokenStream {
146    styles::cursor_style_methods(input)
147}
148
149/// Generates methods for border styles.
150#[doc(hidden)]
151#[proc_macro]
152pub fn border_style_methods(input: TokenStream) -> TokenStream {
153    styles::border_style_methods(input)
154}
155
156/// Generates methods for box shadow styles.
157#[doc(hidden)]
158#[proc_macro]
159pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
160    styles::box_shadow_style_methods(input)
161}
162
163/// `#[gpui::test]` can be used to annotate test functions that run with GPUI support.
164///
165/// It supports both synchronous and asynchronous tests, and can provide you with
166/// as many `TestAppContext` instances as you need.
167/// The output contains a `#[test]` annotation so this can be used with any existing
168/// test harness (`cargo test` or `cargo-nextest`).
169///
170/// ```
171/// #[gpui::test]
172/// async fn test_foo(mut cx: &TestAppContext) { }
173/// ```
174///
175/// In addition to passing a TestAppContext, you can also ask for a `StdRnd` instance.
176/// this will be seeded with the `SEED` environment variable and is used internally by
177/// the ForegroundExecutor and BackgroundExecutor to run tasks deterministically in tests.
178/// Using the same `StdRng` for behavior in your test will allow you to exercise a wide
179/// variety of scenarios and interleavings just by changing the seed.
180///
181/// # Arguments
182///
183/// - `#[gpui::test]` with no arguments runs once with the seed `0` or `SEED` env var if set.
184/// - `#[gpui::test(seed = 10)]` runs once with the seed `10`.
185/// - `#[gpui::test(seeds(10, 20, 30))]` runs three times with seeds `10`, `20`, and `30`.
186/// - `#[gpui::test(iterations = 5)]` runs five times, providing as seed the values in the range `0..5`.
187/// - `#[gpui::test(retries = 3)]` runs up to four times if it fails to try and make it pass.
188/// - `#[gpui::test(on_failure = "crate::test::report_failure")]` will call the specified function after the
189///   tests fail so that you can write out more detail about the failure.
190///
191/// You can combine `iterations = ...` with `seeds(...)`:
192/// - `#[gpui::test(iterations = 5, seed = 10)]` is equivalent to `#[gpui::test(seeds(0, 1, 2, 3, 4, 10))]`.
193/// - `#[gpui::test(iterations = 5, seeds(10, 20, 30)]` is equivalent to `#[gpui::test(seeds(0, 1, 2, 3, 4, 10, 20, 30))]`.
194/// - `#[gpui::test(seeds(10, 20, 30), iterations = 5]` is equivalent to `#[gpui::test(seeds(0, 1, 2, 3, 4, 10, 20, 30))]`.
195///
196/// # Environment Variables
197///
198/// - `SEED`: sets a seed for the first run
199/// - `ITERATIONS`: forces the value of the `iterations` argument
200#[proc_macro_attribute]
201pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
202    test::test(args, function)
203}
204
205/// When added to a trait, `#[derive_inspector_reflection]` generates a module which provides
206/// enumeration and lookup by name of all methods that have the shape `fn method(self) -> Self`.
207/// This is used by the inspector so that it can use the builder methods in `Styled` and
208/// `StyledExt`.
209///
210/// The generated module will have the name `<snake_case_trait_name>_reflection` and contain the
211/// following functions:
212///
213/// ```ignore
214/// pub fn methods::<T: TheTrait + 'static>() -> Vec<gpui::inspector_reflection::FunctionReflection<T>>;
215///
216/// pub fn find_method::<T: TheTrait + 'static>() -> Option<gpui::inspector_reflection::FunctionReflection<T>>;
217/// ```
218///
219/// The `invoke` method on `FunctionReflection` will run the method. `FunctionReflection` also
220/// provides the method's documentation.
221#[cfg(any(feature = "inspector", debug_assertions))]
222#[proc_macro_attribute]
223pub fn derive_inspector_reflection(_args: TokenStream, input: TokenStream) -> TokenStream {
224    derive_inspector_reflection::derive_inspector_reflection(_args, input)
225}
226
227pub(crate) fn gpui_crate_path() -> proc_macro2::TokenStream {
228    if let Some(path) = configured_gpui_crate_path() {
229        return path;
230    }
231
232    let facade = crate_name("fc-gpui").ok();
233    let core = crate_name("fc-gpui-core").ok();
234
235    select_gpui_crate_path(facade, core)
236}
237
238fn select_gpui_crate_path(
239    facade: Option<FoundCrate>,
240    core: Option<FoundCrate>,
241) -> proc_macro2::TokenStream {
242    match (facade, core) {
243        (Some(_), Some(_)) => panic!(
244            "both fc-gpui and fc-gpui-core are direct dependencies; \
245             set [package.metadata.gpui-macros] crate = \"<dependency identifier>\" \
246             in Cargo.toml to select the crate used by GPUI macros"
247        ),
248        (Some(found), None) => crate_path(found, "fc_gpui", "gpui"),
249        (None, Some(found)) => crate_path(found, "fc_gpui_core", "gpui_core"),
250        (None, None) => quote!(::gpui),
251    }
252}
253
254fn crate_path(
255    found: FoundCrate,
256    package_identifier: &str,
257    library_identifier: &str,
258) -> proc_macro2::TokenStream {
259    match found {
260        FoundCrate::Itself => quote!(::gpui),
261        FoundCrate::Name(name) => {
262            // Cargo reports the dependency key, but the default library target can differ.
263            let name = if name == package_identifier {
264                library_identifier
265            } else {
266                &name
267            };
268            let ident = Ident::new(&name.replace('-', "_"), proc_macro2::Span::call_site());
269            quote!(::#ident)
270        }
271    }
272}
273
274fn configured_gpui_crate_path() -> Option<proc_macro2::TokenStream> {
275    static CONFIGURED_CRATE: OnceLock<Option<String>> = OnceLock::new();
276
277    let crate_name = CONFIGURED_CRATE.get_or_init(|| {
278        let manifest_path = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR")?).join("Cargo.toml");
279        let manifest = fs::read_to_string(manifest_path).ok()?;
280        let document = manifest.parse::<DocumentMut>().ok()?;
281        document
282            .get("package")?
283            .get("metadata")?
284            .get("gpui-macros")?
285            .get("crate")?
286            .as_str()
287            .map(str::to_owned)
288    });
289    let ident = syn::parse_str::<Ident>(crate_name.as_deref()?).unwrap_or_else(|_| {
290        panic!(
291            "package.metadata.gpui-macros.crate must be a Rust crate identifier, got {crate_name:?}"
292        )
293    });
294
295    Some(quote!(::#ident))
296}
297
298pub(crate) fn get_simple_attribute_field(ast: &DeriveInput, name: &'static str) -> Option<Ident> {
299    match &ast.data {
300        syn::Data::Struct(data_struct) => data_struct
301            .fields
302            .iter()
303            .find(|field| field.attrs.iter().any(|attr| attr.path().is_ident(name)))
304            .map(|field| field.ident.clone().unwrap()),
305        syn::Data::Enum(_) => None,
306        syn::Data::Union(_) => None,
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::{FoundCrate, select_gpui_crate_path};
313
314    #[test]
315    #[should_panic(expected = "both fc-gpui and fc-gpui-core are direct dependencies")]
316    fn ambiguous_direct_dependencies_require_an_explicit_crate() {
317        select_gpui_crate_path(
318            Some(FoundCrate::Name("facade_ui".into())),
319            Some(FoundCrate::Name("core_ui".into())),
320        );
321    }
322}