1use std::borrow::Cow;
15
16use facet_core::{Def, NumericType, PrimitiveType, Shape, StructKind, TextualType, Type, UserType};
17use facet_reflect::Peek;
18use indextree::{Arena, NodeId};
19
20use super::{
21 Attr, DiffFlavor, ElementChange, FormatArena, FormattedValue, Layout, LayoutNode, ValueType,
22 group_changed_attrs,
23};
24use crate::{Diff, ReplaceGroup, Updates, UpdatesGroup, Value};
25
26fn get_shape_display_name(shape: &Shape) -> &'static str {
28 if let Some(renamed) = shape.get_builtin_attr_value::<&str>("rename") {
29 return renamed;
30 }
31 shape.type_identifier
32}
33
34fn determine_value_type(peek: Peek<'_, '_>) -> ValueType {
36 let shape = peek.shape();
37
38 if let Def::Option(_) = shape.def {
40 if let Ok(opt) = peek.into_option() {
42 if opt.is_none() {
43 return ValueType::Null;
44 }
45 if let Some(inner) = opt.value() {
47 return determine_value_type(inner);
48 }
49 }
50 return ValueType::Other;
51 }
52
53 match shape.ty {
55 Type::Primitive(p) => match p {
56 PrimitiveType::Boolean => ValueType::Boolean,
57 PrimitiveType::Numeric(NumericType::Integer { .. })
58 | PrimitiveType::Numeric(NumericType::Float) => ValueType::Number,
59 PrimitiveType::Textual(TextualType::Char)
60 | PrimitiveType::Textual(TextualType::Str) => ValueType::String,
61 PrimitiveType::Never => ValueType::Null,
62 },
63 _ => ValueType::Other,
64 }
65}
66
67#[derive(Clone, Debug)]
69pub struct BuildOptions {
70 pub max_line_width: usize,
72 pub max_unchanged_fields: usize,
75 pub collapse_threshold: usize,
77}
78
79impl Default for BuildOptions {
80 fn default() -> Self {
81 Self {
82 max_line_width: 80,
83 max_unchanged_fields: 5,
84 collapse_threshold: 3,
85 }
86 }
87}
88
89pub fn build_layout<'mem, 'facet, F: DiffFlavor>(
101 diff: &Diff<'mem, 'facet>,
102 from: Peek<'mem, 'facet>,
103 to: Peek<'mem, 'facet>,
104 opts: &BuildOptions,
105 flavor: &F,
106) -> Layout {
107 let mut builder = LayoutBuilder::new(opts.clone(), flavor);
108 let root_id = builder.build(diff, Some(from), Some(to));
109 builder.finish(root_id)
110}
111
112struct LayoutBuilder<'f, F: DiffFlavor> {
114 strings: FormatArena,
116 tree: Arena<LayoutNode>,
118 opts: BuildOptions,
120 flavor: &'f F,
122}
123
124impl<'f, F: DiffFlavor> LayoutBuilder<'f, F> {
125 fn new(opts: BuildOptions, flavor: &'f F) -> Self {
126 Self {
127 strings: FormatArena::new(),
128 tree: Arena::new(),
129 opts,
130 flavor,
131 }
132 }
133
134 fn build<'mem, 'facet>(
136 &mut self,
137 diff: &Diff<'mem, 'facet>,
138 from: Option<Peek<'mem, 'facet>>,
139 to: Option<Peek<'mem, 'facet>>,
140 ) -> NodeId {
141 self.build_diff(diff, from, to, ElementChange::None)
142 }
143
144 fn build_diff<'mem, 'facet>(
146 &mut self,
147 diff: &Diff<'mem, 'facet>,
148 from: Option<Peek<'mem, 'facet>>,
149 to: Option<Peek<'mem, 'facet>>,
150 change: ElementChange,
151 ) -> NodeId {
152 match diff {
153 Diff::Equal { value } => {
154 if let Some(peek) = value {
156 self.build_peek(*peek, ElementChange::None)
157 } else {
158 let (span, width) = self.strings.push_str("(equal)");
160 let value = FormattedValue::new(span, width);
161 self.tree.new_node(LayoutNode::Text {
162 value,
163 change: ElementChange::None,
164 })
165 }
166 }
167 Diff::Replace { from, to } => {
168 let root = self.tree.new_node(LayoutNode::element("_replace"));
170
171 let from_node = self.build_peek(*from, ElementChange::Deleted);
172 let to_node = self.build_peek(*to, ElementChange::Inserted);
173
174 root.append(from_node, &mut self.tree);
175 root.append(to_node, &mut self.tree);
176
177 root
178 }
179 Diff::User {
180 from: from_shape,
181 to: _to_shape,
182 variant,
183 value,
184 } => {
185 let tag = get_shape_display_name(from_shape);
187
188 match value {
189 Value::Struct {
190 updates,
191 deletions,
192 insertions,
193 unchanged,
194 } => self.build_struct(
195 tag, *variant, updates, deletions, insertions, unchanged, from, to, change,
196 ),
197 Value::Tuple { updates } => {
198 self.build_tuple(tag, *variant, updates, from, to, change)
199 }
200 }
201 }
202 Diff::Sequence {
203 from: _seq_shape_from,
204 to: _seq_shape_to,
205 updates,
206 } => {
207 let item_type = from
209 .and_then(|p| p.into_list_like().ok())
210 .and_then(|list| list.iter().next())
211 .or_else(|| {
212 to.and_then(|p| p.into_list_like().ok())
213 .and_then(|list| list.iter().next())
214 })
215 .map(|item| get_shape_display_name(item.shape()))
216 .unwrap_or("item");
217 self.build_sequence(updates, change, item_type)
218 }
219 }
220 }
221
222 fn build_peek(&mut self, peek: Peek<'_, '_>, change: ElementChange) -> NodeId {
224 let shape = peek.shape();
225
226 match (shape.def, shape.ty) {
228 (_, Type::User(UserType::Struct(ty))) if ty.kind == StructKind::Struct => {
229 if let Ok(struct_peek) = peek.into_struct() {
231 let tag = get_shape_display_name(shape);
232 let mut attrs = Vec::new();
233
234 for (i, field) in ty.fields.iter().enumerate() {
235 if let Ok(field_value) = struct_peek.field(i) {
236 let formatted_value = self.format_peek(field_value);
237 let attr = match change {
238 ElementChange::None => {
239 Attr::unchanged(field.name, field.name.len(), formatted_value)
240 }
241 ElementChange::Deleted => {
242 Attr::deleted(field.name, field.name.len(), formatted_value)
243 }
244 ElementChange::Inserted => {
245 Attr::inserted(field.name, field.name.len(), formatted_value)
246 }
247 ElementChange::MovedFrom | ElementChange::MovedTo => {
248 Attr::unchanged(field.name, field.name.len(), formatted_value)
250 }
251 };
252 attrs.push(attr);
253 }
254 }
255
256 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
257
258 return self.tree.new_node(LayoutNode::Element {
259 tag,
260 field_name: None,
261 attrs,
262 changed_groups,
263 change,
264 });
265 }
266 }
267 (_, Type::User(UserType::Enum(_))) => {
268 if let Ok(enum_peek) = peek.into_enum()
270 && let Ok(variant) = enum_peek.active_variant()
271 {
272 let tag = variant.name;
273 let fields = &variant.data.fields;
274
275 if !fields.is_empty() {
277 if fields.len() == 1
280 && let Ok(Some(inner_value)) = enum_peek.field(0)
281 {
282 let inner_shape = inner_value.shape();
283 if let Type::User(UserType::Struct(s)) = inner_shape.ty
285 && s.kind == StructKind::Struct
286 && let Ok(struct_peek) = inner_value.into_struct()
287 {
288 let mut attrs = Vec::new();
289
290 for (i, field) in s.fields.iter().enumerate() {
291 if let Ok(field_value) = struct_peek.field(i) {
292 let formatted_value = self.format_peek(field_value);
293 let attr = match change {
294 ElementChange::None => Attr::unchanged(
295 field.name,
296 field.name.len(),
297 formatted_value,
298 ),
299 ElementChange::Deleted => Attr::deleted(
300 field.name,
301 field.name.len(),
302 formatted_value,
303 ),
304 ElementChange::Inserted => Attr::inserted(
305 field.name,
306 field.name.len(),
307 formatted_value,
308 ),
309 ElementChange::MovedFrom | ElementChange::MovedTo => {
310 Attr::unchanged(
311 field.name,
312 field.name.len(),
313 formatted_value,
314 )
315 }
316 };
317 attrs.push(attr);
318 }
319 }
320
321 let changed_groups =
322 group_changed_attrs(&attrs, self.opts.max_line_width, 0);
323
324 return self.tree.new_node(LayoutNode::Element {
325 tag,
326 field_name: None,
327 attrs,
328 changed_groups,
329 change,
330 });
331 }
332 }
333
334 let mut attrs = Vec::new();
336
337 for (i, field) in fields.iter().enumerate() {
338 if let Ok(Some(field_value)) = enum_peek.field(i) {
339 let formatted_value = self.format_peek(field_value);
340 let attr = match change {
341 ElementChange::None => Attr::unchanged(
342 field.name,
343 field.name.len(),
344 formatted_value,
345 ),
346 ElementChange::Deleted => {
347 Attr::deleted(field.name, field.name.len(), formatted_value)
348 }
349 ElementChange::Inserted => Attr::inserted(
350 field.name,
351 field.name.len(),
352 formatted_value,
353 ),
354 ElementChange::MovedFrom | ElementChange::MovedTo => {
355 Attr::unchanged(
356 field.name,
357 field.name.len(),
358 formatted_value,
359 )
360 }
361 };
362 attrs.push(attr);
363 }
364 }
365
366 let changed_groups =
367 group_changed_attrs(&attrs, self.opts.max_line_width, 0);
368
369 return self.tree.new_node(LayoutNode::Element {
370 tag,
371 field_name: None,
372 attrs,
373 changed_groups,
374 change,
375 });
376 } else {
377 let (span, width) = self.strings.push_str(tag);
379 return self.tree.new_node(LayoutNode::Text {
380 value: FormattedValue::new(span, width),
381 change,
382 });
383 }
384 }
385 }
386 _ => {}
387 }
388
389 let formatted = self.format_peek(peek);
391 self.tree.new_node(LayoutNode::Text {
392 value: formatted,
393 change,
394 })
395 }
396
397 #[allow(clippy::too_many_arguments)]
399 fn build_struct<'mem, 'facet>(
400 &mut self,
401 tag: &'static str,
402 variant: Option<&'static str>,
403 updates: &std::collections::HashMap<Cow<'static, str>, Diff<'mem, 'facet>>,
404 deletions: &std::collections::HashMap<Cow<'static, str>, Peek<'mem, 'facet>>,
405 insertions: &std::collections::HashMap<Cow<'static, str>, Peek<'mem, 'facet>>,
406 unchanged: &std::collections::HashSet<Cow<'static, str>>,
407 from: Option<Peek<'mem, 'facet>>,
408 to: Option<Peek<'mem, 'facet>>,
409 change: ElementChange,
410 ) -> NodeId {
411 let element_tag = tag;
412
413 if variant.is_some() {
416 }
418
419 let mut attrs = Vec::new();
420 let mut child_nodes = Vec::new();
421
422 if !unchanged.is_empty() {
424 let unchanged_count = unchanged.len();
425
426 if unchanged_count <= self.opts.max_unchanged_fields {
427 if let Some(from_peek) = from {
429 if let Ok(struct_peek) = from_peek.into_struct() {
430 let mut sorted_unchanged: Vec<_> = unchanged.iter().collect();
431 sorted_unchanged.sort();
432
433 for field_name in sorted_unchanged {
434 if let Ok(field_value) = struct_peek.field_by_name(field_name) {
435 let formatted = self.format_peek(field_value);
436 let name_width = field_name.len();
437 let attr =
438 Attr::unchanged(field_name.clone(), name_width, formatted);
439 attrs.push(attr);
440 }
441 }
442 }
443 } else {
444 }
447 }
448 }
450
451 let mut sorted_updates: Vec<_> = updates.iter().collect();
453 sorted_updates.sort_by(|(a, _), (b, _)| a.cmp(b));
454
455 for (field_name, field_diff) in sorted_updates {
456 let field_from = from.and_then(|p| {
458 p.into_struct()
459 .ok()
460 .and_then(|s| s.field_by_name(field_name).ok())
461 });
462 let field_to = to.and_then(|p| {
463 p.into_struct()
464 .ok()
465 .and_then(|s| s.field_by_name(field_name).ok())
466 });
467
468 match field_diff {
469 Diff::Replace { from, to } => {
470 let from_shape = from.shape();
472 let is_complex = match from_shape.ty {
473 Type::User(UserType::Enum(_)) => true,
474 Type::User(UserType::Struct(s)) if s.kind == StructKind::Struct => true,
475 _ => false,
476 };
477
478 if is_complex {
479 let from_node = self.build_peek(*from, ElementChange::Deleted);
481 let to_node = self.build_peek(*to, ElementChange::Inserted);
482
483 if let Cow::Borrowed(name) = field_name {
485 if let Some(node) = self.tree.get_mut(from_node)
486 && let LayoutNode::Element { field_name, .. } = node.get_mut()
487 {
488 *field_name = Some(name);
489 }
490 if let Some(node) = self.tree.get_mut(to_node)
491 && let LayoutNode::Element { field_name, .. } = node.get_mut()
492 {
493 *field_name = Some(name);
494 }
495 }
496
497 child_nodes.push(from_node);
498 child_nodes.push(to_node);
499 } else {
500 let old_value = self.format_peek(*from);
502 let new_value = self.format_peek(*to);
503 let name_width = field_name.len();
504 let attr =
505 Attr::changed(field_name.clone(), name_width, old_value, new_value);
506 attrs.push(attr);
507 }
508 }
509 _ => {
510 let child =
512 self.build_diff(field_diff, field_from, field_to, ElementChange::None);
513
514 if let Cow::Borrowed(name) = field_name
517 && let Some(node) = self.tree.get_mut(child)
518 {
519 match node.get_mut() {
520 LayoutNode::Element { field_name, .. } => {
521 *field_name = Some(name);
522 }
523 LayoutNode::Sequence { field_name, .. } => {
524 *field_name = Some(name);
525 }
526 _ => {}
527 }
528 }
529
530 child_nodes.push(child);
531 }
532 }
533 }
534
535 let mut sorted_deletions: Vec<_> = deletions.iter().collect();
537 sorted_deletions.sort_by(|(a, _), (b, _)| a.cmp(b));
538
539 for (field_name, value) in sorted_deletions {
540 let formatted = self.format_peek(*value);
541 let name_width = field_name.len();
542 let attr = Attr::deleted(field_name.clone(), name_width, formatted);
543 attrs.push(attr);
544 }
545
546 let mut sorted_insertions: Vec<_> = insertions.iter().collect();
548 sorted_insertions.sort_by(|(a, _), (b, _)| a.cmp(b));
549
550 for (field_name, value) in sorted_insertions {
551 let formatted = self.format_peek(*value);
552 let name_width = field_name.len();
553 let attr = Attr::inserted(field_name.clone(), name_width, formatted);
554 attrs.push(attr);
555 }
556
557 let changed_groups = group_changed_attrs(&attrs, self.opts.max_line_width, 0);
559
560 let node = self.tree.new_node(LayoutNode::Element {
562 tag: element_tag,
563 field_name: None, attrs,
565 changed_groups,
566 change,
567 });
568
569 for child in child_nodes {
571 node.append(child, &mut self.tree);
572 }
573
574 let unchanged_count = unchanged.len();
576 if unchanged_count > self.opts.max_unchanged_fields
577 || (unchanged_count > 0 && from.is_none())
578 {
579 let collapsed = self.tree.new_node(LayoutNode::collapsed(unchanged_count));
580 node.append(collapsed, &mut self.tree);
581 }
582
583 node
584 }
585
586 fn build_tuple<'mem, 'facet>(
588 &mut self,
589 tag: &'static str,
590 variant: Option<&'static str>,
591 updates: &Updates<'mem, 'facet>,
592 _from: Option<Peek<'mem, 'facet>>,
593 _to: Option<Peek<'mem, 'facet>>,
594 change: ElementChange,
595 ) -> NodeId {
596 if variant.is_some() {
598 }
600
601 let node = self.tree.new_node(LayoutNode::Element {
603 tag,
604 field_name: None,
605 attrs: Vec::new(),
606 changed_groups: Vec::new(),
607 change,
608 });
609
610 self.build_updates_children(node, updates, "item");
612
613 node
614 }
615
616 fn build_sequence(
618 &mut self,
619 updates: &Updates<'_, '_>,
620 change: ElementChange,
621 item_type: &'static str,
622 ) -> NodeId {
623 let node = self.tree.new_node(LayoutNode::Sequence {
625 change,
626 item_type,
627 field_name: None,
628 });
629
630 self.build_updates_children(node, updates, item_type);
632
633 node
634 }
635
636 fn build_updates_children(
642 &mut self,
643 parent: NodeId,
644 updates: &Updates<'_, '_>,
645 _item_type: &'static str,
646 ) {
647 let mut items: Vec<(Peek<'_, '_>, ElementChange)> = Vec::new();
649 let mut nested_diffs: Vec<&Diff<'_, '_>> = Vec::new();
650
651 let interspersed = &updates.0;
652
653 if let Some(update_group) = &interspersed.first {
655 self.collect_updates_group_items(&mut items, &mut nested_diffs, update_group);
656 }
657
658 for (unchanged_items, update_group) in &interspersed.values {
660 for item in unchanged_items {
662 items.push((*item, ElementChange::None));
663 }
664
665 self.collect_updates_group_items(&mut items, &mut nested_diffs, update_group);
666 }
667
668 if let Some(unchanged_items) = &interspersed.last {
670 for item in unchanged_items {
671 items.push((*item, ElementChange::None));
672 }
673 }
674
675 tracing::debug!(
676 items_count = items.len(),
677 nested_diffs_count = nested_diffs.len(),
678 "collected sequence items"
679 );
680
681 for diff in nested_diffs {
683 let (from_peek, to_peek) = match diff {
685 Diff::User { .. } => {
686 (None, None)
690 }
691 Diff::Replace { from, to } => (Some(*from), Some(*to)),
692 _ => (None, None),
693 };
694 let child = self.build_diff(diff, from_peek, to_peek, ElementChange::None);
695 parent.append(child, &mut self.tree);
696 }
697
698 let _ = items; }
702
703 fn collect_updates_group_items<'a, 'mem: 'a, 'facet: 'a>(
706 &self,
707 items: &mut Vec<(Peek<'mem, 'facet>, ElementChange)>,
708 nested_diffs: &mut Vec<&'a Diff<'mem, 'facet>>,
709 group: &'a UpdatesGroup<'mem, 'facet>,
710 ) {
711 let interspersed = &group.0;
712
713 if let Some(replace) = &interspersed.first {
715 self.collect_replace_group_items(items, replace);
716 }
717
718 for (diffs, replace) in &interspersed.values {
720 for diff in diffs {
722 nested_diffs.push(diff);
723 }
724 self.collect_replace_group_items(items, replace);
725 }
726
727 if let Some(diffs) = &interspersed.last {
729 for diff in diffs {
730 nested_diffs.push(diff);
731 }
732 }
733 }
734
735 fn collect_replace_group_items<'a, 'mem: 'a, 'facet: 'a>(
737 &self,
738 items: &mut Vec<(Peek<'mem, 'facet>, ElementChange)>,
739 group: &'a ReplaceGroup<'mem, 'facet>,
740 ) {
741 for removal in &group.removals {
743 items.push((*removal, ElementChange::Deleted));
744 }
745
746 for addition in &group.additions {
748 items.push((*addition, ElementChange::Inserted));
749 }
750 }
751
752 fn format_peek(&mut self, peek: Peek<'_, '_>) -> FormattedValue {
754 let (span, width) = self.strings.format(|w| self.flavor.format_value(peek, w));
755 let value_type = determine_value_type(peek);
756 FormattedValue::with_type(span, width, value_type)
757 }
758
759 fn finish(self, root: NodeId) -> Layout {
761 Layout {
762 strings: self.strings,
763 tree: self.tree,
764 root,
765 }
766 }
767}
768
769#[cfg(test)]
770mod tests {
771 use super::*;
772 use crate::layout::render::{RenderOptions, render_to_string};
773 use crate::layout::{RustFlavor, XmlFlavor};
774
775 #[test]
776 fn test_build_equal_diff() {
777 let value = 42i32;
778 let peek = Peek::new(&value);
779 let diff = Diff::Equal { value: Some(peek) };
780
781 let layout = build_layout(&diff, peek, peek, &BuildOptions::default(), &RustFlavor);
782
783 let root = layout.get(layout.root).unwrap();
785 assert!(matches!(root, LayoutNode::Text { .. }));
786 }
787
788 #[test]
789 fn test_build_replace_diff() {
790 let from = 10i32;
791 let to = 20i32;
792 let diff = Diff::Replace {
793 from: Peek::new(&from),
794 to: Peek::new(&to),
795 };
796
797 let layout = build_layout(
798 &diff,
799 Peek::new(&from),
800 Peek::new(&to),
801 &BuildOptions::default(),
802 &RustFlavor,
803 );
804
805 let root = layout.get(layout.root).unwrap();
807 assert!(matches!(
808 root,
809 LayoutNode::Element {
810 tag: "_replace",
811 ..
812 }
813 ));
814
815 let children: Vec<_> = layout.children(layout.root).collect();
816 assert_eq!(children.len(), 2);
817 }
818
819 #[test]
820 fn test_build_and_render_replace() {
821 let from = 10i32;
822 let to = 20i32;
823 let diff = Diff::Replace {
824 from: Peek::new(&from),
825 to: Peek::new(&to),
826 };
827
828 let layout = build_layout(
829 &diff,
830 Peek::new(&from),
831 Peek::new(&to),
832 &BuildOptions::default(),
833 &RustFlavor,
834 );
835 let output = render_to_string(&layout, &RenderOptions::plain(), &XmlFlavor);
836
837 assert!(
839 output.contains("10"),
840 "output should contain old value: {}",
841 output
842 );
843 assert!(
844 output.contains("20"),
845 "output should contain new value: {}",
846 output
847 );
848 }
849
850 #[test]
851 fn test_build_options_default() {
852 let opts = BuildOptions::default();
853 assert_eq!(opts.max_line_width, 80);
854 assert_eq!(opts.max_unchanged_fields, 5);
855 assert_eq!(opts.collapse_threshold, 3);
856 }
857}