1use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::{format_ident, quote};
12use std::sync::atomic::{AtomicU64, Ordering};
13use syn::{
14 braced,
15 parse::{Parse, ParseStream},
16 parse_macro_input,
17 punctuated::Punctuated,
18 Expr, Ident, LitStr, Result, Token,
19};
20
21#[proc_macro]
23pub fn view(input: TokenStream) -> TokenStream {
24 let node = parse_macro_input!(input as ViewNode);
25 let expanded = node.to_tokens();
26 TokenStream::from(expanded)
27}
28
29struct WithInput {
31 idents: Vec<Ident>,
32 expr: Expr,
33}
34
35impl Parse for WithInput {
36 fn parse(input: ParseStream) -> Result<Self> {
37 let idents: Punctuated<Ident, Token![,]> = Punctuated::parse_separated_nonempty(input)?;
39 let idents: Vec<Ident> = idents.into_iter().collect();
40
41 input.parse::<Token![=>]>()?;
43
44 let expr: Expr = input.parse()?;
46
47 Ok(WithInput { idents, expr })
48 }
49}
50
51impl WithInput {
52 fn to_tokens(&self) -> TokenStream2 {
53 let clone_statements: Vec<TokenStream2> = self
54 .idents
55 .iter()
56 .map(|ident| quote! { let #ident = #ident.clone(); })
57 .collect();
58
59 let expr = &self.expr;
60
61 quote! {
62 {
63 #(#clone_statements)*
64 #expr
65 }
66 }
67 }
68}
69
70static STATE_COUNTER: AtomicU64 = AtomicU64::new(0);
72
73struct StateInput {
75 scope: Expr,
76 init: Expr,
77}
78
79impl Parse for StateInput {
80 fn parse(input: ParseStream) -> Result<Self> {
81 let scope: Expr = input.parse()?;
83
84 input.parse::<Token![,]>()?;
86
87 let init: Expr = input.parse()?;
89
90 Ok(StateInput { scope, init })
91 }
92}
93
94impl StateInput {
95 fn to_tokens(&self) -> TokenStream2 {
96 let scope = &self.scope;
97 let init = &self.init;
98
99 let counter = STATE_COUNTER.fetch_add(1, Ordering::SeqCst);
102 let key_type = format_ident!("__State_{}", counter);
103
104 quote! {
105 {
106 struct #key_type;
107 #scope.use_state_keyed::<#key_type, _>(#init)
108 }
109 }
110 }
111}
112
113#[proc_macro]
143pub fn state(input: TokenStream) -> TokenStream {
144 let input = parse_macro_input!(input as StateInput);
145 let expanded = input.to_tokens();
146 TokenStream::from(expanded)
147}
148
149static EFFECT_COUNTER: AtomicU64 = AtomicU64::new(0);
151
152struct EffectInput {
154 scope: Expr,
155 deps: Expr,
156 effect_fn: Expr,
157}
158
159impl Parse for EffectInput {
160 fn parse(input: ParseStream) -> Result<Self> {
161 let scope: Expr = input.parse()?;
163 input.parse::<Token![,]>()?;
164
165 let deps: Expr = input.parse()?;
167 input.parse::<Token![,]>()?;
168
169 let effect_fn: Expr = input.parse()?;
171
172 Ok(EffectInput {
173 scope,
174 deps,
175 effect_fn,
176 })
177 }
178}
179
180impl EffectInput {
181 fn to_tokens(&self) -> TokenStream2 {
182 let scope = &self.scope;
183 let deps = &self.deps;
184 let effect_fn = &self.effect_fn;
185
186 let counter = EFFECT_COUNTER.fetch_add(1, Ordering::SeqCst);
187 let key_type = format_ident!("__Effect_{}", counter);
188
189 quote! {
190 {
191 struct #key_type;
192 #scope.use_effect_keyed::<#key_type, _, _, _>(#deps, #effect_fn)
193 }
194 }
195 }
196}
197
198#[proc_macro]
235pub fn effect(input: TokenStream) -> TokenStream {
236 let input = parse_macro_input!(input as EffectInput);
237 let expanded = input.to_tokens();
238 TokenStream::from(expanded)
239}
240
241struct EffectOnceInput {
243 scope: Expr,
244 effect_fn: Expr,
245}
246
247impl Parse for EffectOnceInput {
248 fn parse(input: ParseStream) -> Result<Self> {
249 let scope: Expr = input.parse()?;
251 input.parse::<Token![,]>()?;
252
253 let effect_fn: Expr = input.parse()?;
255
256 Ok(EffectOnceInput { scope, effect_fn })
257 }
258}
259
260impl EffectOnceInput {
261 fn to_tokens(&self) -> TokenStream2 {
262 let scope = &self.scope;
263 let effect_fn = &self.effect_fn;
264
265 let counter = EFFECT_COUNTER.fetch_add(1, Ordering::SeqCst);
266 let key_type = format_ident!("__Effect_{}", counter);
267
268 quote! {
269 {
270 struct #key_type;
271 #scope.use_effect_once_keyed::<#key_type, _, _>(#effect_fn)
272 }
273 }
274 }
275}
276
277#[proc_macro]
308pub fn effect_once(input: TokenStream) -> TokenStream {
309 let input = parse_macro_input!(input as EffectOnceInput);
310 let expanded = input.to_tokens();
311 TokenStream::from(expanded)
312}
313
314static HOOK_COUNTER: AtomicU64 = AtomicU64::new(0);
316
317struct AsyncDataInput {
319 scope: Expr,
320 func: Expr,
321}
322
323impl Parse for AsyncDataInput {
324 fn parse(input: ParseStream) -> Result<Self> {
325 let scope: Expr = input.parse()?;
326 input.parse::<Token![,]>()?;
327 let func: Expr = input.parse()?;
328 Ok(AsyncDataInput { scope, func })
329 }
330}
331
332impl AsyncDataInput {
333 fn to_tokens(&self) -> TokenStream2 {
334 let scope = &self.scope;
335 let func = &self.func;
336 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
337 let key_type = format_ident!("__Async_{}", counter);
338
339 quote! {
340 {
341 struct #key_type;
342 #scope.use_async_keyed::<#key_type, _, _>(#func)
343 }
344 }
345 }
346}
347
348#[proc_macro]
358pub fn async_data(input: TokenStream) -> TokenStream {
359 let input = parse_macro_input!(input as AsyncDataInput);
360 let expanded = input.to_tokens();
361 TokenStream::from(expanded)
362}
363
364struct StreamInput {
366 scope: Expr,
367 func: Expr,
368}
369
370impl Parse for StreamInput {
371 fn parse(input: ParseStream) -> Result<Self> {
372 let scope: Expr = input.parse()?;
373 input.parse::<Token![,]>()?;
374 let func: Expr = input.parse()?;
375 Ok(StreamInput { scope, func })
376 }
377}
378
379impl StreamInput {
380 fn to_tokens(&self) -> TokenStream2 {
381 let scope = &self.scope;
382 let func = &self.func;
383 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
384 let key_type = format_ident!("__Stream_{}", counter);
385
386 quote! {
387 {
388 struct #key_type;
389 #scope.use_stream_keyed::<#key_type, _, _, _>(#func)
390 }
391 }
392 }
393}
394
395#[proc_macro]
405pub fn stream(input: TokenStream) -> TokenStream {
406 let input = parse_macro_input!(input as StreamInput);
407 let expanded = input.to_tokens();
408 TokenStream::from(expanded)
409}
410
411struct TextStreamInput {
413 scope: Expr,
414 func: Expr,
415}
416
417impl Parse for TextStreamInput {
418 fn parse(input: ParseStream) -> Result<Self> {
419 let scope: Expr = input.parse()?;
420 input.parse::<Token![,]>()?;
421 let func: Expr = input.parse()?;
422 Ok(TextStreamInput { scope, func })
423 }
424}
425
426impl TextStreamInput {
427 fn to_tokens(&self) -> TokenStream2 {
428 let scope = &self.scope;
429 let func = &self.func;
430 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
431 let key_type = format_ident!("__TextStream_{}", counter);
432
433 quote! {
434 {
435 struct #key_type;
436 #scope.use_text_stream_keyed::<#key_type, _, _>(#func)
437 }
438 }
439 }
440}
441
442#[proc_macro]
452pub fn text_stream(input: TokenStream) -> TokenStream {
453 let input = parse_macro_input!(input as TextStreamInput);
454 let expanded = input.to_tokens();
455 TokenStream::from(expanded)
456}
457
458struct TextStreamWithRestartInput {
460 scope: Expr,
461 restart: Expr,
462 func: Expr,
463}
464
465impl Parse for TextStreamWithRestartInput {
466 fn parse(input: ParseStream) -> Result<Self> {
467 let scope: Expr = input.parse()?;
468 input.parse::<Token![,]>()?;
469 let restart: Expr = input.parse()?;
470 input.parse::<Token![,]>()?;
471 let func: Expr = input.parse()?;
472 Ok(TextStreamWithRestartInput { scope, restart, func })
473 }
474}
475
476impl TextStreamWithRestartInput {
477 fn to_tokens(&self) -> TokenStream2 {
478 let scope = &self.scope;
479 let restart = &self.restart;
480 let func = &self.func;
481 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
482 let key_type = format_ident!("__TextStreamRestart_{}", counter);
483
484 quote! {
485 {
486 struct #key_type;
487 #scope.use_text_stream_with_restart_keyed::<#key_type, _, _>(#restart, #func)
488 }
489 }
490 }
491}
492
493#[proc_macro]
503pub fn text_stream_with_restart(input: TokenStream) -> TokenStream {
504 let input = parse_macro_input!(input as TextStreamWithRestartInput);
505 let expanded = input.to_tokens();
506 TokenStream::from(expanded)
507}
508
509struct TerminalInput {
511 scope: Expr,
512}
513
514impl Parse for TerminalInput {
515 fn parse(input: ParseStream) -> Result<Self> {
516 let scope: Expr = input.parse()?;
517 Ok(TerminalInput { scope })
518 }
519}
520
521impl TerminalInput {
522 fn to_tokens(&self) -> TokenStream2 {
523 let scope = &self.scope;
524 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
525 let key_type = format_ident!("__Terminal_{}", counter);
526
527 quote! {
528 {
529 struct #key_type;
530 #scope.use_terminal_keyed::<#key_type>()
531 }
532 }
533 }
534}
535
536#[proc_macro]
544pub fn terminal(input: TokenStream) -> TokenStream {
545 let input = parse_macro_input!(input as TerminalInput);
546 let expanded = input.to_tokens();
547 TokenStream::from(expanded)
548}
549
550struct ReducerInput {
552 scope: Expr,
553 initial: Expr,
554 reducer_fn: Expr,
555}
556
557impl Parse for ReducerInput {
558 fn parse(input: ParseStream) -> Result<Self> {
559 let scope: Expr = input.parse()?;
560 input.parse::<Token![,]>()?;
561 let initial: Expr = input.parse()?;
562 input.parse::<Token![,]>()?;
563 let reducer_fn: Expr = input.parse()?;
564 Ok(ReducerInput { scope, initial, reducer_fn })
565 }
566}
567
568impl ReducerInput {
569 fn to_tokens(&self) -> TokenStream2 {
570 let scope = &self.scope;
571 let initial = &self.initial;
572 let reducer_fn = &self.reducer_fn;
573 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
574 let key_type = format_ident!("__Reducer_{}", counter);
575
576 quote! {
577 {
578 struct #key_type;
579 #scope.use_reducer_keyed::<#key_type, _, _>(#initial, #reducer_fn)
580 }
581 }
582 }
583}
584
585#[proc_macro]
604pub fn reducer(input: TokenStream) -> TokenStream {
605 let input = parse_macro_input!(input as ReducerInput);
606 let expanded = input.to_tokens();
607 TokenStream::from(expanded)
608}
609
610struct ChannelInput {
612 scope: Expr,
613 ty: syn::Type,
614}
615
616impl Parse for ChannelInput {
617 fn parse(input: ParseStream) -> Result<Self> {
618 let scope: Expr = input.parse()?;
619 input.parse::<Token![,]>()?;
620 let ty: syn::Type = input.parse()?;
621 Ok(ChannelInput { scope, ty })
622 }
623}
624
625impl ChannelInput {
626 fn to_tokens(&self) -> TokenStream2 {
627 let scope = &self.scope;
628 let ty = &self.ty;
629 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
630 let key_type = format_ident!("__Channel_{}", counter);
631
632 quote! {
633 {
634 struct #key_type;
635 #scope.use_channel_keyed::<#key_type, #ty>()
636 }
637 }
638 }
639}
640
641#[proc_macro]
652pub fn channel(input: TokenStream) -> TokenStream {
653 let input = parse_macro_input!(input as ChannelInput);
654 let expanded = input.to_tokens();
655 TokenStream::from(expanded)
656}
657
658struct PortInput {
660 scope: Expr,
661 in_ty: syn::Type,
662 out_ty: syn::Type,
663}
664
665impl Parse for PortInput {
666 fn parse(input: ParseStream) -> Result<Self> {
667 let scope: Expr = input.parse()?;
668 input.parse::<Token![,]>()?;
669 let in_ty: syn::Type = input.parse()?;
670 input.parse::<Token![,]>()?;
671 let out_ty: syn::Type = input.parse()?;
672 Ok(PortInput { scope, in_ty, out_ty })
673 }
674}
675
676impl PortInput {
677 fn to_tokens(&self) -> TokenStream2 {
678 let scope = &self.scope;
679 let in_ty = &self.in_ty;
680 let out_ty = &self.out_ty;
681 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
682 let key_type = format_ident!("__Port_{}", counter);
683
684 quote! {
685 {
686 struct #key_type;
687 #scope.use_port_keyed::<#key_type, #in_ty, #out_ty>()
688 }
689 }
690 }
691}
692
693#[proc_macro]
704pub fn port(input: TokenStream) -> TokenStream {
705 let input = parse_macro_input!(input as PortInput);
706 let expanded = input.to_tokens();
707 TokenStream::from(expanded)
708}
709
710struct IntervalInput {
712 scope: Expr,
713 duration: Expr,
714 callback: Expr,
715}
716
717impl Parse for IntervalInput {
718 fn parse(input: ParseStream) -> Result<Self> {
719 let scope: Expr = input.parse()?;
720 input.parse::<Token![,]>()?;
721 let duration: Expr = input.parse()?;
722 input.parse::<Token![,]>()?;
723 let callback: Expr = input.parse()?;
724 Ok(IntervalInput { scope, duration, callback })
725 }
726}
727
728impl IntervalInput {
729 fn to_tokens(&self) -> TokenStream2 {
730 let scope = &self.scope;
731 let duration = &self.duration;
732 let callback = &self.callback;
733 let counter = HOOK_COUNTER.fetch_add(1, Ordering::SeqCst);
734 let key_type = format_ident!("__Interval_{}", counter);
735
736 quote! {
737 {
738 struct #key_type;
739 #scope.use_interval_keyed::<#key_type>(#duration, #callback)
740 }
741 }
742 }
743}
744
745#[proc_macro]
759pub fn interval(input: TokenStream) -> TokenStream {
760 let input = parse_macro_input!(input as IntervalInput);
761 let expanded = input.to_tokens();
762 TokenStream::from(expanded)
763}
764
765#[proc_macro]
802pub fn with(input: TokenStream) -> TokenStream {
803 let input = parse_macro_input!(input as WithInput);
804 let expanded = input.to_tokens();
805 TokenStream::from(expanded)
806}
807
808enum ViewNode {
810 Element(ElementNode),
812 Text(String),
814 Expr(Expr),
816}
817
818struct Prop {
820 name: Ident,
821 value: Expr,
822}
823
824struct ElementNode {
825 tag: String,
826 props: Vec<Prop>,
827 children: Vec<ViewNode>,
828}
829
830impl Parse for ViewNode {
831 fn parse(input: ParseStream) -> Result<Self> {
832 if input.peek(Token![<]) {
833 input.parse::<Token![<]>()?;
835 let tag: Ident = input.parse()?;
836
837 let mut props = Vec::new();
839 while !input.peek(Token![>]) && !input.peek(Token![/]) {
840 let name: Ident = input.parse()?;
841 input.parse::<Token![=]>()?;
842 let content;
843 braced!(content in input);
844 let value: Expr = content.parse()?;
845 props.push(Prop { name, value });
846 }
847
848 if input.peek(Token![/]) {
850 input.parse::<Token![/]>()?;
851 input.parse::<Token![>]>()?;
852 return Ok(ViewNode::Element(ElementNode {
853 tag: tag.to_string(),
854 props,
855 children: Vec::new(),
856 }));
857 }
858
859 input.parse::<Token![>]>()?;
860
861 let mut children = Vec::new();
862
863 while !(input.peek(Token![<]) && input.peek2(Token![/])) {
865 if input.is_empty() {
866 return Err(syn::Error::new(
867 tag.span(),
868 format!("Unclosed tag: <{}>", tag),
869 ));
870 }
871 children.push(input.parse()?);
872 }
873
874 input.parse::<Token![<]>()?;
876 input.parse::<Token![/]>()?;
877 let close_tag: Ident = input.parse()?;
878 input.parse::<Token![>]>()?;
879
880 if tag != close_tag {
881 return Err(syn::Error::new(
882 close_tag.span(),
883 format!(
884 "Mismatched tags: expected </{}>, found </{}>",
885 tag, close_tag
886 ),
887 ));
888 }
889
890 Ok(ViewNode::Element(ElementNode {
891 tag: tag.to_string(),
892 props,
893 children,
894 }))
895 } else if input.peek(LitStr) {
896 let lit: LitStr = input.parse()?;
898 Ok(ViewNode::Text(lit.value()))
899 } else if input.peek(syn::token::Brace) {
900 let content;
902 braced!(content in input);
903 let expr: Expr = content.parse()?;
904 Ok(ViewNode::Expr(expr))
905 } else {
906 Err(input.error("Expected <Element>, \"string literal\", or {expression}"))
907 }
908 }
909}
910
911impl ViewNode {
912 fn to_tokens(&self) -> TokenStream2 {
913 match self {
914 ViewNode::Text(s) => {
915 quote! { telex::View::text(#s) }
916 }
917 ViewNode::Expr(expr) => {
918 quote! { telex::View::text(format!("{}", #expr)) }
920 }
921 ViewNode::Element(elem) => elem.to_tokens(),
922 }
923 }
924}
925
926impl ElementNode {
927 fn to_tokens(&self) -> TokenStream2 {
928 match self.tag.as_str() {
929 "Text" => {
930 if let Some(child) = self.children.first() {
932 match child {
933 ViewNode::Text(content) => quote! { telex::View::text(#content) },
934 ViewNode::Expr(expr) => quote! { telex::View::text(format!("{}", #expr)) },
935 _ => quote! { telex::View::text("") },
936 }
937 } else {
938 quote! { telex::View::text("") }
939 }
940 }
941 "VStack" => {
942 let mut builder_calls = Vec::new();
943
944 for prop in &self.props {
946 let name = &prop.name;
947 let value = &prop.value;
948 builder_calls.push(quote! { .#name(#value) });
949 }
950
951 for child in &self.children {
953 let tokens = child.to_tokens();
954 builder_calls.push(quote! { .child(#tokens) });
955 }
956
957 quote! { telex::View::vstack()#(#builder_calls)*.build() }
958 }
959 "HStack" => {
960 let mut builder_calls = Vec::new();
961
962 for prop in &self.props {
964 let name = &prop.name;
965 let value = &prop.value;
966 builder_calls.push(quote! { .#name(#value) });
967 }
968
969 for child in &self.children {
971 let tokens = child.to_tokens();
972 builder_calls.push(quote! { .child(#tokens) });
973 }
974
975 quote! { telex::View::hstack()#(#builder_calls)*.build() }
976 }
977 "Box" => {
978 let mut builder_calls = Vec::new();
979
980 for prop in &self.props {
982 let name = &prop.name;
983 let value = &prop.value;
984 builder_calls.push(quote! { .#name(#value) });
985 }
986
987 if let Some(child) = self.children.first() {
989 let tokens = child.to_tokens();
990 builder_calls.push(quote! { .child(#tokens) });
991 }
992
993 quote! { telex::View::boxed()#(#builder_calls)*.build() }
994 }
995 "Spacer" => {
996 if let Some(prop) = self.props.iter().find(|p| p.name == "flex") {
998 let value = &prop.value;
999 quote! { telex::View::spacer_flex(#value) }
1000 } else {
1001 quote! { telex::View::spacer() }
1002 }
1003 }
1004 "Button" => {
1005 let mut builder_calls = Vec::new();
1007
1008 for prop in &self.props {
1010 let name = &prop.name;
1011 let value = &prop.value;
1012 builder_calls.push(quote! { .#name(#value) });
1013 }
1014
1015 if let Some(child) = self.children.first() {
1017 match child {
1018 ViewNode::Text(label) => {
1019 builder_calls.push(quote! { .label(#label) });
1020 }
1021 ViewNode::Expr(expr) => {
1022 builder_calls.push(quote! { .label(format!("{}", #expr)) });
1023 }
1024 _ => {}
1025 }
1026 }
1027
1028 quote! { telex::View::button()#(#builder_calls)*.build() }
1029 }
1030 "List" => {
1031 let mut builder_calls = Vec::new();
1033
1034 for prop in &self.props {
1035 let name = &prop.name;
1036 let value = &prop.value;
1037 builder_calls.push(quote! { .#name(#value) });
1038 }
1039
1040 quote! { telex::View::list()#(#builder_calls)*.build() }
1041 }
1042 "TextInput" => {
1043 let mut builder_calls = Vec::new();
1045
1046 for prop in &self.props {
1047 let name = &prop.name;
1048 let value = &prop.value;
1049 builder_calls.push(quote! { .#name(#value) });
1050 }
1051
1052 quote! { telex::View::text_input()#(#builder_calls)*.build() }
1053 }
1054 "Checkbox" => {
1055 let mut builder_calls = Vec::new();
1057
1058 for prop in &self.props {
1060 let name = &prop.name;
1061 let value = &prop.value;
1062 builder_calls.push(quote! { .#name(#value) });
1063 }
1064
1065 if let Some(child) = self.children.first() {
1067 match child {
1068 ViewNode::Text(label) => {
1069 builder_calls.push(quote! { .label(#label) });
1070 }
1071 ViewNode::Expr(expr) => {
1072 builder_calls.push(quote! { .label(format!("{}", #expr)) });
1073 }
1074 _ => {}
1075 }
1076 }
1077
1078 quote! { telex::View::checkbox()#(#builder_calls)*.build() }
1079 }
1080 "TextArea" => {
1081 let mut builder_calls = Vec::new();
1083
1084 for prop in &self.props {
1085 let name = &prop.name;
1086 let value = &prop.value;
1087 builder_calls.push(quote! { .#name(#value) });
1088 }
1089
1090 quote! { telex::View::text_area()#(#builder_calls)*.build() }
1091 }
1092 "Modal" => {
1093 let mut builder_calls = Vec::new();
1095
1096 for prop in &self.props {
1097 let name = &prop.name;
1098 let value = &prop.value;
1099 builder_calls.push(quote! { .#name(#value) });
1100 }
1101
1102 if let Some(child) = self.children.first() {
1104 let tokens = child.to_tokens();
1105 builder_calls.push(quote! { .child(#tokens) });
1106 }
1107
1108 quote! { telex::View::modal()#(#builder_calls)*.build() }
1109 }
1110 "StyledText" => {
1111 let mut content = quote! { "" };
1113 let mut bold_val = quote! { false };
1114 let mut italic_val = quote! { false };
1115 let mut underline_val = quote! { false };
1116 let mut dim_val = quote! { false };
1117 let mut color_call = quote! {};
1118 let mut bg_call = quote! {};
1119
1120 for prop in &self.props {
1122 let name_str = prop.name.to_string();
1123 let value = &prop.value;
1124
1125 match name_str.as_str() {
1126 "bold" => bold_val = quote! { #value },
1127 "italic" => italic_val = quote! { #value },
1128 "underline" => underline_val = quote! { #value },
1129 "dim" => dim_val = quote! { #value },
1130 "color" => color_call = quote! { .color(#value) },
1131 "bg" => bg_call = quote! { .bg(#value) },
1132 _ => {}
1133 }
1134 }
1135
1136 if let Some(child) = self.children.first() {
1138 match child {
1139 ViewNode::Text(text) => {
1140 content = quote! { #text };
1141 }
1142 ViewNode::Expr(expr) => {
1143 content = quote! { format!("{}", #expr) };
1144 }
1145 _ => {}
1146 }
1147 }
1148
1149 quote! {
1151 {
1152 let __builder = telex::View::styled_text(#content);
1153 let __builder = if #bold_val { __builder.bold() } else { __builder };
1154 let __builder = if #italic_val { __builder.italic() } else { __builder };
1155 let __builder = if #underline_val { __builder.underline() } else { __builder };
1156 let __builder = if #dim_val { __builder.dim() } else { __builder };
1157 __builder #color_call #bg_call .build()
1158 }
1159 }
1160 }
1161 unknown => {
1162 let known_elements = [
1164 "Text",
1165 "StyledText",
1166 "VStack",
1167 "HStack",
1168 "Box",
1169 "Spacer",
1170 "Button",
1171 "List",
1172 "TextInput",
1173 "TextArea",
1174 "Checkbox",
1175 "Modal",
1176 ];
1177
1178 let suggestion = known_elements
1180 .iter()
1181 .find(|&e| {
1182 let e_lower = e.to_lowercase();
1183 let u_lower = unknown.to_lowercase();
1184 e_lower.starts_with(&u_lower[..1.min(u_lower.len())])
1185 || u_lower.starts_with(&e_lower[..1.min(e_lower.len())])
1186 || e_lower.contains(&u_lower)
1187 || u_lower.contains(&e_lower)
1188 });
1189
1190 let msg = if let Some(suggested) = suggestion {
1191 format!(
1192 "Unknown element: <{}>. Did you mean <{}>?\n\nAvailable elements: {}",
1193 unknown,
1194 suggested,
1195 known_elements.join(", ")
1196 )
1197 } else {
1198 format!(
1199 "Unknown element: <{}>.\n\nAvailable elements: {}",
1200 unknown,
1201 known_elements.join(", ")
1202 )
1203 };
1204 quote! { compile_error!(#msg) }
1205 }
1206 }
1207 }
1208}