1use alloc::borrow::Cow;
4use alloc::collections::BTreeMap;
5use core::{
6 fmt::{self, Write},
7 hash::{Hash, Hasher},
8 str,
9};
10use std::{hash::DefaultHasher, sync::LazyLock};
11
12use facet_core::{
13 Def, DynDateTimeKind, DynValueKind, Facet, Field, PointerType, PrimitiveType, SequenceType,
14 Shape, StructKind, StructType, TextualType, Type, TypeNameOpts, UserType,
15};
16use facet_reflect::{Peek, ValueId};
17
18use owo_colors::OwoColorize;
19
20#[allow(deprecated)]
21use crate::color::ColorGenerator;
22use crate::color::{Palette, Theme};
23use crate::shape::{FieldSpan, Path, PathSegment, Span};
24
25pub mod tokyo_night {
33 use owo_colors::Rgb;
34
35 pub const FOREGROUND: Rgb = Rgb(169, 177, 214);
41 pub const BACKGROUND: Rgb = Rgb(26, 27, 38);
43 pub const COMMENT: Rgb = Rgb(86, 95, 137);
45
46 pub const BLACK: Rgb = Rgb(65, 72, 104);
52 pub const RED: Rgb = Rgb(247, 118, 142);
54 pub const GREEN: Rgb = Rgb(115, 218, 202);
56 pub const YELLOW: Rgb = Rgb(224, 175, 104);
58 pub const BLUE: Rgb = Rgb(122, 162, 247);
60 pub const MAGENTA: Rgb = Rgb(187, 154, 247);
62 pub const CYAN: Rgb = Rgb(125, 207, 255);
64 pub const WHITE: Rgb = Rgb(120, 124, 153);
66
67 pub const BRIGHT_WHITE: Rgb = Rgb(172, 176, 208);
69
70 pub const ORANGE: Rgb = Rgb(255, 158, 100);
76 pub const DARK_GREEN: Rgb = Rgb(158, 206, 106);
78
79 pub const ERROR: Rgb = Rgb(219, 75, 75);
85 pub const WARNING: Rgb = YELLOW;
87 pub const INFO: Rgb = Rgb(13, 185, 215);
89 pub const HINT: Rgb = COMMENT;
91
92 pub const TYPE_NAME: Rgb = BLUE;
98 pub const FIELD_NAME: Rgb = GREEN;
100 pub const STRING: Rgb = DARK_GREEN;
102 pub const NUMBER: Rgb = ORANGE;
104 pub const KEYWORD: Rgb = MAGENTA;
106 pub const DELETION: Rgb = RED;
108 pub const INSERTION: Rgb = GREEN;
110 pub const MUTED: Rgb = COMMENT;
112 pub const BORDER: Rgb = COMMENT;
114}
115
116#[derive(Clone, PartialEq)]
118pub struct PrettyPrinter {
119 indent_size: usize,
121 max_depth: Option<usize>,
122 theme: Theme,
123 colors: ColorMode,
124 list_u8_as_bytes: bool,
125 minimal_option_names: bool,
127 show_doc_comments: bool,
129 max_content_len: Option<usize>,
131 max_collection_len: Option<usize>,
133}
134
135impl Default for PrettyPrinter {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl PrettyPrinter {
142 pub const fn new() -> Self {
144 Self {
145 indent_size: 2,
146 max_depth: None,
147 theme: Theme::Auto,
148 colors: ColorMode::Auto,
149 list_u8_as_bytes: true,
150 minimal_option_names: false,
151 show_doc_comments: false,
152 max_content_len: None,
153 max_collection_len: None,
154 }
155 }
156
157 pub const fn with_indent_size(mut self, size: usize) -> Self {
159 self.indent_size = size;
160 self
161 }
162
163 pub const fn with_max_depth(mut self, depth: usize) -> Self {
165 self.max_depth = Some(depth);
166 self
167 }
168
169 #[deprecated(
175 since = "0.46.3",
176 note = "no-op; colours now come from `Theme`/`Palette`, configure via `with_theme`/`with_palette`"
177 )]
178 #[allow(deprecated)]
179 pub const fn with_color_generator(self, _generator: ColorGenerator) -> Self {
180 self
181 }
182
183 pub const fn with_theme(mut self, theme: Theme) -> Self {
188 self.theme = theme;
189 self
190 }
191
192 pub const fn with_palette(mut self, palette: Palette) -> Self {
194 self.theme = Theme::Custom(palette);
195 self
196 }
197
198 #[inline]
200 fn palette(&self) -> Palette {
201 self.theme.palette()
202 }
203
204 pub const fn with_colors(mut self, enable_colors: ColorMode) -> Self {
206 self.colors = enable_colors;
207 self
208 }
209
210 pub const fn with_minimal_option_names(mut self, minimal: bool) -> Self {
212 self.minimal_option_names = minimal;
213 self
214 }
215
216 pub const fn with_doc_comments(mut self, show: bool) -> Self {
218 self.show_doc_comments = show;
219 self
220 }
221
222 pub const fn with_max_content_len(mut self, max_len: usize) -> Self {
227 self.max_content_len = Some(max_len);
228 self
229 }
230
231 pub const fn with_max_collection_len(mut self, max_len: usize) -> Self {
237 self.max_collection_len = Some(max_len);
238 self
239 }
240
241 pub fn format<'a, T: ?Sized + Facet<'a>>(&self, value: &T) -> String {
243 let value = Peek::new(value);
244
245 let mut output = String::new();
246 self.format_peek_internal(value, &mut output, &mut BTreeMap::new())
247 .expect("Formatting failed");
248
249 output
250 }
251
252 pub fn format_to<'a, T: ?Sized + Facet<'a>>(
254 &self,
255 value: &T,
256 f: &mut fmt::Formatter<'_>,
257 ) -> fmt::Result {
258 let value = Peek::new(value);
259 self.format_peek_internal(value, f, &mut BTreeMap::new())
260 }
261
262 pub fn format_peek(&self, value: Peek<'_, '_>) -> String {
264 let mut output = String::new();
265 self.format_peek_internal(value, &mut output, &mut BTreeMap::new())
266 .expect("Formatting failed");
267 output
268 }
269
270 pub(crate) fn shape_chunkiness(shape: &Shape) -> usize {
271 let mut shape = shape;
272 while let Type::Pointer(PointerType::Reference(inner)) = shape.ty {
273 shape = inner.target;
274 }
275
276 match shape.ty {
277 Type::Pointer(_) | Type::Primitive(_) => 1,
278 Type::Sequence(SequenceType::Array(ty)) => {
279 Self::shape_chunkiness(ty.t).saturating_mul(ty.n)
280 }
281 Type::Sequence(SequenceType::Slice(_)) => usize::MAX,
282 Type::User(ty) => match ty {
283 UserType::Struct(ty) => {
284 let mut sum = 0usize;
285 for field in ty.fields {
286 sum = sum.saturating_add(Self::shape_chunkiness(field.shape()));
287 }
288 sum
289 }
290 UserType::Enum(ty) => {
291 let mut max = 0usize;
292 for variant in ty.variants {
293 max = Ord::max(max, {
294 let mut sum = 0usize;
295 for field in variant.data.fields {
296 sum = sum.saturating_add(Self::shape_chunkiness(field.shape()));
297 }
298 sum
299 })
300 }
301 max
302 }
303 UserType::Opaque | UserType::Union(_) => 1,
304 },
305 Type::Undefined => 1,
306 }
307 }
308
309 #[inline]
310 fn use_colors(&self) -> bool {
311 self.colors.enabled()
312 }
313
314 #[allow(clippy::too_many_arguments)]
315 pub(crate) fn format_peek_internal_(
316 &self,
317 value: Peek<'_, '_>,
318 f: &mut dyn Write,
319 visited: &mut BTreeMap<ValueId, usize>,
320 format_depth: usize,
321 type_depth: usize,
322 short: bool,
323 ) -> fmt::Result {
324 let mut value = value;
325 while let Ok(ptr) = value.into_pointer()
326 && let Some(pointee) = ptr.borrow_inner()
327 {
328 value = pointee;
329 }
330
331 let value = value.innermost_peek();
334 let shape = value.shape();
335
336 if let Some(prev_type_depth) = visited.insert(value.id(), type_depth) {
337 self.write_type_name(f, &value)?;
338 self.write_punctuation(f, " { ")?;
339 self.write_comment(
340 f,
341 &format!(
342 "/* cycle detected at {} (first seen at type_depth {}) */",
343 value.id(),
344 prev_type_depth,
345 ),
346 )?;
347 visited.remove(&value.id());
348 return Ok(());
349 }
350
351 if let Some(proxy_def) = shape.proxy {
353 let result = self.format_via_proxy(
354 value,
355 proxy_def,
356 f,
357 visited,
358 format_depth,
359 type_depth,
360 short,
361 );
362
363 visited.remove(&value.id());
364 return result;
365 }
366
367 match (shape.def, shape.ty) {
368 (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
369 let value = value.get::<str>().unwrap();
370 self.format_str_value(f, value)?;
371 }
372 (Def::Scalar, _) if value.shape().id == <alloc::string::String as Facet>::SHAPE.id => {
374 let s = value.get::<alloc::string::String>().unwrap();
375 self.format_str_value(f, s)?;
376 }
377 (Def::Scalar, _) => self.format_scalar(value, f)?,
378 (Def::Option(_), _) => {
379 let option = value.into_option().unwrap();
380
381 if !self.minimal_option_names {
383 self.write_type_name(f, &value)?;
384 }
385
386 if let Some(inner) = option.value() {
387 let prefix = if self.minimal_option_names {
388 "Some("
389 } else {
390 "::Some("
391 };
392 self.write_punctuation(f, prefix)?;
393 self.format_peek_internal_(
394 inner,
395 f,
396 visited,
397 format_depth,
398 type_depth + 1,
399 short,
400 )?;
401 self.write_punctuation(f, ")")?;
402 } else {
403 let suffix = if self.minimal_option_names {
404 "None"
405 } else {
406 "::None"
407 };
408 self.write_punctuation(f, suffix)?;
409 }
410 }
411
412 (Def::Result(_), _) => {
413 let result = value.into_result().unwrap();
414 self.write_type_name(f, &value)?;
415 if result.is_ok() {
416 self.write_punctuation(f, " Ok(")?;
417 if let Some(ok_val) = result.ok() {
418 self.format_peek_internal_(
419 ok_val,
420 f,
421 visited,
422 format_depth,
423 type_depth + 1,
424 short,
425 )?;
426 }
427 self.write_punctuation(f, ")")?;
428 } else {
429 self.write_punctuation(f, " Err(")?;
430 if let Some(err_val) = result.err() {
431 self.format_peek_internal_(
432 err_val,
433 f,
434 visited,
435 format_depth,
436 type_depth + 1,
437 short,
438 )?;
439 }
440 self.write_punctuation(f, ")")?;
441 }
442 }
443
444 (_, Type::Pointer(PointerType::Raw(_) | PointerType::Function(_))) => {
445 self.write_type_name(f, &value)?;
446 let addr = unsafe { value.data().read::<*const ()>() };
447 let value = Peek::new(&addr);
448 self.format_scalar(value, f)?;
449 }
450
451 (_, Type::User(UserType::Union(_))) => {
452 if !short && self.show_doc_comments {
453 for &line in shape.doc {
454 self.write_comment(f, &format!("///{line}"))?;
455 writeln!(f)?;
456 self.indent(f, format_depth)?;
457 }
458 }
459 self.write_type_name(f, &value)?;
460
461 self.write_punctuation(f, " { ")?;
462 self.write_comment(f, "/* contents of untagged union */")?;
463 self.write_punctuation(f, " }")?;
464 }
465
466 (
467 _,
468 Type::User(UserType::Struct(
469 ty @ StructType {
470 kind: StructKind::Tuple | StructKind::TupleStruct,
471 ..
472 },
473 )),
474 ) => {
475 if !short && self.show_doc_comments {
476 for &line in shape.doc {
477 self.write_comment(f, &format!("///{line}"))?;
478 writeln!(f)?;
479 self.indent(f, format_depth)?;
480 }
481 }
482
483 self.write_type_name(f, &value)?;
484 if matches!(ty.kind, StructKind::Tuple) {
485 write!(f, " ")?;
486 }
487 let value = value.into_struct().unwrap();
488
489 let fields = ty.fields;
490 self.format_tuple_fields(
491 &|i| value.field(i).unwrap(),
492 f,
493 visited,
494 format_depth,
495 type_depth,
496 fields,
497 short,
498 matches!(ty.kind, StructKind::Tuple),
499 )?;
500 }
501
502 (
503 _,
504 Type::User(UserType::Struct(
505 ty @ StructType {
506 kind: StructKind::Struct | StructKind::Unit,
507 ..
508 },
509 )),
510 ) => {
511 if !short && self.show_doc_comments {
512 for &line in shape.doc {
513 self.write_comment(f, &format!("///{line}"))?;
514 writeln!(f)?;
515 self.indent(f, format_depth)?;
516 }
517 }
518
519 self.write_type_name(f, &value)?;
520
521 if matches!(ty.kind, StructKind::Struct) {
522 let value = value.into_struct().unwrap();
523 self.format_struct_fields(
524 &|i| value.field(i).unwrap(),
525 f,
526 visited,
527 format_depth,
528 type_depth,
529 ty.fields,
530 short,
531 )?;
532 }
533 }
534
535 (_, Type::User(UserType::Enum(_))) => {
536 let enum_peek = value.into_enum().unwrap();
537 match enum_peek.active_variant() {
538 Err(_) => {
539 self.write_type_name(f, &value)?;
541 self.write_punctuation(f, " {")?;
542 self.write_comment(f, " /* cannot determine variant */ ")?;
543 self.write_punctuation(f, "}")?;
544 }
545 Ok(variant) => {
546 if !short && self.show_doc_comments {
547 for &line in shape.doc {
548 self.write_comment(f, &format!("///{line}"))?;
549 writeln!(f)?;
550 self.indent(f, format_depth)?;
551 }
552 for &line in variant.doc {
553 self.write_comment(f, &format!("///{line}"))?;
554 writeln!(f)?;
555 self.indent(f, format_depth)?;
556 }
557 }
558 self.write_type_name(f, &value)?;
559 self.write_punctuation(f, "::")?;
560
561 if self.use_colors() {
568 write!(f, "{}", variant.name.bold())?;
569 } else {
570 write!(f, "{}", variant.name)?;
571 }
572
573 match variant.data.kind {
575 StructKind::Unit => {
576 }
578 StructKind::Struct => self.format_struct_fields(
579 &|i| enum_peek.field(i).unwrap().unwrap(),
580 f,
581 visited,
582 format_depth,
583 type_depth,
584 variant.data.fields,
585 short,
586 )?,
587 _ => self.format_tuple_fields(
588 &|i| enum_peek.field(i).unwrap().unwrap(),
589 f,
590 visited,
591 format_depth,
592 type_depth,
593 variant.data.fields,
594 short,
595 false,
596 )?,
597 }
598 }
599 };
600 }
601
602 _ if value.into_list_like().is_ok() => {
603 let list = value.into_list_like().unwrap();
604
605 self.write_type_name(f, &value)?;
610
611 if !list.is_empty() {
612 if list.def().t().is_type::<u8>() && self.list_u8_as_bytes {
613 let total_len = list.len();
614 let truncate = self.max_content_len.is_some_and(|max| total_len > max);
615
616 self.write_punctuation(f, " [")?;
617
618 if truncate {
619 let max = self.max_content_len.unwrap();
620 let half = max / 2;
621 let start_count = half;
622 let end_count = half;
623
624 for (idx, item) in list.iter().enumerate().take(start_count) {
626 if !short && idx % 16 == 0 {
627 writeln!(f)?;
628 self.indent(f, format_depth + 1)?;
629 }
630 write!(f, " ")?;
631 let byte = *item.get::<u8>().unwrap();
632 if self.use_colors() {
633 let mut hasher = DefaultHasher::new();
634 byte.hash(&mut hasher);
635 let hash = hasher.finish();
636 let rgb = self.palette().accent(hash);
637 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
638 } else {
639 write!(f, "{byte:02x}")?;
640 }
641 }
642
643 let omitted = total_len - start_count - end_count;
645 if !short {
646 writeln!(f)?;
647 self.indent(f, format_depth + 1)?;
648 writeln!(f, " ...({omitted} bytes)...")?;
649 self.indent(f, format_depth + 1)?;
650 } else {
651 write!(f, " ...({omitted} bytes)...")?;
652 }
653
654 for (idx, item) in list.iter().enumerate().skip(total_len - end_count) {
656 let display_idx = start_count + 1 + (idx - (total_len - end_count));
657 if !short && display_idx.is_multiple_of(16) {
658 writeln!(f)?;
659 self.indent(f, format_depth + 1)?;
660 }
661 write!(f, " ")?;
662 let byte = *item.get::<u8>().unwrap();
663 if self.use_colors() {
664 let mut hasher = DefaultHasher::new();
665 byte.hash(&mut hasher);
666 let hash = hasher.finish();
667 let rgb = self.palette().accent(hash);
668 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
669 } else {
670 write!(f, "{byte:02x}")?;
671 }
672 }
673 } else {
674 for (idx, item) in list.iter().enumerate() {
675 if !short && idx % 16 == 0 {
676 writeln!(f)?;
677 self.indent(f, format_depth + 1)?;
678 }
679 write!(f, " ")?;
680
681 let byte = *item.get::<u8>().unwrap();
682 if self.use_colors() {
683 let mut hasher = DefaultHasher::new();
684 byte.hash(&mut hasher);
685 let hash = hasher.finish();
686 let rgb = self.palette().accent(hash);
687 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
688 } else {
689 write!(f, "{byte:02x}")?;
690 }
691 }
692 }
693
694 if !short {
695 writeln!(f)?;
696 self.indent(f, format_depth)?;
697 }
698 self.write_punctuation(f, "]")?;
699 } else {
700 let elem_shape = list.def().t();
702 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
703
704 self.write_punctuation(f, " [")?;
705 let len = list.len();
706 let visible_len = self.visible_collection_len(len);
707 for (idx, item) in list.iter().take(visible_len).enumerate() {
708 if !short && !is_simple {
709 writeln!(f)?;
710 self.indent(f, format_depth + 1)?;
711 } else if idx > 0 {
712 write!(f, " ")?;
713 }
714 self.format_peek_internal_(
715 item,
716 f,
717 visited,
718 format_depth + 1,
719 type_depth + 1,
720 short || is_simple,
721 )?;
722
723 if (!short && !is_simple) || idx + 1 < visible_len || visible_len < len
724 {
725 self.write_punctuation(f, ",")?;
726 }
727 }
728 if visible_len < len {
729 if !short && !is_simple {
730 writeln!(f)?;
731 self.indent(f, format_depth + 1)?;
732 } else if visible_len > 0 {
733 write!(f, " ")?;
734 }
735 self.write_collection_ellipsis(f, len - visible_len, "items")?;
736 }
737 if !short && !is_simple {
738 writeln!(f)?;
739 self.indent(f, format_depth)?;
740 }
741 self.write_punctuation(f, "]")?;
742 }
743 } else {
744 self.write_punctuation(f, "[]")?;
745 }
746 }
747
748 _ if value.into_set().is_ok() => {
749 self.write_type_name(f, &value)?;
750
751 let value = value.into_set().unwrap();
752 self.write_punctuation(f, " [")?;
753 if !value.is_empty() {
754 let len = value.len();
755 let visible_len = self.visible_collection_len(len);
756 for (idx, item) in value.iter().take(visible_len).enumerate() {
757 if !short {
758 writeln!(f)?;
759 self.indent(f, format_depth + 1)?;
760 }
761 self.format_peek_internal_(
762 item,
763 f,
764 visited,
765 format_depth + 1,
766 type_depth + 1,
767 short,
768 )?;
769 if !short || idx + 1 < visible_len || visible_len < len {
770 self.write_punctuation(f, ",")?;
771 } else {
772 write!(f, " ")?;
773 }
774 }
775 if visible_len < len {
776 if !short {
777 writeln!(f)?;
778 self.indent(f, format_depth + 1)?;
779 } else if visible_len > 0 {
780 write!(f, " ")?;
781 }
782 self.write_collection_ellipsis(f, len - visible_len, "items")?;
783 }
784 if !short {
785 writeln!(f)?;
786 self.indent(f, format_depth)?;
787 }
788 }
789 self.write_punctuation(f, "]")?;
790 }
791
792 (Def::Map(def), _) => {
793 let key_is_short = Self::shape_chunkiness(def.k) <= 2;
794
795 self.write_type_name(f, &value)?;
796
797 let value = value.into_map().unwrap();
798 self.write_punctuation(f, " [")?;
799
800 if !value.is_empty() {
801 let len = value.len();
802 let visible_len = self.visible_collection_len(len);
803 for (idx, (key, value)) in value.iter().take(visible_len).enumerate() {
804 if !short {
805 writeln!(f)?;
806 self.indent(f, format_depth + 1)?;
807 }
808 self.format_peek_internal_(
809 key,
810 f,
811 visited,
812 format_depth + 1,
813 type_depth + 1,
814 key_is_short,
815 )?;
816 self.write_punctuation(f, " => ")?;
817 self.format_peek_internal_(
818 value,
819 f,
820 visited,
821 format_depth + 1,
822 type_depth + 1,
823 short,
824 )?;
825 if !short || idx + 1 < visible_len || visible_len < len {
826 self.write_punctuation(f, ",")?;
827 } else {
828 write!(f, " ")?;
829 }
830 }
831 if visible_len < len {
832 if !short {
833 writeln!(f)?;
834 self.indent(f, format_depth + 1)?;
835 } else if visible_len > 0 {
836 write!(f, " ")?;
837 }
838 self.write_collection_ellipsis(f, len - visible_len, "entries")?;
839 }
840 if !short {
841 writeln!(f)?;
842 self.indent(f, format_depth)?;
843 }
844 }
845
846 self.write_punctuation(f, "]")?;
847 }
848
849 (Def::DynamicValue(_), _) => {
850 let dyn_val = value.into_dynamic_value().unwrap();
851 match dyn_val.kind() {
852 DynValueKind::Null => {
853 self.write_keyword(f, "null")?;
854 }
855 DynValueKind::Bool => {
856 if let Some(b) = dyn_val.as_bool() {
857 self.write_keyword(f, if b { "true" } else { "false" })?;
858 }
859 }
860 DynValueKind::Number => {
861 if let Some(n) = dyn_val.as_i64() {
862 self.format_number(f, &n.to_string())?;
863 } else if let Some(n) = dyn_val.as_u64() {
864 self.format_number(f, &n.to_string())?;
865 } else if let Some(n) = dyn_val.as_f64() {
866 self.format_number(f, &n.to_string())?;
867 }
868 }
869 DynValueKind::String => {
870 if let Some(s) = dyn_val.as_str() {
871 self.format_string(f, s)?;
872 }
873 }
874 DynValueKind::Bytes => {
875 if let Some(bytes) = dyn_val.as_bytes() {
876 self.format_bytes(f, bytes)?;
877 }
878 }
879 DynValueKind::Array => {
880 let len = dyn_val.array_len().unwrap_or(0);
881 if len == 0 {
882 self.write_punctuation(f, "[]")?;
883 } else {
884 self.write_punctuation(f, "[")?;
885 let visible_len = self.visible_collection_len(len);
886 for idx in 0..visible_len {
887 if !short {
888 writeln!(f)?;
889 self.indent(f, format_depth + 1)?;
890 }
891 if let Some(elem) = dyn_val.array_get(idx) {
892 self.format_peek_internal_(
893 elem,
894 f,
895 visited,
896 format_depth + 1,
897 type_depth + 1,
898 short,
899 )?;
900 }
901 if !short || idx + 1 < visible_len || visible_len < len {
902 self.write_punctuation(f, ",")?;
903 } else {
904 write!(f, " ")?;
905 }
906 }
907 if visible_len < len {
908 if !short {
909 writeln!(f)?;
910 self.indent(f, format_depth + 1)?;
911 } else if visible_len > 0 {
912 write!(f, " ")?;
913 }
914 self.write_collection_ellipsis(f, len - visible_len, "items")?;
915 }
916 if !short {
917 writeln!(f)?;
918 self.indent(f, format_depth)?;
919 }
920 self.write_punctuation(f, "]")?;
921 }
922 }
923 DynValueKind::Object => {
924 let len = dyn_val.object_len().unwrap_or(0);
925 if len == 0 {
926 self.write_punctuation(f, "{}")?;
927 } else {
928 self.write_punctuation(f, "{")?;
929 let visible_len = self.visible_collection_len(len);
930 for idx in 0..visible_len {
931 if !short {
932 writeln!(f)?;
933 self.indent(f, format_depth + 1)?;
934 }
935 if let Some((key, val)) = dyn_val.object_get_entry(idx) {
936 self.write_field_name(f, key)?;
937 self.write_punctuation(f, ": ")?;
938 self.format_peek_internal_(
939 val,
940 f,
941 visited,
942 format_depth + 1,
943 type_depth + 1,
944 short,
945 )?;
946 }
947 if !short || idx + 1 < visible_len || visible_len < len {
948 self.write_punctuation(f, ",")?;
949 } else {
950 write!(f, " ")?;
951 }
952 }
953 if visible_len < len {
954 if !short {
955 writeln!(f)?;
956 self.indent(f, format_depth + 1)?;
957 } else if visible_len > 0 {
958 write!(f, " ")?;
959 }
960 self.write_collection_ellipsis(f, len - visible_len, "entries")?;
961 }
962 if !short {
963 writeln!(f)?;
964 self.indent(f, format_depth)?;
965 }
966 self.write_punctuation(f, "}")?;
967 }
968 }
969 DynValueKind::DateTime => {
970 #[allow(clippy::uninlined_format_args)]
972 if let Some((year, month, day, hour, minute, second, nanos, kind)) =
973 dyn_val.as_datetime()
974 {
975 match kind {
976 DynDateTimeKind::Offset { offset_minutes } => {
977 if nanos > 0 {
978 write!(
979 f,
980 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
981 year, month, day, hour, minute, second, nanos
982 )?;
983 } else {
984 write!(
985 f,
986 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
987 year, month, day, hour, minute, second
988 )?;
989 }
990 if offset_minutes == 0 {
991 write!(f, "Z")?;
992 } else {
993 let sign = if offset_minutes >= 0 { '+' } else { '-' };
994 let abs = offset_minutes.abs();
995 write!(f, "{}{:02}:{:02}", sign, abs / 60, abs % 60)?;
996 }
997 }
998 DynDateTimeKind::LocalDateTime => {
999 if nanos > 0 {
1000 write!(
1001 f,
1002 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
1003 year, month, day, hour, minute, second, nanos
1004 )?;
1005 } else {
1006 write!(
1007 f,
1008 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1009 year, month, day, hour, minute, second
1010 )?;
1011 }
1012 }
1013 DynDateTimeKind::LocalDate => {
1014 write!(f, "{:04}-{:02}-{:02}", year, month, day)?;
1015 }
1016 DynDateTimeKind::LocalTime => {
1017 if nanos > 0 {
1018 write!(
1019 f,
1020 "{:02}:{:02}:{:02}.{:09}",
1021 hour, minute, second, nanos
1022 )?;
1023 } else {
1024 write!(f, "{:02}:{:02}:{:02}", hour, minute, second)?;
1025 }
1026 }
1027 }
1028 }
1029 }
1030 DynValueKind::QName => {
1031 write!(f, "<qname>")?;
1033 }
1034 DynValueKind::Uuid => {
1035 write!(f, "<uuid>")?;
1037 }
1038 }
1039 }
1040
1041 (d, t) => write!(f, "unsupported peek variant: {value:?} ({d:?}, {t:?})")?,
1042 }
1043
1044 visited.remove(&value.id());
1045 Ok(())
1046 }
1047
1048 #[allow(clippy::too_many_arguments)]
1053 fn format_via_proxy(
1054 &self,
1055 value: Peek<'_, '_>,
1056 proxy_def: &'static facet_core::ProxyDef,
1057 f: &mut dyn Write,
1058 visited: &mut BTreeMap<ValueId, usize>,
1059 format_depth: usize,
1060 type_depth: usize,
1061 short: bool,
1062 ) -> fmt::Result {
1063 let proxy_shape = proxy_def.shape;
1064 let proxy_layout = match proxy_shape.layout.sized_layout() {
1065 Ok(layout) => layout,
1066 Err(_) => {
1067 return write!(f, "/* proxy type must be sized for formatting */");
1068 }
1069 };
1070
1071 let proxy_uninit = facet_core::alloc_for_layout(proxy_layout);
1073
1074 let convert_result = unsafe { (proxy_def.convert_out)(value.data(), proxy_uninit) };
1076
1077 let proxy_ptr = match convert_result {
1078 Ok(ptr) => ptr,
1079 Err(msg) => {
1080 unsafe { facet_core::dealloc_for_layout(proxy_uninit.assume_init(), proxy_layout) };
1081 return write!(f, "/* proxy conversion failed: {msg} */");
1082 }
1083 };
1084
1085 let proxy_peek = unsafe { Peek::unchecked_new(proxy_ptr.as_const(), proxy_shape) };
1087 let result =
1088 self.format_peek_internal_(proxy_peek, f, visited, format_depth, type_depth, short);
1089
1090 unsafe {
1092 let _ = proxy_shape.call_drop_in_place(proxy_ptr);
1093 facet_core::dealloc_for_layout(proxy_ptr, proxy_layout);
1094 }
1095
1096 result
1097 }
1098
1099 #[allow(clippy::too_many_arguments)]
1104 fn format_via_proxy_unified<O: FormatOutput>(
1105 &self,
1106 value: Peek<'_, '_>,
1107 proxy_def: &'static facet_core::ProxyDef,
1108 out: &mut O,
1109 visited: &mut BTreeMap<ValueId, usize>,
1110 format_depth: usize,
1111 type_depth: usize,
1112 short: bool,
1113 current_path: Path,
1114 ) -> fmt::Result {
1115 let proxy_shape = proxy_def.shape;
1116 let proxy_layout = match proxy_shape.layout.sized_layout() {
1117 Ok(layout) => layout,
1118 Err(_) => {
1119 return write!(out, "/* proxy type must be sized for formatting */");
1120 }
1121 };
1122
1123 let proxy_uninit = facet_core::alloc_for_layout(proxy_layout);
1125
1126 let convert_result = unsafe { (proxy_def.convert_out)(value.data(), proxy_uninit) };
1128
1129 let proxy_ptr = match convert_result {
1130 Ok(ptr) => ptr,
1131 Err(msg) => {
1132 unsafe { facet_core::dealloc_for_layout(proxy_uninit.assume_init(), proxy_layout) };
1133 return write!(out, "/* proxy conversion failed: {msg} */");
1134 }
1135 };
1136
1137 let proxy_peek = unsafe { Peek::unchecked_new(proxy_ptr.as_const(), proxy_shape) };
1139 let result = self.format_unified(
1140 proxy_peek,
1141 out,
1142 visited,
1143 format_depth,
1144 type_depth,
1145 short,
1146 current_path,
1147 );
1148
1149 unsafe {
1151 let _ = proxy_shape.call_drop_in_place(proxy_ptr);
1152 facet_core::dealloc_for_layout(proxy_ptr, proxy_layout);
1153 }
1154
1155 result
1156 }
1157
1158 #[allow(clippy::too_many_arguments)]
1159 fn format_tuple_fields<'mem, 'facet>(
1160 &self,
1161 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
1162 f: &mut dyn Write,
1163 visited: &mut BTreeMap<ValueId, usize>,
1164 format_depth: usize,
1165 type_depth: usize,
1166 fields: &[Field],
1167 short: bool,
1168 force_trailing_comma: bool,
1169 ) -> fmt::Result {
1170 self.write_punctuation(f, "(")?;
1171 if let [field] = fields
1172 && field.doc.is_empty()
1173 {
1174 let field_value = peek_field(0);
1175 if let Some(proxy_def) = field.proxy() {
1176 self.format_via_proxy(
1177 field_value,
1178 proxy_def,
1179 f,
1180 visited,
1181 format_depth,
1182 type_depth,
1183 short,
1184 )?;
1185 } else {
1186 self.format_peek_internal_(
1187 field_value,
1188 f,
1189 visited,
1190 format_depth,
1191 type_depth,
1192 short,
1193 )?;
1194 }
1195
1196 if force_trailing_comma {
1197 self.write_punctuation(f, ",")?;
1198 }
1199 } else if !fields.is_empty() {
1200 let visible_len = self.visible_collection_len(fields.len());
1201 for idx in 0..visible_len {
1202 if !short {
1203 writeln!(f)?;
1204 self.indent(f, format_depth + 1)?;
1205
1206 if self.show_doc_comments {
1207 for &line in fields[idx].doc {
1208 self.write_comment(f, &format!("///{line}"))?;
1209 writeln!(f)?;
1210 self.indent(f, format_depth + 1)?;
1211 }
1212 }
1213 }
1214
1215 if fields[idx].is_sensitive() {
1216 self.write_redacted(f, "[REDACTED]")?;
1217 } else if let Some(proxy_def) = fields[idx].proxy() {
1218 self.format_via_proxy(
1220 peek_field(idx),
1221 proxy_def,
1222 f,
1223 visited,
1224 format_depth + 1,
1225 type_depth + 1,
1226 short,
1227 )?;
1228 } else {
1229 self.format_peek_internal_(
1230 peek_field(idx),
1231 f,
1232 visited,
1233 format_depth + 1,
1234 type_depth + 1,
1235 short,
1236 )?;
1237 }
1238
1239 if !short || idx + 1 < visible_len || visible_len < fields.len() {
1240 self.write_punctuation(f, ",")?;
1241 } else {
1242 write!(f, " ")?;
1243 }
1244 }
1245 if visible_len < fields.len() {
1246 if !short {
1247 writeln!(f)?;
1248 self.indent(f, format_depth + 1)?;
1249 } else if visible_len > 0 {
1250 write!(f, " ")?;
1251 }
1252 self.write_collection_ellipsis(f, fields.len() - visible_len, "fields")?;
1253 }
1254 if !short {
1255 writeln!(f)?;
1256 self.indent(f, format_depth)?;
1257 }
1258 }
1259 self.write_punctuation(f, ")")?;
1260 Ok(())
1261 }
1262
1263 #[allow(clippy::too_many_arguments)]
1264 fn format_struct_fields<'mem, 'facet>(
1265 &self,
1266 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
1267 f: &mut dyn Write,
1268 visited: &mut BTreeMap<ValueId, usize>,
1269 format_depth: usize,
1270 type_depth: usize,
1271 fields: &[Field],
1272 short: bool,
1273 ) -> fmt::Result {
1274 let mut visible_indices: Vec<usize> = (0..fields.len())
1276 .filter(|&idx| {
1277 let field = &fields[idx];
1278 let field_ptr = peek_field(idx).data();
1280 !unsafe { field.should_skip_serializing(field_ptr) }
1281 })
1282 .collect();
1283 let total_visible = visible_indices.len();
1284 if let Some(max) = self.max_collection_len {
1285 visible_indices.truncate(max);
1286 }
1287
1288 self.write_punctuation(f, " {")?;
1289 if !visible_indices.is_empty() {
1290 for (i, &idx) in visible_indices.iter().enumerate() {
1291 let is_last = i + 1 == visible_indices.len();
1292
1293 if !short {
1294 writeln!(f)?;
1295 self.indent(f, format_depth + 1)?;
1296 }
1297
1298 if self.show_doc_comments {
1299 for &line in fields[idx].doc {
1300 self.write_comment(f, &format!("///{line}"))?;
1301 writeln!(f)?;
1302 self.indent(f, format_depth + 1)?;
1303 }
1304 }
1305
1306 self.write_field_name(f, fields[idx].name)?;
1307 self.write_punctuation(f, ": ")?;
1308 if fields[idx].is_sensitive() {
1309 self.write_redacted(f, "[REDACTED]")?;
1310 } else if let Some(proxy_def) = fields[idx].proxy() {
1311 self.format_via_proxy(
1313 peek_field(idx),
1314 proxy_def,
1315 f,
1316 visited,
1317 format_depth + 1,
1318 type_depth + 1,
1319 short,
1320 )?;
1321 } else {
1322 self.format_peek_internal_(
1323 peek_field(idx),
1324 f,
1325 visited,
1326 format_depth + 1,
1327 type_depth + 1,
1328 short,
1329 )?;
1330 }
1331
1332 if !short || !is_last {
1333 self.write_punctuation(f, ",")?;
1334 } else {
1335 write!(f, " ")?;
1336 }
1337 }
1338 if total_visible > visible_indices.len() {
1339 if !short {
1340 writeln!(f)?;
1341 self.indent(f, format_depth + 1)?;
1342 } else {
1343 write!(f, " ")?;
1344 }
1345 self.write_collection_ellipsis(f, total_visible - visible_indices.len(), "fields")?;
1346 }
1347 if !short {
1348 writeln!(f)?;
1349 self.indent(f, format_depth)?;
1350 }
1351 }
1352 self.write_punctuation(f, "}")?;
1353 Ok(())
1354 }
1355
1356 fn indent(&self, f: &mut dyn Write, indent: usize) -> fmt::Result {
1357 if self.indent_size == usize::MAX {
1358 write!(f, "{:\t<width$}", "", width = indent)
1359 } else {
1360 write!(f, "{: <width$}", "", width = indent * self.indent_size)
1361 }
1362 }
1363
1364 fn visible_collection_len(&self, len: usize) -> usize {
1365 self.max_collection_len
1366 .map_or(len, |max| Ord::min(len, max))
1367 }
1368
1369 fn write_collection_ellipsis(
1370 &self,
1371 f: &mut dyn Write,
1372 omitted: usize,
1373 noun: &str,
1374 ) -> fmt::Result {
1375 self.write_comment(f, &format!("...({omitted} more {noun})..."))
1376 }
1377
1378 pub(crate) fn format_peek_internal(
1380 &self,
1381 value: Peek<'_, '_>,
1382 f: &mut dyn Write,
1383 visited: &mut BTreeMap<ValueId, usize>,
1384 ) -> fmt::Result {
1385 self.format_peek_internal_(value, f, visited, 0, 0, false)
1386 }
1387
1388 fn format_scalar(&self, value: Peek, f: &mut dyn Write) -> fmt::Result {
1390 let mut hasher = DefaultHasher::new();
1392 value.shape().id.hash(&mut hasher);
1393 let hash = hasher.finish();
1394
1395 struct DisplayWrapper<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
1397
1398 impl fmt::Display for DisplayWrapper<'_, '_> {
1399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1400 if self.0.shape().is_display() {
1401 write!(f, "{}", self.0)?;
1402 } else if self.0.shape().is_debug() {
1403 write!(f, "{:?}", self.0)?;
1404 } else {
1405 write!(f, "{}", self.0.shape())?;
1406 write!(f, "(…)")?;
1407 }
1408 Ok(())
1409 }
1410 }
1411
1412 if self.use_colors() {
1414 let rgb = self.palette().accent(hash);
1415 write!(f, "{}", DisplayWrapper(&value).color(rgb))?;
1416 } else {
1417 write!(f, "{}", DisplayWrapper(&value))?;
1418 }
1419
1420 Ok(())
1421 }
1422
1423 fn write_keyword(&self, f: &mut dyn Write, keyword: &str) -> fmt::Result {
1425 if self.use_colors() {
1426 write!(f, "{}", keyword.color(self.palette().keyword))
1427 } else {
1428 write!(f, "{keyword}")
1429 }
1430 }
1431
1432 fn format_number(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1434 if self.use_colors() {
1435 write!(f, "{}", s.color(self.palette().number))
1436 } else {
1437 write!(f, "{s}")
1438 }
1439 }
1440
1441 fn format_str_value(&self, f: &mut dyn Write, value: &str) -> fmt::Result {
1443 if let Some(max) = self.max_content_len
1445 && value.len() > max
1446 {
1447 return self.format_truncated_str(f, value, max);
1448 }
1449
1450 let mut hashes = 0usize;
1452 let mut rest = value;
1453 while let Some(idx) = rest.find('"') {
1454 rest = &rest[idx + 1..];
1455 let before = rest.len();
1456 rest = rest.trim_start_matches('#');
1457 let after = rest.len();
1458 let count = before - after;
1459 hashes = Ord::max(hashes, 1 + count);
1460 }
1461
1462 let pad = "";
1463 let width = hashes.saturating_sub(1);
1464 if hashes > 0 {
1465 write!(f, "r{pad:#<width$}")?;
1466 }
1467 write!(f, "\"")?;
1468 if self.use_colors() {
1469 write!(f, "{}", value.color(self.palette().string))?;
1470 } else {
1471 write!(f, "{value}")?;
1472 }
1473 write!(f, "\"")?;
1474 if hashes > 0 {
1475 write!(f, "{pad:#<width$}")?;
1476 }
1477 Ok(())
1478 }
1479
1480 fn format_truncated_str(&self, f: &mut dyn Write, s: &str, max: usize) -> fmt::Result {
1482 let half = max / 2;
1483
1484 let start_end = s
1486 .char_indices()
1487 .take_while(|(i, _)| *i < half)
1488 .last()
1489 .map(|(i, c)| i + c.len_utf8())
1490 .unwrap_or(0);
1491
1492 let end_start = s
1494 .char_indices()
1495 .rev()
1496 .take_while(|(i, _)| s.len() - *i <= half)
1497 .last()
1498 .map(|(i, _)| i)
1499 .unwrap_or(s.len());
1500
1501 let omitted = s[start_end..end_start].chars().count();
1502 let start_part = &s[..start_end];
1503 let end_part = &s[end_start..];
1504
1505 if self.use_colors() {
1506 let string = self.palette().string;
1507 write!(
1508 f,
1509 "\"{}\"...({omitted} chars)...\"{}\"",
1510 start_part.color(string),
1511 end_part.color(string)
1512 )
1513 } else {
1514 write!(f, "\"{start_part}\"...({omitted} chars)...\"{end_part}\"")
1515 }
1516 }
1517
1518 fn format_string(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1520 if let Some(max) = self.max_content_len
1521 && s.len() > max
1522 {
1523 return self.format_truncated_str(f, s, max);
1524 }
1525
1526 if self.use_colors() {
1527 write!(f, "\"{}\"", s.color(self.palette().string))
1528 } else {
1529 write!(f, "{s:?}")
1530 }
1531 }
1532
1533 fn format_bytes(&self, f: &mut dyn Write, bytes: &[u8]) -> fmt::Result {
1535 write!(f, "b\"")?;
1536
1537 match self.max_content_len {
1538 Some(max) if bytes.len() > max => {
1539 let half = max / 2;
1541 let start = half;
1542 let end = half;
1543
1544 for byte in &bytes[..start] {
1545 write!(f, "\\x{byte:02x}")?;
1546 }
1547 let omitted = bytes.len() - start - end;
1548 write!(f, "\"...({omitted} bytes)...b\"")?;
1549 for byte in &bytes[bytes.len() - end..] {
1550 write!(f, "\\x{byte:02x}")?;
1551 }
1552 }
1553 _ => {
1554 for byte in bytes {
1555 write!(f, "\\x{byte:02x}")?;
1556 }
1557 }
1558 }
1559
1560 write!(f, "\"")
1561 }
1562
1563 fn write_type_name(&self, f: &mut dyn Write, peek: &Peek) -> fmt::Result {
1565 struct TypeNameWriter<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
1566
1567 impl core::fmt::Display for TypeNameWriter<'_, '_> {
1568 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1569 self.0.type_name(f, TypeNameOpts::infinite())
1570 }
1571 }
1572 let type_name = TypeNameWriter(peek);
1573
1574 if self.use_colors() {
1575 write!(f, "{}", type_name.color(self.palette().type_name).bold())
1576 } else {
1577 write!(f, "{type_name}")
1578 }
1579 }
1580
1581 #[allow(dead_code)]
1583 fn style_type_name(&self, peek: &Peek) -> String {
1584 let mut result = String::new();
1585 self.write_type_name(&mut result, peek).unwrap();
1586 result
1587 }
1588
1589 fn write_field_name(&self, f: &mut dyn Write, name: &str) -> fmt::Result {
1591 if self.use_colors() {
1592 write!(f, "{}", name.color(self.palette().field_name))
1593 } else {
1594 write!(f, "{name}")
1595 }
1596 }
1597
1598 fn write_punctuation(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1600 if self.use_colors() {
1601 write!(f, "{}", text.color(self.palette().punctuation))
1602 } else {
1603 write!(f, "{text}")
1604 }
1605 }
1606
1607 fn write_comment(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1609 if self.use_colors() {
1610 write!(f, "{}", text.color(self.palette().comment))
1611 } else {
1612 write!(f, "{text}")
1613 }
1614 }
1615
1616 fn write_redacted(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1618 if self.use_colors() {
1619 write!(f, "{}", text.color(self.palette().error).bold())
1620 } else {
1621 write!(f, "{text}")
1622 }
1623 }
1624
1625 #[allow(dead_code)]
1627 fn style_redacted(&self, text: &str) -> String {
1628 let mut result = String::new();
1629 self.write_redacted(&mut result, text).unwrap();
1630 result
1631 }
1632
1633 pub fn format_peek_with_spans(&self, value: Peek<'_, '_>) -> FormattedValue {
1641 let mut output = SpanTrackingOutput::new();
1642 let printer = Self {
1643 colors: ColorMode::Never,
1645 ..self.clone()
1646 };
1647 printer
1648 .format_unified(
1649 value,
1650 &mut output,
1651 &mut BTreeMap::new(),
1652 0,
1653 0,
1654 false,
1655 vec![],
1656 )
1657 .expect("Formatting failed");
1658
1659 output.into_formatted_value()
1660 }
1661
1662 #[allow(clippy::too_many_arguments)]
1667 fn format_unified<O: FormatOutput>(
1668 &self,
1669 value: Peek<'_, '_>,
1670 out: &mut O,
1671 visited: &mut BTreeMap<ValueId, usize>,
1672 format_depth: usize,
1673 type_depth: usize,
1674 short: bool,
1675 current_path: Path,
1676 ) -> fmt::Result {
1677 let mut value = value;
1678 while let Ok(ptr) = value.into_pointer()
1679 && let Some(pointee) = ptr.borrow_inner()
1680 {
1681 value = pointee;
1682 }
1683
1684 let value = value.innermost_peek();
1687 let shape = value.shape();
1688
1689 let value_start = out.position();
1691
1692 if let Some(prev_type_depth) = visited.insert(value.id(), type_depth) {
1693 write!(out, "{} {{ ", shape)?;
1694 write!(
1695 out,
1696 "/* cycle detected at {} (first seen at type_depth {}) */",
1697 value.id(),
1698 prev_type_depth,
1699 )?;
1700 visited.remove(&value.id());
1701 let value_end = out.position();
1702 out.record_span(current_path, (value_start, value_end));
1703 return Ok(());
1704 }
1705
1706 if let Some(proxy_def) = shape.proxy {
1708 let result = self.format_via_proxy_unified(
1709 value,
1710 proxy_def,
1711 out,
1712 visited,
1713 format_depth,
1714 type_depth,
1715 short,
1716 current_path.clone(),
1717 );
1718
1719 visited.remove(&value.id());
1720
1721 let value_end = out.position();
1723 out.record_span(current_path, (value_start, value_end));
1724
1725 return result;
1726 }
1727
1728 match (shape.def, shape.ty) {
1729 (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
1730 let s = value.get::<str>().unwrap();
1731 write!(out, "\"{}\"", s)?;
1732 }
1733 (Def::Scalar, _) if value.shape().id == <alloc::string::String as Facet>::SHAPE.id => {
1734 let s = value.get::<alloc::string::String>().unwrap();
1735 write!(out, "\"{}\"", s)?;
1736 }
1737 (Def::Scalar, _) => {
1738 self.format_scalar_to_output(value, out)?;
1739 }
1740 (Def::Option(_), _) => {
1741 let option = value.into_option().unwrap();
1742 if let Some(inner) = option.value() {
1743 write!(out, "Some(")?;
1744 self.format_unified(
1745 inner,
1746 out,
1747 visited,
1748 format_depth,
1749 type_depth + 1,
1750 short,
1751 current_path.clone(),
1752 )?;
1753 write!(out, ")")?;
1754 } else {
1755 write!(out, "None")?;
1756 }
1757 }
1758 (Def::Result(_), _) => {
1759 let result = value.into_result().unwrap();
1760 write!(out, "{}", shape)?;
1761 if result.is_ok() {
1762 write!(out, " Ok(")?;
1763 if let Some(ok_val) = result.ok() {
1764 self.format_unified(
1765 ok_val,
1766 out,
1767 visited,
1768 format_depth,
1769 type_depth + 1,
1770 short,
1771 current_path.clone(),
1772 )?;
1773 }
1774 write!(out, ")")?;
1775 } else {
1776 write!(out, " Err(")?;
1777 if let Some(err_val) = result.err() {
1778 self.format_unified(
1779 err_val,
1780 out,
1781 visited,
1782 format_depth,
1783 type_depth + 1,
1784 short,
1785 current_path.clone(),
1786 )?;
1787 }
1788 write!(out, ")")?;
1789 }
1790 }
1791 (
1792 _,
1793 Type::User(UserType::Struct(
1794 ty @ StructType {
1795 kind: StructKind::Struct | StructKind::Unit,
1796 ..
1797 },
1798 )),
1799 ) => {
1800 write!(out, "{}", shape)?;
1801 if matches!(ty.kind, StructKind::Struct) {
1802 let struct_peek = value.into_struct().unwrap();
1803 write!(out, " {{")?;
1804 for (i, field) in ty.fields.iter().enumerate() {
1805 if !short {
1806 writeln!(out)?;
1807 self.indent_to_output(out, format_depth + 1)?;
1808 }
1809 let field_name_start = out.position();
1811 write!(out, "{}", field.name)?;
1812 let field_name_end = out.position();
1813 write!(out, ": ")?;
1814
1815 let mut field_path = current_path.clone();
1817 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1818
1819 let field_value_start = out.position();
1821 if let Ok(field_value) = struct_peek.field(i) {
1822 if let Some(proxy_def) = field.proxy() {
1824 self.format_via_proxy_unified(
1825 field_value,
1826 proxy_def,
1827 out,
1828 visited,
1829 format_depth + 1,
1830 type_depth + 1,
1831 short,
1832 field_path.clone(),
1833 )?;
1834 } else {
1835 self.format_unified(
1836 field_value,
1837 out,
1838 visited,
1839 format_depth + 1,
1840 type_depth + 1,
1841 short,
1842 field_path.clone(),
1843 )?;
1844 }
1845 }
1846 let field_value_end = out.position();
1847
1848 out.record_field_span(
1850 field_path,
1851 (field_name_start, field_name_end),
1852 (field_value_start, field_value_end),
1853 );
1854
1855 if !short || i + 1 < ty.fields.len() {
1856 write!(out, ",")?;
1857 }
1858 }
1859 if !short {
1860 writeln!(out)?;
1861 self.indent_to_output(out, format_depth)?;
1862 }
1863 write!(out, "}}")?;
1864 }
1865 }
1866 (
1867 _,
1868 Type::User(UserType::Struct(
1869 ty @ StructType {
1870 kind: StructKind::Tuple | StructKind::TupleStruct,
1871 ..
1872 },
1873 )),
1874 ) => {
1875 write!(out, "{}", shape)?;
1876 if matches!(ty.kind, StructKind::Tuple) {
1877 write!(out, " ")?;
1878 }
1879 let struct_peek = value.into_struct().unwrap();
1880 write!(out, "(")?;
1881 for (i, field) in ty.fields.iter().enumerate() {
1882 if i > 0 {
1883 write!(out, ", ")?;
1884 }
1885 let mut elem_path = current_path.clone();
1886 elem_path.push(PathSegment::Index(i));
1887
1888 let elem_start = out.position();
1889 if let Ok(field_value) = struct_peek.field(i) {
1890 if let Some(proxy_def) = field.proxy() {
1892 self.format_via_proxy_unified(
1893 field_value,
1894 proxy_def,
1895 out,
1896 visited,
1897 format_depth + 1,
1898 type_depth + 1,
1899 short,
1900 elem_path.clone(),
1901 )?;
1902 } else {
1903 self.format_unified(
1904 field_value,
1905 out,
1906 visited,
1907 format_depth + 1,
1908 type_depth + 1,
1909 short,
1910 elem_path.clone(),
1911 )?;
1912 }
1913 }
1914 let elem_end = out.position();
1915 out.record_span(elem_path, (elem_start, elem_end));
1916 }
1917 write!(out, ")")?;
1918 }
1919 (_, Type::User(UserType::Enum(_))) => {
1920 let enum_peek = value.into_enum().unwrap();
1921 match enum_peek.active_variant() {
1922 Err(_) => {
1923 write!(out, "{} {{ /* cannot determine variant */ }}", shape)?;
1924 }
1925 Ok(variant) => {
1926 write!(out, "{}::{}", shape, variant.name)?;
1927
1928 match variant.data.kind {
1929 StructKind::Unit => {}
1930 StructKind::Struct => {
1931 write!(out, " {{")?;
1932 for (i, field) in variant.data.fields.iter().enumerate() {
1933 if !short {
1934 writeln!(out)?;
1935 self.indent_to_output(out, format_depth + 1)?;
1936 }
1937 let field_name_start = out.position();
1938 write!(out, "{}", field.name)?;
1939 let field_name_end = out.position();
1940 write!(out, ": ")?;
1941
1942 let mut field_path = current_path.clone();
1943 field_path
1944 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
1945 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1946
1947 let field_value_start = out.position();
1948 if let Ok(Some(field_value)) = enum_peek.field(i) {
1949 if let Some(proxy_def) = field.proxy() {
1951 self.format_via_proxy_unified(
1952 field_value,
1953 proxy_def,
1954 out,
1955 visited,
1956 format_depth + 1,
1957 type_depth + 1,
1958 short,
1959 field_path.clone(),
1960 )?;
1961 } else {
1962 self.format_unified(
1963 field_value,
1964 out,
1965 visited,
1966 format_depth + 1,
1967 type_depth + 1,
1968 short,
1969 field_path.clone(),
1970 )?;
1971 }
1972 }
1973 let field_value_end = out.position();
1974
1975 out.record_field_span(
1976 field_path,
1977 (field_name_start, field_name_end),
1978 (field_value_start, field_value_end),
1979 );
1980
1981 if !short || i + 1 < variant.data.fields.len() {
1982 write!(out, ",")?;
1983 }
1984 }
1985 if !short {
1986 writeln!(out)?;
1987 self.indent_to_output(out, format_depth)?;
1988 }
1989 write!(out, "}}")?;
1990 }
1991 _ => {
1992 write!(out, "(")?;
1993 for (i, field) in variant.data.fields.iter().enumerate() {
1994 if i > 0 {
1995 write!(out, ", ")?;
1996 }
1997 let mut elem_path = current_path.clone();
1998 elem_path
1999 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
2000 elem_path.push(PathSegment::Index(i));
2001
2002 let elem_start = out.position();
2003 if let Ok(Some(field_value)) = enum_peek.field(i) {
2004 if let Some(proxy_def) = field.proxy() {
2006 self.format_via_proxy_unified(
2007 field_value,
2008 proxy_def,
2009 out,
2010 visited,
2011 format_depth + 1,
2012 type_depth + 1,
2013 short,
2014 elem_path.clone(),
2015 )?;
2016 } else {
2017 self.format_unified(
2018 field_value,
2019 out,
2020 visited,
2021 format_depth + 1,
2022 type_depth + 1,
2023 short,
2024 elem_path.clone(),
2025 )?;
2026 }
2027 }
2028 let elem_end = out.position();
2029 out.record_span(elem_path, (elem_start, elem_end));
2030 }
2031 write!(out, ")")?;
2032 }
2033 }
2034 }
2035 }
2036 }
2037 _ if value.into_list_like().is_ok() => {
2038 let list = value.into_list_like().unwrap();
2039
2040 let elem_shape = list.def().t();
2042 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
2043
2044 write!(out, "[")?;
2045 let len = list.len();
2046 for (i, item) in list.iter().enumerate() {
2047 if !short && !is_simple {
2048 writeln!(out)?;
2049 self.indent_to_output(out, format_depth + 1)?;
2050 } else if i > 0 {
2051 write!(out, " ")?;
2052 }
2053 let mut elem_path = current_path.clone();
2054 elem_path.push(PathSegment::Index(i));
2055
2056 let elem_start = out.position();
2057 self.format_unified(
2058 item,
2059 out,
2060 visited,
2061 format_depth + 1,
2062 type_depth + 1,
2063 short || is_simple,
2064 elem_path.clone(),
2065 )?;
2066 let elem_end = out.position();
2067 out.record_span(elem_path, (elem_start, elem_end));
2068
2069 if (!short && !is_simple) || i + 1 < len {
2070 write!(out, ",")?;
2071 }
2072 }
2073 if !short && !is_simple {
2074 writeln!(out)?;
2075 self.indent_to_output(out, format_depth)?;
2076 }
2077 write!(out, "]")?;
2078 }
2079 _ if value.into_map().is_ok() => {
2080 let map = value.into_map().unwrap();
2081 write!(out, "{{")?;
2082 for (i, (key, val)) in map.iter().enumerate() {
2083 if !short {
2084 writeln!(out)?;
2085 self.indent_to_output(out, format_depth + 1)?;
2086 }
2087 let key_start = out.position();
2089 self.format_unified(
2090 key,
2091 out,
2092 visited,
2093 format_depth + 1,
2094 type_depth + 1,
2095 true, vec![],
2097 )?;
2098 let key_end = out.position();
2099
2100 write!(out, ": ")?;
2101
2102 let key_str = self.format_peek(key);
2104 let mut entry_path = current_path.clone();
2105 entry_path.push(PathSegment::Key(Cow::Owned(key_str)));
2106
2107 let val_start = out.position();
2108 self.format_unified(
2109 val,
2110 out,
2111 visited,
2112 format_depth + 1,
2113 type_depth + 1,
2114 short,
2115 entry_path.clone(),
2116 )?;
2117 let val_end = out.position();
2118
2119 out.record_field_span(entry_path, (key_start, key_end), (val_start, val_end));
2120
2121 if !short || i + 1 < map.len() {
2122 write!(out, ",")?;
2123 }
2124 }
2125 if !short && !map.is_empty() {
2126 writeln!(out)?;
2127 self.indent_to_output(out, format_depth)?;
2128 }
2129 write!(out, "}}")?;
2130 }
2131 _ => {
2132 write!(out, "{} {{ ... }}", shape)?;
2134 }
2135 }
2136
2137 visited.remove(&value.id());
2138
2139 let value_end = out.position();
2141 out.record_span(current_path, (value_start, value_end));
2142
2143 Ok(())
2144 }
2145
2146 fn format_scalar_to_output(&self, value: Peek<'_, '_>, out: &mut impl Write) -> fmt::Result {
2147 if value.shape().is_display() {
2149 write!(out, "{}", value)
2150 } else if value.shape().is_debug() {
2151 write!(out, "{:?}", value)
2152 } else {
2153 write!(out, "{}(…)", value.shape())
2154 }
2155 }
2156
2157 fn indent_to_output(&self, out: &mut impl Write, depth: usize) -> fmt::Result {
2158 for _ in 0..depth {
2159 for _ in 0..self.indent_size {
2160 out.write_char(' ')?;
2161 }
2162 }
2163 Ok(())
2164 }
2165}
2166
2167#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2169pub enum ColorMode {
2170 Auto,
2172 Always,
2174 Never,
2176}
2177
2178impl ColorMode {
2179 pub fn enabled(&self) -> bool {
2181 static NO_COLOR: LazyLock<bool> = LazyLock::new(|| std::env::var_os("NO_COLOR").is_some());
2182 match self {
2183 ColorMode::Auto => !*NO_COLOR,
2184 ColorMode::Always => true,
2185 ColorMode::Never => false,
2186 }
2187 }
2188}
2189
2190impl From<bool> for ColorMode {
2191 fn from(value: bool) -> Self {
2192 if value {
2193 ColorMode::Always
2194 } else {
2195 ColorMode::Never
2196 }
2197 }
2198}
2199
2200impl From<ColorMode> for Option<bool> {
2201 fn from(value: ColorMode) -> Self {
2202 match value {
2203 ColorMode::Auto => None,
2204 ColorMode::Always => Some(true),
2205 ColorMode::Never => Some(false),
2206 }
2207 }
2208}
2209
2210#[derive(Debug)]
2212pub struct FormattedValue {
2213 pub text: String,
2215 pub spans: BTreeMap<Path, FieldSpan>,
2217}
2218
2219trait FormatOutput: Write {
2224 fn position(&self) -> usize;
2226
2227 fn record_span(&mut self, _path: Path, _span: Span) {}
2229
2230 fn record_field_span(&mut self, _path: Path, _key_span: Span, _value_span: Span) {}
2232}
2233
2234#[allow(dead_code)]
2237struct NonTrackingOutput<W> {
2238 inner: W,
2239 position: usize,
2240}
2241
2242#[allow(dead_code)]
2243impl<W> NonTrackingOutput<W> {
2244 const fn new(inner: W) -> Self {
2245 Self { inner, position: 0 }
2246 }
2247}
2248
2249impl<W: Write> Write for NonTrackingOutput<W> {
2250 fn write_str(&mut self, s: &str) -> fmt::Result {
2251 self.position += s.len();
2252 self.inner.write_str(s)
2253 }
2254}
2255
2256impl<W: Write> FormatOutput for NonTrackingOutput<W> {
2257 fn position(&self) -> usize {
2258 self.position
2259 }
2260 }
2262
2263struct SpanTrackingOutput {
2265 output: String,
2266 spans: BTreeMap<Path, FieldSpan>,
2267}
2268
2269impl SpanTrackingOutput {
2270 const fn new() -> Self {
2271 Self {
2272 output: String::new(),
2273 spans: BTreeMap::new(),
2274 }
2275 }
2276
2277 fn into_formatted_value(self) -> FormattedValue {
2278 FormattedValue {
2279 text: self.output,
2280 spans: self.spans,
2281 }
2282 }
2283}
2284
2285impl Write for SpanTrackingOutput {
2286 fn write_str(&mut self, s: &str) -> fmt::Result {
2287 self.output.push_str(s);
2288 Ok(())
2289 }
2290}
2291
2292impl FormatOutput for SpanTrackingOutput {
2293 fn position(&self) -> usize {
2294 self.output.len()
2295 }
2296
2297 fn record_span(&mut self, path: Path, span: Span) {
2298 self.spans.insert(
2299 path,
2300 FieldSpan {
2301 key: span,
2302 value: span,
2303 },
2304 );
2305 }
2306
2307 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
2308 self.spans.insert(
2309 path,
2310 FieldSpan {
2311 key: key_span,
2312 value: value_span,
2313 },
2314 );
2315 }
2316}
2317
2318#[cfg(test)]
2319mod tests {
2320 use super::*;
2321
2322 #[test]
2324 fn test_pretty_printer_default() {
2325 let printer = PrettyPrinter::default();
2326 assert_eq!(printer.indent_size, 2);
2327 assert_eq!(printer.max_depth, None);
2328 assert_eq!(printer.use_colors(), std::env::var_os("NO_COLOR").is_none());
2331 }
2332
2333 #[test]
2334 fn test_pretty_printer_with_methods() {
2335 let printer = PrettyPrinter::new()
2336 .with_indent_size(4)
2337 .with_max_depth(3)
2338 .with_colors(ColorMode::Never);
2339
2340 assert_eq!(printer.indent_size, 4);
2341 assert_eq!(printer.max_depth, Some(3));
2342 assert!(!printer.use_colors());
2343 }
2344
2345 #[test]
2346 fn test_format_peek_with_spans() {
2347 use crate::PathSegment;
2348 use facet_reflect::Peek;
2349
2350 let value = ("Alice", 30u32);
2352
2353 let printer = PrettyPrinter::new();
2354 let formatted = printer.format_peek_with_spans(Peek::new(&value));
2355
2356 assert!(!formatted.text.is_empty());
2358 assert!(formatted.text.contains("Alice"));
2359 assert!(formatted.text.contains("30"));
2360
2361 assert!(!formatted.spans.is_empty());
2363
2364 assert!(formatted.spans.contains_key(&vec![]));
2366
2367 let idx0_path = vec![PathSegment::Index(0)];
2369 let idx1_path = vec![PathSegment::Index(1)];
2370 assert!(
2371 formatted.spans.contains_key(&idx0_path),
2372 "index 0 span not found"
2373 );
2374 assert!(
2375 formatted.spans.contains_key(&idx1_path),
2376 "index 1 span not found"
2377 );
2378 }
2379
2380 #[test]
2381 fn test_max_content_len_string() {
2382 let printer = PrettyPrinter::new()
2383 .with_colors(ColorMode::Never)
2384 .with_max_content_len(20);
2385
2386 let short = "hello";
2388 let output = printer.format(&short);
2389 assert_eq!(output, "\"hello\"");
2390
2391 let long = "abcdefghijklmnopqrstuvwxyz0123456789";
2393 let output = printer.format(&long);
2394 assert!(
2395 output.contains("..."),
2396 "should contain ellipsis: {}",
2397 output
2398 );
2399 assert!(output.contains("chars"), "should mention chars: {}", output);
2400 assert!(
2401 output.starts_with("\"abc"),
2402 "should start with beginning: {}",
2403 output
2404 );
2405 assert!(
2406 output.ends_with("89\""),
2407 "should end with ending: {}",
2408 output
2409 );
2410 }
2411
2412 #[test]
2413 fn test_max_content_len_bytes() {
2414 let printer = PrettyPrinter::new()
2415 .with_colors(ColorMode::Never)
2416 .with_max_content_len(10);
2417
2418 let short: Vec<u8> = vec![1, 2, 3];
2420 let output = printer.format(&short);
2421 assert!(
2422 output.contains("01 02 03"),
2423 "should show all bytes: {}",
2424 output
2425 );
2426
2427 let long: Vec<u8> = (0..50).collect();
2429 let output = printer.format(&long);
2430 assert!(
2431 output.contains("..."),
2432 "should contain ellipsis: {}",
2433 output
2434 );
2435 assert!(output.contains("bytes"), "should mention bytes: {}", output);
2436 }
2437
2438 #[test]
2439 fn test_max_content_formatting() {
2440 let printer = PrettyPrinter::new()
2441 .with_colors(ColorMode::Never)
2442 .with_max_content_len(12);
2443 let data: Vec<u8> = (0..50).collect();
2444 let output = printer.format(&data);
2445 insta::assert_snapshot!(output, @"
2446 Vec<u8> [
2447 00 01 02 03 04 05
2448 ...(38 bytes)...
2449 2c 2d 2e 2f 30 31
2450 ]
2451 ");
2452 }
2453
2454 #[test]
2455 fn test_max_collection_len_sequence() {
2456 let printer = PrettyPrinter::new()
2457 .with_colors(ColorMode::Never)
2458 .with_max_collection_len(3);
2459
2460 let value = vec![1u32, 2, 3, 4, 5];
2461 let output = printer.format(&value);
2462
2463 assert!(output.contains("1"));
2464 assert!(output.contains("2"));
2465 assert!(output.contains("3"));
2466 assert!(!output.contains("5"));
2467 assert!(output.contains("more items"), "output: {output}");
2468 }
2469
2470 #[test]
2471 fn test_max_collection_len_struct_fields() {
2472 #[derive(facet::Facet)]
2473 struct Record {
2474 alpha: u32,
2475 beta: u32,
2476 gamma: u32,
2477 delta: u32,
2478 }
2479
2480 let printer = PrettyPrinter::new()
2481 .with_colors(ColorMode::Never)
2482 .with_max_collection_len(2);
2483
2484 let value = Record {
2485 alpha: 1,
2486 beta: 2,
2487 gamma: 3,
2488 delta: 4,
2489 };
2490 let output = printer.format(&value);
2491
2492 assert!(output.contains("alpha"));
2493 assert!(output.contains("beta"));
2494 assert!(!output.contains("gamma"));
2495 assert!(output.contains("more fields"), "output: {output}");
2496 }
2497}