dioxus_rsx/
rsx_call.rs

1//! The actual rsx! macro implementation.
2//!
3//! This mostly just defers to the root TemplateBody with some additional tooling to provide better errors.
4//! Currently the additional tooling doesn't do much.
5
6use proc_macro2::TokenStream as TokenStream2;
7use quote::ToTokens;
8use std::{cell::Cell, fmt::Debug};
9use syn::{
10    parse::{Parse, ParseStream},
11    Result,
12};
13
14use crate::{BodyNode, TemplateBody};
15
16/// The Callbody is the contents of the rsx! macro
17///
18/// It is a list of BodyNodes, which are the different parts of the template.
19/// The Callbody contains no information about how the template will be rendered, only information about the parsed tokens.
20///
21/// Every callbody should be valid, so you can use it to build a template.
22/// To generate the code used to render the template, use the ToTokens impl on the Callbody, or with the `render_with_location` method.
23///
24/// Ideally we don't need the metadata here and can bake the idx-es into the templates themselves but I haven't figured out how to do that yet.
25#[derive(Debug, Clone)]
26pub struct CallBody {
27    pub body: TemplateBody,
28    pub template_idx: Cell<usize>,
29}
30
31impl Parse for CallBody {
32    fn parse(input: ParseStream) -> Result<Self> {
33        // Defer to the `new` method such that we can wire up hotreload information
34        Ok(CallBody::new(input.parse()?))
35    }
36}
37
38impl ToTokens for CallBody {
39    fn to_tokens(&self, out: &mut TokenStream2) {
40        self.body.to_tokens(out)
41    }
42}
43
44impl CallBody {
45    /// Create a new CallBody from a TemplateBody
46    ///
47    /// This will overwrite all internal metadata regarding hotreloading.
48    pub fn new(body: TemplateBody) -> Self {
49        let body = CallBody {
50            body,
51            template_idx: Cell::new(0),
52        };
53
54        body.body.template_idx.set(body.next_template_idx());
55
56        body.cascade_hotreload_info(&body.body.roots);
57
58        body
59    }
60
61    /// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro
62    ///
63    /// This should be preferred over `parse` if you are outside of a macro
64    pub fn parse_strict(input: ParseStream) -> Result<Self> {
65        // todo: actually throw warnings if there are any
66        Self::parse(input)
67    }
68
69    /// With the entire knowledge of the macro call, wire up location information for anything hotreloading
70    /// specific. It's a little bit simpler just to have a global id per callbody than to try and track it
71    /// relative to each template, though we could do that if we wanted to.
72    ///
73    /// For now this is just information for ifmts and templates so that when they generate, they can be
74    /// tracked back to the original location in the source code, to support formatted string hotreloading.
75    ///
76    /// Note that there are some more complex cases we could in theory support, but have bigger plans
77    /// to enable just pure rust hotreloading that would make those tricks moot. So, manage more of
78    /// the simple cases until that proper stuff ships.
79    ///
80    /// We need to make sure to wire up:
81    /// - subtemplate IDs
82    /// - ifmt IDs
83    /// - dynamic node IDs
84    /// - dynamic attribute IDs
85    /// - paths for dynamic nodes and attributes
86    ///
87    /// Lots of wiring!
88    ///
89    /// However, here, we only need to wire up template IDs since TemplateBody will handle the rest.
90    ///
91    /// This is better though since we can save the relevant data on the structures themselves.
92    fn cascade_hotreload_info(&self, nodes: &[BodyNode]) {
93        for node in nodes.iter() {
94            match node {
95                BodyNode::Element(el) => {
96                    self.cascade_hotreload_info(&el.children);
97                }
98
99                BodyNode::Component(comp) => {
100                    comp.children.template_idx.set(self.next_template_idx());
101                    self.cascade_hotreload_info(&comp.children.roots);
102                }
103
104                BodyNode::ForLoop(floop) => {
105                    floop.body.template_idx.set(self.next_template_idx());
106                    self.cascade_hotreload_info(&floop.body.roots);
107                }
108
109                BodyNode::IfChain(chain) => chain.for_each_branch(&mut |body| {
110                    body.template_idx.set(self.next_template_idx());
111                    self.cascade_hotreload_info(&body.roots)
112                }),
113
114                _ => {}
115            }
116        }
117    }
118
119    fn next_template_idx(&self) -> usize {
120        let idx = self.template_idx.get();
121        self.template_idx.set(idx + 1);
122        idx
123    }
124}