1use indextree::{Arena, NodeId};
4
5use super::{Attr, ChangedGroup, FormatArena, FormattedValue};
6
7#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
9pub enum ElementChange {
10 #[default]
12 None,
13 Deleted,
15 Inserted,
17 MovedFrom,
19 MovedTo,
21}
22
23impl ElementChange {
24 pub fn prefix(self) -> Option<char> {
26 match self {
27 Self::None => None,
28 Self::Deleted => Some('-'),
29 Self::Inserted => Some('+'),
30 Self::MovedFrom => Some('←'),
31 Self::MovedTo => Some('→'),
32 }
33 }
34
35 pub fn has_prefix(self) -> bool {
37 !matches!(self, Self::None)
38 }
39}
40
41#[derive(Clone, Debug)]
43pub enum LayoutNode {
44 Element {
46 tag: &'static str,
48 field_name: Option<&'static str>,
50 attrs: Vec<Attr>,
52 changed_groups: Vec<ChangedGroup>,
54 change: ElementChange,
56 },
57
58 Sequence {
60 change: ElementChange,
62 item_type: &'static str,
64 field_name: Option<&'static str>,
66 },
67
68 Collapsed {
70 count: usize,
72 },
73
74 Text {
76 value: FormattedValue,
78 change: ElementChange,
80 },
81
82 ItemGroup {
85 items: Vec<FormattedValue>,
87 change: ElementChange,
89 collapsed_suffix: Option<usize>,
92 item_type: &'static str,
94 },
95}
96
97impl LayoutNode {
98 pub fn element(tag: &'static str) -> Self {
100 Self::Element {
101 tag,
102 field_name: None,
103 attrs: Vec::new(),
104 changed_groups: Vec::new(),
105 change: ElementChange::None,
106 }
107 }
108
109 pub fn element_with_change(tag: &'static str, change: ElementChange) -> Self {
111 Self::Element {
112 tag,
113 field_name: None,
114 attrs: Vec::new(),
115 changed_groups: Vec::new(),
116 change,
117 }
118 }
119
120 pub fn sequence(change: ElementChange, item_type: &'static str) -> Self {
122 Self::Sequence {
123 change,
124 item_type,
125 field_name: None,
126 }
127 }
128
129 pub fn collapsed(count: usize) -> Self {
131 Self::Collapsed { count }
132 }
133
134 pub fn text(value: FormattedValue, change: ElementChange) -> Self {
136 Self::Text { value, change }
137 }
138
139 pub fn item_group(
141 items: Vec<FormattedValue>,
142 change: ElementChange,
143 collapsed_suffix: Option<usize>,
144 item_type: &'static str,
145 ) -> Self {
146 Self::ItemGroup {
147 items,
148 change,
149 collapsed_suffix,
150 item_type,
151 }
152 }
153
154 pub fn change(&self) -> ElementChange {
156 match self {
157 Self::Element { change, .. } => *change,
158 Self::Sequence { change, .. } => *change,
159 Self::Collapsed { .. } => ElementChange::None,
160 Self::Text { change, .. } => *change,
161 Self::ItemGroup { change, .. } => *change,
162 }
163 }
164
165 pub fn has_changes(&self) -> bool {
167 match self {
168 Self::Element {
169 attrs,
170 change,
171 changed_groups,
172 ..
173 } => {
174 change.has_prefix()
175 || !changed_groups.is_empty()
176 || attrs.iter().any(|a| {
177 matches!(
178 a.status,
179 super::AttrStatus::Changed { .. }
180 | super::AttrStatus::Deleted { .. }
181 | super::AttrStatus::Inserted { .. }
182 )
183 })
184 }
185 Self::Sequence { change, .. } => change.has_prefix(),
186 Self::Collapsed { .. } => false,
187 Self::Text { change, .. } => change.has_prefix(),
188 Self::ItemGroup { change, .. } => change.has_prefix(),
189 }
190 }
191}
192
193pub struct Layout {
195 pub strings: FormatArena,
197 pub tree: Arena<LayoutNode>,
199 pub root: NodeId,
201}
202
203impl Layout {
204 pub fn new(strings: FormatArena, mut tree: Arena<LayoutNode>, root_node: LayoutNode) -> Self {
206 let root = tree.new_node(root_node);
207 Self {
208 strings,
209 tree,
210 root,
211 }
212 }
213
214 pub fn get(&self, id: NodeId) -> Option<&LayoutNode> {
216 self.tree.get(id).map(|n| n.get())
217 }
218
219 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut LayoutNode> {
221 self.tree.get_mut(id).map(|n| n.get_mut())
222 }
223
224 pub fn add_child(&mut self, parent: NodeId, child_node: LayoutNode) -> NodeId {
226 let child = self.tree.new_node(child_node);
227 parent.append(child, &mut self.tree);
228 child
229 }
230
231 pub fn children(&self, parent: NodeId) -> impl Iterator<Item = NodeId> + '_ {
233 parent.children(&self.tree)
234 }
235
236 pub fn get_string(&self, span: super::Span) -> &str {
238 self.strings.get(span)
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_element_change_prefix() {
248 assert_eq!(ElementChange::None.prefix(), None);
249 assert_eq!(ElementChange::Deleted.prefix(), Some('-'));
250 assert_eq!(ElementChange::Inserted.prefix(), Some('+'));
251 assert_eq!(ElementChange::MovedFrom.prefix(), Some('←'));
252 assert_eq!(ElementChange::MovedTo.prefix(), Some('→'));
253 }
254
255 #[test]
256 fn test_layout_tree() {
257 let arena = FormatArena::new();
258 let tree = Arena::new();
259
260 let mut layout = Layout::new(arena, tree, LayoutNode::element("root"));
261
262 let child1 = layout.add_child(layout.root, LayoutNode::element("child1"));
263 let child2 = layout.add_child(layout.root, LayoutNode::element("child2"));
264
265 let children: Vec<_> = layout.children(layout.root).collect();
266 assert_eq!(children.len(), 2);
267 assert_eq!(children[0], child1);
268 assert_eq!(children[1], child2);
269 }
270
271 #[test]
272 fn test_collapsed_node() {
273 let node = LayoutNode::collapsed(5);
274 assert!(!node.has_changes());
275
276 if let LayoutNode::Collapsed { count } = node {
277 assert_eq!(count, 5);
278 } else {
279 panic!("expected Collapsed node");
280 }
281 }
282}