1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4 parse_macro_input, Data, DeriveInput, Fields, ImplItem, ImplItemFn, ItemImpl, Meta, Type,
5};
6
7fn parse_reactive_attr(meta: &Meta) -> Option<(ReactiveKind, bool)> {
9 let nested = match meta {
10 Meta::List(list) => &list.tokens,
11 _ => return None,
12 };
13
14 let parts: Vec<String> = nested
15 .to_string()
16 .split(',')
17 .map(|s| s.trim().to_string())
18 .collect();
19
20 let kind = match parts.first()?.as_str() {
21 "paint" => ReactiveKind::Paint,
22 "layout" => ReactiveKind::Layout,
23 "tree" => ReactiveKind::Tree,
24 _ => return None,
25 };
26
27 let copy = parts.iter().skip(1).any(|p| p == "copy");
28
29 Some((kind, copy))
30}
31
32enum ReactiveKind {
33 Paint,
34 Layout,
35 Tree,
36}
37
38impl ReactiveKind {
39 fn invalidate_call(&self) -> proc_macro2::TokenStream {
40 match self {
41 ReactiveKind::Paint => quote! { cx.invalidate_paint(); },
42 ReactiveKind::Layout => quote! { cx.invalidate_layout(); },
43 ReactiveKind::Tree => quote! { cx.invalidate_tree(); },
44 }
45 }
46}
47
48struct ReactiveField {
49 name: proc_macro2::Ident,
50 ty: Type,
51 kind: ReactiveKind,
52 copy: bool,
53}
54
55#[proc_macro_derive(Component, attributes(reactive))]
56pub fn derive_component(input: TokenStream) -> TokenStream {
57 let input = parse_macro_input!(input as DeriveInput);
58
59 let struct_name = &input.ident;
60
61 let fields = match &input.data {
62 Data::Struct(data) => match &data.fields {
63 Fields::Named(fields) => &fields.named,
64 _ => {
65 return syn::Error::new_spanned(&data.fields, "only named fields are supported")
66 .to_compile_error()
67 .into();
68 }
69 },
70 _ => {
71 return syn::Error::new_spanned(&input, "Component can only be derived for structs")
72 .to_compile_error()
73 .into();
74 }
75 };
76
77 let mut reactive_fields: Vec<ReactiveField> = Vec::new();
78
79 for field in fields.iter() {
80 for attr in &field.attrs {
81 if attr.path().is_ident("reactive") {
82 if let Some((kind, copy)) = parse_reactive_attr(&attr.meta) {
83 let name = field.ident.clone().unwrap();
84 let ty = field.ty.clone();
85 reactive_fields.push(ReactiveField { name, ty, kind, copy });
86 }
87 }
88 }
89 }
90
91 let mut reactive_impls = Vec::new();
92
93 for rf in &reactive_fields {
94 let name = &rf.name;
95 let ty = &rf.ty;
96 let getter = format_ident!("{}", name);
97 let setter = format_ident!("set_{}", name);
98 let updater = format_ident!("update_{}", name);
99 let invalidate = rf.kind.invalidate_call();
100
101 let copy_getter = if rf.copy {
102 let copy_name = format_ident!("get_{}", name);
103 quote! {
104 pub fn #copy_name(&self) -> #ty {
105 self.#name
106 }
107 }
108 } else {
109 quote! {}
110 };
111
112 let generated = quote! {
113 #[allow(dead_code)]
114 impl #struct_name {
115 pub fn #getter(&self) -> &#ty {
116 &self.#name
117 }
118
119 #copy_getter
120
121 pub fn #setter(&mut self, value: #ty, cx: &mut lv_tui::component::EventCx) {
122 if self.#name != value {
123 self.#name = value;
124 #invalidate
125 }
126 }
127
128 pub fn #updater(&mut self, cx: &mut lv_tui::component::EventCx, f: impl FnOnce(&mut #ty)) {
129 let old = self.#name.clone();
130 f(&mut self.#name);
131 if self.#name != old {
132 #invalidate
133 }
134 }
135 }
136 };
137
138 reactive_impls.push(generated);
139 }
140
141 let output = quote! {
142 #(#reactive_impls)*
143 };
144
145 output.into()
146}
147
148enum HandlerKind {
151 Focus,
152 Blur,
153 Tick,
154 KeyChar(char),
155 KeyNamed(String),
156}
157
158fn resolve_key_name(name: &str) -> Option<String> {
159 match name {
160 "tab" => Some("Tab".into()),
161 "enter" => Some("Enter".into()),
162 "esc" => Some("Esc".into()),
163 "backspace" => Some("Backspace".into()),
164 "delete" => Some("Delete".into()),
165 "up" => Some("Up".into()),
166 "down" => Some("Down".into()),
167 "left" => Some("Left".into()),
168 "right" => Some("Right".into()),
169 "home" => Some("Home".into()),
170 "end" => Some("End".into()),
171 "pageup" => Some("PageUp".into()),
172 "pagedown" => Some("PageDown".into()),
173 "space" => Some("Char(' ')".into()),
174 "plus" => Some("Char('+')".into()),
175 "minus" => Some("Char('-')".into()),
176 "equals" => Some("Char('=')".into()),
177 "slash" => Some("Char('/')".into()),
178 "backslash" => Some("Char('\\\\')".into()),
179 "star" => Some("Char('*')".into()),
180 "dot" => Some("Char('.')".into()),
181 "comma" => Some("Char(',')".into()),
182 "semicolon" => Some("Char(';')".into()),
183 "colon" => Some("Char(':')".into()),
184 "bang" => Some("Char('!')".into()),
185 "question" => Some("Char('?')".into()),
186 "hash" => Some("Char('#')".into()),
187 "at" => Some("Char('@')".into()),
188 "dollar" => Some("Char('$')".into()),
189 "percent" => Some("Char('%')".into()),
190 "caret" => Some("Char('^')".into()),
191 "ampersand" => Some("Char('&')".into()),
192 "pipe" => Some("Char('|')".into()),
193 "tilde" => Some("Char('~')".into()),
194 "underscore" => Some("Char('_')".into()),
195 _ => None,
196 }
197}
198
199fn parse_handler_name(method_name: &str) -> Option<HandlerKind> {
200 if method_name == "on_focus" {
201 return Some(HandlerKind::Focus);
202 }
203 if method_name == "on_blur" {
204 return Some(HandlerKind::Blur);
205 }
206 if method_name == "on_tick" {
207 return Some(HandlerKind::Tick);
208 }
209 if let Some(rest) = method_name.strip_prefix("on_key_") {
210 if rest.is_empty() {
211 return None;
212 }
213 if rest.chars().count() == 1 {
214 let c = rest.chars().next().unwrap();
215 return Some(HandlerKind::KeyChar(c));
216 }
217 if let Some(key_name) = resolve_key_name(rest) {
218 return Some(HandlerKind::KeyNamed(key_name));
219 }
220 }
221 None
222}
223
224#[proc_macro_attribute]
244pub fn event_handlers(_attr: TokenStream, item: TokenStream) -> TokenStream {
245 let impl_block = parse_macro_input!(item as ItemImpl);
246
247 let struct_ty = match &impl_block.self_ty.as_ref() {
248 syn::Type::Path(type_path) => type_path.clone(),
249 _ => {
250 return syn::Error::new_spanned(
251 &impl_block.self_ty,
252 "#[event_handlers] requires `impl TypeName`",
253 )
254 .to_compile_error()
255 .into();
256 }
257 };
258
259 if impl_block.trait_.is_some() {
261 return syn::Error::new_spanned(
262 &impl_block,
263 "#[event_handlers] must be used on an inherent impl block (`impl Foo`), \
264 not a trait impl (`impl Component for Foo`)",
265 )
266 .to_compile_error()
267 .into();
268 }
269
270 const TRAIT_METHODS: &[&str] = &[
272 "render", "event", "style", "measure", "layout", "mount", "unmount",
273 "update", "type_name", "id", "class", "focusable",
274 "for_each_child", "for_each_child_mut",
275 ];
276
277 let mut trait_methods: Vec<&ImplItemFn> = Vec::new();
279 let mut handler_methods: Vec<&ImplItemFn> = Vec::new();
280 let mut inherent_only: Vec<&ImplItem> = Vec::new();
281
282 for item in &impl_block.items {
283 if let ImplItem::Fn(method) = item {
284 let name_str = method.sig.ident.to_string();
285 if name_str == "event" {
286 return syn::Error::new_spanned(
288 method,
289 "#[event_handlers] generates event() automatically; \
290 remove the manual `fn event` and use `on_*` handlers instead",
291 )
292 .to_compile_error()
293 .into();
294 }
295 if parse_handler_name(&name_str).is_some() {
296 handler_methods.push(method);
297 } else if TRAIT_METHODS.contains(&name_str.as_str()) {
298 trait_methods.push(method);
299 } else {
300 inherent_only.push(item);
301 }
302 } else {
303 inherent_only.push(item);
304 }
305 }
306
307 if handler_methods.is_empty() {
308 return syn::Error::new_spanned(
309 &impl_block,
310 "#[event_handlers] requires at least one `on_*` handler method \
311 (e.g. `fn on_focus(&mut self, cx: &mut EventCx)`)",
312 )
313 .to_compile_error()
314 .into();
315 }
316
317 let mut focus_calls: Vec<proc_macro2::Ident> = Vec::new();
319 let mut blur_calls: Vec<proc_macro2::Ident> = Vec::new();
320 let mut tick_calls: Vec<proc_macro2::Ident> = Vec::new();
321 let mut key_char_arms: Vec<(char, proc_macro2::Ident)> = Vec::new();
322 let mut key_named_arms: Vec<(String, proc_macro2::Ident)> = Vec::new();
323
324 for method in &handler_methods {
325 let name_str = method.sig.ident.to_string();
326 if let Some(kind) = parse_handler_name(&name_str) {
327 match kind {
328 HandlerKind::Focus => focus_calls.push(method.sig.ident.clone()),
329 HandlerKind::Blur => blur_calls.push(method.sig.ident.clone()),
330 HandlerKind::Tick => tick_calls.push(method.sig.ident.clone()),
331 HandlerKind::KeyChar(c) => key_char_arms.push((c, method.sig.ident.clone())),
332 HandlerKind::KeyNamed(name) => key_named_arms.push((name, method.sig.ident.clone())),
333 }
334 }
335 }
336
337 let mut arms: Vec<proc_macro2::TokenStream> = Vec::new();
338
339 if !focus_calls.is_empty() {
340 let calls: Vec<_> = focus_calls.iter().map(|name| quote! { self.#name(cx); }).collect();
341 arms.push(quote! { lv_tui::event::Event::Focus => { #(#calls)* } });
342 }
343
344 if !blur_calls.is_empty() {
345 let calls: Vec<_> = blur_calls.iter().map(|name| quote! { self.#name(cx); }).collect();
346 arms.push(quote! { lv_tui::event::Event::Blur => { #(#calls)* } });
347 }
348
349 if !tick_calls.is_empty() {
350 let calls: Vec<_> = tick_calls.iter().map(|name| quote! { self.#name(cx); }).collect();
351 arms.push(quote! { lv_tui::event::Event::Tick => { #(#calls)* } });
352 }
353
354 if !key_char_arms.is_empty() || !key_named_arms.is_empty() {
355 let mut key_arms: Vec<proc_macro2::TokenStream> = Vec::new();
356
357 for (c, method_name) in &key_char_arms {
358 key_arms.push(quote! {
359 lv_tui::event::Key::Char(#c) => { self.#method_name(cx); }
360 });
361 }
362
363 for (name, method_name) in &key_named_arms {
364 let key_pat: proc_macro2::TokenStream =
365 syn::parse_str(&format!("lv_tui::event::Key::{}", name)).unwrap();
366 key_arms.push(quote! {
367 #key_pat => { self.#method_name(cx); }
368 });
369 }
370
371 arms.push(quote! {
372 lv_tui::event::Event::Key(key_event) => {
373 match &key_event.key {
374 #(#key_arms)*
375 _ => {}
376 }
377 }
378 });
379 }
380
381 arms.push(quote! { _ => {} });
382
383 let inherent_cloned: Vec<ImplItem> = inherent_only.iter().map(|&item| item.clone()).collect();
385 let inherent_impl = quote! {
386 impl #struct_ty {
387 #(#inherent_cloned)*
388 #(#handler_methods)*
389 }
390 };
391
392 let generated_event: ImplItemFn = syn::parse_quote! {
394 fn event(&mut self, event: &lv_tui::event::Event, cx: &mut lv_tui::component::EventCx) {
395 match event {
396 #(#arms)*
397 }
398 }
399 };
400
401 let mut component_items: Vec<ImplItem> = trait_methods.iter().map(|&m| ImplItem::Fn(m.clone())).collect();
402 component_items.push(ImplItem::Fn(generated_event));
403
404 let component_impl = quote! {
405 impl lv_tui::component::Component for #struct_ty {
406 #(#component_items)*
407 }
408 };
409
410 let output = quote! {
411 #inherent_impl
412 #component_impl
413 };
414
415 output.into()
416}