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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use crate::attribute::Attribute;
use crate::{
AttributeValue, BodyNode, HotLiteral, HotReloadFormattedSegment, Segment, TemplateBody,
};
/// A visitor that assigns dynamic ids to nodes and attributes and accumulates paths to dynamic nodes and attributes
struct DynIdVisitor<'a> {
body: &'a mut TemplateBody,
current_path: Vec<u8>,
dynamic_text_index: usize,
component_literal_index: usize,
}
impl<'a> DynIdVisitor<'a> {
fn new(body: &'a mut TemplateBody) -> Self {
Self {
body,
current_path: Vec::new(),
dynamic_text_index: 0,
component_literal_index: 0,
}
}
fn visit_children(&mut self, children: &[BodyNode]) {
for (idx, node) in children.iter().enumerate() {
self.current_path.push(idx as u8);
self.visit(node);
self.current_path.pop();
}
}
fn visit(&mut self, node: &BodyNode) {
match node {
// Just descend into elements - they're not dynamic
BodyNode::Element(el) => {
for (idx, attr) in el.merged_attributes.iter().enumerate() {
if !attr.is_static_str_literal() {
self.assign_path_to_attribute(attr, idx);
if let AttributeValue::AttrLiteral(HotLiteral::Fmted(lit)) = &attr.value {
self.assign_formatted_segment(lit);
}
}
}
// Assign formatted segments to the key which is not included in the merged_attributes
if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(fmted))) = el.key() {
self.assign_formatted_segment(fmted);
}
self.visit_children(&el.children);
}
// Text nodes are dynamic if they contain dynamic segments
BodyNode::Text(txt) => {
if !txt.is_static() {
self.assign_path_to_node(node);
self.assign_formatted_segment(&txt.input);
}
}
// Raw exprs are always dynamic
BodyNode::RawExpr(_) | BodyNode::ForLoop(_) | BodyNode::IfChain(_) => {
self.assign_path_to_node(node)
}
BodyNode::Component(component) => {
self.assign_path_to_node(node);
let mut index = 0;
for property in &component.fields {
if let AttributeValue::AttrLiteral(literal) = &property.value {
if let HotLiteral::Fmted(segments) = literal {
self.assign_formatted_segment(segments);
}
// Don't include keys in the component dynamic pool
if !property.name.is_likely_key() {
component.component_literal_dyn_idx[index]
.set(self.component_literal_index);
self.component_literal_index += 1;
index += 1;
}
}
}
}
};
}
/// Assign ids to a formatted segment
fn assign_formatted_segment(&mut self, segments: &HotReloadFormattedSegment) {
let mut dynamic_node_indexes = segments.dynamic_node_indexes.iter();
for segment in &segments.segments {
if let Segment::Formatted(segment) = segment {
dynamic_node_indexes
.next()
.unwrap()
.set(self.dynamic_text_index);
self.dynamic_text_index += 1;
self.body.dynamic_text_segments.push(segment.clone());
}
}
}
/// Assign a path to a node and give it its dynamic index
/// This simplifies the ToTokens implementation for the macro to be a little less centralized
fn assign_path_to_node(&mut self, node: &BodyNode) {
// Assign the TemplateNode::Dynamic index to the node
node.set_dyn_idx(self.body.node_paths.len());
// And then save the current path as the corresponding path
self.body.node_paths.push(self.current_path.clone());
}
/// Assign a path to a attribute and give it its dynamic index
/// This simplifies the ToTokens implementation for the macro to be a little less centralized
pub(crate) fn assign_path_to_attribute(
&mut self,
attribute: &Attribute,
attribute_index: usize,
) {
// Assign the dynamic index to the attribute
attribute.set_dyn_idx(self.body.attr_paths.len());
// And then save the current path as the corresponding path
self.body
.attr_paths
.push((self.current_path.clone(), attribute_index));
}
}
impl TemplateBody {
/// Cascade down path information into the children of this template
///
/// This provides the necessary path and index information for the children of this template
/// so that they can render out their dynamic nodes correctly. Also does plumbing for things like
/// hotreloaded literals which need to be tracked on a per-template basis.
///
/// This can only operate with knowledge of this template, not the surrounding callbody. Things like
/// wiring of ifmt literals need to be done at the callbody level since those final IDs need to
/// be unique to the entire app.
pub(crate) fn assign_paths_inner(&mut self, nodes: &[BodyNode]) {
let mut visitor = DynIdVisitor::new(self);
visitor.visit_children(nodes);
}
}