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