Skip to main content

lv_tui_macros/
lib.rs

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
7/// 解析 `#[reactive(paint)]` 或 `#[reactive(paint, copy)]`
8fn 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
148// ── event_handlers macro ──────────────────────────────────────────
149
150enum 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/// Generates `impl Component for Foo` from an inherent impl block with
225/// `on_*` handler methods.
226///
227/// Place this attribute on an `impl Foo` block (NOT `impl Component for Foo`).
228/// Methods named `on_focus`, `on_blur`, `on_tick`, `on_key_*` are detected
229/// and used to generate `fn event()`. The `fn render()` method is used as
230/// the Component's render implementation.
231///
232/// ```rust,ignore
233/// #[lv_tui::event_handlers]
234/// impl MyWidget {
235///     fn render(&self, cx: &mut RenderCx) { ... }
236///
237///     fn on_focus(&mut self, cx: &mut EventCx) { ... }
238///     fn on_blur(&mut self, cx: &mut EventCx) { ... }
239///     fn on_key_q(&mut self, cx: &mut EventCx) { cx.quit(); }
240///     fn on_key_tab(&mut self, cx: &mut EventCx) { ... }
241/// }
242/// ```
243#[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    // Reject `impl Component for Foo` — must be `impl Foo`
260    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    // Component trait method names that can be overridden.
271    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    // Categorize methods
278    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                // Don't allow manual event() in #[event_handlers] block
287                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    // Build event dispatch arms
318    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    // Inherent impl: keeps handler methods + non-trait methods
384    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    // Component trait impl: trait overrides + generated event()
393    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}