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