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 writeln!(f, " ...({omitted} bytes)...")?;
615 self.indent(f, format_depth + 1)?;
616 } else {
617 write!(f, " ...({omitted} bytes)...")?;
618 }
619
620 for (idx, item) in list.iter().enumerate().skip(total_len - end_count) {
622 let display_idx = start_count + 1 + (idx - (total_len - end_count));
623 if !short && display_idx.is_multiple_of(16) {
624 writeln!(f)?;
625 self.indent(f, format_depth + 1)?;
626 }
627 write!(f, " ")?;
628 let byte = *item.get::<u8>().unwrap();
629 if self.use_colors() {
630 let mut hasher = DefaultHasher::new();
631 byte.hash(&mut hasher);
632 let hash = hasher.finish();
633 let color = self.color_generator.generate_color(hash);
634 let rgb = Rgb(color.r, color.g, color.b);
635 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
636 } else {
637 write!(f, "{byte:02x}")?;
638 }
639 }
640 } else {
641 for (idx, item) in list.iter().enumerate() {
642 if !short && idx % 16 == 0 {
643 writeln!(f)?;
644 self.indent(f, format_depth + 1)?;
645 }
646 write!(f, " ")?;
647
648 let byte = *item.get::<u8>().unwrap();
649 if self.use_colors() {
650 let mut hasher = DefaultHasher::new();
651 byte.hash(&mut hasher);
652 let hash = hasher.finish();
653 let color = self.color_generator.generate_color(hash);
654 let rgb = Rgb(color.r, color.g, color.b);
655 write!(f, "{}", format!("{byte:02x}").color(rgb))?;
656 } else {
657 write!(f, "{byte:02x}")?;
658 }
659 }
660 }
661
662 if !short {
663 writeln!(f)?;
664 self.indent(f, format_depth)?;
665 }
666 self.write_punctuation(f, "]")?;
667 } else {
668 let elem_shape = list.def().t();
670 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
671
672 self.write_punctuation(f, " [")?;
673 let len = list.len();
674 let visible_len = self.visible_collection_len(len);
675 for (idx, item) in list.iter().take(visible_len).enumerate() {
676 if !short && !is_simple {
677 writeln!(f)?;
678 self.indent(f, format_depth + 1)?;
679 } else if idx > 0 {
680 write!(f, " ")?;
681 }
682 self.format_peek_internal_(
683 item,
684 f,
685 visited,
686 format_depth + 1,
687 type_depth + 1,
688 short || is_simple,
689 )?;
690
691 if (!short && !is_simple) || idx + 1 < visible_len || visible_len < len
692 {
693 self.write_punctuation(f, ",")?;
694 }
695 }
696 if visible_len < len {
697 if !short && !is_simple {
698 writeln!(f)?;
699 self.indent(f, format_depth + 1)?;
700 } else if visible_len > 0 {
701 write!(f, " ")?;
702 }
703 self.write_collection_ellipsis(f, len - visible_len, "items")?;
704 }
705 if !short && !is_simple {
706 writeln!(f)?;
707 self.indent(f, format_depth)?;
708 }
709 self.write_punctuation(f, "]")?;
710 }
711 } else {
712 self.write_punctuation(f, "[]")?;
713 }
714 }
715
716 _ if value.into_set().is_ok() => {
717 self.write_type_name(f, &value)?;
718
719 let value = value.into_set().unwrap();
720 self.write_punctuation(f, " [")?;
721 if !value.is_empty() {
722 let len = value.len();
723 let visible_len = self.visible_collection_len(len);
724 for (idx, item) in value.iter().take(visible_len).enumerate() {
725 if !short {
726 writeln!(f)?;
727 self.indent(f, format_depth + 1)?;
728 }
729 self.format_peek_internal_(
730 item,
731 f,
732 visited,
733 format_depth + 1,
734 type_depth + 1,
735 short,
736 )?;
737 if !short || idx + 1 < visible_len || visible_len < len {
738 self.write_punctuation(f, ",")?;
739 } else {
740 write!(f, " ")?;
741 }
742 }
743 if visible_len < len {
744 if !short {
745 writeln!(f)?;
746 self.indent(f, format_depth + 1)?;
747 } else if visible_len > 0 {
748 write!(f, " ")?;
749 }
750 self.write_collection_ellipsis(f, len - visible_len, "items")?;
751 }
752 if !short {
753 writeln!(f)?;
754 self.indent(f, format_depth)?;
755 }
756 }
757 self.write_punctuation(f, "]")?;
758 }
759
760 (Def::Map(def), _) => {
761 let key_is_short = Self::shape_chunkiness(def.k) <= 2;
762
763 self.write_type_name(f, &value)?;
764
765 let value = value.into_map().unwrap();
766 self.write_punctuation(f, " [")?;
767
768 if !value.is_empty() {
769 let len = value.len();
770 let visible_len = self.visible_collection_len(len);
771 for (idx, (key, value)) in value.iter().take(visible_len).enumerate() {
772 if !short {
773 writeln!(f)?;
774 self.indent(f, format_depth + 1)?;
775 }
776 self.format_peek_internal_(
777 key,
778 f,
779 visited,
780 format_depth + 1,
781 type_depth + 1,
782 key_is_short,
783 )?;
784 self.write_punctuation(f, " => ")?;
785 self.format_peek_internal_(
786 value,
787 f,
788 visited,
789 format_depth + 1,
790 type_depth + 1,
791 short,
792 )?;
793 if !short || idx + 1 < visible_len || visible_len < len {
794 self.write_punctuation(f, ",")?;
795 } else {
796 write!(f, " ")?;
797 }
798 }
799 if visible_len < len {
800 if !short {
801 writeln!(f)?;
802 self.indent(f, format_depth + 1)?;
803 } else if visible_len > 0 {
804 write!(f, " ")?;
805 }
806 self.write_collection_ellipsis(f, len - visible_len, "entries")?;
807 }
808 if !short {
809 writeln!(f)?;
810 self.indent(f, format_depth)?;
811 }
812 }
813
814 self.write_punctuation(f, "]")?;
815 }
816
817 (Def::DynamicValue(_), _) => {
818 let dyn_val = value.into_dynamic_value().unwrap();
819 match dyn_val.kind() {
820 DynValueKind::Null => {
821 self.write_keyword(f, "null")?;
822 }
823 DynValueKind::Bool => {
824 if let Some(b) = dyn_val.as_bool() {
825 self.write_keyword(f, if b { "true" } else { "false" })?;
826 }
827 }
828 DynValueKind::Number => {
829 if let Some(n) = dyn_val.as_i64() {
830 self.format_number(f, &n.to_string())?;
831 } else if let Some(n) = dyn_val.as_u64() {
832 self.format_number(f, &n.to_string())?;
833 } else if let Some(n) = dyn_val.as_f64() {
834 self.format_number(f, &n.to_string())?;
835 }
836 }
837 DynValueKind::String => {
838 if let Some(s) = dyn_val.as_str() {
839 self.format_string(f, s)?;
840 }
841 }
842 DynValueKind::Bytes => {
843 if let Some(bytes) = dyn_val.as_bytes() {
844 self.format_bytes(f, bytes)?;
845 }
846 }
847 DynValueKind::Array => {
848 let len = dyn_val.array_len().unwrap_or(0);
849 if len == 0 {
850 self.write_punctuation(f, "[]")?;
851 } else {
852 self.write_punctuation(f, "[")?;
853 let visible_len = self.visible_collection_len(len);
854 for idx in 0..visible_len {
855 if !short {
856 writeln!(f)?;
857 self.indent(f, format_depth + 1)?;
858 }
859 if let Some(elem) = dyn_val.array_get(idx) {
860 self.format_peek_internal_(
861 elem,
862 f,
863 visited,
864 format_depth + 1,
865 type_depth + 1,
866 short,
867 )?;
868 }
869 if !short || idx + 1 < visible_len || visible_len < len {
870 self.write_punctuation(f, ",")?;
871 } else {
872 write!(f, " ")?;
873 }
874 }
875 if visible_len < len {
876 if !short {
877 writeln!(f)?;
878 self.indent(f, format_depth + 1)?;
879 } else if visible_len > 0 {
880 write!(f, " ")?;
881 }
882 self.write_collection_ellipsis(f, len - visible_len, "items")?;
883 }
884 if !short {
885 writeln!(f)?;
886 self.indent(f, format_depth)?;
887 }
888 self.write_punctuation(f, "]")?;
889 }
890 }
891 DynValueKind::Object => {
892 let len = dyn_val.object_len().unwrap_or(0);
893 if len == 0 {
894 self.write_punctuation(f, "{}")?;
895 } else {
896 self.write_punctuation(f, "{")?;
897 let visible_len = self.visible_collection_len(len);
898 for idx in 0..visible_len {
899 if !short {
900 writeln!(f)?;
901 self.indent(f, format_depth + 1)?;
902 }
903 if let Some((key, val)) = dyn_val.object_get_entry(idx) {
904 self.write_field_name(f, key)?;
905 self.write_punctuation(f, ": ")?;
906 self.format_peek_internal_(
907 val,
908 f,
909 visited,
910 format_depth + 1,
911 type_depth + 1,
912 short,
913 )?;
914 }
915 if !short || idx + 1 < visible_len || visible_len < len {
916 self.write_punctuation(f, ",")?;
917 } else {
918 write!(f, " ")?;
919 }
920 }
921 if visible_len < len {
922 if !short {
923 writeln!(f)?;
924 self.indent(f, format_depth + 1)?;
925 } else if visible_len > 0 {
926 write!(f, " ")?;
927 }
928 self.write_collection_ellipsis(f, len - visible_len, "entries")?;
929 }
930 if !short {
931 writeln!(f)?;
932 self.indent(f, format_depth)?;
933 }
934 self.write_punctuation(f, "}")?;
935 }
936 }
937 DynValueKind::DateTime => {
938 #[allow(clippy::uninlined_format_args)]
940 if let Some((year, month, day, hour, minute, second, nanos, kind)) =
941 dyn_val.as_datetime()
942 {
943 match kind {
944 DynDateTimeKind::Offset { offset_minutes } => {
945 if nanos > 0 {
946 write!(
947 f,
948 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
949 year, month, day, hour, minute, second, nanos
950 )?;
951 } else {
952 write!(
953 f,
954 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
955 year, month, day, hour, minute, second
956 )?;
957 }
958 if offset_minutes == 0 {
959 write!(f, "Z")?;
960 } else {
961 let sign = if offset_minutes >= 0 { '+' } else { '-' };
962 let abs = offset_minutes.abs();
963 write!(f, "{}{:02}:{:02}", sign, abs / 60, abs % 60)?;
964 }
965 }
966 DynDateTimeKind::LocalDateTime => {
967 if nanos > 0 {
968 write!(
969 f,
970 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
971 year, month, day, hour, minute, second, nanos
972 )?;
973 } else {
974 write!(
975 f,
976 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
977 year, month, day, hour, minute, second
978 )?;
979 }
980 }
981 DynDateTimeKind::LocalDate => {
982 write!(f, "{:04}-{:02}-{:02}", year, month, day)?;
983 }
984 DynDateTimeKind::LocalTime => {
985 if nanos > 0 {
986 write!(
987 f,
988 "{:02}:{:02}:{:02}.{:09}",
989 hour, minute, second, nanos
990 )?;
991 } else {
992 write!(f, "{:02}:{:02}:{:02}", hour, minute, second)?;
993 }
994 }
995 }
996 }
997 }
998 DynValueKind::QName => {
999 write!(f, "<qname>")?;
1001 }
1002 DynValueKind::Uuid => {
1003 write!(f, "<uuid>")?;
1005 }
1006 }
1007 }
1008
1009 (d, t) => write!(f, "unsupported peek variant: {value:?} ({d:?}, {t:?})")?,
1010 }
1011
1012 visited.remove(&value.id());
1013 Ok(())
1014 }
1015
1016 #[allow(clippy::too_many_arguments)]
1021 fn format_via_proxy(
1022 &self,
1023 value: Peek<'_, '_>,
1024 proxy_def: &'static facet_core::ProxyDef,
1025 f: &mut dyn Write,
1026 visited: &mut BTreeMap<ValueId, usize>,
1027 format_depth: usize,
1028 type_depth: usize,
1029 short: bool,
1030 ) -> fmt::Result {
1031 let proxy_shape = proxy_def.shape;
1032 let proxy_layout = match proxy_shape.layout.sized_layout() {
1033 Ok(layout) => layout,
1034 Err(_) => {
1035 return write!(f, "/* proxy type must be sized for formatting */");
1036 }
1037 };
1038
1039 let proxy_uninit = facet_core::alloc_for_layout(proxy_layout);
1041
1042 let convert_result = unsafe { (proxy_def.convert_out)(value.data(), proxy_uninit) };
1044
1045 let proxy_ptr = match convert_result {
1046 Ok(ptr) => ptr,
1047 Err(msg) => {
1048 unsafe { facet_core::dealloc_for_layout(proxy_uninit.assume_init(), proxy_layout) };
1049 return write!(f, "/* proxy conversion failed: {msg} */");
1050 }
1051 };
1052
1053 let proxy_peek = unsafe { Peek::unchecked_new(proxy_ptr.as_const(), proxy_shape) };
1055 let result =
1056 self.format_peek_internal_(proxy_peek, f, visited, format_depth, type_depth, short);
1057
1058 unsafe {
1060 let _ = proxy_shape.call_drop_in_place(proxy_ptr);
1061 facet_core::dealloc_for_layout(proxy_ptr, proxy_layout);
1062 }
1063
1064 result
1065 }
1066
1067 #[allow(clippy::too_many_arguments)]
1072 fn format_via_proxy_unified<O: FormatOutput>(
1073 &self,
1074 value: Peek<'_, '_>,
1075 proxy_def: &'static facet_core::ProxyDef,
1076 out: &mut O,
1077 visited: &mut BTreeMap<ValueId, usize>,
1078 format_depth: usize,
1079 type_depth: usize,
1080 short: bool,
1081 current_path: Path,
1082 ) -> fmt::Result {
1083 let proxy_shape = proxy_def.shape;
1084 let proxy_layout = match proxy_shape.layout.sized_layout() {
1085 Ok(layout) => layout,
1086 Err(_) => {
1087 return write!(out, "/* proxy type must be sized for formatting */");
1088 }
1089 };
1090
1091 let proxy_uninit = facet_core::alloc_for_layout(proxy_layout);
1093
1094 let convert_result = unsafe { (proxy_def.convert_out)(value.data(), proxy_uninit) };
1096
1097 let proxy_ptr = match convert_result {
1098 Ok(ptr) => ptr,
1099 Err(msg) => {
1100 unsafe { facet_core::dealloc_for_layout(proxy_uninit.assume_init(), proxy_layout) };
1101 return write!(out, "/* proxy conversion failed: {msg} */");
1102 }
1103 };
1104
1105 let proxy_peek = unsafe { Peek::unchecked_new(proxy_ptr.as_const(), proxy_shape) };
1107 let result = self.format_unified(
1108 proxy_peek,
1109 out,
1110 visited,
1111 format_depth,
1112 type_depth,
1113 short,
1114 current_path,
1115 );
1116
1117 unsafe {
1119 let _ = proxy_shape.call_drop_in_place(proxy_ptr);
1120 facet_core::dealloc_for_layout(proxy_ptr, proxy_layout);
1121 }
1122
1123 result
1124 }
1125
1126 #[allow(clippy::too_many_arguments)]
1127 fn format_tuple_fields<'mem, 'facet>(
1128 &self,
1129 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
1130 f: &mut dyn Write,
1131 visited: &mut BTreeMap<ValueId, usize>,
1132 format_depth: usize,
1133 type_depth: usize,
1134 fields: &[Field],
1135 short: bool,
1136 force_trailing_comma: bool,
1137 ) -> fmt::Result {
1138 self.write_punctuation(f, "(")?;
1139 if let [field] = fields
1140 && field.doc.is_empty()
1141 {
1142 let field_value = peek_field(0);
1143 if let Some(proxy_def) = field.proxy() {
1144 self.format_via_proxy(
1145 field_value,
1146 proxy_def,
1147 f,
1148 visited,
1149 format_depth,
1150 type_depth,
1151 short,
1152 )?;
1153 } else {
1154 self.format_peek_internal_(
1155 field_value,
1156 f,
1157 visited,
1158 format_depth,
1159 type_depth,
1160 short,
1161 )?;
1162 }
1163
1164 if force_trailing_comma {
1165 self.write_punctuation(f, ",")?;
1166 }
1167 } else if !fields.is_empty() {
1168 let visible_len = self.visible_collection_len(fields.len());
1169 for idx in 0..visible_len {
1170 if !short {
1171 writeln!(f)?;
1172 self.indent(f, format_depth + 1)?;
1173
1174 if self.show_doc_comments {
1175 for &line in fields[idx].doc {
1176 self.write_comment(f, &format!("///{line}"))?;
1177 writeln!(f)?;
1178 self.indent(f, format_depth + 1)?;
1179 }
1180 }
1181 }
1182
1183 if fields[idx].is_sensitive() {
1184 self.write_redacted(f, "[REDACTED]")?;
1185 } else if let Some(proxy_def) = fields[idx].proxy() {
1186 self.format_via_proxy(
1188 peek_field(idx),
1189 proxy_def,
1190 f,
1191 visited,
1192 format_depth + 1,
1193 type_depth + 1,
1194 short,
1195 )?;
1196 } else {
1197 self.format_peek_internal_(
1198 peek_field(idx),
1199 f,
1200 visited,
1201 format_depth + 1,
1202 type_depth + 1,
1203 short,
1204 )?;
1205 }
1206
1207 if !short || idx + 1 < visible_len || visible_len < fields.len() {
1208 self.write_punctuation(f, ",")?;
1209 } else {
1210 write!(f, " ")?;
1211 }
1212 }
1213 if visible_len < fields.len() {
1214 if !short {
1215 writeln!(f)?;
1216 self.indent(f, format_depth + 1)?;
1217 } else if visible_len > 0 {
1218 write!(f, " ")?;
1219 }
1220 self.write_collection_ellipsis(f, fields.len() - visible_len, "fields")?;
1221 }
1222 if !short {
1223 writeln!(f)?;
1224 self.indent(f, format_depth)?;
1225 }
1226 }
1227 self.write_punctuation(f, ")")?;
1228 Ok(())
1229 }
1230
1231 #[allow(clippy::too_many_arguments)]
1232 fn format_struct_fields<'mem, 'facet>(
1233 &self,
1234 peek_field: &dyn Fn(usize) -> Peek<'mem, 'facet>,
1235 f: &mut dyn Write,
1236 visited: &mut BTreeMap<ValueId, usize>,
1237 format_depth: usize,
1238 type_depth: usize,
1239 fields: &[Field],
1240 short: bool,
1241 ) -> fmt::Result {
1242 let mut visible_indices: Vec<usize> = (0..fields.len())
1244 .filter(|&idx| {
1245 let field = &fields[idx];
1246 let field_ptr = peek_field(idx).data();
1248 !unsafe { field.should_skip_serializing(field_ptr) }
1249 })
1250 .collect();
1251 let total_visible = visible_indices.len();
1252 if let Some(max) = self.max_collection_len {
1253 visible_indices.truncate(max);
1254 }
1255
1256 self.write_punctuation(f, " {")?;
1257 if !visible_indices.is_empty() {
1258 for (i, &idx) in visible_indices.iter().enumerate() {
1259 let is_last = i + 1 == visible_indices.len();
1260
1261 if !short {
1262 writeln!(f)?;
1263 self.indent(f, format_depth + 1)?;
1264 }
1265
1266 if self.show_doc_comments {
1267 for &line in fields[idx].doc {
1268 self.write_comment(f, &format!("///{line}"))?;
1269 writeln!(f)?;
1270 self.indent(f, format_depth + 1)?;
1271 }
1272 }
1273
1274 self.write_field_name(f, fields[idx].name)?;
1275 self.write_punctuation(f, ": ")?;
1276 if fields[idx].is_sensitive() {
1277 self.write_redacted(f, "[REDACTED]")?;
1278 } else if let Some(proxy_def) = fields[idx].proxy() {
1279 self.format_via_proxy(
1281 peek_field(idx),
1282 proxy_def,
1283 f,
1284 visited,
1285 format_depth + 1,
1286 type_depth + 1,
1287 short,
1288 )?;
1289 } else {
1290 self.format_peek_internal_(
1291 peek_field(idx),
1292 f,
1293 visited,
1294 format_depth + 1,
1295 type_depth + 1,
1296 short,
1297 )?;
1298 }
1299
1300 if !short || !is_last {
1301 self.write_punctuation(f, ",")?;
1302 } else {
1303 write!(f, " ")?;
1304 }
1305 }
1306 if total_visible > visible_indices.len() {
1307 if !short {
1308 writeln!(f)?;
1309 self.indent(f, format_depth + 1)?;
1310 } else {
1311 write!(f, " ")?;
1312 }
1313 self.write_collection_ellipsis(f, total_visible - visible_indices.len(), "fields")?;
1314 }
1315 if !short {
1316 writeln!(f)?;
1317 self.indent(f, format_depth)?;
1318 }
1319 }
1320 self.write_punctuation(f, "}")?;
1321 Ok(())
1322 }
1323
1324 fn indent(&self, f: &mut dyn Write, indent: usize) -> fmt::Result {
1325 if self.indent_size == usize::MAX {
1326 write!(f, "{:\t<width$}", "", width = indent)
1327 } else {
1328 write!(f, "{: <width$}", "", width = indent * self.indent_size)
1329 }
1330 }
1331
1332 fn visible_collection_len(&self, len: usize) -> usize {
1333 self.max_collection_len
1334 .map_or(len, |max| Ord::min(len, max))
1335 }
1336
1337 fn write_collection_ellipsis(
1338 &self,
1339 f: &mut dyn Write,
1340 omitted: usize,
1341 noun: &str,
1342 ) -> fmt::Result {
1343 self.write_comment(f, &format!("...({omitted} more {noun})..."))
1344 }
1345
1346 pub(crate) fn format_peek_internal(
1348 &self,
1349 value: Peek<'_, '_>,
1350 f: &mut dyn Write,
1351 visited: &mut BTreeMap<ValueId, usize>,
1352 ) -> fmt::Result {
1353 self.format_peek_internal_(value, f, visited, 0, 0, false)
1354 }
1355
1356 fn format_scalar(&self, value: Peek, f: &mut dyn Write) -> fmt::Result {
1358 let mut hasher = DefaultHasher::new();
1360 value.shape().id.hash(&mut hasher);
1361 let hash = hasher.finish();
1362 let color = self.color_generator.generate_color(hash);
1363
1364 struct DisplayWrapper<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
1366
1367 impl fmt::Display for DisplayWrapper<'_, '_> {
1368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1369 if self.0.shape().is_display() {
1370 write!(f, "{}", self.0)?;
1371 } else if self.0.shape().is_debug() {
1372 write!(f, "{:?}", self.0)?;
1373 } else {
1374 write!(f, "{}", self.0.shape())?;
1375 write!(f, "(…)")?;
1376 }
1377 Ok(())
1378 }
1379 }
1380
1381 if self.use_colors() {
1383 let rgb = Rgb(color.r, color.g, color.b);
1384 write!(f, "{}", DisplayWrapper(&value).color(rgb))?;
1385 } else {
1386 write!(f, "{}", DisplayWrapper(&value))?;
1387 }
1388
1389 Ok(())
1390 }
1391
1392 fn write_keyword(&self, f: &mut dyn Write, keyword: &str) -> fmt::Result {
1394 if self.use_colors() {
1395 write!(f, "{}", keyword.color(tokyo_night::KEYWORD))
1396 } else {
1397 write!(f, "{keyword}")
1398 }
1399 }
1400
1401 fn format_number(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1403 if self.use_colors() {
1404 write!(f, "{}", s.color(tokyo_night::NUMBER))
1405 } else {
1406 write!(f, "{s}")
1407 }
1408 }
1409
1410 fn format_str_value(&self, f: &mut dyn Write, value: &str) -> fmt::Result {
1412 if let Some(max) = self.max_content_len
1414 && value.len() > max
1415 {
1416 return self.format_truncated_str(f, value, max);
1417 }
1418
1419 let mut hashes = 0usize;
1421 let mut rest = value;
1422 while let Some(idx) = rest.find('"') {
1423 rest = &rest[idx + 1..];
1424 let before = rest.len();
1425 rest = rest.trim_start_matches('#');
1426 let after = rest.len();
1427 let count = before - after;
1428 hashes = Ord::max(hashes, 1 + count);
1429 }
1430
1431 let pad = "";
1432 let width = hashes.saturating_sub(1);
1433 if hashes > 0 {
1434 write!(f, "r{pad:#<width$}")?;
1435 }
1436 write!(f, "\"")?;
1437 if self.use_colors() {
1438 write!(f, "{}", value.color(tokyo_night::STRING))?;
1439 } else {
1440 write!(f, "{value}")?;
1441 }
1442 write!(f, "\"")?;
1443 if hashes > 0 {
1444 write!(f, "{pad:#<width$}")?;
1445 }
1446 Ok(())
1447 }
1448
1449 fn format_truncated_str(&self, f: &mut dyn Write, s: &str, max: usize) -> fmt::Result {
1451 let half = max / 2;
1452
1453 let start_end = s
1455 .char_indices()
1456 .take_while(|(i, _)| *i < half)
1457 .last()
1458 .map(|(i, c)| i + c.len_utf8())
1459 .unwrap_or(0);
1460
1461 let end_start = s
1463 .char_indices()
1464 .rev()
1465 .take_while(|(i, _)| s.len() - *i <= half)
1466 .last()
1467 .map(|(i, _)| i)
1468 .unwrap_or(s.len());
1469
1470 let omitted = s[start_end..end_start].chars().count();
1471 let start_part = &s[..start_end];
1472 let end_part = &s[end_start..];
1473
1474 if self.use_colors() {
1475 write!(
1476 f,
1477 "\"{}\"...({omitted} chars)...\"{}\"",
1478 start_part.color(tokyo_night::STRING),
1479 end_part.color(tokyo_night::STRING)
1480 )
1481 } else {
1482 write!(f, "\"{start_part}\"...({omitted} chars)...\"{end_part}\"")
1483 }
1484 }
1485
1486 fn format_string(&self, f: &mut dyn Write, s: &str) -> fmt::Result {
1488 if let Some(max) = self.max_content_len
1489 && s.len() > max
1490 {
1491 return self.format_truncated_str(f, s, max);
1492 }
1493
1494 if self.use_colors() {
1495 write!(f, "\"{}\"", s.color(tokyo_night::STRING))
1496 } else {
1497 write!(f, "{s:?}")
1498 }
1499 }
1500
1501 fn format_bytes(&self, f: &mut dyn Write, bytes: &[u8]) -> fmt::Result {
1503 write!(f, "b\"")?;
1504
1505 match self.max_content_len {
1506 Some(max) if bytes.len() > max => {
1507 let half = max / 2;
1509 let start = half;
1510 let end = half;
1511
1512 for byte in &bytes[..start] {
1513 write!(f, "\\x{byte:02x}")?;
1514 }
1515 let omitted = bytes.len() - start - end;
1516 write!(f, "\"...({omitted} bytes)...b\"")?;
1517 for byte in &bytes[bytes.len() - end..] {
1518 write!(f, "\\x{byte:02x}")?;
1519 }
1520 }
1521 _ => {
1522 for byte in bytes {
1523 write!(f, "\\x{byte:02x}")?;
1524 }
1525 }
1526 }
1527
1528 write!(f, "\"")
1529 }
1530
1531 fn write_type_name(&self, f: &mut dyn Write, peek: &Peek) -> fmt::Result {
1533 struct TypeNameWriter<'mem, 'facet>(&'mem Peek<'mem, 'facet>);
1534
1535 impl core::fmt::Display for TypeNameWriter<'_, '_> {
1536 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1537 self.0.type_name(f, TypeNameOpts::infinite())
1538 }
1539 }
1540 let type_name = TypeNameWriter(peek);
1541
1542 if self.use_colors() {
1543 write!(f, "{}", type_name.color(tokyo_night::TYPE_NAME).bold())
1544 } else {
1545 write!(f, "{type_name}")
1546 }
1547 }
1548
1549 #[allow(dead_code)]
1551 fn style_type_name(&self, peek: &Peek) -> String {
1552 let mut result = String::new();
1553 self.write_type_name(&mut result, peek).unwrap();
1554 result
1555 }
1556
1557 fn write_field_name(&self, f: &mut dyn Write, name: &str) -> fmt::Result {
1559 if self.use_colors() {
1560 write!(f, "{}", name.color(tokyo_night::FIELD_NAME))
1561 } else {
1562 write!(f, "{name}")
1563 }
1564 }
1565
1566 fn write_punctuation(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1568 if self.use_colors() {
1569 write!(f, "{}", text.dimmed())
1570 } else {
1571 write!(f, "{text}")
1572 }
1573 }
1574
1575 fn write_comment(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1577 if self.use_colors() {
1578 write!(f, "{}", text.color(tokyo_night::MUTED))
1579 } else {
1580 write!(f, "{text}")
1581 }
1582 }
1583
1584 fn write_redacted(&self, f: &mut dyn Write, text: &str) -> fmt::Result {
1586 if self.use_colors() {
1587 write!(f, "{}", text.color(tokyo_night::ERROR).bold())
1588 } else {
1589 write!(f, "{text}")
1590 }
1591 }
1592
1593 #[allow(dead_code)]
1595 fn style_redacted(&self, text: &str) -> String {
1596 let mut result = String::new();
1597 self.write_redacted(&mut result, text).unwrap();
1598 result
1599 }
1600
1601 pub fn format_peek_with_spans(&self, value: Peek<'_, '_>) -> FormattedValue {
1609 let mut output = SpanTrackingOutput::new();
1610 let printer = Self {
1611 colors: ColorMode::Never, indent_size: self.indent_size,
1613 max_depth: self.max_depth,
1614 color_generator: self.color_generator.clone(),
1615 list_u8_as_bytes: self.list_u8_as_bytes,
1616 minimal_option_names: self.minimal_option_names,
1617 show_doc_comments: self.show_doc_comments,
1618 max_content_len: self.max_content_len,
1619 max_collection_len: self.max_collection_len,
1620 };
1621 printer
1622 .format_unified(
1623 value,
1624 &mut output,
1625 &mut BTreeMap::new(),
1626 0,
1627 0,
1628 false,
1629 vec![],
1630 )
1631 .expect("Formatting failed");
1632
1633 output.into_formatted_value()
1634 }
1635
1636 #[allow(clippy::too_many_arguments)]
1641 fn format_unified<O: FormatOutput>(
1642 &self,
1643 value: Peek<'_, '_>,
1644 out: &mut O,
1645 visited: &mut BTreeMap<ValueId, usize>,
1646 format_depth: usize,
1647 type_depth: usize,
1648 short: bool,
1649 current_path: Path,
1650 ) -> fmt::Result {
1651 let mut value = value;
1652 while let Ok(ptr) = value.into_pointer()
1653 && let Some(pointee) = ptr.borrow_inner()
1654 {
1655 value = pointee;
1656 }
1657
1658 let value = value.innermost_peek();
1661 let shape = value.shape();
1662
1663 let value_start = out.position();
1665
1666 if let Some(prev_type_depth) = visited.insert(value.id(), type_depth) {
1667 write!(out, "{} {{ ", shape)?;
1668 write!(
1669 out,
1670 "/* cycle detected at {} (first seen at type_depth {}) */",
1671 value.id(),
1672 prev_type_depth,
1673 )?;
1674 visited.remove(&value.id());
1675 let value_end = out.position();
1676 out.record_span(current_path, (value_start, value_end));
1677 return Ok(());
1678 }
1679
1680 if let Some(proxy_def) = shape.proxy {
1682 let result = self.format_via_proxy_unified(
1683 value,
1684 proxy_def,
1685 out,
1686 visited,
1687 format_depth,
1688 type_depth,
1689 short,
1690 current_path.clone(),
1691 );
1692
1693 visited.remove(&value.id());
1694
1695 let value_end = out.position();
1697 out.record_span(current_path, (value_start, value_end));
1698
1699 return result;
1700 }
1701
1702 match (shape.def, shape.ty) {
1703 (_, Type::Primitive(PrimitiveType::Textual(TextualType::Str))) => {
1704 let s = value.get::<str>().unwrap();
1705 write!(out, "\"{}\"", s)?;
1706 }
1707 (Def::Scalar, _) if value.shape().id == <alloc::string::String as Facet>::SHAPE.id => {
1708 let s = value.get::<alloc::string::String>().unwrap();
1709 write!(out, "\"{}\"", s)?;
1710 }
1711 (Def::Scalar, _) => {
1712 self.format_scalar_to_output(value, out)?;
1713 }
1714 (Def::Option(_), _) => {
1715 let option = value.into_option().unwrap();
1716 if let Some(inner) = option.value() {
1717 write!(out, "Some(")?;
1718 self.format_unified(
1719 inner,
1720 out,
1721 visited,
1722 format_depth,
1723 type_depth + 1,
1724 short,
1725 current_path.clone(),
1726 )?;
1727 write!(out, ")")?;
1728 } else {
1729 write!(out, "None")?;
1730 }
1731 }
1732 (Def::Result(_), _) => {
1733 let result = value.into_result().unwrap();
1734 write!(out, "{}", shape)?;
1735 if result.is_ok() {
1736 write!(out, " Ok(")?;
1737 if let Some(ok_val) = result.ok() {
1738 self.format_unified(
1739 ok_val,
1740 out,
1741 visited,
1742 format_depth,
1743 type_depth + 1,
1744 short,
1745 current_path.clone(),
1746 )?;
1747 }
1748 write!(out, ")")?;
1749 } else {
1750 write!(out, " Err(")?;
1751 if let Some(err_val) = result.err() {
1752 self.format_unified(
1753 err_val,
1754 out,
1755 visited,
1756 format_depth,
1757 type_depth + 1,
1758 short,
1759 current_path.clone(),
1760 )?;
1761 }
1762 write!(out, ")")?;
1763 }
1764 }
1765 (
1766 _,
1767 Type::User(UserType::Struct(
1768 ty @ StructType {
1769 kind: StructKind::Struct | StructKind::Unit,
1770 ..
1771 },
1772 )),
1773 ) => {
1774 write!(out, "{}", shape)?;
1775 if matches!(ty.kind, StructKind::Struct) {
1776 let struct_peek = value.into_struct().unwrap();
1777 write!(out, " {{")?;
1778 for (i, field) in ty.fields.iter().enumerate() {
1779 if !short {
1780 writeln!(out)?;
1781 self.indent_to_output(out, format_depth + 1)?;
1782 }
1783 let field_name_start = out.position();
1785 write!(out, "{}", field.name)?;
1786 let field_name_end = out.position();
1787 write!(out, ": ")?;
1788
1789 let mut field_path = current_path.clone();
1791 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1792
1793 let field_value_start = out.position();
1795 if let Ok(field_value) = struct_peek.field(i) {
1796 if let Some(proxy_def) = field.proxy() {
1798 self.format_via_proxy_unified(
1799 field_value,
1800 proxy_def,
1801 out,
1802 visited,
1803 format_depth + 1,
1804 type_depth + 1,
1805 short,
1806 field_path.clone(),
1807 )?;
1808 } else {
1809 self.format_unified(
1810 field_value,
1811 out,
1812 visited,
1813 format_depth + 1,
1814 type_depth + 1,
1815 short,
1816 field_path.clone(),
1817 )?;
1818 }
1819 }
1820 let field_value_end = out.position();
1821
1822 out.record_field_span(
1824 field_path,
1825 (field_name_start, field_name_end),
1826 (field_value_start, field_value_end),
1827 );
1828
1829 if !short || i + 1 < ty.fields.len() {
1830 write!(out, ",")?;
1831 }
1832 }
1833 if !short {
1834 writeln!(out)?;
1835 self.indent_to_output(out, format_depth)?;
1836 }
1837 write!(out, "}}")?;
1838 }
1839 }
1840 (
1841 _,
1842 Type::User(UserType::Struct(
1843 ty @ StructType {
1844 kind: StructKind::Tuple | StructKind::TupleStruct,
1845 ..
1846 },
1847 )),
1848 ) => {
1849 write!(out, "{}", shape)?;
1850 if matches!(ty.kind, StructKind::Tuple) {
1851 write!(out, " ")?;
1852 }
1853 let struct_peek = value.into_struct().unwrap();
1854 write!(out, "(")?;
1855 for (i, field) in ty.fields.iter().enumerate() {
1856 if i > 0 {
1857 write!(out, ", ")?;
1858 }
1859 let mut elem_path = current_path.clone();
1860 elem_path.push(PathSegment::Index(i));
1861
1862 let elem_start = out.position();
1863 if let Ok(field_value) = struct_peek.field(i) {
1864 if let Some(proxy_def) = field.proxy() {
1866 self.format_via_proxy_unified(
1867 field_value,
1868 proxy_def,
1869 out,
1870 visited,
1871 format_depth + 1,
1872 type_depth + 1,
1873 short,
1874 elem_path.clone(),
1875 )?;
1876 } else {
1877 self.format_unified(
1878 field_value,
1879 out,
1880 visited,
1881 format_depth + 1,
1882 type_depth + 1,
1883 short,
1884 elem_path.clone(),
1885 )?;
1886 }
1887 }
1888 let elem_end = out.position();
1889 out.record_span(elem_path, (elem_start, elem_end));
1890 }
1891 write!(out, ")")?;
1892 }
1893 (_, Type::User(UserType::Enum(_))) => {
1894 let enum_peek = value.into_enum().unwrap();
1895 match enum_peek.active_variant() {
1896 Err(_) => {
1897 write!(out, "{} {{ /* cannot determine variant */ }}", shape)?;
1898 }
1899 Ok(variant) => {
1900 write!(out, "{}::{}", shape, variant.name)?;
1901
1902 match variant.data.kind {
1903 StructKind::Unit => {}
1904 StructKind::Struct => {
1905 write!(out, " {{")?;
1906 for (i, field) in variant.data.fields.iter().enumerate() {
1907 if !short {
1908 writeln!(out)?;
1909 self.indent_to_output(out, format_depth + 1)?;
1910 }
1911 let field_name_start = out.position();
1912 write!(out, "{}", field.name)?;
1913 let field_name_end = out.position();
1914 write!(out, ": ")?;
1915
1916 let mut field_path = current_path.clone();
1917 field_path
1918 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
1919 field_path.push(PathSegment::Field(Cow::Borrowed(field.name)));
1920
1921 let field_value_start = out.position();
1922 if let Ok(Some(field_value)) = enum_peek.field(i) {
1923 if let Some(proxy_def) = field.proxy() {
1925 self.format_via_proxy_unified(
1926 field_value,
1927 proxy_def,
1928 out,
1929 visited,
1930 format_depth + 1,
1931 type_depth + 1,
1932 short,
1933 field_path.clone(),
1934 )?;
1935 } else {
1936 self.format_unified(
1937 field_value,
1938 out,
1939 visited,
1940 format_depth + 1,
1941 type_depth + 1,
1942 short,
1943 field_path.clone(),
1944 )?;
1945 }
1946 }
1947 let field_value_end = out.position();
1948
1949 out.record_field_span(
1950 field_path,
1951 (field_name_start, field_name_end),
1952 (field_value_start, field_value_end),
1953 );
1954
1955 if !short || i + 1 < variant.data.fields.len() {
1956 write!(out, ",")?;
1957 }
1958 }
1959 if !short {
1960 writeln!(out)?;
1961 self.indent_to_output(out, format_depth)?;
1962 }
1963 write!(out, "}}")?;
1964 }
1965 _ => {
1966 write!(out, "(")?;
1967 for (i, field) in variant.data.fields.iter().enumerate() {
1968 if i > 0 {
1969 write!(out, ", ")?;
1970 }
1971 let mut elem_path = current_path.clone();
1972 elem_path
1973 .push(PathSegment::Variant(Cow::Borrowed(variant.name)));
1974 elem_path.push(PathSegment::Index(i));
1975
1976 let elem_start = out.position();
1977 if let Ok(Some(field_value)) = enum_peek.field(i) {
1978 if let Some(proxy_def) = field.proxy() {
1980 self.format_via_proxy_unified(
1981 field_value,
1982 proxy_def,
1983 out,
1984 visited,
1985 format_depth + 1,
1986 type_depth + 1,
1987 short,
1988 elem_path.clone(),
1989 )?;
1990 } else {
1991 self.format_unified(
1992 field_value,
1993 out,
1994 visited,
1995 format_depth + 1,
1996 type_depth + 1,
1997 short,
1998 elem_path.clone(),
1999 )?;
2000 }
2001 }
2002 let elem_end = out.position();
2003 out.record_span(elem_path, (elem_start, elem_end));
2004 }
2005 write!(out, ")")?;
2006 }
2007 }
2008 }
2009 }
2010 }
2011 _ if value.into_list_like().is_ok() => {
2012 let list = value.into_list_like().unwrap();
2013
2014 let elem_shape = list.def().t();
2016 let is_simple = Self::shape_chunkiness(elem_shape) <= 1;
2017
2018 write!(out, "[")?;
2019 let len = list.len();
2020 for (i, item) in list.iter().enumerate() {
2021 if !short && !is_simple {
2022 writeln!(out)?;
2023 self.indent_to_output(out, format_depth + 1)?;
2024 } else if i > 0 {
2025 write!(out, " ")?;
2026 }
2027 let mut elem_path = current_path.clone();
2028 elem_path.push(PathSegment::Index(i));
2029
2030 let elem_start = out.position();
2031 self.format_unified(
2032 item,
2033 out,
2034 visited,
2035 format_depth + 1,
2036 type_depth + 1,
2037 short || is_simple,
2038 elem_path.clone(),
2039 )?;
2040 let elem_end = out.position();
2041 out.record_span(elem_path, (elem_start, elem_end));
2042
2043 if (!short && !is_simple) || i + 1 < len {
2044 write!(out, ",")?;
2045 }
2046 }
2047 if !short && !is_simple {
2048 writeln!(out)?;
2049 self.indent_to_output(out, format_depth)?;
2050 }
2051 write!(out, "]")?;
2052 }
2053 _ if value.into_map().is_ok() => {
2054 let map = value.into_map().unwrap();
2055 write!(out, "{{")?;
2056 for (i, (key, val)) in map.iter().enumerate() {
2057 if !short {
2058 writeln!(out)?;
2059 self.indent_to_output(out, format_depth + 1)?;
2060 }
2061 let key_start = out.position();
2063 self.format_unified(
2064 key,
2065 out,
2066 visited,
2067 format_depth + 1,
2068 type_depth + 1,
2069 true, vec![],
2071 )?;
2072 let key_end = out.position();
2073
2074 write!(out, ": ")?;
2075
2076 let key_str = self.format_peek(key);
2078 let mut entry_path = current_path.clone();
2079 entry_path.push(PathSegment::Key(Cow::Owned(key_str)));
2080
2081 let val_start = out.position();
2082 self.format_unified(
2083 val,
2084 out,
2085 visited,
2086 format_depth + 1,
2087 type_depth + 1,
2088 short,
2089 entry_path.clone(),
2090 )?;
2091 let val_end = out.position();
2092
2093 out.record_field_span(entry_path, (key_start, key_end), (val_start, val_end));
2094
2095 if !short || i + 1 < map.len() {
2096 write!(out, ",")?;
2097 }
2098 }
2099 if !short && !map.is_empty() {
2100 writeln!(out)?;
2101 self.indent_to_output(out, format_depth)?;
2102 }
2103 write!(out, "}}")?;
2104 }
2105 _ => {
2106 write!(out, "{} {{ ... }}", shape)?;
2108 }
2109 }
2110
2111 visited.remove(&value.id());
2112
2113 let value_end = out.position();
2115 out.record_span(current_path, (value_start, value_end));
2116
2117 Ok(())
2118 }
2119
2120 fn format_scalar_to_output(&self, value: Peek<'_, '_>, out: &mut impl Write) -> fmt::Result {
2121 if value.shape().is_display() {
2123 write!(out, "{}", value)
2124 } else if value.shape().is_debug() {
2125 write!(out, "{:?}", value)
2126 } else {
2127 write!(out, "{}(…)", value.shape())
2128 }
2129 }
2130
2131 fn indent_to_output(&self, out: &mut impl Write, depth: usize) -> fmt::Result {
2132 for _ in 0..depth {
2133 for _ in 0..self.indent_size {
2134 out.write_char(' ')?;
2135 }
2136 }
2137 Ok(())
2138 }
2139}
2140
2141#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2143pub enum ColorMode {
2144 Auto,
2146 Always,
2148 Never,
2150}
2151
2152impl ColorMode {
2153 pub fn enabled(&self) -> bool {
2155 static NO_COLOR: LazyLock<bool> = LazyLock::new(|| std::env::var_os("NO_COLOR").is_some());
2156 match self {
2157 ColorMode::Auto => !*NO_COLOR,
2158 ColorMode::Always => true,
2159 ColorMode::Never => false,
2160 }
2161 }
2162}
2163
2164impl From<bool> for ColorMode {
2165 fn from(value: bool) -> Self {
2166 if value {
2167 ColorMode::Always
2168 } else {
2169 ColorMode::Never
2170 }
2171 }
2172}
2173
2174impl From<ColorMode> for Option<bool> {
2175 fn from(value: ColorMode) -> Self {
2176 match value {
2177 ColorMode::Auto => None,
2178 ColorMode::Always => Some(true),
2179 ColorMode::Never => Some(false),
2180 }
2181 }
2182}
2183
2184#[derive(Debug)]
2186pub struct FormattedValue {
2187 pub text: String,
2189 pub spans: BTreeMap<Path, FieldSpan>,
2191}
2192
2193trait FormatOutput: Write {
2198 fn position(&self) -> usize;
2200
2201 fn record_span(&mut self, _path: Path, _span: Span) {}
2203
2204 fn record_field_span(&mut self, _path: Path, _key_span: Span, _value_span: Span) {}
2206}
2207
2208#[allow(dead_code)]
2211struct NonTrackingOutput<W> {
2212 inner: W,
2213 position: usize,
2214}
2215
2216#[allow(dead_code)]
2217impl<W> NonTrackingOutput<W> {
2218 const fn new(inner: W) -> Self {
2219 Self { inner, position: 0 }
2220 }
2221}
2222
2223impl<W: Write> Write for NonTrackingOutput<W> {
2224 fn write_str(&mut self, s: &str) -> fmt::Result {
2225 self.position += s.len();
2226 self.inner.write_str(s)
2227 }
2228}
2229
2230impl<W: Write> FormatOutput for NonTrackingOutput<W> {
2231 fn position(&self) -> usize {
2232 self.position
2233 }
2234 }
2236
2237struct SpanTrackingOutput {
2239 output: String,
2240 spans: BTreeMap<Path, FieldSpan>,
2241}
2242
2243impl SpanTrackingOutput {
2244 const fn new() -> Self {
2245 Self {
2246 output: String::new(),
2247 spans: BTreeMap::new(),
2248 }
2249 }
2250
2251 fn into_formatted_value(self) -> FormattedValue {
2252 FormattedValue {
2253 text: self.output,
2254 spans: self.spans,
2255 }
2256 }
2257}
2258
2259impl Write for SpanTrackingOutput {
2260 fn write_str(&mut self, s: &str) -> fmt::Result {
2261 self.output.push_str(s);
2262 Ok(())
2263 }
2264}
2265
2266impl FormatOutput for SpanTrackingOutput {
2267 fn position(&self) -> usize {
2268 self.output.len()
2269 }
2270
2271 fn record_span(&mut self, path: Path, span: Span) {
2272 self.spans.insert(
2273 path,
2274 FieldSpan {
2275 key: span,
2276 value: span,
2277 },
2278 );
2279 }
2280
2281 fn record_field_span(&mut self, path: Path, key_span: Span, value_span: Span) {
2282 self.spans.insert(
2283 path,
2284 FieldSpan {
2285 key: key_span,
2286 value: value_span,
2287 },
2288 );
2289 }
2290}
2291
2292#[cfg(test)]
2293mod tests {
2294 use super::*;
2295
2296 #[test]
2298 fn test_pretty_printer_default() {
2299 let printer = PrettyPrinter::default();
2300 assert_eq!(printer.indent_size, 2);
2301 assert_eq!(printer.max_depth, None);
2302 assert_eq!(printer.use_colors(), std::env::var_os("NO_COLOR").is_none());
2305 }
2306
2307 #[test]
2308 fn test_pretty_printer_with_methods() {
2309 let printer = PrettyPrinter::new()
2310 .with_indent_size(4)
2311 .with_max_depth(3)
2312 .with_colors(ColorMode::Never);
2313
2314 assert_eq!(printer.indent_size, 4);
2315 assert_eq!(printer.max_depth, Some(3));
2316 assert!(!printer.use_colors());
2317 }
2318
2319 #[test]
2320 fn test_format_peek_with_spans() {
2321 use crate::PathSegment;
2322 use facet_reflect::Peek;
2323
2324 let value = ("Alice", 30u32);
2326
2327 let printer = PrettyPrinter::new();
2328 let formatted = printer.format_peek_with_spans(Peek::new(&value));
2329
2330 assert!(!formatted.text.is_empty());
2332 assert!(formatted.text.contains("Alice"));
2333 assert!(formatted.text.contains("30"));
2334
2335 assert!(!formatted.spans.is_empty());
2337
2338 assert!(formatted.spans.contains_key(&vec![]));
2340
2341 let idx0_path = vec![PathSegment::Index(0)];
2343 let idx1_path = vec![PathSegment::Index(1)];
2344 assert!(
2345 formatted.spans.contains_key(&idx0_path),
2346 "index 0 span not found"
2347 );
2348 assert!(
2349 formatted.spans.contains_key(&idx1_path),
2350 "index 1 span not found"
2351 );
2352 }
2353
2354 #[test]
2355 fn test_max_content_len_string() {
2356 let printer = PrettyPrinter::new()
2357 .with_colors(ColorMode::Never)
2358 .with_max_content_len(20);
2359
2360 let short = "hello";
2362 let output = printer.format(&short);
2363 assert_eq!(output, "\"hello\"");
2364
2365 let long = "abcdefghijklmnopqrstuvwxyz0123456789";
2367 let output = printer.format(&long);
2368 assert!(
2369 output.contains("..."),
2370 "should contain ellipsis: {}",
2371 output
2372 );
2373 assert!(output.contains("chars"), "should mention chars: {}", output);
2374 assert!(
2375 output.starts_with("\"abc"),
2376 "should start with beginning: {}",
2377 output
2378 );
2379 assert!(
2380 output.ends_with("89\""),
2381 "should end with ending: {}",
2382 output
2383 );
2384 }
2385
2386 #[test]
2387 fn test_max_content_len_bytes() {
2388 let printer = PrettyPrinter::new()
2389 .with_colors(ColorMode::Never)
2390 .with_max_content_len(10);
2391
2392 let short: Vec<u8> = vec![1, 2, 3];
2394 let output = printer.format(&short);
2395 assert!(
2396 output.contains("01 02 03"),
2397 "should show all bytes: {}",
2398 output
2399 );
2400
2401 let long: Vec<u8> = (0..50).collect();
2403 let output = printer.format(&long);
2404 assert!(
2405 output.contains("..."),
2406 "should contain ellipsis: {}",
2407 output
2408 );
2409 assert!(output.contains("bytes"), "should mention bytes: {}", output);
2410 }
2411
2412 #[test]
2413 fn test_max_content_formatting() {
2414 let printer = PrettyPrinter::new()
2415 .with_colors(ColorMode::Never)
2416 .with_max_content_len(12);
2417 let data: Vec<u8> = (0..50).collect();
2418 let output = printer.format(&data);
2419 insta::assert_snapshot!(output, @"
2420 Vec<u8> [
2421 00 01 02 03 04 05
2422 ...(38 bytes)...
2423 2c 2d 2e 2f 30 31
2424 ]
2425 ");
2426 }
2427
2428 #[test]
2429 fn test_max_collection_len_sequence() {
2430 let printer = PrettyPrinter::new()
2431 .with_colors(ColorMode::Never)
2432 .with_max_collection_len(3);
2433
2434 let value = vec![1u32, 2, 3, 4, 5];
2435 let output = printer.format(&value);
2436
2437 assert!(output.contains("1"));
2438 assert!(output.contains("2"));
2439 assert!(output.contains("3"));
2440 assert!(!output.contains("5"));
2441 assert!(output.contains("more items"), "output: {output}");
2442 }
2443
2444 #[test]
2445 fn test_max_collection_len_struct_fields() {
2446 #[derive(facet::Facet)]
2447 struct Record {
2448 alpha: u32,
2449 beta: u32,
2450 gamma: u32,
2451 delta: u32,
2452 }
2453
2454 let printer = PrettyPrinter::new()
2455 .with_colors(ColorMode::Never)
2456 .with_max_collection_len(2);
2457
2458 let value = Record {
2459 alpha: 1,
2460 beta: 2,
2461 gamma: 3,
2462 delta: 4,
2463 };
2464 let output = printer.format(&value);
2465
2466 assert!(output.contains("alpha"));
2467 assert!(output.contains("beta"));
2468 assert!(!output.contains("gamma"));
2469 assert!(output.contains("more fields"), "output: {output}");
2470 }
2471}