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;
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
110pub struct PrettyPrinter {
112 indent_size: usize,
114 max_depth: Option<usize>,
115 color_generator: ColorGenerator,
116 use_colors: bool,
117 list_u8_as_bytes: bool,
118 minimal_option_names: bool,
120 show_doc_comments: bool,
122}
123
124impl Default for PrettyPrinter {
125 fn default() -> Self {
126 Self {
127 indent_size: 2,
128 max_depth: None,
129 color_generator: ColorGenerator::default(),
130 use_colors: std::env::var_os("NO_COLOR").is_none(),
131 list_u8_as_bytes: true,
132 minimal_option_names: false,
133 show_doc_comments: false,
134 }
135 }
136}
137
138impl PrettyPrinter {
139 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn with_indent_size(mut self, size: usize) -> Self {
146 self.indent_size = size;
147 self
148 }
149
150 pub fn with_max_depth(mut self, depth: usize) -> Self {
152 self.max_depth = Some(depth);
153 self
154 }
155
156 pub fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
158 self.color_generator = generator;
159 self
160 }
161
162 pub fn with_colors(mut self, use_colors: bool) -> Self {
164 self.use_colors = use_colors;
165 self
166 }
167
168 pub fn with_minimal_option_names(mut self, minimal: bool) -> Self {
170 self.minimal_option_names = minimal;
171 self
172 }
173
174 pub fn with_doc_comments(mut self, show: bool) -> Self {
176 self.show_doc_comments = show;
177 self
178 }
179
180 pub fn format<'a, T: ?Sized + Facet<'a>>(&self, value: &T) -> String {
182 let value = Peek::new(value);
183
184 let mut output = String::new();
185 self.format_peek_internal(value, &mut output, &mut BTreeMap::new())
186 .expect("Formatting failed");
187
188 output
189 }
190
191 pub fn format_to<'a, T: ?Sized + Facet<'a>>(
193 &self,
194 value: &T,
195 f: &mut fmt::Formatter<'_>,
196 ) -> fmt::Result {
197 let value = Peek::new(value);
198 self.format_peek_internal(value, f, &mut BTreeMap::new())
199 }
200
201 pub fn format_peek(&self, value: Peek<'_, '_>) -> String {
203 let mut output = String::new();
204 self.format_peek_internal(value, &mut output, &mut BTreeMap::new())
205 .expect("Formatting failed");
206 output
207 }
208
209 pub(crate) fn shape_chunkiness(shape: &Shape) -> usize {
210 let mut shape = shape;
211 while let Type::Pointer(PointerType::Reference(inner)) = shape.ty {
212 shape = inner.target;
213 }
214
215 match shape.ty {
216 Type::Pointer(_) | Type::Primitive(_) => 1,
217 Type::Sequence(SequenceType::Array(ty)) => {
218 Self::shape_chunkiness(ty.t).saturating_mul(ty.n)
219 }
220 Type::Sequence(SequenceType::Slice(_)) => usize::MAX,
221 Type::User(ty) => match ty {
222 UserType::Struct(ty) => {
223 let mut sum = 0usize;
224 for field in ty.fields {
225 sum = sum.saturating_add(Self::shape_chunkiness(field.shape()));
226 }
227 sum
228 }
229 UserType::Enum(ty) => {
230 let mut max = 0usize;
231 for variant in ty.variants {
232 max = Ord::max(max, {
233 let mut sum = 0usize;
234 for field in variant.data.fields {
235 sum = sum.saturating_add(Self::shape_chunkiness(field.shape()));
236 }
237 sum
238 })
239 }
240 max
241 }
242 UserType::Opaque | UserType::Union(_) => 1,
243 },
244 Type::Undefined => 1,
245 }
246 }
247
248 #[allow(clippy::too_many_arguments)]
249 pub(crate) fn format_peek_internal_(
250 &self,
251 value: Peek<'_, '_>,
252 f: &mut dyn Write,
253 visited: &mut BTreeMap<ValueId, usize>,
254 format_depth: usize,
255 type_depth: usize,
256 short: bool,
257 ) -> fmt::Result {
258 let mut value = value;
259 while let Ok(ptr) = value.into_pointer()
260 && let Some(pointee) = ptr.borrow_inner()
261 {
262 value = pointee;
263 }
264 let shape = value.shape();
265
266 if let Some(prev_type_depth) = visited.insert(value.id(), type_depth) {
267 self.write_type_name(f, &value)?;
268 self.write_punctuation(f, " { ")?;
269 self.write_comment(
270 f,
271 &format!(
272 "/* cycle detected at {} (first seen at type_depth {}) */",
273 value.id(),
274 prev_type_depth,
275 ),
276 )?;
277 visited.remove(&value.id());
278 return Ok(());
279 }
280
281 match (shape.def, shape.ty) {
282 (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
283 let value = value.get::<str>().unwrap();
284 let mut hashes = 0usize;
285
286 let mut rest = value;
287 while let Some(idx) = rest.find('"') {
288 rest = &rest[idx + 1..];
289 let before = rest.len();
290 rest = rest.trim_start_matches('#');
291 let after = rest.len();
292 let count = before - after;
293 hashes = Ord::max(hashes, 1 + count);
294 }
295
296 let pad = "";
297 let width = hashes.saturating_sub(1);
298 if hashes > 0 {
299 write!(f, "r{pad:#<width$}")?;
300 }
301 write!(f, "\"")?;
302 if self.use_colors {
303 write!(f, "{}", value.color(tokyo_night::STRING))?;
304 } else {
305 write!(f, "{value}")?;
306 }
307 write!(f, "\"")?;
308 if hashes > 0 {
309 write!(f, "{pad:#<width$}")?;
310 }
311 }
312 (Def::Scalar, _) if value.shape().id == <alloc::string::String as Facet>::SHAPE.id => {
314 let s = value.get::<alloc::string::String>().unwrap();
315 write!(f, "\"")?;
316 if self.use_colors {
317 write!(f, "{}", s.color(tokyo_night::STRING))?;
318 } else {
319 write!(f, "{s}")?;
320 }
321 write!(f, "\"")?;
322 }
323 (Def::Scalar, _) => self.format_scalar(value, f)?,
324 (Def::Option(_), _) => {
325 let option = value.into_option().unwrap();
326
327 if !self.minimal_option_names {
329 self.write_type_name(f, &value)?;
330 }
331
332 if let Some(inner) = option.value() {
333 let prefix = if self.minimal_option_names {
334 "Some("
335 } else {
336 "::Some("
337 };
338 self.write_punctuation(f, prefix)?;
339 self.format_peek_internal_(
340 inner,
341 f,
342 visited,
343 format_depth,
344 type_depth + 1,
345 short,
346 )?;
347 self.write_punctuation(f, ")")?;
348 } else {
349 let suffix = if self.minimal_option_names {
350 "None"
351 } else {
352 "::None"
353 };
354 self.write_punctuation(f, suffix)?;
355 }
356 }
357
358 (_, Type::Pointer(PointerType::Raw(_) | PointerType::Function(_))) => {
359 self.write_type_name(f, &value)?;
360 let addr = unsafe { value.data().read::<*const ()>() };
361 let value = Peek::new(&addr);
362 self.format_scalar(value, f)?;
363 }
364
365 (_, Type::User(UserType::Union(_))) => {
366 if !short && self.show_doc_comments {
367 for &line in shape.doc {
368 self.write_comment(f, &format!("///{line}"))?;
369 writeln!(f)?;
370 self.indent(f, format_depth)?;
371 }
372 }
373 self.write_type_name(f, &value)?;
374
375 self.write_punctuation(f, " { ")?;
376 self.write_comment(f, "/* contents of untagged union */")?;
377 self.write_punctuation(f, " }")?;
378 }
379
380 (
381 _,
382 Type::User(UserType::Struct(
383 ty @ StructType {
384 kind: StructKind::Tuple | StructKind::TupleStruct,
385 ..
386 },
387 )),
388 ) => {
389 if !short && self.show_doc_comments {
390 for &line in shape.doc {
391 self.write_comment(f, &format!("///{line}"))?;
392 writeln!(f)?;
393 self.indent(f, format_depth)?;
394 }
395 }
396
397 self.write_type_name(f, &value)?;
398 if matches!(ty.kind, StructKind::Tuple) {
399 write!(f, " ")?;
400 }
401 let value = value.into_struct().unwrap();
402
403 let fields = ty.fields;
404 self.format_tuple_fields(
405 &|i| value.field(i).unwrap(),
406 f,
407 visited,
408 format_depth,
409 type_depth,
410 fields,
411 short,
412 matches!(ty.kind, StructKind::Tuple),
413 )?;
414 }
415
416 (
417 _,
418 Type::User(UserType::Struct(
419 ty @ StructType {
420 kind: StructKind::Struct | StructKind::Unit,
421 ..
422 },
423 )),
424 ) => {
425 if !short && self.show_doc_comments {
426 for &line in shape.doc {
427 self.write_comment(f, &format!("///{line}"))?;
428 writeln!(f)?;
429 self.indent(f, format_depth)?;
430 }
431 }
432
433 self.write_type_name(f, &value)?;
434
435 if matches!(ty.kind, StructKind::Struct) {
436 let value = value.into_struct().unwrap();
437 self.format_struct_fields(
438 &|i| value.field(i).unwrap(),
439 f,
440 visited,
441 format_depth,
442 type_depth,
443 ty.fields,
444 short,
445 )?;
446 }
447 }
448
449 (_, Type::User(UserType::Enum(_))) => {
450 let enum_peek = value.into_enum().unwrap();
451 match enum_peek.active_variant() {
452 Err(_) => {
453 self.write_type_name(f, &value)?;
455 self.write_punctuation(f, " {")?;
456 self.write_comment(f, " /* cannot determine variant */ ")?;
457 self.write_punctuation(f, "}")?;
458 }
459 Ok(variant) => {
460 if !short && self.show_doc_comments {
461 for &line in shape.doc {
462 self.write_comment(f, &format!("///{line}"))?;
463 writeln!(f)?;
464 self.indent(f, format_depth)?;
465 }
466 for &line in variant.doc {
467 self.write_comment(f, &format!("///{line}"))?;
468 writeln!(f)?;
469 self.indent(f, format_depth)?;
470 }
471 }
472 self.write_type_name(f, &value)?;
473 self.write_punctuation(f, "::")?;
474
475 if self.use_colors {
482 write!(f, "{}", variant.name.bold())?;
483 } else {
484 write!(f, "{}", variant.name)?;
485 }
486
487 match variant.data.kind {
489 StructKind::Unit => {
490 }
492 StructKind::Struct => self.format_struct_fields(
493 &|i| enum_peek.field(i).unwrap().unwrap(),
494 f,
495 visited,
496 format_depth,
497 type_depth,
498 variant.data.fields,
499 short,
500 )?,
501 _ => self.format_tuple_fields(
502 &|i| enum_peek.field(i).unwrap().unwrap(),
503 f,
504 visited,
505 format_depth,
506 type_depth,
507 variant.data.fields,
508 short,
509 false,
510 )?,
511 }
512 }
513 };
514 }
515
516 _ if value.into_list_like().is_ok() => {
517 let list = value.into_list_like().unwrap();
518
519 self.write_type_name(f, &value)?;
524
525 if !list.is_empty() {
526 if list.def().t().is_type::<u8>() && self.list_u8_as_bytes {
527 self.write_punctuation(f, " [")?;
528 for (idx, item) in list.iter().enumerate() {
529 if !short && idx % 16 == 0 {
530 writeln!(f)?;
531 self.indent(f, format_depth + 1)?;
532 }
533 write!(f, " ")?;
534
535 let byte = *item.get::<u8>().unwrap();
536 if self.use_colors {
537 let mut hasher = DefaultHasher::new();
538 byte.hash(&mut hasher);
539 let hash = hasher.finish();
540 let color = self.color_generator.generate_color(hash);
541 let rgb = Rgb(color.r, color.g, color.b);
542 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
543 } else {
544 write!(f, "{byte:02x}")?;
545 }
546 }
547 if !short {
548 writeln!(f)?;
549 self.indent(f, format_depth)?;
550 }
551 self.write_punctuation(f, "]")?;
552 } else {
553 let elem_shape = list.def().t();
555 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
556
557 self.write_punctuation(f, " [")?;
558 let len = list.len();
559 for (idx, item) in list.iter().enumerate() {
560 if !short && !is_simple {
561 writeln!(f)?;
562 self.indent(f, format_depth + 1)?;
563 } else if idx > 0 {
564 write!(f, " ")?;
565 }
566 self.format_peek_internal_(
567 item,
568 f,
569 visited,
570 format_depth + 1,
571 type_depth + 1,
572 short || is_simple,
573 )?;
574
575 if (!short && !is_simple) || idx + 1 < len {
576 self.write_punctuation(f, ",")?;
577 }
578 }
579 if !short && !is_simple {
580 writeln!(f)?;
581 self.indent(f, format_depth)?;
582 }
583 self.write_punctuation(f, "]")?;
584 }
585 } else {
586 self.write_punctuation(f, "[]")?;
587 }
588 }
589
590 _ if value.into_set().is_ok() => {
591 self.write_type_name(f, &value)?;
592
593 let value = value.into_set().unwrap();
594 self.write_punctuation(f, " [")?;
595 if !value.is_empty() {
596 let len = value.len();
597 for (idx, item) in value.iter().enumerate() {
598 if !short {
599 writeln!(f)?;
600 self.indent(f, format_depth + 1)?;
601 }
602 self.format_peek_internal_(
603 item,
604 f,
605 visited,
606 format_depth + 1,
607 type_depth + 1,
608 short,
609 )?;
610 if !short || idx + 1 < len {
611 self.write_punctuation(f, ",")?;
612 } else {
613 write!(f, " ")?;
614 }
615 }
616 if !short {
617 writeln!(f)?;
618 self.indent(f, format_depth)?;
619 }
620 }
621 self.write_punctuation(f, "]")?;
622 }
623
624 (Def::Map(def), _) => {
625 let key_is_short = Self::shape_chunkiness(def.k) <= 2;
626
627 self.write_type_name(f, &value)?;
628
629 let value = value.into_map().unwrap();
630 self.write_punctuation(f, " [")?;
631
632 if !value.is_empty() {
633 let len = value.len();
634 for (idx, (key, value)) in value.iter().enumerate() {
635 if !short {
636 writeln!(f)?;
637 self.indent(f, format_depth + 1)?;
638 }
639 self.format_peek_internal_(
640 key,
641 f,
642 visited,
643 format_depth + 1,
644 type_depth + 1,
645 key_is_short,
646 )?;
647 self.write_punctuation(f, " => ")?;
648 self.format_peek_internal_(
649 value,
650 f,
651 visited,
652 format_depth + 1,
653 type_depth + 1,
654 short,
655 )?;
656 if !short || idx + 1 < len {
657 self.write_punctuation(f, ",")?;
658 } else {
659 write!(f, " ")?;
660 }
661 }
662 if !short {
663 writeln!(f)?;
664 self.indent(f, format_depth)?;
665 }
666 }
667
668 self.write_punctuation(f, "]")?;
669 }
670
671 (Def::DynamicValue(_), _) => {
672 let dyn_val = value.into_dynamic_value().unwrap();
673 match dyn_val.kind() {
674 DynValueKind::Null => {
675 self.write_keyword(f, "null")?;
676 }
677 DynValueKind::Bool => {
678 if let Some(b) = dyn_val.as_bool() {
679 self.write_keyword(f, if b { "true" } else { "false" })?;
680 }
681 }
682 DynValueKind::Number => {
683 if let Some(n) = dyn_val.as_i64() {
684 self.format_number(f, &n.to_string())?;
685 } else if let Some(n) = dyn_val.as_u64() {
686 self.format_number(f, &n.to_string())?;
687 } else if let Some(n) = dyn_val.as_f64() {
688 self.format_number(f, &n.to_string())?;
689 }
690 }
691 DynValueKind::String => {
692 if let Some(s) = dyn_val.as_str() {
693 self.format_string(f, s)?;
694 }
695 }
696 DynValueKind::Bytes => {
697 if let Some(bytes) = dyn_val.as_bytes() {
698 self.format_bytes(f, bytes)?;
699 }
700 }
701 DynValueKind::Array => {
702 let len = dyn_val.array_len().unwrap_or(0);
703 if len == 0 {
704 self.write_punctuation(f, "[]")?;
705 } else {
706 self.write_punctuation(f, "[")?;
707 for idx in 0..len {
708 if !short {
709 writeln!(f)?;
710 self.indent(f, format_depth + 1)?;
711 }
712 if let Some(elem) = dyn_val.array_get(idx) {
713 self.format_peek_internal_(
714 elem,
715 f,
716 visited,
717 format_depth + 1,
718 type_depth + 1,
719 short,
720 )?;
721 }
722 if !short || idx + 1 < len {
723 self.write_punctuation(f, ",")?;
724 } else {
725 write!(f, " ")?;
726 }
727 }
728 if !short {
729 writeln!(f)?;
730 self.indent(f, format_depth)?;
731 }
732 self.write_punctuation(f, "]")?;
733 }
734 }
735 DynValueKind::Object => {
736 let len = dyn_val.object_len().unwrap_or(0);
737 if len == 0 {
738 self.write_punctuation(f, "{}")?;
739 } else {
740 self.write_punctuation(f, "{")?;
741 for idx in 0..len {
742 if !short {
743 writeln!(f)?;
744 self.indent(f, format_depth + 1)?;
745 }
746 if let Some((key, val)) = dyn_val.object_get_entry(idx) {
747 self.write_field_name(f, key)?;
748 self.write_punctuation(f, ": ")?;
749 self.format_peek_internal_(
750 val,
751 f,
752 visited,
753 format_depth + 1,
754 type_depth + 1,
755 short,
756 )?;
757 }
758 if !short || idx + 1 < len {
759 self.write_punctuation(f, ",")?;
760 } else {
761 write!(f, " ")?;
762 }
763 }
764 if !short {
765 writeln!(f)?;
766 self.indent(f, format_depth)?;
767 }
768 self.write_punctuation(f, "}")?;
769 }
770 }
771 DynValueKind::DateTime => {
772 #[allow(clippy::uninlined_format_args)]
774 if let Some((year, month, day, hour, minute, second, nanos, kind)) =
775 dyn_val.as_datetime()
776 {
777 match kind {
778 DynDateTimeKind::Offset { offset_minutes } => {
779 if nanos > 0 {
780 write!(
781 f,
782 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
783 year, month, day, hour, minute, second, nanos
784 )?;
785 } else {
786 write!(
787 f,
788 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
789 year, month, day, hour, minute, second
790 )?;
791 }
792 if offset_minutes == 0 {
793 write!(f, "Z")?;
794 } else {
795 let sign = if offset_minutes >= 0 { '+' } else { '-' };
796 let abs = offset_minutes.abs();
797 write!(f, "{}{:02}:{:02}", sign, abs / 60, abs % 60)?;
798 }
799 }
800 DynDateTimeKind::LocalDateTime => {
801 if nanos > 0 {
802 write!(
803 f,
804 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
805 year, month, day, hour, minute, second, nanos
806 )?;
807 } else {
808 write!(
809 f,
810 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
811 year, month, day, hour, minute, second
812 )?;
813 }
814 }
815 DynDateTimeKind::LocalDate => {
816 write!(f, "{:04}-{:02}-{:02}", year, month, day)?;
817 }
818 DynDateTimeKind::LocalTime => {
819 if nanos > 0 {
820 write!(
821 f,
822 "{:02}:{:02}:{:02}.{:09}",
823 hour, minute, second, nanos
824 )?;
825 } else {
826 write!(f, "{:02}:{:02}:{:02}", hour, minute, second)?;
827 }
828 }
829 }
830 }
831 }
832 DynValueKind::QName => {
833 write!(f, "<qname>")?;
835 }
836 DynValueKind::Uuid => {
837 write!(f, "<uuid>")?;
839 }
840 }
841 }
842
843 _ => write!(f, "unsupported peek variant: {value:?}")?,
844 }
845
846 Ok(())
847 }
848
849 #[allow(clippy::too_many_arguments)]
850 fn format_tuple_fields<'mem, 'facet>(
851 &self,
852 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
853 f: &mut dyn Write,
854 visited: &mut BTreeMap<ValueId, usize>,
855 format_depth: usize,
856 type_depth: usize,
857 fields: &[Field],
858 short: bool,
859 force_trailing_comma: bool,
860 ) -> fmt::Result {
861 self.write_punctuation(f, "(")?;
862 if let [field] = fields
863 && field.doc.is_empty()
864 {
865 let field = peek_field(0);
866 self.format_peek_internal_(field, f, visited, format_depth, type_depth, short)?;
867
868 if force_trailing_comma {
869 self.write_punctuation(f, ",")?;
870 }
871 } else if !fields.is_empty() {
872 for idx in 0..fields.len() {
873 if !short {
874 writeln!(f)?;
875 self.indent(f, format_depth + 1)?;
876
877 if self.show_doc_comments {
878 for &line in fields[idx].doc {
879 self.write_comment(f, &format!("///{line}"))?;
880 writeln!(f)?;
881 self.indent(f, format_depth + 1)?;
882 }
883 }
884 }
885
886 if fields[idx].is_sensitive() {
887 self.write_redacted(f, "[REDACTED]")?;
888 } else {
889 self.format_peek_internal_(
890 peek_field(idx),
891 f,
892 visited,
893 format_depth + 1,
894 type_depth + 1,
895 short,
896 )?;
897 }
898
899 if !short || idx + 1 < fields.len() {
900 self.write_punctuation(f, ",")?;
901 } else {
902 write!(f, " ")?;
903 }
904 }
905 if !short {
906 writeln!(f)?;
907 self.indent(f, format_depth)?;
908 }
909 }
910 self.write_punctuation(f, ")")?;
911 Ok(())
912 }
913
914 #[allow(clippy::too_many_arguments)]
915 fn format_struct_fields<'mem, 'facet>(
916 &self,
917 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
918 f: &mut dyn Write,
919 visited: &mut BTreeMap<ValueId, usize>,
920 format_depth: usize,
921 type_depth: usize,
922 fields: &[Field],
923 short: bool,
924 ) -> fmt::Result {
925 self.write_punctuation(f, " {")?;
926 if !fields.is_empty() {
927 for idx in 0..fields.len() {
928 if !short {
929 writeln!(f)?;
930 self.indent(f, format_depth + 1)?;
931 }
932
933 if self.show_doc_comments {
934 for &line in fields[idx].doc {
935 self.write_comment(f, &format!("///{line}"))?;
936 writeln!(f)?;
937 self.indent(f, format_depth + 1)?;
938 }
939 }
940
941 self.write_field_name(f, fields[idx].name)?;
942 self.write_punctuation(f, ": ")?;
943 if fields[idx].is_sensitive() {
944 self.write_redacted(f, "[REDACTED]")?;
945 } else {
946 self.format_peek_internal_(
947 peek_field(idx),
948 f,
949 visited,
950 format_depth + 1,
951 type_depth + 1,
952 short,
953 )?;
954 }
955
956 if !short || idx + 1 < fields.len() {
957 self.write_punctuation(f, ",")?;
958 } else {
959 write!(f, " ")?;
960 }
961 }
962 if !short {
963 writeln!(f)?;
964 self.indent(f, format_depth)?;
965 }
966 }
967 self.write_punctuation(f, "}")?;
968 Ok(())
969 }
970
971 fn indent(&self, f: &mut dyn Write, indent: usize) -> fmt::Result {
972 if self.indent_size == usize::MAX {
973 write!(f, "{:\t<width$}", "", width = indent)
974 } else {
975 write!(f, "{: <width$}", "", width = indent * self.indent_size)
976 }
977 }
978
979 pub(crate) fn format_peek_internal(
981 &self,
982 value: Peek<'_, '_>,
983 f: &mut dyn Write,
984 visited: &mut BTreeMap<ValueId, usize>,
985 ) -> fmt::Result {
986 self.format_peek_internal_(value, f, visited, 0, 0, false)
987 }
988
989 fn format_scalar(&self, value: Peek, f: &mut dyn Write) -> fmt::Result {
991 let mut hasher = DefaultHasher::new();
993 value.shape().id.hash(&mut hasher);
994 let hash = hasher.finish();
995 let color = self.color_generator.generate_color(hash);
996
997 struct DisplayWrapper<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
999
1000 impl fmt::Display for DisplayWrapper<'_, '_> {
1001 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1002 if self.0.shape().is_display() {
1003 write!(f, "{}", self.0)?;
1004 } else if self.0.shape().is_debug() {
1005 write!(f, "{:?}", self.0)?;
1006 } else {
1007 write!(f, "{}", self.0.shape())?;
1008 write!(f, "(…)")?;
1009 }
1010 Ok(())
1011 }
1012 }
1013
1014 if self.use_colors {
1016 let rgb = Rgb(color.r, color.g, color.b);
1017 write!(f, "{}", DisplayWrapper(&value).color(rgb))?;
1018 } else {
1019 write!(f, "{}", DisplayWrapper(&value))?;
1020 }
1021
1022 Ok(())
1023 }
1024
1025 fn write_keyword(&self, f: &mut dyn Write, keyword: &str) -> fmt::Result {
1027 if self.use_colors {
1028 write!(f, "{}", keyword.color(tokyo_night::KEYWORD))
1029 } else {
1030 write!(f, "{keyword}")
1031 }
1032 }
1033
1034 fn format_number(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1036 if self.use_colors {
1037 write!(f, "{}", s.color(tokyo_night::NUMBER))
1038 } else {
1039 write!(f, "{s}")
1040 }
1041 }
1042
1043 fn format_string(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1045 if self.use_colors {
1046 write!(f, "\"{}\"", s.color(tokyo_night::STRING))
1047 } else {
1048 write!(f, "{s:?}")
1049 }
1050 }
1051
1052 fn format_bytes(&self, f: &mut dyn Write, bytes: &[u8]) -> fmt::Result {
1054 write!(f, "b\"")?;
1055 for byte in bytes {
1056 write!(f, "\\x{byte:02x}")?;
1057 }
1058 write!(f, "\"")
1059 }
1060
1061 fn write_type_name(&self, f: &mut dyn Write, peek: &Peek) -> fmt::Result {
1063 struct TypeNameWriter<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
1064
1065 impl core::fmt::Display for TypeNameWriter<'_, '_> {
1066 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1067 self.0.type_name(f, TypeNameOpts::infinite())
1068 }
1069 }
1070 let type_name = TypeNameWriter(peek);
1071
1072 if self.use_colors {
1073 write!(f, "{}", type_name.color(tokyo_night::TYPE_NAME).bold())
1074 } else {
1075 write!(f, "{type_name}")
1076 }
1077 }
1078
1079 #[allow(dead_code)]
1081 fn style_type_name(&self, peek: &Peek) -> String {
1082 let mut result = String::new();
1083 self.write_type_name(&mut result, peek).unwrap();
1084 result
1085 }
1086
1087 fn write_field_name(&self, f: &mut dyn Write, name: &str) -> fmt::Result {
1089 if self.use_colors {
1090 write!(f, "{}", name.color(tokyo_night::FIELD_NAME))
1091 } else {
1092 write!(f, "{name}")
1093 }
1094 }
1095
1096 fn write_punctuation(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1098 if self.use_colors {
1099 write!(f, "{}", text.dimmed())
1100 } else {
1101 write!(f, "{text}")
1102 }
1103 }
1104
1105 fn write_comment(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1107 if self.use_colors {
1108 write!(f, "{}", text.color(tokyo_night::MUTED))
1109 } else {
1110 write!(f, "{text}")
1111 }
1112 }
1113
1114 fn write_redacted(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1116 if self.use_colors {
1117 write!(f, "{}", text.color(tokyo_night::ERROR).bold())
1118 } else {
1119 write!(f, "{text}")
1120 }
1121 }
1122
1123 #[allow(dead_code)]
1125 fn style_redacted(&self, text: &str) -> String {
1126 let mut result = String::new();
1127 self.write_redacted(&mut result, text).unwrap();
1128 result
1129 }
1130
1131 pub fn format_peek_with_spans(&self, value: Peek<'_, '_>) -> FormattedValue {
1139 let mut output = SpanTrackingOutput::new();
1140 let printer = Self {
1141 use_colors: false, indent_size: self.indent_size,
1143 max_depth: self.max_depth,
1144 color_generator: self.color_generator.clone(),
1145 list_u8_as_bytes: self.list_u8_as_bytes,
1146 minimal_option_names: self.minimal_option_names,
1147 show_doc_comments: self.show_doc_comments,
1148 };
1149 printer
1150 .format_unified(
1151 value,
1152 &mut output,
1153 &mut BTreeMap::new(),
1154 0,
1155 0,
1156 false,
1157 vec![],
1158 )
1159 .expect("Formatting failed");
1160
1161 output.into_formatted_value()
1162 }
1163
1164 #[allow(clippy::too_many_arguments)]
1169 fn format_unified<O: FormatOutput>(
1170 &self,
1171 value: Peek<'_, '_>,
1172 out: &mut O,
1173 visited: &mut BTreeMap<ValueId, usize>,
1174 format_depth: usize,
1175 type_depth: usize,
1176 short: bool,
1177 current_path: Path,
1178 ) -> fmt::Result {
1179 let mut value = value;
1180 while let Ok(ptr) = value.into_pointer()
1181 && let Some(pointee) = ptr.borrow_inner()
1182 {
1183 value = pointee;
1184 }
1185 let shape = value.shape();
1186
1187 let value_start = out.position();
1189
1190 if let Some(prev_type_depth) = visited.insert(value.id(), type_depth) {
1191 write!(out, "{} {{ ", shape.type_identifier)?;
1192 write!(
1193 out,
1194 "/* cycle detected at {} (first seen at type_depth {}) */",
1195 value.id(),
1196 prev_type_depth,
1197 )?;
1198 visited.remove(&value.id());
1199 let value_end = out.position();
1200 out.record_span(current_path, (value_start, value_end));
1201 return Ok(());
1202 }
1203
1204 match (shape.def, shape.ty) {
1205 (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
1206 let s = value.get::<str>().unwrap();
1207 write!(out, "\"{}\"", s)?;
1208 }
1209 (Def::Scalar, _) if value.shape().id == <alloc::string::String as Facet>::SHAPE.id => {
1210 let s = value.get::<alloc::string::String>().unwrap();
1211 write!(out, "\"{}\"", s)?;
1212 }
1213 (Def::Scalar, _) => {
1214 self.format_scalar_to_output(value, out)?;
1215 }
1216 (Def::Option(_), _) => {
1217 let option = value.into_option().unwrap();
1218 if let Some(inner) = option.value() {
1219 write!(out, "Some(")?;
1220 self.format_unified(
1221 inner,
1222 out,
1223 visited,
1224 format_depth,
1225 type_depth + 1,
1226 short,
1227 current_path.clone(),
1228 )?;
1229 write!(out, ")")?;
1230 } else {
1231 write!(out, "None")?;
1232 }
1233 }
1234 (
1235 _,
1236 Type::User(UserType::Struct(
1237 ty @ StructType {
1238 kind: StructKind::Struct | StructKind::Unit,
1239 ..
1240 },
1241 )),
1242 ) => {
1243 write!(out, "{}", shape.type_identifier)?;
1244 if matches!(ty.kind, StructKind::Struct) {
1245 let struct_peek = value.into_struct().unwrap();
1246 write!(out, " {{")?;
1247 for (i, field) in ty.fields.iter().enumerate() {
1248 if !short {
1249 writeln!(out)?;
1250 self.indent_to_output(out, format_depth + 1)?;
1251 }
1252 let field_name_start = out.position();
1254 write!(out, "{}", field.name)?;
1255 let field_name_end = out.position();
1256 write!(out, ": ")?;
1257
1258 let mut field_path = current_path.clone();
1260 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1261
1262 let field_value_start = out.position();
1264 if let Ok(field_value) = struct_peek.field(i) {
1265 self.format_unified(
1266 field_value,
1267 out,
1268 visited,
1269 format_depth + 1,
1270 type_depth + 1,
1271 short,
1272 field_path.clone(),
1273 )?;
1274 }
1275 let field_value_end = out.position();
1276
1277 out.record_field_span(
1279 field_path,
1280 (field_name_start, field_name_end),
1281 (field_value_start, field_value_end),
1282 );
1283
1284 if !short || i + 1 < ty.fields.len() {
1285 write!(out, ",")?;
1286 }
1287 }
1288 if !short {
1289 writeln!(out)?;
1290 self.indent_to_output(out, format_depth)?;
1291 }
1292 write!(out, "}}")?;
1293 }
1294 }
1295 (
1296 _,
1297 Type::User(UserType::Struct(
1298 ty @ StructType {
1299 kind: StructKind::Tuple | StructKind::TupleStruct,
1300 ..
1301 },
1302 )),
1303 ) => {
1304 write!(out, "{}", shape.type_identifier)?;
1305 if matches!(ty.kind, StructKind::Tuple) {
1306 write!(out, " ")?;
1307 }
1308 let struct_peek = value.into_struct().unwrap();
1309 write!(out, "(")?;
1310 for (i, _field) in ty.fields.iter().enumerate() {
1311 if i > 0 {
1312 write!(out, ", ")?;
1313 }
1314 let mut elem_path = current_path.clone();
1315 elem_path.push(PathSegment::Index(i));
1316
1317 let elem_start = out.position();
1318 if let Ok(field_value) = struct_peek.field(i) {
1319 self.format_unified(
1320 field_value,
1321 out,
1322 visited,
1323 format_depth + 1,
1324 type_depth + 1,
1325 short,
1326 elem_path.clone(),
1327 )?;
1328 }
1329 let elem_end = out.position();
1330 out.record_span(elem_path, (elem_start, elem_end));
1331 }
1332 write!(out, ")")?;
1333 }
1334 (_, Type::User(UserType::Enum(_))) => {
1335 let enum_peek = value.into_enum().unwrap();
1336 match enum_peek.active_variant() {
1337 Err(_) => {
1338 write!(
1339 out,
1340 "{} {{ /* cannot determine variant */ }}",
1341 shape.type_identifier
1342 )?;
1343 }
1344 Ok(variant) => {
1345 write!(out, "{}::{}", shape.type_identifier, variant.name)?;
1346
1347 match variant.data.kind {
1348 StructKind::Unit => {}
1349 StructKind::Struct => {
1350 write!(out, " {{")?;
1351 for (i, field) in variant.data.fields.iter().enumerate() {
1352 if !short {
1353 writeln!(out)?;
1354 self.indent_to_output(out, format_depth + 1)?;
1355 }
1356 let field_name_start = out.position();
1357 write!(out, "{}", field.name)?;
1358 let field_name_end = out.position();
1359 write!(out, ": ")?;
1360
1361 let mut field_path = current_path.clone();
1362 field_path
1363 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
1364 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1365
1366 let field_value_start = out.position();
1367 if let Ok(Some(field_value)) = enum_peek.field(i) {
1368 self.format_unified(
1369 field_value,
1370 out,
1371 visited,
1372 format_depth + 1,
1373 type_depth + 1,
1374 short,
1375 field_path.clone(),
1376 )?;
1377 }
1378 let field_value_end = out.position();
1379
1380 out.record_field_span(
1381 field_path,
1382 (field_name_start, field_name_end),
1383 (field_value_start, field_value_end),
1384 );
1385
1386 if !short || i + 1 < variant.data.fields.len() {
1387 write!(out, ",")?;
1388 }
1389 }
1390 if !short {
1391 writeln!(out)?;
1392 self.indent_to_output(out, format_depth)?;
1393 }
1394 write!(out, "}}")?;
1395 }
1396 _ => {
1397 write!(out, "(")?;
1398 for (i, _field) in variant.data.fields.iter().enumerate() {
1399 if i > 0 {
1400 write!(out, ", ")?;
1401 }
1402 let mut elem_path = current_path.clone();
1403 elem_path
1404 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
1405 elem_path.push(PathSegment::Index(i));
1406
1407 let elem_start = out.position();
1408 if let Ok(Some(field_value)) = enum_peek.field(i) {
1409 self.format_unified(
1410 field_value,
1411 out,
1412 visited,
1413 format_depth + 1,
1414 type_depth + 1,
1415 short,
1416 elem_path.clone(),
1417 )?;
1418 }
1419 let elem_end = out.position();
1420 out.record_span(elem_path, (elem_start, elem_end));
1421 }
1422 write!(out, ")")?;
1423 }
1424 }
1425 }
1426 }
1427 }
1428 _ if value.into_list_like().is_ok() => {
1429 let list = value.into_list_like().unwrap();
1430
1431 let elem_shape = list.def().t();
1433 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
1434
1435 write!(out, "[")?;
1436 let len = list.len();
1437 for (i, item) in list.iter().enumerate() {
1438 if !short && !is_simple {
1439 writeln!(out)?;
1440 self.indent_to_output(out, format_depth + 1)?;
1441 } else if i > 0 {
1442 write!(out, " ")?;
1443 }
1444 let mut elem_path = current_path.clone();
1445 elem_path.push(PathSegment::Index(i));
1446
1447 let elem_start = out.position();
1448 self.format_unified(
1449 item,
1450 out,
1451 visited,
1452 format_depth + 1,
1453 type_depth + 1,
1454 short || is_simple,
1455 elem_path.clone(),
1456 )?;
1457 let elem_end = out.position();
1458 out.record_span(elem_path, (elem_start, elem_end));
1459
1460 if (!short && !is_simple) || i + 1 < len {
1461 write!(out, ",")?;
1462 }
1463 }
1464 if !short && !is_simple {
1465 writeln!(out)?;
1466 self.indent_to_output(out, format_depth)?;
1467 }
1468 write!(out, "]")?;
1469 }
1470 _ if value.into_map().is_ok() => {
1471 let map = value.into_map().unwrap();
1472 write!(out, "{{")?;
1473 for (i, (key, val)) in map.iter().enumerate() {
1474 if !short {
1475 writeln!(out)?;
1476 self.indent_to_output(out, format_depth + 1)?;
1477 }
1478 let key_start = out.position();
1480 self.format_unified(
1481 key,
1482 out,
1483 visited,
1484 format_depth + 1,
1485 type_depth + 1,
1486 true, vec![],
1488 )?;
1489 let key_end = out.position();
1490
1491 write!(out, ": ")?;
1492
1493 let key_str = self.format_peek(key);
1495 let mut entry_path = current_path.clone();
1496 entry_path.push(PathSegment::Key(Cow::Owned(key_str)));
1497
1498 let val_start = out.position();
1499 self.format_unified(
1500 val,
1501 out,
1502 visited,
1503 format_depth + 1,
1504 type_depth + 1,
1505 short,
1506 entry_path.clone(),
1507 )?;
1508 let val_end = out.position();
1509
1510 out.record_field_span(entry_path, (key_start, key_end), (val_start, val_end));
1511
1512 if !short || i + 1 < map.len() {
1513 write!(out, ",")?;
1514 }
1515 }
1516 if !short && !map.is_empty() {
1517 writeln!(out)?;
1518 self.indent_to_output(out, format_depth)?;
1519 }
1520 write!(out, "}}")?;
1521 }
1522 _ => {
1523 write!(out, "{} {{ ... }}", shape.type_identifier)?;
1525 }
1526 }
1527
1528 visited.remove(&value.id());
1529
1530 let value_end = out.position();
1532 out.record_span(current_path, (value_start, value_end));
1533
1534 Ok(())
1535 }
1536
1537 fn format_scalar_to_output(&self, value: Peek<'_, '_>, out: &mut impl Write) -> fmt::Result {
1538 if value.shape().is_display() {
1540 write!(out, "{}", value)
1541 } else if value.shape().is_debug() {
1542 write!(out, "{:?}", value)
1543 } else {
1544 write!(out, "{}(…)", value.shape())
1545 }
1546 }
1547
1548 fn indent_to_output(&self, out: &mut impl Write, depth: usize) -> fmt::Result {
1549 for _ in 0..depth {
1550 for _ in 0..self.indent_size {
1551 out.write_char(' ')?;
1552 }
1553 }
1554 Ok(())
1555 }
1556}
1557
1558#[derive(Debug)]
1560pub struct FormattedValue {
1561 pub text: String,
1563 pub spans: BTreeMap<Path, FieldSpan>,
1565}
1566
1567trait FormatOutput: Write {
1572 fn position(&self) -> usize;
1574
1575 fn record_span(&mut self, _path: Path, _span: Span) {}
1577
1578 fn record_field_span(&mut self, _path: Path, _key_span: Span, _value_span: Span) {}
1580}
1581
1582#[allow(dead_code)]
1585struct NonTrackingOutput<W> {
1586 inner: W,
1587 position: usize,
1588}
1589
1590#[allow(dead_code)]
1591impl<W> NonTrackingOutput<W> {
1592 fn new(inner: W) -> Self {
1593 Self { inner, position: 0 }
1594 }
1595}
1596
1597impl<W: Write> Write for NonTrackingOutput<W> {
1598 fn write_str(&mut self, s: &str) -> fmt::Result {
1599 self.position += s.len();
1600 self.inner.write_str(s)
1601 }
1602}
1603
1604impl<W: Write> FormatOutput for NonTrackingOutput<W> {
1605 fn position(&self) -> usize {
1606 self.position
1607 }
1608 }
1610
1611struct SpanTrackingOutput {
1613 output: String,
1614 spans: BTreeMap<Path, FieldSpan>,
1615}
1616
1617impl SpanTrackingOutput {
1618 fn new() -> Self {
1619 Self {
1620 output: String::new(),
1621 spans: BTreeMap::new(),
1622 }
1623 }
1624
1625 fn into_formatted_value(self) -> FormattedValue {
1626 FormattedValue {
1627 text: self.output,
1628 spans: self.spans,
1629 }
1630 }
1631}
1632
1633impl Write for SpanTrackingOutput {
1634 fn write_str(&mut self, s: &str) -> fmt::Result {
1635 self.output.push_str(s);
1636 Ok(())
1637 }
1638}
1639
1640impl FormatOutput for SpanTrackingOutput {
1641 fn position(&self) -> usize {
1642 self.output.len()
1643 }
1644
1645 fn record_span(&mut self, path: Path, span: Span) {
1646 self.spans.insert(
1647 path,
1648 FieldSpan {
1649 key: span,
1650 value: span,
1651 },
1652 );
1653 }
1654
1655 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
1656 self.spans.insert(
1657 path,
1658 FieldSpan {
1659 key: key_span,
1660 value: value_span,
1661 },
1662 );
1663 }
1664}
1665
1666#[cfg(test)]
1667mod tests {
1668 use super::*;
1669
1670 #[test]
1672 fn test_pretty_printer_default() {
1673 let printer = PrettyPrinter::default();
1674 assert_eq!(printer.indent_size, 2);
1675 assert_eq!(printer.max_depth, None);
1676 assert_eq!(printer.use_colors, std::env::var_os("NO_COLOR").is_none());
1679 }
1680
1681 #[test]
1682 fn test_pretty_printer_with_methods() {
1683 let printer = PrettyPrinter::new()
1684 .with_indent_size(4)
1685 .with_max_depth(3)
1686 .with_colors(false);
1687
1688 assert_eq!(printer.indent_size, 4);
1689 assert_eq!(printer.max_depth, Some(3));
1690 assert!(!printer.use_colors);
1691 }
1692
1693 #[test]
1694 fn test_format_peek_with_spans() {
1695 use crate::PathSegment;
1696 use facet_reflect::Peek;
1697
1698 let value = ("Alice", 30u32);
1700
1701 let printer = PrettyPrinter::new();
1702 let formatted = printer.format_peek_with_spans(Peek::new(&value));
1703
1704 assert!(!formatted.text.is_empty());
1706 assert!(formatted.text.contains("Alice"));
1707 assert!(formatted.text.contains("30"));
1708
1709 assert!(!formatted.spans.is_empty());
1711
1712 assert!(formatted.spans.contains_key(&vec![]));
1714
1715 let idx0_path = vec![PathSegment::Index(0)];
1717 let idx1_path = vec![PathSegment::Index(1)];
1718 assert!(
1719 formatted.spans.contains_key(&idx0_path),
1720 "index 0 span not found"
1721 );
1722 assert!(
1723 formatted.spans.contains_key(&idx1_path),
1724 "index 1 span not found"
1725 );
1726 }
1727}