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, PtrUninit,
14 SequenceType, Shape, StructKind, StructType, TextualType, Type, TypeNameOpts, UserType,
15};
16use facet_reflect::{Peek, ValueId};
17
18use owo_colors::{OwoColorize, Rgb};
19
20use crate::color::ColorGenerator;
21use crate::shape::{FieldSpan, Path, PathSegment, Span};
22
23pub mod tokyo_night {
27 use owo_colors::Rgb;
28
29 pub const FOREGROUND: Rgb = Rgb(169, 177, 214);
35 pub const BACKGROUND: Rgb = Rgb(26, 27, 38);
37 pub const COMMENT: Rgb = Rgb(86, 95, 137);
39
40 pub const BLACK: Rgb = Rgb(65, 72, 104);
46 pub const RED: Rgb = Rgb(247, 118, 142);
48 pub const GREEN: Rgb = Rgb(115, 218, 202);
50 pub const YELLOW: Rgb = Rgb(224, 175, 104);
52 pub const BLUE: Rgb = Rgb(122, 162, 247);
54 pub const MAGENTA: Rgb = Rgb(187, 154, 247);
56 pub const CYAN: Rgb = Rgb(125, 207, 255);
58 pub const WHITE: Rgb = Rgb(120, 124, 153);
60
61 pub const BRIGHT_WHITE: Rgb = Rgb(172, 176, 208);
63
64 pub const ORANGE: Rgb = Rgb(255, 158, 100);
70 pub const DARK_GREEN: Rgb = Rgb(158, 206, 106);
72
73 pub const ERROR: Rgb = Rgb(219, 75, 75);
79 pub const WARNING: Rgb = YELLOW;
81 pub const INFO: Rgb = Rgb(13, 185, 215);
83 pub const HINT: Rgb = COMMENT;
85
86 pub const TYPE_NAME: Rgb = BLUE;
92 pub const FIELD_NAME: Rgb = GREEN;
94 pub const STRING: Rgb = DARK_GREEN;
96 pub const NUMBER: Rgb = ORANGE;
98 pub const KEYWORD: Rgb = MAGENTA;
100 pub const DELETION: Rgb = RED;
102 pub const INSERTION: Rgb = GREEN;
104 pub const MUTED: Rgb = COMMENT;
106 pub const BORDER: Rgb = COMMENT;
108}
109
110#[derive(Clone, PartialEq)]
112pub struct PrettyPrinter {
113 indent_size: usize,
115 max_depth: Option<usize>,
116 color_generator: ColorGenerator,
117 colors: ColorMode,
118 list_u8_as_bytes: bool,
119 minimal_option_names: bool,
121 show_doc_comments: bool,
123 max_content_len: Option<usize>,
125}
126
127impl Default for PrettyPrinter {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133impl PrettyPrinter {
134 pub const fn new() -> Self {
136 Self {
137 indent_size: 2,
138 max_depth: None,
139 color_generator: ColorGenerator::new(),
140 colors: ColorMode::Auto,
141 list_u8_as_bytes: true,
142 minimal_option_names: false,
143 show_doc_comments: false,
144 max_content_len: None,
145 }
146 }
147
148 pub const fn with_indent_size(mut self, size: usize) -> Self {
150 self.indent_size = size;
151 self
152 }
153
154 pub const fn with_max_depth(mut self, depth: usize) -> Self {
156 self.max_depth = Some(depth);
157 self
158 }
159
160 pub const fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
162 self.color_generator = generator;
163 self
164 }
165
166 pub const fn with_colors(mut self, enable_colors: ColorMode) -> Self {
168 self.colors = enable_colors;
169 self
170 }
171
172 pub const fn with_minimal_option_names(mut self, minimal: bool) -> Self {
174 self.minimal_option_names = minimal;
175 self
176 }
177
178 pub const fn with_doc_comments(mut self, show: bool) -> Self {
180 self.show_doc_comments = show;
181 self
182 }
183
184 pub const fn with_max_content_len(mut self, max_len: usize) -> Self {
189 self.max_content_len = Some(max_len);
190 self
191 }
192
193 pub fn format<'a, T: ?Sized + Facet<'a>>(&self, value: &T) -> String {
195 let value = Peek::new(value);
196
197 let mut output = String::new();
198 self.format_peek_internal(value, &mut output, &mut BTreeMap::new())
199 .expect("Formatting failed");
200
201 output
202 }
203
204 pub fn format_to<'a, T: ?Sized + Facet<'a>>(
206 &self,
207 value: &T,
208 f: &mut fmt::Formatter<'_>,
209 ) -> fmt::Result {
210 let value = Peek::new(value);
211 self.format_peek_internal(value, f, &mut BTreeMap::new())
212 }
213
214 pub fn format_peek(&self, value: Peek<'_, '_>) -> String {
216 let mut output = String::new();
217 self.format_peek_internal(value, &mut output, &mut BTreeMap::new())
218 .expect("Formatting failed");
219 output
220 }
221
222 pub(crate) fn shape_chunkiness(shape: &Shape) -> usize {
223 let mut shape = shape;
224 while let Type::Pointer(PointerType::Reference(inner)) = shape.ty {
225 shape = inner.target;
226 }
227
228 match shape.ty {
229 Type::Pointer(_) | Type::Primitive(_) => 1,
230 Type::Sequence(SequenceType::Array(ty)) => {
231 Self::shape_chunkiness(ty.t).saturating_mul(ty.n)
232 }
233 Type::Sequence(SequenceType::Slice(_)) => usize::MAX,
234 Type::User(ty) => match ty {
235 UserType::Struct(ty) => {
236 let mut sum = 0usize;
237 for field in ty.fields {
238 sum = sum.saturating_add(Self::shape_chunkiness(field.shape()));
239 }
240 sum
241 }
242 UserType::Enum(ty) => {
243 let mut max = 0usize;
244 for variant in ty.variants {
245 max = Ord::max(max, {
246 let mut sum = 0usize;
247 for field in variant.data.fields {
248 sum = sum.saturating_add(Self::shape_chunkiness(field.shape()));
249 }
250 sum
251 })
252 }
253 max
254 }
255 UserType::Opaque | UserType::Union(_) => 1,
256 },
257 Type::Undefined => 1,
258 }
259 }
260
261 #[inline]
262 fn use_colors(&self) -> bool {
263 self.colors.enabled()
264 }
265
266 #[allow(clippy::too_many_arguments)]
267 pub(crate) fn format_peek_internal_(
268 &self,
269 value: Peek<'_, '_>,
270 f: &mut dyn Write,
271 visited: &mut BTreeMap<ValueId, usize>,
272 format_depth: usize,
273 type_depth: usize,
274 short: bool,
275 ) -> fmt::Result {
276 let mut value = value;
277 while let Ok(ptr) = value.into_pointer()
278 && let Some(pointee) = ptr.borrow_inner()
279 {
280 value = pointee;
281 }
282
283 let value = value.innermost_peek();
286 let shape = value.shape();
287
288 if let Some(prev_type_depth) = visited.insert(value.id(), type_depth) {
289 self.write_type_name(f, &value)?;
290 self.write_punctuation(f, " { ")?;
291 self.write_comment(
292 f,
293 &format!(
294 "/* cycle detected at {} (first seen at type_depth {}) */",
295 value.id(),
296 prev_type_depth,
297 ),
298 )?;
299 visited.remove(&value.id());
300 return Ok(());
301 }
302
303 if let Some(proxy_def) = shape.proxy {
305 let result = self.format_via_proxy(
306 value,
307 proxy_def,
308 f,
309 visited,
310 format_depth,
311 type_depth,
312 short,
313 );
314
315 visited.remove(&value.id());
316 return result;
317 }
318
319 match (shape.def, shape.ty) {
320 (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
321 let value = value.get::<str>().unwrap();
322 self.format_str_value(f, value)?;
323 }
324 (Def::Scalar, _) if value.shape().id == <alloc::string::String as Facet>::SHAPE.id => {
326 let s = value.get::<alloc::string::String>().unwrap();
327 self.format_str_value(f, s)?;
328 }
329 (Def::Scalar, _) => self.format_scalar(value, f)?,
330 (Def::Option(_), _) => {
331 let option = value.into_option().unwrap();
332
333 if !self.minimal_option_names {
335 self.write_type_name(f, &value)?;
336 }
337
338 if let Some(inner) = option.value() {
339 let prefix = if self.minimal_option_names {
340 "Some("
341 } else {
342 "::Some("
343 };
344 self.write_punctuation(f, prefix)?;
345 self.format_peek_internal_(
346 inner,
347 f,
348 visited,
349 format_depth,
350 type_depth + 1,
351 short,
352 )?;
353 self.write_punctuation(f, ")")?;
354 } else {
355 let suffix = if self.minimal_option_names {
356 "None"
357 } else {
358 "::None"
359 };
360 self.write_punctuation(f, suffix)?;
361 }
362 }
363
364 (_, Type::Pointer(PointerType::Raw(_) | PointerType::Function(_))) => {
365 self.write_type_name(f, &value)?;
366 let addr = unsafe { value.data().read::<*const ()>() };
367 let value = Peek::new(&addr);
368 self.format_scalar(value, f)?;
369 }
370
371 (_, Type::User(UserType::Union(_))) => {
372 if !short && self.show_doc_comments {
373 for &line in shape.doc {
374 self.write_comment(f, &format!("///{line}"))?;
375 writeln!(f)?;
376 self.indent(f, format_depth)?;
377 }
378 }
379 self.write_type_name(f, &value)?;
380
381 self.write_punctuation(f, " { ")?;
382 self.write_comment(f, "/* contents of untagged union */")?;
383 self.write_punctuation(f, " }")?;
384 }
385
386 (
387 _,
388 Type::User(UserType::Struct(
389 ty @ StructType {
390 kind: StructKind::Tuple | StructKind::TupleStruct,
391 ..
392 },
393 )),
394 ) => {
395 if !short && self.show_doc_comments {
396 for &line in shape.doc {
397 self.write_comment(f, &format!("///{line}"))?;
398 writeln!(f)?;
399 self.indent(f, format_depth)?;
400 }
401 }
402
403 self.write_type_name(f, &value)?;
404 if matches!(ty.kind, StructKind::Tuple) {
405 write!(f, " ")?;
406 }
407 let value = value.into_struct().unwrap();
408
409 let fields = ty.fields;
410 self.format_tuple_fields(
411 &|i| value.field(i).unwrap(),
412 f,
413 visited,
414 format_depth,
415 type_depth,
416 fields,
417 short,
418 matches!(ty.kind, StructKind::Tuple),
419 )?;
420 }
421
422 (
423 _,
424 Type::User(UserType::Struct(
425 ty @ StructType {
426 kind: StructKind::Struct | StructKind::Unit,
427 ..
428 },
429 )),
430 ) => {
431 if !short && self.show_doc_comments {
432 for &line in shape.doc {
433 self.write_comment(f, &format!("///{line}"))?;
434 writeln!(f)?;
435 self.indent(f, format_depth)?;
436 }
437 }
438
439 self.write_type_name(f, &value)?;
440
441 if matches!(ty.kind, StructKind::Struct) {
442 let value = value.into_struct().unwrap();
443 self.format_struct_fields(
444 &|i| value.field(i).unwrap(),
445 f,
446 visited,
447 format_depth,
448 type_depth,
449 ty.fields,
450 short,
451 )?;
452 }
453 }
454
455 (_, Type::User(UserType::Enum(_))) => {
456 let enum_peek = value.into_enum().unwrap();
457 match enum_peek.active_variant() {
458 Err(_) => {
459 self.write_type_name(f, &value)?;
461 self.write_punctuation(f, " {")?;
462 self.write_comment(f, " /* cannot determine variant */ ")?;
463 self.write_punctuation(f, "}")?;
464 }
465 Ok(variant) => {
466 if !short && self.show_doc_comments {
467 for &line in shape.doc {
468 self.write_comment(f, &format!("///{line}"))?;
469 writeln!(f)?;
470 self.indent(f, format_depth)?;
471 }
472 for &line in variant.doc {
473 self.write_comment(f, &format!("///{line}"))?;
474 writeln!(f)?;
475 self.indent(f, format_depth)?;
476 }
477 }
478 self.write_type_name(f, &value)?;
479 self.write_punctuation(f, "::")?;
480
481 if self.use_colors() {
488 write!(f, "{}", variant.name.bold())?;
489 } else {
490 write!(f, "{}", variant.name)?;
491 }
492
493 match variant.data.kind {
495 StructKind::Unit => {
496 }
498 StructKind::Struct => self.format_struct_fields(
499 &|i| enum_peek.field(i).unwrap().unwrap(),
500 f,
501 visited,
502 format_depth,
503 type_depth,
504 variant.data.fields,
505 short,
506 )?,
507 _ => self.format_tuple_fields(
508 &|i| enum_peek.field(i).unwrap().unwrap(),
509 f,
510 visited,
511 format_depth,
512 type_depth,
513 variant.data.fields,
514 short,
515 false,
516 )?,
517 }
518 }
519 };
520 }
521
522 _ if value.into_list_like().is_ok() => {
523 let list = value.into_list_like().unwrap();
524
525 self.write_type_name(f, &value)?;
530
531 if !list.is_empty() {
532 if list.def().t().is_type::<u8>() && self.list_u8_as_bytes {
533 let total_len = list.len();
534 let truncate = self.max_content_len.is_some_and(|max| total_len > max);
535
536 self.write_punctuation(f, " [")?;
537
538 if truncate {
539 let max = self.max_content_len.unwrap();
540 let half = max / 2;
541 let start_count = half;
542 let end_count = half;
543
544 for (idx, item) in list.iter().enumerate().take(start_count) {
546 if !short && idx % 16 == 0 {
547 writeln!(f)?;
548 self.indent(f, format_depth + 1)?;
549 }
550 write!(f, " ")?;
551 let byte = *item.get::<u8>().unwrap();
552 if self.use_colors() {
553 let mut hasher = DefaultHasher::new();
554 byte.hash(&mut hasher);
555 let hash = hasher.finish();
556 let color = self.color_generator.generate_color(hash);
557 let rgb = Rgb(color.r, color.g, color.b);
558 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
559 } else {
560 write!(f, "{byte:02x}")?;
561 }
562 }
563
564 let omitted = total_len - start_count - end_count;
566 if !short {
567 writeln!(f)?;
568 self.indent(f, format_depth + 1)?;
569 }
570 write!(f, " ...({omitted} bytes)...")?;
571
572 for (idx, item) in list.iter().enumerate().skip(total_len - end_count) {
574 let display_idx = start_count + 1 + (idx - (total_len - end_count));
575 if !short && display_idx.is_multiple_of(16) {
576 writeln!(f)?;
577 self.indent(f, format_depth + 1)?;
578 }
579 write!(f, " ")?;
580 let byte = *item.get::<u8>().unwrap();
581 if self.use_colors() {
582 let mut hasher = DefaultHasher::new();
583 byte.hash(&mut hasher);
584 let hash = hasher.finish();
585 let color = self.color_generator.generate_color(hash);
586 let rgb = Rgb(color.r, color.g, color.b);
587 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
588 } else {
589 write!(f, "{byte:02x}")?;
590 }
591 }
592 } else {
593 for (idx, item) in list.iter().enumerate() {
594 if !short && idx % 16 == 0 {
595 writeln!(f)?;
596 self.indent(f, format_depth + 1)?;
597 }
598 write!(f, " ")?;
599
600 let byte = *item.get::<u8>().unwrap();
601 if self.use_colors() {
602 let mut hasher = DefaultHasher::new();
603 byte.hash(&mut hasher);
604 let hash = hasher.finish();
605 let color = self.color_generator.generate_color(hash);
606 let rgb = Rgb(color.r, color.g, color.b);
607 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
608 } else {
609 write!(f, "{byte:02x}")?;
610 }
611 }
612 }
613
614 if !short {
615 writeln!(f)?;
616 self.indent(f, format_depth)?;
617 }
618 self.write_punctuation(f, "]")?;
619 } else {
620 let elem_shape = list.def().t();
622 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
623
624 self.write_punctuation(f, " [")?;
625 let len = list.len();
626 for (idx, item) in list.iter().enumerate() {
627 if !short && !is_simple {
628 writeln!(f)?;
629 self.indent(f, format_depth + 1)?;
630 } else if idx > 0 {
631 write!(f, " ")?;
632 }
633 self.format_peek_internal_(
634 item,
635 f,
636 visited,
637 format_depth + 1,
638 type_depth + 1,
639 short || is_simple,
640 )?;
641
642 if (!short && !is_simple) || idx + 1 < len {
643 self.write_punctuation(f, ",")?;
644 }
645 }
646 if !short && !is_simple {
647 writeln!(f)?;
648 self.indent(f, format_depth)?;
649 }
650 self.write_punctuation(f, "]")?;
651 }
652 } else {
653 self.write_punctuation(f, "[]")?;
654 }
655 }
656
657 _ if value.into_set().is_ok() => {
658 self.write_type_name(f, &value)?;
659
660 let value = value.into_set().unwrap();
661 self.write_punctuation(f, " [")?;
662 if !value.is_empty() {
663 let len = value.len();
664 for (idx, item) in value.iter().enumerate() {
665 if !short {
666 writeln!(f)?;
667 self.indent(f, format_depth + 1)?;
668 }
669 self.format_peek_internal_(
670 item,
671 f,
672 visited,
673 format_depth + 1,
674 type_depth + 1,
675 short,
676 )?;
677 if !short || idx + 1 < len {
678 self.write_punctuation(f, ",")?;
679 } else {
680 write!(f, " ")?;
681 }
682 }
683 if !short {
684 writeln!(f)?;
685 self.indent(f, format_depth)?;
686 }
687 }
688 self.write_punctuation(f, "]")?;
689 }
690
691 (Def::Map(def), _) => {
692 let key_is_short = Self::shape_chunkiness(def.k) <= 2;
693
694 self.write_type_name(f, &value)?;
695
696 let value = value.into_map().unwrap();
697 self.write_punctuation(f, " [")?;
698
699 if !value.is_empty() {
700 let len = value.len();
701 for (idx, (key, value)) in value.iter().enumerate() {
702 if !short {
703 writeln!(f)?;
704 self.indent(f, format_depth + 1)?;
705 }
706 self.format_peek_internal_(
707 key,
708 f,
709 visited,
710 format_depth + 1,
711 type_depth + 1,
712 key_is_short,
713 )?;
714 self.write_punctuation(f, " => ")?;
715 self.format_peek_internal_(
716 value,
717 f,
718 visited,
719 format_depth + 1,
720 type_depth + 1,
721 short,
722 )?;
723 if !short || idx + 1 < len {
724 self.write_punctuation(f, ",")?;
725 } else {
726 write!(f, " ")?;
727 }
728 }
729 if !short {
730 writeln!(f)?;
731 self.indent(f, format_depth)?;
732 }
733 }
734
735 self.write_punctuation(f, "]")?;
736 }
737
738 (Def::DynamicValue(_), _) => {
739 let dyn_val = value.into_dynamic_value().unwrap();
740 match dyn_val.kind() {
741 DynValueKind::Null => {
742 self.write_keyword(f, "null")?;
743 }
744 DynValueKind::Bool => {
745 if let Some(b) = dyn_val.as_bool() {
746 self.write_keyword(f, if b { "true" } else { "false" })?;
747 }
748 }
749 DynValueKind::Number => {
750 if let Some(n) = dyn_val.as_i64() {
751 self.format_number(f, &n.to_string())?;
752 } else if let Some(n) = dyn_val.as_u64() {
753 self.format_number(f, &n.to_string())?;
754 } else if let Some(n) = dyn_val.as_f64() {
755 self.format_number(f, &n.to_string())?;
756 }
757 }
758 DynValueKind::String => {
759 if let Some(s) = dyn_val.as_str() {
760 self.format_string(f, s)?;
761 }
762 }
763 DynValueKind::Bytes => {
764 if let Some(bytes) = dyn_val.as_bytes() {
765 self.format_bytes(f, bytes)?;
766 }
767 }
768 DynValueKind::Array => {
769 let len = dyn_val.array_len().unwrap_or(0);
770 if len == 0 {
771 self.write_punctuation(f, "[]")?;
772 } else {
773 self.write_punctuation(f, "[")?;
774 for idx in 0..len {
775 if !short {
776 writeln!(f)?;
777 self.indent(f, format_depth + 1)?;
778 }
779 if let Some(elem) = dyn_val.array_get(idx) {
780 self.format_peek_internal_(
781 elem,
782 f,
783 visited,
784 format_depth + 1,
785 type_depth + 1,
786 short,
787 )?;
788 }
789 if !short || idx + 1 < len {
790 self.write_punctuation(f, ",")?;
791 } else {
792 write!(f, " ")?;
793 }
794 }
795 if !short {
796 writeln!(f)?;
797 self.indent(f, format_depth)?;
798 }
799 self.write_punctuation(f, "]")?;
800 }
801 }
802 DynValueKind::Object => {
803 let len = dyn_val.object_len().unwrap_or(0);
804 if len == 0 {
805 self.write_punctuation(f, "{}")?;
806 } else {
807 self.write_punctuation(f, "{")?;
808 for idx in 0..len {
809 if !short {
810 writeln!(f)?;
811 self.indent(f, format_depth + 1)?;
812 }
813 if let Some((key, val)) = dyn_val.object_get_entry(idx) {
814 self.write_field_name(f, key)?;
815 self.write_punctuation(f, ": ")?;
816 self.format_peek_internal_(
817 val,
818 f,
819 visited,
820 format_depth + 1,
821 type_depth + 1,
822 short,
823 )?;
824 }
825 if !short || idx + 1 < len {
826 self.write_punctuation(f, ",")?;
827 } else {
828 write!(f, " ")?;
829 }
830 }
831 if !short {
832 writeln!(f)?;
833 self.indent(f, format_depth)?;
834 }
835 self.write_punctuation(f, "}")?;
836 }
837 }
838 DynValueKind::DateTime => {
839 #[allow(clippy::uninlined_format_args)]
841 if let Some((year, month, day, hour, minute, second, nanos, kind)) =
842 dyn_val.as_datetime()
843 {
844 match kind {
845 DynDateTimeKind::Offset { offset_minutes } => {
846 if nanos > 0 {
847 write!(
848 f,
849 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
850 year, month, day, hour, minute, second, nanos
851 )?;
852 } else {
853 write!(
854 f,
855 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
856 year, month, day, hour, minute, second
857 )?;
858 }
859 if offset_minutes == 0 {
860 write!(f, "Z")?;
861 } else {
862 let sign = if offset_minutes >= 0 { '+' } else { '-' };
863 let abs = offset_minutes.abs();
864 write!(f, "{}{:02}:{:02}", sign, abs / 60, abs % 60)?;
865 }
866 }
867 DynDateTimeKind::LocalDateTime => {
868 if nanos > 0 {
869 write!(
870 f,
871 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
872 year, month, day, hour, minute, second, nanos
873 )?;
874 } else {
875 write!(
876 f,
877 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
878 year, month, day, hour, minute, second
879 )?;
880 }
881 }
882 DynDateTimeKind::LocalDate => {
883 write!(f, "{:04}-{:02}-{:02}", year, month, day)?;
884 }
885 DynDateTimeKind::LocalTime => {
886 if nanos > 0 {
887 write!(
888 f,
889 "{:02}:{:02}:{:02}.{:09}",
890 hour, minute, second, nanos
891 )?;
892 } else {
893 write!(f, "{:02}:{:02}:{:02}", hour, minute, second)?;
894 }
895 }
896 }
897 }
898 }
899 DynValueKind::QName => {
900 write!(f, "<qname>")?;
902 }
903 DynValueKind::Uuid => {
904 write!(f, "<uuid>")?;
906 }
907 }
908 }
909
910 (d, t) => write!(f, "unsupported peek variant: {value:?} ({d:?}, {t:?})")?,
911 }
912
913 visited.remove(&value.id());
914 Ok(())
915 }
916
917 #[allow(clippy::too_many_arguments)]
922 fn format_via_proxy(
923 &self,
924 value: Peek<'_, '_>,
925 proxy_def: &'static facet_core::ProxyDef,
926 f: &mut dyn Write,
927 visited: &mut BTreeMap<ValueId, usize>,
928 format_depth: usize,
929 type_depth: usize,
930 short: bool,
931 ) -> fmt::Result {
932 let proxy_shape = proxy_def.shape;
933 let proxy_layout = match proxy_shape.layout.sized_layout() {
934 Ok(layout) => layout,
935 Err(_) => {
936 return write!(f, "/* proxy type must be sized for formatting */");
937 }
938 };
939
940 let proxy_mem = unsafe { alloc::alloc::alloc(proxy_layout) };
942 if proxy_mem.is_null() {
943 return write!(f, "/* failed to allocate proxy memory */");
944 }
945
946 let proxy_uninit = PtrUninit::new(proxy_mem);
948 let convert_result = unsafe { (proxy_def.convert_out)(value.data(), proxy_uninit) };
949
950 let proxy_ptr = match convert_result {
951 Ok(ptr) => ptr,
952 Err(msg) => {
953 unsafe { alloc::alloc::dealloc(proxy_mem, proxy_layout) };
954 return write!(f, "/* proxy conversion failed: {msg} */");
955 }
956 };
957
958 let proxy_peek = unsafe { Peek::unchecked_new(proxy_ptr.as_const(), proxy_shape) };
960 let result =
961 self.format_peek_internal_(proxy_peek, f, visited, format_depth, type_depth, short);
962
963 unsafe {
965 let _ = proxy_shape.call_drop_in_place(proxy_ptr);
966 alloc::alloc::dealloc(proxy_mem, proxy_layout);
967 }
968
969 result
970 }
971
972 #[allow(clippy::too_many_arguments)]
977 fn format_via_proxy_unified<O: FormatOutput>(
978 &self,
979 value: Peek<'_, '_>,
980 proxy_def: &'static facet_core::ProxyDef,
981 out: &mut O,
982 visited: &mut BTreeMap<ValueId, usize>,
983 format_depth: usize,
984 type_depth: usize,
985 short: bool,
986 current_path: Path,
987 ) -> fmt::Result {
988 let proxy_shape = proxy_def.shape;
989 let proxy_layout = match proxy_shape.layout.sized_layout() {
990 Ok(layout) => layout,
991 Err(_) => {
992 return write!(out, "/* proxy type must be sized for formatting */");
993 }
994 };
995
996 let proxy_mem = unsafe { alloc::alloc::alloc(proxy_layout) };
998 if proxy_mem.is_null() {
999 return write!(out, "/* failed to allocate proxy memory */");
1000 }
1001
1002 let proxy_uninit = PtrUninit::new(proxy_mem);
1004 let convert_result = unsafe { (proxy_def.convert_out)(value.data(), proxy_uninit) };
1005
1006 let proxy_ptr = match convert_result {
1007 Ok(ptr) => ptr,
1008 Err(msg) => {
1009 unsafe { alloc::alloc::dealloc(proxy_mem, proxy_layout) };
1010 return write!(out, "/* proxy conversion failed: {msg} */");
1011 }
1012 };
1013
1014 let proxy_peek = unsafe { Peek::unchecked_new(proxy_ptr.as_const(), proxy_shape) };
1016 let result = self.format_unified(
1017 proxy_peek,
1018 out,
1019 visited,
1020 format_depth,
1021 type_depth,
1022 short,
1023 current_path,
1024 );
1025
1026 unsafe {
1028 let _ = proxy_shape.call_drop_in_place(proxy_ptr);
1029 alloc::alloc::dealloc(proxy_mem, proxy_layout);
1030 }
1031
1032 result
1033 }
1034
1035 #[allow(clippy::too_many_arguments)]
1036 fn format_tuple_fields<'mem, 'facet>(
1037 &self,
1038 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
1039 f: &mut dyn Write,
1040 visited: &mut BTreeMap<ValueId, usize>,
1041 format_depth: usize,
1042 type_depth: usize,
1043 fields: &[Field],
1044 short: bool,
1045 force_trailing_comma: bool,
1046 ) -> fmt::Result {
1047 self.write_punctuation(f, "(")?;
1048 if let [field] = fields
1049 && field.doc.is_empty()
1050 {
1051 let field_value = peek_field(0);
1052 if let Some(proxy_def) = field.proxy() {
1053 self.format_via_proxy(
1054 field_value,
1055 proxy_def,
1056 f,
1057 visited,
1058 format_depth,
1059 type_depth,
1060 short,
1061 )?;
1062 } else {
1063 self.format_peek_internal_(
1064 field_value,
1065 f,
1066 visited,
1067 format_depth,
1068 type_depth,
1069 short,
1070 )?;
1071 }
1072
1073 if force_trailing_comma {
1074 self.write_punctuation(f, ",")?;
1075 }
1076 } else if !fields.is_empty() {
1077 for idx in 0..fields.len() {
1078 if !short {
1079 writeln!(f)?;
1080 self.indent(f, format_depth + 1)?;
1081
1082 if self.show_doc_comments {
1083 for &line in fields[idx].doc {
1084 self.write_comment(f, &format!("///{line}"))?;
1085 writeln!(f)?;
1086 self.indent(f, format_depth + 1)?;
1087 }
1088 }
1089 }
1090
1091 if fields[idx].is_sensitive() {
1092 self.write_redacted(f, "[REDACTED]")?;
1093 } else if let Some(proxy_def) = fields[idx].proxy() {
1094 self.format_via_proxy(
1096 peek_field(idx),
1097 proxy_def,
1098 f,
1099 visited,
1100 format_depth + 1,
1101 type_depth + 1,
1102 short,
1103 )?;
1104 } else {
1105 self.format_peek_internal_(
1106 peek_field(idx),
1107 f,
1108 visited,
1109 format_depth + 1,
1110 type_depth + 1,
1111 short,
1112 )?;
1113 }
1114
1115 if !short || idx + 1 < fields.len() {
1116 self.write_punctuation(f, ",")?;
1117 } else {
1118 write!(f, " ")?;
1119 }
1120 }
1121 if !short {
1122 writeln!(f)?;
1123 self.indent(f, format_depth)?;
1124 }
1125 }
1126 self.write_punctuation(f, ")")?;
1127 Ok(())
1128 }
1129
1130 #[allow(clippy::too_many_arguments)]
1131 fn format_struct_fields<'mem, 'facet>(
1132 &self,
1133 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
1134 f: &mut dyn Write,
1135 visited: &mut BTreeMap<ValueId, usize>,
1136 format_depth: usize,
1137 type_depth: usize,
1138 fields: &[Field],
1139 short: bool,
1140 ) -> fmt::Result {
1141 let visible_indices: Vec<usize> = (0..fields.len())
1143 .filter(|&idx| {
1144 let field = &fields[idx];
1145 let field_ptr = peek_field(idx).data();
1147 !unsafe { field.should_skip_serializing(field_ptr) }
1148 })
1149 .collect();
1150
1151 self.write_punctuation(f, " {")?;
1152 if !visible_indices.is_empty() {
1153 for (i, &idx) in visible_indices.iter().enumerate() {
1154 let is_last = i + 1 == visible_indices.len();
1155
1156 if !short {
1157 writeln!(f)?;
1158 self.indent(f, format_depth + 1)?;
1159 }
1160
1161 if self.show_doc_comments {
1162 for &line in fields[idx].doc {
1163 self.write_comment(f, &format!("///{line}"))?;
1164 writeln!(f)?;
1165 self.indent(f, format_depth + 1)?;
1166 }
1167 }
1168
1169 self.write_field_name(f, fields[idx].name)?;
1170 self.write_punctuation(f, ": ")?;
1171 if fields[idx].is_sensitive() {
1172 self.write_redacted(f, "[REDACTED]")?;
1173 } else if let Some(proxy_def) = fields[idx].proxy() {
1174 self.format_via_proxy(
1176 peek_field(idx),
1177 proxy_def,
1178 f,
1179 visited,
1180 format_depth + 1,
1181 type_depth + 1,
1182 short,
1183 )?;
1184 } else {
1185 self.format_peek_internal_(
1186 peek_field(idx),
1187 f,
1188 visited,
1189 format_depth + 1,
1190 type_depth + 1,
1191 short,
1192 )?;
1193 }
1194
1195 if !short || !is_last {
1196 self.write_punctuation(f, ",")?;
1197 } else {
1198 write!(f, " ")?;
1199 }
1200 }
1201 if !short {
1202 writeln!(f)?;
1203 self.indent(f, format_depth)?;
1204 }
1205 }
1206 self.write_punctuation(f, "}")?;
1207 Ok(())
1208 }
1209
1210 fn indent(&self, f: &mut dyn Write, indent: usize) -> fmt::Result {
1211 if self.indent_size == usize::MAX {
1212 write!(f, "{:\t<width$}", "", width = indent)
1213 } else {
1214 write!(f, "{: <width$}", "", width = indent * self.indent_size)
1215 }
1216 }
1217
1218 pub(crate) fn format_peek_internal(
1220 &self,
1221 value: Peek<'_, '_>,
1222 f: &mut dyn Write,
1223 visited: &mut BTreeMap<ValueId, usize>,
1224 ) -> fmt::Result {
1225 self.format_peek_internal_(value, f, visited, 0, 0, false)
1226 }
1227
1228 fn format_scalar(&self, value: Peek, f: &mut dyn Write) -> fmt::Result {
1230 let mut hasher = DefaultHasher::new();
1232 value.shape().id.hash(&mut hasher);
1233 let hash = hasher.finish();
1234 let color = self.color_generator.generate_color(hash);
1235
1236 struct DisplayWrapper<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
1238
1239 impl fmt::Display for DisplayWrapper<'_, '_> {
1240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1241 if self.0.shape().is_display() {
1242 write!(f, "{}", self.0)?;
1243 } else if self.0.shape().is_debug() {
1244 write!(f, "{:?}", self.0)?;
1245 } else {
1246 write!(f, "{}", self.0.shape())?;
1247 write!(f, "(…)")?;
1248 }
1249 Ok(())
1250 }
1251 }
1252
1253 if self.use_colors() {
1255 let rgb = Rgb(color.r, color.g, color.b);
1256 write!(f, "{}", DisplayWrapper(&value).color(rgb))?;
1257 } else {
1258 write!(f, "{}", DisplayWrapper(&value))?;
1259 }
1260
1261 Ok(())
1262 }
1263
1264 fn write_keyword(&self, f: &mut dyn Write, keyword: &str) -> fmt::Result {
1266 if self.use_colors() {
1267 write!(f, "{}", keyword.color(tokyo_night::KEYWORD))
1268 } else {
1269 write!(f, "{keyword}")
1270 }
1271 }
1272
1273 fn format_number(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1275 if self.use_colors() {
1276 write!(f, "{}", s.color(tokyo_night::NUMBER))
1277 } else {
1278 write!(f, "{s}")
1279 }
1280 }
1281
1282 fn format_str_value(&self, f: &mut dyn Write, value: &str) -> fmt::Result {
1284 if let Some(max) = self.max_content_len
1286 && value.len() > max
1287 {
1288 return self.format_truncated_str(f, value, max);
1289 }
1290
1291 let mut hashes = 0usize;
1293 let mut rest = value;
1294 while let Some(idx) = rest.find('"') {
1295 rest = &rest[idx + 1..];
1296 let before = rest.len();
1297 rest = rest.trim_start_matches('#');
1298 let after = rest.len();
1299 let count = before - after;
1300 hashes = Ord::max(hashes, 1 + count);
1301 }
1302
1303 let pad = "";
1304 let width = hashes.saturating_sub(1);
1305 if hashes > 0 {
1306 write!(f, "r{pad:#<width$}")?;
1307 }
1308 write!(f, "\"")?;
1309 if self.use_colors() {
1310 write!(f, "{}", value.color(tokyo_night::STRING))?;
1311 } else {
1312 write!(f, "{value}")?;
1313 }
1314 write!(f, "\"")?;
1315 if hashes > 0 {
1316 write!(f, "{pad:#<width$}")?;
1317 }
1318 Ok(())
1319 }
1320
1321 fn format_truncated_str(&self, f: &mut dyn Write, s: &str, max: usize) -> fmt::Result {
1323 let half = max / 2;
1324
1325 let start_end = s
1327 .char_indices()
1328 .take_while(|(i, _)| *i < half)
1329 .last()
1330 .map(|(i, c)| i + c.len_utf8())
1331 .unwrap_or(0);
1332
1333 let end_start = s
1335 .char_indices()
1336 .rev()
1337 .take_while(|(i, _)| s.len() - *i <= half)
1338 .last()
1339 .map(|(i, _)| i)
1340 .unwrap_or(s.len());
1341
1342 let omitted = s[start_end..end_start].chars().count();
1343 let start_part = &s[..start_end];
1344 let end_part = &s[end_start..];
1345
1346 if self.use_colors() {
1347 write!(
1348 f,
1349 "\"{}\"...({omitted} chars)...\"{}\"",
1350 start_part.color(tokyo_night::STRING),
1351 end_part.color(tokyo_night::STRING)
1352 )
1353 } else {
1354 write!(f, "\"{start_part}\"...({omitted} chars)...\"{end_part}\"")
1355 }
1356 }
1357
1358 fn format_string(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1360 if let Some(max) = self.max_content_len
1361 && s.len() > max
1362 {
1363 return self.format_truncated_str(f, s, max);
1364 }
1365
1366 if self.use_colors() {
1367 write!(f, "\"{}\"", s.color(tokyo_night::STRING))
1368 } else {
1369 write!(f, "{s:?}")
1370 }
1371 }
1372
1373 fn format_bytes(&self, f: &mut dyn Write, bytes: &[u8]) -> fmt::Result {
1375 write!(f, "b\"")?;
1376
1377 match self.max_content_len {
1378 Some(max) if bytes.len() > max => {
1379 let half = max / 2;
1381 let start = half;
1382 let end = half;
1383
1384 for byte in &bytes[..start] {
1385 write!(f, "\\x{byte:02x}")?;
1386 }
1387 let omitted = bytes.len() - start - end;
1388 write!(f, "\"...({omitted} bytes)...b\"")?;
1389 for byte in &bytes[bytes.len() - end..] {
1390 write!(f, "\\x{byte:02x}")?;
1391 }
1392 }
1393 _ => {
1394 for byte in bytes {
1395 write!(f, "\\x{byte:02x}")?;
1396 }
1397 }
1398 }
1399
1400 write!(f, "\"")
1401 }
1402
1403 fn write_type_name(&self, f: &mut dyn Write, peek: &Peek) -> fmt::Result {
1405 struct TypeNameWriter<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
1406
1407 impl core::fmt::Display for TypeNameWriter<'_, '_> {
1408 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1409 self.0.type_name(f, TypeNameOpts::infinite())
1410 }
1411 }
1412 let type_name = TypeNameWriter(peek);
1413
1414 if self.use_colors() {
1415 write!(f, "{}", type_name.color(tokyo_night::TYPE_NAME).bold())
1416 } else {
1417 write!(f, "{type_name}")
1418 }
1419 }
1420
1421 #[allow(dead_code)]
1423 fn style_type_name(&self, peek: &Peek) -> String {
1424 let mut result = String::new();
1425 self.write_type_name(&mut result, peek).unwrap();
1426 result
1427 }
1428
1429 fn write_field_name(&self, f: &mut dyn Write, name: &str) -> fmt::Result {
1431 if self.use_colors() {
1432 write!(f, "{}", name.color(tokyo_night::FIELD_NAME))
1433 } else {
1434 write!(f, "{name}")
1435 }
1436 }
1437
1438 fn write_punctuation(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1440 if self.use_colors() {
1441 write!(f, "{}", text.dimmed())
1442 } else {
1443 write!(f, "{text}")
1444 }
1445 }
1446
1447 fn write_comment(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1449 if self.use_colors() {
1450 write!(f, "{}", text.color(tokyo_night::MUTED))
1451 } else {
1452 write!(f, "{text}")
1453 }
1454 }
1455
1456 fn write_redacted(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1458 if self.use_colors() {
1459 write!(f, "{}", text.color(tokyo_night::ERROR).bold())
1460 } else {
1461 write!(f, "{text}")
1462 }
1463 }
1464
1465 #[allow(dead_code)]
1467 fn style_redacted(&self, text: &str) -> String {
1468 let mut result = String::new();
1469 self.write_redacted(&mut result, text).unwrap();
1470 result
1471 }
1472
1473 pub fn format_peek_with_spans(&self, value: Peek<'_, '_>) -> FormattedValue {
1481 let mut output = SpanTrackingOutput::new();
1482 let printer = Self {
1483 colors: ColorMode::Never, indent_size: self.indent_size,
1485 max_depth: self.max_depth,
1486 color_generator: self.color_generator.clone(),
1487 list_u8_as_bytes: self.list_u8_as_bytes,
1488 minimal_option_names: self.minimal_option_names,
1489 show_doc_comments: self.show_doc_comments,
1490 max_content_len: self.max_content_len,
1491 };
1492 printer
1493 .format_unified(
1494 value,
1495 &mut output,
1496 &mut BTreeMap::new(),
1497 0,
1498 0,
1499 false,
1500 vec![],
1501 )
1502 .expect("Formatting failed");
1503
1504 output.into_formatted_value()
1505 }
1506
1507 #[allow(clippy::too_many_arguments)]
1512 fn format_unified<O: FormatOutput>(
1513 &self,
1514 value: Peek<'_, '_>,
1515 out: &mut O,
1516 visited: &mut BTreeMap<ValueId, usize>,
1517 format_depth: usize,
1518 type_depth: usize,
1519 short: bool,
1520 current_path: Path,
1521 ) -> fmt::Result {
1522 let mut value = value;
1523 while let Ok(ptr) = value.into_pointer()
1524 && let Some(pointee) = ptr.borrow_inner()
1525 {
1526 value = pointee;
1527 }
1528
1529 let value = value.innermost_peek();
1532 let shape = value.shape();
1533
1534 let value_start = out.position();
1536
1537 if let Some(prev_type_depth) = visited.insert(value.id(), type_depth) {
1538 write!(out, "{} {{ ", shape.type_identifier)?;
1539 write!(
1540 out,
1541 "/* cycle detected at {} (first seen at type_depth {}) */",
1542 value.id(),
1543 prev_type_depth,
1544 )?;
1545 visited.remove(&value.id());
1546 let value_end = out.position();
1547 out.record_span(current_path, (value_start, value_end));
1548 return Ok(());
1549 }
1550
1551 if let Some(proxy_def) = shape.proxy {
1553 let result = self.format_via_proxy_unified(
1554 value,
1555 proxy_def,
1556 out,
1557 visited,
1558 format_depth,
1559 type_depth,
1560 short,
1561 current_path.clone(),
1562 );
1563
1564 visited.remove(&value.id());
1565
1566 let value_end = out.position();
1568 out.record_span(current_path, (value_start, value_end));
1569
1570 return result;
1571 }
1572
1573 match (shape.def, shape.ty) {
1574 (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
1575 let s = value.get::<str>().unwrap();
1576 write!(out, "\"{}\"", s)?;
1577 }
1578 (Def::Scalar, _) if value.shape().id == <alloc::string::String as Facet>::SHAPE.id => {
1579 let s = value.get::<alloc::string::String>().unwrap();
1580 write!(out, "\"{}\"", s)?;
1581 }
1582 (Def::Scalar, _) => {
1583 self.format_scalar_to_output(value, out)?;
1584 }
1585 (Def::Option(_), _) => {
1586 let option = value.into_option().unwrap();
1587 if let Some(inner) = option.value() {
1588 write!(out, "Some(")?;
1589 self.format_unified(
1590 inner,
1591 out,
1592 visited,
1593 format_depth,
1594 type_depth + 1,
1595 short,
1596 current_path.clone(),
1597 )?;
1598 write!(out, ")")?;
1599 } else {
1600 write!(out, "None")?;
1601 }
1602 }
1603 (
1604 _,
1605 Type::User(UserType::Struct(
1606 ty @ StructType {
1607 kind: StructKind::Struct | StructKind::Unit,
1608 ..
1609 },
1610 )),
1611 ) => {
1612 write!(out, "{}", shape.type_identifier)?;
1613 if matches!(ty.kind, StructKind::Struct) {
1614 let struct_peek = value.into_struct().unwrap();
1615 write!(out, " {{")?;
1616 for (i, field) in ty.fields.iter().enumerate() {
1617 if !short {
1618 writeln!(out)?;
1619 self.indent_to_output(out, format_depth + 1)?;
1620 }
1621 let field_name_start = out.position();
1623 write!(out, "{}", field.name)?;
1624 let field_name_end = out.position();
1625 write!(out, ": ")?;
1626
1627 let mut field_path = current_path.clone();
1629 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1630
1631 let field_value_start = out.position();
1633 if let Ok(field_value) = struct_peek.field(i) {
1634 if let Some(proxy_def) = field.proxy() {
1636 self.format_via_proxy_unified(
1637 field_value,
1638 proxy_def,
1639 out,
1640 visited,
1641 format_depth + 1,
1642 type_depth + 1,
1643 short,
1644 field_path.clone(),
1645 )?;
1646 } else {
1647 self.format_unified(
1648 field_value,
1649 out,
1650 visited,
1651 format_depth + 1,
1652 type_depth + 1,
1653 short,
1654 field_path.clone(),
1655 )?;
1656 }
1657 }
1658 let field_value_end = out.position();
1659
1660 out.record_field_span(
1662 field_path,
1663 (field_name_start, field_name_end),
1664 (field_value_start, field_value_end),
1665 );
1666
1667 if !short || i + 1 < ty.fields.len() {
1668 write!(out, ",")?;
1669 }
1670 }
1671 if !short {
1672 writeln!(out)?;
1673 self.indent_to_output(out, format_depth)?;
1674 }
1675 write!(out, "}}")?;
1676 }
1677 }
1678 (
1679 _,
1680 Type::User(UserType::Struct(
1681 ty @ StructType {
1682 kind: StructKind::Tuple | StructKind::TupleStruct,
1683 ..
1684 },
1685 )),
1686 ) => {
1687 write!(out, "{}", shape.type_identifier)?;
1688 if matches!(ty.kind, StructKind::Tuple) {
1689 write!(out, " ")?;
1690 }
1691 let struct_peek = value.into_struct().unwrap();
1692 write!(out, "(")?;
1693 for (i, field) in ty.fields.iter().enumerate() {
1694 if i > 0 {
1695 write!(out, ", ")?;
1696 }
1697 let mut elem_path = current_path.clone();
1698 elem_path.push(PathSegment::Index(i));
1699
1700 let elem_start = out.position();
1701 if let Ok(field_value) = struct_peek.field(i) {
1702 if let Some(proxy_def) = field.proxy() {
1704 self.format_via_proxy_unified(
1705 field_value,
1706 proxy_def,
1707 out,
1708 visited,
1709 format_depth + 1,
1710 type_depth + 1,
1711 short,
1712 elem_path.clone(),
1713 )?;
1714 } else {
1715 self.format_unified(
1716 field_value,
1717 out,
1718 visited,
1719 format_depth + 1,
1720 type_depth + 1,
1721 short,
1722 elem_path.clone(),
1723 )?;
1724 }
1725 }
1726 let elem_end = out.position();
1727 out.record_span(elem_path, (elem_start, elem_end));
1728 }
1729 write!(out, ")")?;
1730 }
1731 (_, Type::User(UserType::Enum(_))) => {
1732 let enum_peek = value.into_enum().unwrap();
1733 match enum_peek.active_variant() {
1734 Err(_) => {
1735 write!(
1736 out,
1737 "{} {{ /* cannot determine variant */ }}",
1738 shape.type_identifier
1739 )?;
1740 }
1741 Ok(variant) => {
1742 write!(out, "{}::{}", shape.type_identifier, variant.name)?;
1743
1744 match variant.data.kind {
1745 StructKind::Unit => {}
1746 StructKind::Struct => {
1747 write!(out, " {{")?;
1748 for (i, field) in variant.data.fields.iter().enumerate() {
1749 if !short {
1750 writeln!(out)?;
1751 self.indent_to_output(out, format_depth + 1)?;
1752 }
1753 let field_name_start = out.position();
1754 write!(out, "{}", field.name)?;
1755 let field_name_end = out.position();
1756 write!(out, ": ")?;
1757
1758 let mut field_path = current_path.clone();
1759 field_path
1760 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
1761 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1762
1763 let field_value_start = out.position();
1764 if let Ok(Some(field_value)) = enum_peek.field(i) {
1765 if let Some(proxy_def) = field.proxy() {
1767 self.format_via_proxy_unified(
1768 field_value,
1769 proxy_def,
1770 out,
1771 visited,
1772 format_depth + 1,
1773 type_depth + 1,
1774 short,
1775 field_path.clone(),
1776 )?;
1777 } else {
1778 self.format_unified(
1779 field_value,
1780 out,
1781 visited,
1782 format_depth + 1,
1783 type_depth + 1,
1784 short,
1785 field_path.clone(),
1786 )?;
1787 }
1788 }
1789 let field_value_end = out.position();
1790
1791 out.record_field_span(
1792 field_path,
1793 (field_name_start, field_name_end),
1794 (field_value_start, field_value_end),
1795 );
1796
1797 if !short || i + 1 < variant.data.fields.len() {
1798 write!(out, ",")?;
1799 }
1800 }
1801 if !short {
1802 writeln!(out)?;
1803 self.indent_to_output(out, format_depth)?;
1804 }
1805 write!(out, "}}")?;
1806 }
1807 _ => {
1808 write!(out, "(")?;
1809 for (i, field) in variant.data.fields.iter().enumerate() {
1810 if i > 0 {
1811 write!(out, ", ")?;
1812 }
1813 let mut elem_path = current_path.clone();
1814 elem_path
1815 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
1816 elem_path.push(PathSegment::Index(i));
1817
1818 let elem_start = out.position();
1819 if let Ok(Some(field_value)) = enum_peek.field(i) {
1820 if let Some(proxy_def) = field.proxy() {
1822 self.format_via_proxy_unified(
1823 field_value,
1824 proxy_def,
1825 out,
1826 visited,
1827 format_depth + 1,
1828 type_depth + 1,
1829 short,
1830 elem_path.clone(),
1831 )?;
1832 } else {
1833 self.format_unified(
1834 field_value,
1835 out,
1836 visited,
1837 format_depth + 1,
1838 type_depth + 1,
1839 short,
1840 elem_path.clone(),
1841 )?;
1842 }
1843 }
1844 let elem_end = out.position();
1845 out.record_span(elem_path, (elem_start, elem_end));
1846 }
1847 write!(out, ")")?;
1848 }
1849 }
1850 }
1851 }
1852 }
1853 _ if value.into_list_like().is_ok() => {
1854 let list = value.into_list_like().unwrap();
1855
1856 let elem_shape = list.def().t();
1858 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
1859
1860 write!(out, "[")?;
1861 let len = list.len();
1862 for (i, item) in list.iter().enumerate() {
1863 if !short && !is_simple {
1864 writeln!(out)?;
1865 self.indent_to_output(out, format_depth + 1)?;
1866 } else if i > 0 {
1867 write!(out, " ")?;
1868 }
1869 let mut elem_path = current_path.clone();
1870 elem_path.push(PathSegment::Index(i));
1871
1872 let elem_start = out.position();
1873 self.format_unified(
1874 item,
1875 out,
1876 visited,
1877 format_depth + 1,
1878 type_depth + 1,
1879 short || is_simple,
1880 elem_path.clone(),
1881 )?;
1882 let elem_end = out.position();
1883 out.record_span(elem_path, (elem_start, elem_end));
1884
1885 if (!short && !is_simple) || i + 1 < len {
1886 write!(out, ",")?;
1887 }
1888 }
1889 if !short && !is_simple {
1890 writeln!(out)?;
1891 self.indent_to_output(out, format_depth)?;
1892 }
1893 write!(out, "]")?;
1894 }
1895 _ if value.into_map().is_ok() => {
1896 let map = value.into_map().unwrap();
1897 write!(out, "{{")?;
1898 for (i, (key, val)) in map.iter().enumerate() {
1899 if !short {
1900 writeln!(out)?;
1901 self.indent_to_output(out, format_depth + 1)?;
1902 }
1903 let key_start = out.position();
1905 self.format_unified(
1906 key,
1907 out,
1908 visited,
1909 format_depth + 1,
1910 type_depth + 1,
1911 true, vec![],
1913 )?;
1914 let key_end = out.position();
1915
1916 write!(out, ": ")?;
1917
1918 let key_str = self.format_peek(key);
1920 let mut entry_path = current_path.clone();
1921 entry_path.push(PathSegment::Key(Cow::Owned(key_str)));
1922
1923 let val_start = out.position();
1924 self.format_unified(
1925 val,
1926 out,
1927 visited,
1928 format_depth + 1,
1929 type_depth + 1,
1930 short,
1931 entry_path.clone(),
1932 )?;
1933 let val_end = out.position();
1934
1935 out.record_field_span(entry_path, (key_start, key_end), (val_start, val_end));
1936
1937 if !short || i + 1 < map.len() {
1938 write!(out, ",")?;
1939 }
1940 }
1941 if !short && !map.is_empty() {
1942 writeln!(out)?;
1943 self.indent_to_output(out, format_depth)?;
1944 }
1945 write!(out, "}}")?;
1946 }
1947 _ => {
1948 write!(out, "{} {{ ... }}", shape.type_identifier)?;
1950 }
1951 }
1952
1953 visited.remove(&value.id());
1954
1955 let value_end = out.position();
1957 out.record_span(current_path, (value_start, value_end));
1958
1959 Ok(())
1960 }
1961
1962 fn format_scalar_to_output(&self, value: Peek<'_, '_>, out: &mut impl Write) -> fmt::Result {
1963 if value.shape().is_display() {
1965 write!(out, "{}", value)
1966 } else if value.shape().is_debug() {
1967 write!(out, "{:?}", value)
1968 } else {
1969 write!(out, "{}(…)", value.shape())
1970 }
1971 }
1972
1973 fn indent_to_output(&self, out: &mut impl Write, depth: usize) -> fmt::Result {
1974 for _ in 0..depth {
1975 for _ in 0..self.indent_size {
1976 out.write_char(' ')?;
1977 }
1978 }
1979 Ok(())
1980 }
1981}
1982
1983#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1985pub enum ColorMode {
1986 Auto,
1988 Always,
1990 Never,
1992}
1993
1994impl ColorMode {
1995 pub fn enabled(&self) -> bool {
1997 static NO_COLOR: LazyLock<bool> = LazyLock::new(|| std::env::var_os("NO_COLOR").is_some());
1998 match self {
1999 ColorMode::Auto => !*NO_COLOR,
2000 ColorMode::Always => true,
2001 ColorMode::Never => false,
2002 }
2003 }
2004}
2005
2006impl From<bool> for ColorMode {
2007 fn from(value: bool) -> Self {
2008 if value {
2009 ColorMode::Always
2010 } else {
2011 ColorMode::Never
2012 }
2013 }
2014}
2015
2016impl From<ColorMode> for Option<bool> {
2017 fn from(value: ColorMode) -> Self {
2018 match value {
2019 ColorMode::Auto => None,
2020 ColorMode::Always => Some(true),
2021 ColorMode::Never => Some(false),
2022 }
2023 }
2024}
2025
2026#[derive(Debug)]
2028pub struct FormattedValue {
2029 pub text: String,
2031 pub spans: BTreeMap<Path, FieldSpan>,
2033}
2034
2035trait FormatOutput: Write {
2040 fn position(&self) -> usize;
2042
2043 fn record_span(&mut self, _path: Path, _span: Span) {}
2045
2046 fn record_field_span(&mut self, _path: Path, _key_span: Span, _value_span: Span) {}
2048}
2049
2050#[allow(dead_code)]
2053struct NonTrackingOutput<W> {
2054 inner: W,
2055 position: usize,
2056}
2057
2058#[allow(dead_code)]
2059impl<W> NonTrackingOutput<W> {
2060 const fn new(inner: W) -> Self {
2061 Self { inner, position: 0 }
2062 }
2063}
2064
2065impl<W: Write> Write for NonTrackingOutput<W> {
2066 fn write_str(&mut self, s: &str) -> fmt::Result {
2067 self.position += s.len();
2068 self.inner.write_str(s)
2069 }
2070}
2071
2072impl<W: Write> FormatOutput for NonTrackingOutput<W> {
2073 fn position(&self) -> usize {
2074 self.position
2075 }
2076 }
2078
2079struct SpanTrackingOutput {
2081 output: String,
2082 spans: BTreeMap<Path, FieldSpan>,
2083}
2084
2085impl SpanTrackingOutput {
2086 const fn new() -> Self {
2087 Self {
2088 output: String::new(),
2089 spans: BTreeMap::new(),
2090 }
2091 }
2092
2093 fn into_formatted_value(self) -> FormattedValue {
2094 FormattedValue {
2095 text: self.output,
2096 spans: self.spans,
2097 }
2098 }
2099}
2100
2101impl Write for SpanTrackingOutput {
2102 fn write_str(&mut self, s: &str) -> fmt::Result {
2103 self.output.push_str(s);
2104 Ok(())
2105 }
2106}
2107
2108impl FormatOutput for SpanTrackingOutput {
2109 fn position(&self) -> usize {
2110 self.output.len()
2111 }
2112
2113 fn record_span(&mut self, path: Path, span: Span) {
2114 self.spans.insert(
2115 path,
2116 FieldSpan {
2117 key: span,
2118 value: span,
2119 },
2120 );
2121 }
2122
2123 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
2124 self.spans.insert(
2125 path,
2126 FieldSpan {
2127 key: key_span,
2128 value: value_span,
2129 },
2130 );
2131 }
2132}
2133
2134#[cfg(test)]
2135mod tests {
2136 use super::*;
2137
2138 #[test]
2140 fn test_pretty_printer_default() {
2141 let printer = PrettyPrinter::default();
2142 assert_eq!(printer.indent_size, 2);
2143 assert_eq!(printer.max_depth, None);
2144 assert_eq!(printer.use_colors(), std::env::var_os("NO_COLOR").is_none());
2147 }
2148
2149 #[test]
2150 fn test_pretty_printer_with_methods() {
2151 let printer = PrettyPrinter::new()
2152 .with_indent_size(4)
2153 .with_max_depth(3)
2154 .with_colors(ColorMode::Never);
2155
2156 assert_eq!(printer.indent_size, 4);
2157 assert_eq!(printer.max_depth, Some(3));
2158 assert!(!printer.use_colors());
2159 }
2160
2161 #[test]
2162 fn test_format_peek_with_spans() {
2163 use crate::PathSegment;
2164 use facet_reflect::Peek;
2165
2166 let value = ("Alice", 30u32);
2168
2169 let printer = PrettyPrinter::new();
2170 let formatted = printer.format_peek_with_spans(Peek::new(&value));
2171
2172 assert!(!formatted.text.is_empty());
2174 assert!(formatted.text.contains("Alice"));
2175 assert!(formatted.text.contains("30"));
2176
2177 assert!(!formatted.spans.is_empty());
2179
2180 assert!(formatted.spans.contains_key(&vec![]));
2182
2183 let idx0_path = vec![PathSegment::Index(0)];
2185 let idx1_path = vec![PathSegment::Index(1)];
2186 assert!(
2187 formatted.spans.contains_key(&idx0_path),
2188 "index 0 span not found"
2189 );
2190 assert!(
2191 formatted.spans.contains_key(&idx1_path),
2192 "index 1 span not found"
2193 );
2194 }
2195
2196 #[test]
2197 fn test_max_content_len_string() {
2198 let printer = PrettyPrinter::new()
2199 .with_colors(ColorMode::Never)
2200 .with_max_content_len(20);
2201
2202 let short = "hello";
2204 let output = printer.format(&short);
2205 assert_eq!(output, "\"hello\"");
2206
2207 let long = "abcdefghijklmnopqrstuvwxyz0123456789";
2209 let output = printer.format(&long);
2210 assert!(
2211 output.contains("..."),
2212 "should contain ellipsis: {}",
2213 output
2214 );
2215 assert!(output.contains("chars"), "should mention chars: {}", output);
2216 assert!(
2217 output.starts_with("\"abc"),
2218 "should start with beginning: {}",
2219 output
2220 );
2221 assert!(
2222 output.ends_with("89\""),
2223 "should end with ending: {}",
2224 output
2225 );
2226 }
2227
2228 #[test]
2229 fn test_max_content_len_bytes() {
2230 let printer = PrettyPrinter::new()
2231 .with_colors(ColorMode::Never)
2232 .with_max_content_len(10);
2233
2234 let short: Vec<u8> = vec![1, 2, 3];
2236 let output = printer.format(&short);
2237 assert!(
2238 output.contains("01 02 03"),
2239 "should show all bytes: {}",
2240 output
2241 );
2242
2243 let long: Vec<u8> = (0..50).collect();
2245 let output = printer.format(&long);
2246 assert!(
2247 output.contains("..."),
2248 "should contain ellipsis: {}",
2249 output
2250 );
2251 assert!(output.contains("bytes"), "should mention bytes: {}", output);
2252 }
2253}