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