Skip to main content

ansiq_macros/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{
5    Expr, ExprLit, Ident, Lit, LitStr, Result, Token, braced,
6    parse::{Parse, ParseStream},
7    parse_macro_input,
8};
9
10#[proc_macro]
11pub fn view(input: TokenStream) -> TokenStream {
12    let root = parse_macro_input!(input as ViewRoot);
13    root.expand().into()
14}
15
16struct ViewRoot {
17    node: Node,
18}
19
20impl ViewRoot {
21    fn expand(self) -> TokenStream2 {
22        self.node.expand()
23    }
24}
25
26impl Parse for ViewRoot {
27    fn parse(input: ParseStream<'_>) -> Result<Self> {
28        Ok(Self {
29            node: input.parse()?,
30        })
31    }
32}
33
34struct Node {
35    name: Ident,
36    attrs: Vec<Attribute>,
37    children: Vec<Child>,
38}
39
40struct Attribute {
41    name: Ident,
42    value: Expr,
43}
44
45enum Child {
46    Node(Node),
47    Expr(Expr),
48    Text(LitStr),
49}
50
51impl Parse for Node {
52    fn parse(input: ParseStream<'_>) -> Result<Self> {
53        input.parse::<Token![<]>()?;
54        let name: Ident = input.parse()?;
55        let mut attrs = Vec::new();
56
57        // Parse a deliberately small JSX-like surface: `name=value` pairs where
58        // values are either normal Rust expressions or `{expr}` interpolations.
59        while !(input.peek(Token![>]) || (input.peek(Token![/]) && input.peek2(Token![>]))) {
60            let attr_name: Ident = input.parse()?;
61            input.parse::<Token![=]>()?;
62            let value = if input.peek(syn::token::Brace) {
63                let content;
64                braced!(content in input);
65                content.parse()?
66            } else {
67                Expr::Lit(ExprLit {
68                    attrs: Vec::new(),
69                    lit: input.parse::<Lit>()?,
70                })
71            };
72            attrs.push(Attribute {
73                name: attr_name,
74                value,
75            });
76        }
77
78        if input.peek(Token![/]) {
79            input.parse::<Token![/]>()?;
80            input.parse::<Token![>]>()?;
81            return Ok(Self {
82                name,
83                attrs,
84                children: Vec::new(),
85            });
86        }
87
88        input.parse::<Token![>]>()?;
89        let mut children = Vec::new();
90
91        while !(input.peek(Token![<]) && input.peek2(Token![/])) {
92            children.push(input.parse()?);
93        }
94
95        input.parse::<Token![<]>()?;
96        input.parse::<Token![/]>()?;
97        let close_name: Ident = input.parse()?;
98        if close_name != name {
99            return Err(syn::Error::new(
100                close_name.span(),
101                format!("expected closing tag </{}>", name),
102            ));
103        }
104        input.parse::<Token![>]>()?;
105
106        Ok(Self {
107            name,
108            attrs,
109            children,
110        })
111    }
112}
113
114impl Parse for Child {
115    fn parse(input: ParseStream<'_>) -> Result<Self> {
116        if input.peek(Token![<]) {
117            Ok(Self::Node(input.parse()?))
118        } else if input.peek(LitStr) {
119            Ok(Self::Text(input.parse()?))
120        } else if input.peek(syn::token::Brace) {
121            let content;
122            braced!(content in input);
123            Ok(Self::Expr(content.parse()?))
124        } else {
125            Err(input.error("expected a child element, string literal, or {expr}"))
126        }
127    }
128}
129
130impl Child {
131    fn expand(self) -> TokenStream2 {
132        match self {
133            Self::Node(node) => node.expand(),
134            Self::Expr(expr) => quote! { ::ansiq_core::IntoElement::into_element(#expr) },
135            Self::Text(text) => quote! { ::ansiq_core::Element::new_text(#text) },
136        }
137    }
138}
139
140impl Node {
141    fn expand(self) -> TokenStream2 {
142        match self.name.to_string().as_str() {
143            "Box" => self.expand_box(),
144            "Text" => self.expand_text(),
145            "Paragraph" => self.expand_paragraph(),
146            "RichText" => self.expand_rich_text(),
147            "Pane" => self.expand_pane(),
148            "Block" => self.expand_block(),
149            "List" => self.expand_list(),
150            "Tabs" => self.expand_tabs(),
151            "Gauge" => self.expand_gauge(),
152            "Clear" => self.expand_clear(),
153            "LineGauge" => self.expand_line_gauge(),
154            "Table" => self.expand_table(),
155            "Sparkline" => self.expand_sparkline(),
156            "BarChart" => self.expand_bar_chart(),
157            "Chart" => self.expand_chart(),
158            "Canvas" => self.expand_canvas(),
159            "Monthly" => self.expand_monthly(),
160            "ScrollView" => self.expand_scroll_view(),
161            "Scrollbar" => self.expand_scrollbar(),
162            "StreamingText" => self.expand_streaming_text(),
163            "Input" => self.expand_input(),
164            "StatusBar" => self.expand_status_bar(),
165            _ => self.expand_custom_component(),
166        }
167    }
168
169    fn expand_box(self) -> TokenStream2 {
170        let attrs = self.attrs;
171        let children = self.children;
172        let direction = attr_expr(&attrs, "direction")
173            .and_then(expr_as_string)
174            .unwrap_or_else(|| "column".to_string());
175        let mut builder = match direction.as_str() {
176            "row" => quote! { ::ansiq_widgets::Box::row() },
177            _ => quote! { ::ansiq_widgets::Box::column() },
178        };
179
180        if let Some(gap) = attr_expr(&attrs, "gap") {
181            builder = quote! { #builder .gap(#gap) };
182        }
183
184        for child in children {
185            let child = child.expand();
186            builder = quote! { #builder .child(#child) };
187        }
188
189        finish_element(builder, &attrs)
190    }
191
192    fn expand_text(self) -> TokenStream2 {
193        let attrs = self.attrs;
194        let children = self.children;
195        let content = content_expr(&attrs, &children).unwrap_or_else(|| quote! { "" });
196        finish_element(quote! { ::ansiq_widgets::Text::new(#content) }, &attrs)
197    }
198
199    fn expand_paragraph(self) -> TokenStream2 {
200        let attrs = self.attrs;
201        let children = self.children;
202        let content = content_expr(&attrs, &children).unwrap_or_else(|| quote! { "" });
203        let mut builder = quote! { ::ansiq_widgets::Paragraph::new(#content) };
204        if let Some(alignment) = attr_expr(&attrs, "alignment") {
205            builder = quote! { #builder .alignment(#alignment) };
206        }
207        if let Some(wrap) = attr_expr(&attrs, "wrap") {
208            builder = quote! { #builder .wrap(#wrap) };
209        }
210        if let Some(block) = attr_expr(&attrs, "block") {
211            builder = quote! { #builder .block(#block) };
212        }
213        if let Some(scroll) = attr_expr(&attrs, "scroll") {
214            builder = quote! { #builder .scroll(#scroll) };
215        } else if attr_expr(&attrs, "scroll_y").is_some() || attr_expr(&attrs, "scroll_x").is_some()
216        {
217            let scroll_y = attr_expr(&attrs, "scroll_y")
218                .map(|expr| quote! { #expr })
219                .unwrap_or_else(|| quote! { 0u16 });
220            let scroll_x = attr_expr(&attrs, "scroll_x")
221                .map(|expr| quote! { #expr })
222                .unwrap_or_else(|| quote! { 0u16 });
223            builder = quote! { #builder .scroll((#scroll_y, #scroll_x)) };
224        }
225        finish_element(builder, &attrs)
226    }
227
228    fn expand_rich_text(self) -> TokenStream2 {
229        let attrs = self.attrs;
230        let block = attr_expr(&attrs, "block")
231            .map(|expr| quote! { #expr })
232            .unwrap_or_else(|| {
233                quote! { ::ansiq_core::HistoryBlock { lines: ::std::vec::Vec::new() } }
234            });
235        finish_element(quote! { ::ansiq_widgets::RichText::new(#block) }, &attrs)
236    }
237
238    fn expand_pane(self) -> TokenStream2 {
239        let attrs = self.attrs;
240        let children = self.children;
241        let mut builder = quote! { ::ansiq_widgets::Pane::new() };
242        if let Some(title) = attr_expr(&attrs, "title") {
243            builder = quote! { #builder .title(#title) };
244        }
245        for child in children {
246            let child = child.expand();
247            builder = quote! { #builder .child(#child) };
248        }
249        finish_element(builder, &attrs)
250    }
251
252    fn expand_block(self) -> TokenStream2 {
253        let attrs = self.attrs;
254        let children = self.children;
255        let mut builder = quote! { ::ansiq_widgets::Block::new() };
256        if let Some(title) = attr_expr(&attrs, "title") {
257            builder = quote! { #builder .title(#title) };
258        }
259        if let Some(title_top) = attr_expr(&attrs, "title_top") {
260            builder = quote! { #builder .title_top(#title_top) };
261        }
262        if let Some(title_bottom) = attr_expr(&attrs, "title_bottom") {
263            builder = quote! { #builder .title_bottom(#title_bottom) };
264        }
265        if let Some(title_alignment) = attr_expr(&attrs, "title_alignment") {
266            builder = quote! { #builder .title_alignment(#title_alignment) };
267        }
268        if let Some(title_position) = attr_expr(&attrs, "title_position") {
269            builder = quote! { #builder .title_position(#title_position) };
270        }
271        if let Some(bordered) = attr_expr(&attrs, "bordered") {
272            builder = quote! { #builder .bordered_flag(#bordered) };
273        }
274        if let Some(borders) = attr_expr(&attrs, "borders") {
275            builder = quote! { #builder .borders(#borders) };
276        }
277        if let Some(border_type) = attr_expr(&attrs, "border_type") {
278            builder = quote! { #builder .border_type(#border_type) };
279        }
280        if let Some(border_set) = attr_expr(&attrs, "border_set") {
281            builder = quote! { #builder .border_set(#border_set) };
282        }
283        if let Some(padding) = attr_expr(&attrs, "padding") {
284            builder = quote! { #builder .padding(#padding) };
285        }
286        if let Some(border_style) = attr_expr(&attrs, "border_style") {
287            builder = quote! { #builder .border_style(#border_style) };
288        }
289        if let Some(title_style) = attr_expr(&attrs, "title_style") {
290            builder = quote! { #builder .title_style(#title_style) };
291        }
292        for child in children {
293            let child = child.expand();
294            builder = quote! { #builder .child(#child) };
295        }
296        finish_element(builder, &attrs)
297    }
298
299    fn expand_list(self) -> TokenStream2 {
300        let attrs = self.attrs;
301        let items = attr_expr(&attrs, "items")
302            .map(|expr| quote! { #expr })
303            .unwrap_or_else(|| quote! { ::std::vec::Vec::<::std::string::String>::new() });
304        let mut builder = quote! { ::ansiq_widgets::List::new(#items) };
305        if let Some(items) = attr_expr(&attrs, "items") {
306            builder = quote! { #builder .items(#items) };
307        }
308        if let Some(block) = attr_expr(&attrs, "block") {
309            builder = quote! { #builder .block(#block) };
310        }
311        if let Some(selected) = attr_expr(&attrs, "selected") {
312            builder = quote! { #builder .selected(#selected) };
313        }
314        if let Some(on_select) = attr_expr(&attrs, "on_select") {
315            builder = quote! { #builder .on_select(#on_select) };
316        }
317        finish_element(builder, &attrs)
318    }
319
320    fn expand_tabs(self) -> TokenStream2 {
321        let attrs = self.attrs;
322        let titles = attr_expr(&attrs, "titles")
323            .map(|expr| quote! { #expr })
324            .unwrap_or_else(|| quote! { ::std::vec::Vec::<::std::string::String>::new() });
325        let mut builder = quote! { ::ansiq_widgets::Tabs::new(#titles) };
326        if let Some(block) = attr_expr(&attrs, "block") {
327            builder = quote! { #builder .block(#block) };
328        }
329        if let Some(titles) = attr_expr(&attrs, "titles") {
330            builder = quote! { #builder .titles(#titles) };
331        }
332        if let Some(selected) = attr_expr(&attrs, "selected") {
333            builder = quote! { #builder .selected(#selected) };
334        }
335        if let Some(highlight_style) = attr_expr(&attrs, "highlight_style") {
336            builder = quote! { #builder .highlight_style(#highlight_style) };
337        }
338        if let Some(divider) = attr_expr(&attrs, "divider") {
339            builder = quote! { #builder .divider(#divider) };
340        }
341        if let Some(padding_left) = attr_expr(&attrs, "padding_left") {
342            builder = quote! { #builder .padding_left(#padding_left) };
343        }
344        if let Some(padding_right) = attr_expr(&attrs, "padding_right") {
345            builder = quote! { #builder .padding_right(#padding_right) };
346        }
347        if let Some(on_select) = attr_expr(&attrs, "on_select") {
348            builder = quote! { #builder .on_select(#on_select) };
349        }
350        finish_element(builder, &attrs)
351    }
352
353    fn expand_gauge(self) -> TokenStream2 {
354        let attrs = self.attrs;
355        let mut builder = quote! { ::ansiq_widgets::Gauge::new() };
356        if let Some(block) = attr_expr(&attrs, "block") {
357            builder = quote! { #builder .block(#block) };
358        }
359        if let Some(ratio) = attr_expr(&attrs, "ratio") {
360            builder = quote! { #builder .ratio(#ratio) };
361        }
362        if let Some(percent) = attr_expr(&attrs, "percent") {
363            builder = quote! { #builder .percent(#percent) };
364        }
365        if let Some(label) = attr_expr(&attrs, "label") {
366            builder = quote! { #builder .label(#label) };
367        }
368        if let Some(use_unicode) = attr_expr(&attrs, "use_unicode") {
369            builder = quote! { #builder .use_unicode(#use_unicode) };
370        }
371        if let Some(gauge_style) = attr_expr(&attrs, "gauge_style") {
372            builder = quote! { #builder .gauge_style(#gauge_style) };
373        }
374        finish_element(builder, &attrs)
375    }
376
377    fn expand_clear(self) -> TokenStream2 {
378        let attrs = self.attrs;
379        finish_element(quote! { ::ansiq_widgets::Clear::new() }, &attrs)
380    }
381
382    fn expand_line_gauge(self) -> TokenStream2 {
383        let attrs = self.attrs;
384        let mut builder = quote! { ::ansiq_widgets::LineGauge::new() };
385        if let Some(block) = attr_expr(&attrs, "block") {
386            builder = quote! { #builder .block(#block) };
387        }
388        if let Some(ratio) = attr_expr(&attrs, "ratio") {
389            builder = quote! { #builder .ratio(#ratio) };
390        }
391        if let Some(percent) = attr_expr(&attrs, "percent") {
392            builder = quote! { #builder .percent(#percent) };
393        }
394        if let Some(label) = attr_expr(&attrs, "label") {
395            builder = quote! { #builder .label(#label) };
396        }
397        if let Some(line_set) = attr_expr(&attrs, "line_set") {
398            builder = quote! { #builder .line_set(#line_set) };
399        }
400        if let Some(filled_symbol) = attr_expr(&attrs, "filled_symbol") {
401            builder = quote! { #builder .filled_symbol(#filled_symbol) };
402        }
403        if let Some(unfilled_symbol) = attr_expr(&attrs, "unfilled_symbol") {
404            builder = quote! { #builder .unfilled_symbol(#unfilled_symbol) };
405        }
406        if let Some(filled_style) = attr_expr(&attrs, "filled_style") {
407            builder = quote! { #builder .filled_style(#filled_style) };
408        }
409        if let Some(unfilled_style) = attr_expr(&attrs, "unfilled_style") {
410            builder = quote! { #builder .unfilled_style(#unfilled_style) };
411        }
412        finish_element(builder, &attrs)
413    }
414
415    fn expand_table(self) -> TokenStream2 {
416        let attrs = self.attrs;
417        let rows = attr_expr(&attrs, "rows")
418            .map(|expr| quote! { #expr })
419            .unwrap_or_else(
420                || quote! { ::std::vec::Vec::<::std::vec::Vec<::std::string::String>>::new() },
421            );
422        let widths = attr_expr(&attrs, "widths")
423            .map(|expr| quote! { #expr })
424            .unwrap_or_else(|| quote! { ::std::vec::Vec::<::ansiq_core::Constraint>::new() });
425        let mut builder = quote! { ::ansiq_widgets::Table::new(#rows, #widths) };
426        if let Some(block) = attr_expr(&attrs, "block") {
427            builder = quote! { #builder .block(#block) };
428        }
429        if let Some(header) = attr_expr(&attrs, "header") {
430            builder = quote! { #builder .header(#header) };
431        } else if let Some(headers) = attr_expr(&attrs, "headers") {
432            builder = quote! { #builder .headers(#headers) };
433        }
434        if let Some(footer) = attr_expr(&attrs, "footer") {
435            builder = quote! { #builder .footer(#footer) };
436        }
437        if let Some(widths) = attr_expr(&attrs, "widths") {
438            builder = quote! { #builder .widths(#widths) };
439        }
440        if let Some(column_spacing) = attr_expr(&attrs, "column_spacing") {
441            builder = quote! { #builder .column_spacing(#column_spacing) };
442        }
443        if let Some(flex) = attr_expr(&attrs, "flex") {
444            builder = quote! { #builder .flex(#flex) };
445        }
446        if let Some(alignments) = attr_expr(&attrs, "alignments") {
447            builder = quote! { #builder .alignments(#alignments) };
448        }
449        if let Some(selected) = attr_expr(&attrs, "selected") {
450            builder = quote! { #builder .selected(#selected) };
451        }
452        if let Some(highlight_symbol) = attr_expr(&attrs, "highlight_symbol") {
453            builder = quote! { #builder .highlight_symbol(#highlight_symbol) };
454        }
455        if let Some(on_select) = attr_expr(&attrs, "on_select") {
456            builder = quote! { #builder .on_select(#on_select) };
457        }
458        finish_element(builder, &attrs)
459    }
460
461    fn expand_sparkline(self) -> TokenStream2 {
462        let attrs = self.attrs;
463        let mut builder = quote! { ::ansiq_widgets::Sparkline::new() };
464        if let Some(values) = attr_expr(&attrs, "values") {
465            builder = quote! { #builder .values(#values) };
466        }
467        if let Some(max) = attr_expr(&attrs, "max") {
468            builder = quote! { #builder .max(#max) };
469        }
470        finish_element(builder, &attrs)
471    }
472
473    fn expand_bar_chart(self) -> TokenStream2 {
474        let attrs = self.attrs;
475        let mut builder = quote! { ::ansiq_widgets::BarChart::new() };
476        if let Some(bars) = attr_expr(&attrs, "bars") {
477            builder = quote! { #builder .bars(#bars) };
478        }
479        if let Some(max) = attr_expr(&attrs, "max") {
480            builder = quote! { #builder .max(#max) };
481        }
482        if let Some(bar_width) = attr_expr(&attrs, "bar_width") {
483            builder = quote! { #builder .bar_width(#bar_width) };
484        }
485        finish_element(builder, &attrs)
486    }
487
488    fn expand_chart(self) -> TokenStream2 {
489        let attrs = self.attrs;
490        let datasets = attr_expr(&attrs, "datasets");
491        let mut builder = quote! { ::ansiq_widgets::Chart::new() };
492        if let Some(min_y) = attr_expr(&attrs, "min_y") {
493            builder = quote! { #builder .min_y(#min_y) };
494        }
495        if let Some(max_y) = attr_expr(&attrs, "max_y") {
496            builder = quote! { #builder .max_y(#max_y) };
497        }
498        let builder = if let Some(datasets) = datasets {
499            quote! {{
500                let mut chart = #builder;
501                for dataset in #datasets {
502                    chart = if let Some(label) = dataset.label {
503                        chart.named_dataset(label, dataset.points)
504                    } else {
505                        chart.dataset(dataset.points)
506                    };
507                }
508                chart
509            }}
510        } else {
511            builder
512        };
513        finish_element(builder, &attrs)
514    }
515
516    fn expand_canvas(self) -> TokenStream2 {
517        let attrs = self.attrs;
518        let cells = attr_expr(&attrs, "cells");
519        let mut builder = quote! { ::ansiq_widgets::Canvas::new() };
520        if let Some(width) = attr_expr(&attrs, "width") {
521            let height = attr_expr(&attrs, "height")
522                .map(|expr| quote! { #expr })
523                .unwrap_or_else(|| quote! { 8u16 });
524            builder = quote! { #builder .size(#width, #height) };
525        }
526        let builder = if let Some(cells) = cells {
527            quote! {{
528                let mut canvas = #builder;
529                for cell in #cells {
530                    canvas = canvas.point(cell.x, cell.y, cell.symbol);
531                }
532                canvas
533            }}
534        } else {
535            builder
536        };
537        finish_element(builder, &attrs)
538    }
539
540    fn expand_monthly(self) -> TokenStream2 {
541        let attrs = self.attrs;
542        let mut builder = quote! { ::ansiq_widgets::Monthly::new() };
543        if let Some(year) = attr_expr(&attrs, "year") {
544            builder = quote! { #builder .year(#year) };
545        }
546        if let Some(month) = attr_expr(&attrs, "month") {
547            builder = quote! { #builder .month(#month) };
548        }
549        if let Some(selected_day) = attr_expr(&attrs, "selected_day") {
550            builder = quote! { #builder .selected_day(#selected_day) };
551        }
552        finish_element(builder, &attrs)
553    }
554
555    fn expand_scrollbar(self) -> TokenStream2 {
556        let attrs = self.attrs;
557        let orientation = attr_expr(&attrs, "orientation")
558            .map(|expr| quote! { #expr })
559            .unwrap_or_else(|| quote! { ::ansiq_core::ScrollbarOrientation::VerticalRight });
560        let mut builder = quote! { ::ansiq_widgets::Scrollbar::new(#orientation) };
561        if let Some(position) = attr_expr(&attrs, "position") {
562            builder = quote! { #builder .position(#position) };
563        }
564        if let Some(content_length) = attr_expr(&attrs, "content_length") {
565            builder = quote! { #builder .content_length(#content_length) };
566        }
567        if let Some(viewport_length) = attr_expr(&attrs, "viewport_length") {
568            builder = quote! { #builder .viewport_length(#viewport_length) };
569        }
570        if let Some(viewport_content_length) = attr_expr(&attrs, "viewport_content_length") {
571            builder = quote! { #builder .viewport_content_length(#viewport_content_length) };
572        }
573        if let Some(symbols) = attr_expr(&attrs, "symbols") {
574            builder = quote! { #builder .symbols(#symbols) };
575        }
576        if let Some(thumb_symbol) = attr_expr(&attrs, "thumb_symbol") {
577            builder = quote! { #builder .thumb_symbol(#thumb_symbol) };
578        }
579        if let Some(track_symbol) = attr_expr(&attrs, "track_symbol") {
580            builder = quote! { #builder .track_symbol(#track_symbol) };
581        }
582        if let Some(begin_symbol) = attr_expr(&attrs, "begin_symbol") {
583            builder = quote! { #builder .begin_symbol(#begin_symbol) };
584        }
585        if let Some(end_symbol) = attr_expr(&attrs, "end_symbol") {
586            builder = quote! { #builder .end_symbol(#end_symbol) };
587        }
588        if let Some(thumb_style) = attr_expr(&attrs, "thumb_style") {
589            builder = quote! { #builder .thumb_style(#thumb_style) };
590        }
591        if let Some(track_style) = attr_expr(&attrs, "track_style") {
592            builder = quote! { #builder .track_style(#track_style) };
593        }
594        if let Some(begin_style) = attr_expr(&attrs, "begin_style") {
595            builder = quote! { #builder .begin_style(#begin_style) };
596        }
597        if let Some(end_style) = attr_expr(&attrs, "end_style") {
598            builder = quote! { #builder .end_style(#end_style) };
599        }
600        if let Some(on_scroll) = attr_expr(&attrs, "on_scroll") {
601            builder = quote! { #builder .on_scroll(#on_scroll) };
602        }
603        finish_element(builder, &attrs)
604    }
605
606    fn expand_scroll_view(self) -> TokenStream2 {
607        let attrs = self.attrs;
608        let children = self.children;
609        let mut builder = quote! { ::ansiq_widgets::ScrollView::new() };
610        if let Some(follow_bottom) = attr_expr(&attrs, "follow_bottom") {
611            builder = quote! { #builder .follow_bottom(#follow_bottom) };
612        }
613        if let Some(offset) = attr_expr(&attrs, "offset") {
614            builder = quote! { #builder .offset(#offset) };
615        }
616        if let Some(on_scroll) = attr_expr(&attrs, "on_scroll") {
617            builder = quote! { #builder .on_scroll(#on_scroll) };
618        }
619        for child in children {
620            let child = child.expand();
621            builder = quote! { #builder .child(#child) };
622        }
623        finish_element(builder, &attrs)
624    }
625
626    fn expand_streaming_text(self) -> TokenStream2 {
627        let attrs = self.attrs;
628        let children = self.children;
629        let content = content_expr(&attrs, &children).unwrap_or_else(|| quote! { "" });
630        finish_element(
631            quote! { ::ansiq_widgets::StreamingText::new(#content) },
632            &attrs,
633        )
634    }
635
636    fn expand_input(self) -> TokenStream2 {
637        let attrs = self.attrs;
638        let mut builder = quote! { ::ansiq_widgets::Input::new() };
639        if let Some(value) = attr_expr(&attrs, "value") {
640            builder = quote! { #builder .value(#value) };
641        }
642        if let Some(placeholder) = attr_expr(&attrs, "placeholder") {
643            builder = quote! { #builder .placeholder(#placeholder) };
644        }
645        if let Some(on_change) = attr_expr(&attrs, "on_change") {
646            builder = quote! { #builder .on_change(#on_change) };
647        }
648        if let Some(on_submit) = attr_expr(&attrs, "on_submit") {
649            builder = quote! { #builder .on_submit(#on_submit) };
650        }
651        finish_element(builder, &attrs)
652    }
653
654    fn expand_status_bar(self) -> TokenStream2 {
655        let attrs = self.attrs;
656        let content = attr_expr(&attrs, "text")
657            .or_else(|| attr_expr(&attrs, "content"))
658            .map(|expr| quote! { #expr })
659            .unwrap_or_else(|| quote! { "" });
660        finish_element(quote! { ::ansiq_widgets::StatusBar::new(#content) }, &attrs)
661    }
662
663    fn expand_custom_component(self) -> TokenStream2 {
664        if !self.attrs.is_empty() || !self.children.is_empty() {
665            return compile_error(
666                self.name.span(),
667                "custom components with props or children are not supported yet",
668            );
669        }
670
671        let name = self.name;
672        quote! { ::ansiq_core::component_with_cx(stringify!(#name), #name) }
673    }
674}
675
676fn finish_element(builder: TokenStream2, attrs: &[Attribute]) -> TokenStream2 {
677    let mut element = quote! { #builder .build() };
678
679    if let Some(layout) = attr_expr(attrs, "layout") {
680        element = quote! { #element .with_layout(#layout) };
681    }
682    if let Some(style) = attr_expr(attrs, "style") {
683        element = quote! { #element .with_style(#style) };
684    }
685    if let Some(focusable) = attr_expr(attrs, "focusable") {
686        element = quote! { #element .with_focusable(#focusable) };
687    }
688
689    element
690}
691
692fn attr_expr<'a>(attrs: &'a [Attribute], name: &str) -> Option<&'a Expr> {
693    attrs
694        .iter()
695        .find(|attr| attr.name == Ident::new(name, Span::call_site()))
696        .map(|attr| &attr.value)
697}
698
699fn content_expr(attrs: &[Attribute], children: &[Child]) -> Option<TokenStream2> {
700    attr_expr(attrs, "content")
701        .or_else(|| attr_expr(attrs, "text"))
702        .map(|expr| quote! { #expr })
703        .or_else(|| {
704            if children.len() == 1 {
705                match &children[0] {
706                    Child::Text(text) => Some(quote! { #text }),
707                    Child::Expr(expr) => Some(quote! { #expr }),
708                    Child::Node(_) => None,
709                }
710            } else {
711                None
712            }
713        })
714}
715
716fn expr_as_string(expr: &Expr) -> Option<String> {
717    match expr {
718        Expr::Lit(lit) => match &lit.lit {
719            syn::Lit::Str(value) => Some(value.value()),
720            _ => None,
721        },
722        _ => None,
723    }
724}
725
726fn compile_error(span: Span, message: &str) -> TokenStream2 {
727    syn::Error::new(span, message).to_compile_error()
728}