gpui_markup/
lib.rs

1//! gpui-markup - A declarative markup DSL for building GPUI applications.
2//!
3//! This crate provides a Rust-native syntax for building GPUI UIs:
4//!
5//! ```ignore
6//! ui! {
7//!     div @[flex, flex_col, w: px(200.0), bg: theme.secondary] {
8//!         div @[text_size: px(16.0)] {
9//!             "Hello World",
10//!         },
11//!     }
12//! }
13//! ```
14//!
15//! Which expands to:
16//!
17//! ```ignore
18//! gpui::ParentElement::child(
19//!     div()
20//!         .flex()
21//!         .flex_col()
22//!         .w(px(200.0))
23//!         .bg(theme.secondary),
24//!     gpui::ParentElement::child(
25//!         div().text_size(px(16.0)),
26//!         "Hello World"
27//!     )
28//! )
29//! ```
30
31mod ast;
32mod codegen;
33mod parser;
34
35use proc_macro::TokenStream;
36use proc_macro_error2::proc_macro_error;
37use quote::quote;
38use syn::parse_macro_input;
39
40use crate::ast::Markup;
41
42/// A declarative markup macro for building GPUI UIs.
43///
44/// # Syntax
45///
46/// ## Basic Elements
47///
48/// ```ignore
49/// ui! { div {} }                        // -> div()
50/// ui! { div @[flex] {} }                // -> div().flex()
51/// ui! { div @[w: px(200.0)] {} }        // -> div().w(px(200.0))
52/// ```
53///
54/// ## Children
55///
56/// ```ignore
57/// // Comma-separated children
58/// ui! {
59///     div {
60///         "First",
61///         "Second",
62///     }
63/// }
64/// // -> gpui::ParentElement::child(gpui::ParentElement::child(div(), "First"), "Second")
65/// ```
66///
67/// ## Spread Children
68///
69/// Use `..expr` to spread an iterable as children:
70///
71/// ```ignore
72/// let items: Vec<Div> = vec![div(), div()];
73///
74/// ui! {
75///     div {
76///         ..items,
77///     }
78/// }
79/// // -> gpui::ParentElement::children(div(), items)
80///
81/// // Can be mixed with regular children
82/// ui! {
83///     div {
84///         "Header",
85///         ..items,
86///         "Footer",
87///     }
88/// }
89/// // -> gpui::ParentElement::child(
90/// //      gpui::ParentElement::children(
91/// //        gpui::ParentElement::child(div(), "Header"),
92/// //        items
93/// //      ),
94/// //      "Footer"
95/// //    )
96/// ```
97///
98/// ## Method Chains
99///
100/// Use `.method(args)` to insert method calls at any position.
101/// Supports method chains and generics:
102///
103/// ```ignore
104/// ui! {
105///     div {
106///         "static child",
107///         .when(condition, |d| d.child("dynamic")),
108///         .flex().gap_2(),
109///         .map::<Div, _>(|d| d),
110///     }
111/// }
112/// ```
113///
114/// ## Comments
115///
116/// Use standard Rust comments (`//` or `/* */`) inside `ui!`.
117///
118/// ## Expression Elements
119///
120/// Any expression can be used as an element (braces required at top level):
121///
122/// ```ignore
123/// ui! { Button::new("Click") {} }              // -> Button::new("Click")
124/// ui! { Button::new("Click") @[style: Primary] {} }
125///                                              // -> Button::new("Click").style(Primary)
126/// ui! {
127///     div().flex() @[flex_col] {
128///         "Content",
129///     }
130/// }
131/// // -> gpui::ParentElement::child(div().flex().flex_col(), "Content")
132///
133/// // Parentheses for complex expressions (braces optional)
134/// ui! { (a + b) }                              // -> a + b
135/// ```
136///
137/// **Why braces at top level?** The `ui!` macro builds a component tree.
138/// Braces declare "this is a UI element" - they mark it as a tree node,
139/// trigger implicit `::new()` for components, and provide a place for
140/// attributes and children.
141///
142/// ## Multi-value Attributes
143///
144/// Use tuples for attributes with multiple arguments:
145///
146/// ```ignore
147/// ui! { div @[when: (condition, |d| d.flex())] {} }
148/// // -> div().when(condition, |d| d.flex())
149/// ```
150#[proc_macro]
151#[proc_macro_error]
152pub fn ui(input: TokenStream) -> TokenStream {
153    let markup = parse_macro_input!(input as Markup);
154    let output = quote! { #markup };
155    output.into()
156}