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