diff --git a/examples/hot_reload.rs b/examples/hot_reload.rs index d98c42323..1fda9a977 100644 --- a/examples/hot_reload.rs +++ b/examples/hot_reload.rs @@ -1,15 +1,24 @@ use dioxus::prelude::*; +use std::time::Duration; fn main() { dioxus::desktop::launch_with_props(with_hot_reload, app, |b| b); } fn app(cx: Scope) -> Element { - let mut count = use_state(&cx, || 0); + let count = use_state(&cx, || 0); + + use_future(&cx, (), move |_| { + let mut count = count.clone(); + async move { + loop { + tokio::time::sleep(Duration::from_millis(10)).await; + count += 1; + } + } + }); cx.render(rsx! { h1 { "High-Five counter: {count}" } - button { onclick: move |_| count += 1, "Up high!" } - button { onclick: move |_| count -= 1, "Down low!" } }) } diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 6f1ac3c18..ea187ce7d 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -180,6 +180,8 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token #[proc_macro_error::proc_macro_error] #[proc_macro] pub fn rsx(s: TokenStream) -> TokenStream { + #[cfg(feature = "hot_reload")] + let rsx_text = s.to_string(); match syn::parse::(s) { Err(err) => err.to_compile_error().into(), Ok(body) => { @@ -191,10 +193,16 @@ pub fn rsx(s: TokenStream) -> TokenStream { quote! { { let line_num = get_line_num(); + let rsx_text_index: RsxTextIndex = cx.consume_context().unwrap(); + use_state(&cx, || { + rsx_text_index.insert( + line_num.clone(), + #rsx_text.to_string(), + ); + }); LazyNodes::new(move |factory|{ - let rsx_text_index: RsxTextIndex = cx.consume_context().unwrap(); let read = rsx_text_index.read(); - if let Some(text) = read.get(line_num){ + if let Some(text) = read.get(&line_num){ interpert_rsx( factory, &text, @@ -202,7 +210,7 @@ pub fn rsx(s: TokenStream) -> TokenStream { ) } else{ - println!("rsx: line number {} not found", line_num); + println!("rsx: line number {:?} not found", line_num); factory.static_text("") } }) diff --git a/packages/rsx_interperter/src/captuered_context.rs b/packages/rsx_interperter/src/captuered_context.rs index cefabf6dd..9564186d1 100644 --- a/packages/rsx_interperter/src/captuered_context.rs +++ b/packages/rsx_interperter/src/captuered_context.rs @@ -38,13 +38,13 @@ impl CapturedContextBuilder { (name.to_string(), value.to_token_stream()) } ElementAttr::AttrExpression { name, value } => { - (name.to_string(), value.to_token_stream()) + todo!() } ElementAttr::CustomAttrText { name, value } => { (name.value(), value.to_token_stream()) } ElementAttr::CustomAttrExpression { name, value } => { - (name.value(), value.to_token_stream()) + todo!() } _ => continue, }; @@ -56,7 +56,6 @@ impl CapturedContextBuilder { } } BodyNode::Component(comp) => { - let fn_name = comp.name.segments.last().unwrap().ident.to_string(); captured.components.push(comp); } BodyNode::Text(t) => { @@ -105,8 +104,10 @@ struct CapturedComponentBuilder { } pub struct CapturedContext<'a> { - // map of the attribute name to the formated value + // map of the variable name to the formated value pub captured: IfmtArgs, + // // map of the attribute name and element path to the formated value + // pub captured_attribute_values: IfmtArgs, // the only thing we can update in component is the children pub components: Vec>, // we can't reasonably interpert iterators, so they are staticly inserted @@ -117,22 +118,3 @@ pub struct IfmtArgs { // live reload only supports named arguments pub named_args: Vec<(&'static str, String)>, } - -enum IfmtSegment<'a> { - Static(&'a str), - Dynamic(&'a str), -} - -enum RsxNode<'a> { - Element { - name: String, - attributes: Vec<(String, IfmtSegment<'a>)>, - children: Vec>, - }, - Text { - text: Vec>, - }, - Component { - children: Vec>, - }, -} diff --git a/packages/rsx_interperter/src/interperter.rs b/packages/rsx_interperter/src/interperter.rs index ce7dac013..28abffef9 100644 --- a/packages/rsx_interperter/src/interperter.rs +++ b/packages/rsx_interperter/src/interperter.rs @@ -1,44 +1,128 @@ use dioxus_core::{Attribute, NodeFactory, VNode}; use dioxus_rsx::{BodyNode, CallBody, ElementAttr, IfmtInput}; use quote::ToTokens; +use std::str::FromStr; use syn::parse2; use crate::attributes::attrbute_to_static_str; +use crate::captuered_context::{CapturedContext, IfmtArgs}; use crate::elements::element_to_static_str; -pub fn build<'a>(rsx: CallBody, factory: &NodeFactory<'a>) -> VNode<'a> { +#[derive(Debug)] +enum Segment { + Ident(String), + Literal(String), +} + +struct InterperedIfmt { + segments: Vec, +} + +impl InterperedIfmt { + fn resolve(&self, captured: &IfmtArgs) -> String { + let mut result = String::new(); + for seg in &self.segments { + match seg { + Segment::Ident(name) => { + let (_, value) = captured + .named_args + .iter() + .find(|(n, _)| *n == name) + .expect(format!("could not resolve {}", name).as_str()); + result.push_str(value); + } + Segment::Literal(lit) => result.push_str(lit), + } + } + result + } +} + +impl FromStr for InterperedIfmt { + type Err = (); + fn from_str(input: &str) -> Result { + let mut segments = Vec::new(); + let mut segment = String::new(); + let mut chars = input.chars().peekable(); + while let Some(c) = chars.next() { + if c == '{' { + if chars.peek().copied() != Some('{') { + let old; + (old, segment) = (segment, String::new()); + if !old.is_empty() { + segments.push(Segment::Literal(old)); + } + while let Some(c) = chars.next() { + if c == '}' { + let old; + (old, segment) = (segment, String::new()); + if !old.is_empty() { + segments.push(Segment::Ident(old)); + } + break; + } + if c == ':' { + while Some('}') != chars.next() {} + let old; + (old, segment) = (segment, String::new()); + if !old.is_empty() { + segments.push(Segment::Ident(old)); + } + break; + } + segment.push(c); + } + } + } else { + segment.push(c); + } + } + if !segment.is_empty() { + segments.push(Segment::Literal(segment)); + } + Ok(Self { segments }) + } +} + +pub fn build<'a>(rsx: CallBody, ctx: CapturedContext, factory: &NodeFactory<'a>) -> VNode<'a> { let children_built = factory.bump().alloc(Vec::new()); for (i, child) in rsx.roots.into_iter().enumerate() { - children_built.push(build_node(child, factory, i.to_string().as_str())); + children_built.push(build_node(child, &ctx, factory, i.to_string().as_str())); } factory.fragment_from_iter(children_built.iter()) } -fn build_node<'a>(node: BodyNode, factory: &NodeFactory<'a>, key: &str) -> Option> { +fn build_node<'a>( + node: BodyNode, + ctx: &CapturedContext, + factory: &NodeFactory<'a>, + key: &str, +) -> Option> { let bump = factory.bump(); match node { BodyNode::Text(text) => { - let ifmt_input: IfmtInput = parse2(text.into_token_stream()).unwrap(); - Some(factory.text(format_args!("{}", ifmt_input.format_literal.value()))) + let ifmt: InterperedIfmt = text.value().parse().unwrap(); + let text = bump.alloc(ifmt.resolve(&ctx.captured)); + Some(factory.text(format_args!("{}", text))) } BodyNode::Element(el) => { let attributes: &mut Vec = bump.alloc(Vec::new()); for attr in el.attributes { - let result: Option<(String, IfmtInput)> = match attr.attr { + let result: Option<(String, InterperedIfmt)> = match attr.attr { ElementAttr::AttrText { name, value } => { - Some((name.to_string(), parse2(value.into_token_stream()).unwrap())) + Some((name.to_string(), value.value().parse().unwrap())) } ElementAttr::AttrExpression { name, value } => { - Some((name.to_string(), parse2(value.into_token_stream()).unwrap())) + todo!() } ElementAttr::CustomAttrText { name, value } => { - Some((name.value(), parse2(value.into_token_stream()).unwrap())) + Some((name.value(), value.value().parse().unwrap())) } ElementAttr::CustomAttrExpression { name, value } => { - Some((name.value(), parse2(value.into_token_stream()).unwrap())) + todo!() } ElementAttr::EventTokens { .. } => None, @@ -47,7 +131,7 @@ fn build_node<'a>(node: BodyNode, factory: &NodeFactory<'a>, key: &str) -> Optio }; if let Some((name, value)) = result { if let Some((name, namespace)) = attrbute_to_static_str(&name) { - let value = bump.alloc(value.format_literal.value()); + let value = bump.alloc(value.resolve(&ctx.captured)); attributes.push(Attribute { name, value, @@ -62,7 +146,7 @@ fn build_node<'a>(node: BodyNode, factory: &NodeFactory<'a>, key: &str) -> Optio } let children = bump.alloc(Vec::new()); for (i, child) in el.children.into_iter().enumerate() { - let node = build_node(child, factory, i.to_string().as_str()); + let node = build_node(child, ctx, factory, i.to_string().as_str()); if let Some(node) = node { children.push(node); } diff --git a/packages/rsx_interperter/src/lib.rs b/packages/rsx_interperter/src/lib.rs index 673061332..aae307bef 100644 --- a/packages/rsx_interperter/src/lib.rs +++ b/packages/rsx_interperter/src/lib.rs @@ -1,5 +1,6 @@ use dioxus_core::{Component, Element, LazyNodes, Scope, VNode}; use dioxus_hooks::*; +use interperter::build; use std::collections::HashMap; use std::panic::Location; use std::rc::Rc; @@ -11,10 +12,18 @@ pub mod captuered_context; mod elements; mod interperter; +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct CodeLocation { + pub file: String, + pub line: u32, + pub column: u32, +} + pub fn with_hot_reload(cx: Scope) -> Element { use_state(&cx, || { if cx.consume_context::().is_none() { - cx.provide_context(RsxTextIndex::default()); + let index = RsxTextIndex::default(); + cx.provide_context(index); } }); cx.render(LazyNodes::new(|node_factory| { @@ -27,25 +36,30 @@ pub fn interpert_rsx<'a, 'b>( text: &str, context: captuered_context::CapturedContext, ) -> VNode<'a> { - panic!() + build(parse_str(text).unwrap(), context, &factory) } #[track_caller] -pub fn get_line_num() -> &'static Location<'static> { - Location::caller() +pub fn get_line_num() -> CodeLocation { + let location = Location::caller(); + CodeLocation { + file: location.file().to_string(), + line: location.line(), + column: location.column(), + } } #[derive(Debug, Default, Clone)] pub struct RsxTextIndex { - hm: Rc, String>>>, + hm: Rc>>, } impl RsxTextIndex { - pub fn insert(&self, loc: &'static Location<'static>, text: String) { + pub fn insert(&self, loc: CodeLocation, text: String) { self.hm.write().unwrap().insert(loc, text); } - pub fn read(&self) -> RwLockReadGuard, String>> { + pub fn read(&self) -> RwLockReadGuard> { self.hm.read().unwrap() } }