facet_pretty/
printer.rs

1//! Pretty printer implementation for Facet types
2
3use std::{
4    collections::{HashMap, VecDeque},
5    fmt::{self, Write},
6    hash::{DefaultHasher, Hash, Hasher},
7    str,
8};
9
10use facet_core::Facet;
11use facet_peek::Peek;
12
13use crate::{ansi, color::ColorGenerator};
14
15/// A formatter for pretty-printing Facet types
16pub struct PrettyPrinter {
17    indent_size: usize,
18    max_depth: Option<usize>,
19    color_generator: ColorGenerator,
20    use_colors: bool,
21    list_u8_as_bytes: bool,
22}
23
24impl Default for PrettyPrinter {
25    fn default() -> Self {
26        Self {
27            indent_size: 2,
28            max_depth: None,
29            color_generator: ColorGenerator::default(),
30            use_colors: true,
31            list_u8_as_bytes: true,
32        }
33    }
34}
35
36/// Stack state for iterative formatting
37enum StackState {
38    Start,
39    ProcessStructField { field_index: usize },
40    ProcessListItem { item_index: usize },
41    ProcessBytesItem { item_index: usize },
42    ProcessMapEntry,
43    Finish,
44    OptionFinish,
45}
46
47/// Stack item for iterative traversal
48struct StackItem<'a> {
49    peek: Peek<'a>,
50    format_depth: usize,
51    type_depth: usize,
52    state: StackState,
53}
54
55impl PrettyPrinter {
56    /// Create a new PrettyPrinter with default settings
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Set the indentation size
62    pub fn with_indent_size(mut self, size: usize) -> Self {
63        self.indent_size = size;
64        self
65    }
66
67    /// Set the maximum depth for recursive printing
68    pub fn with_max_depth(mut self, depth: usize) -> Self {
69        self.max_depth = Some(depth);
70        self
71    }
72
73    /// Set the color generator
74    pub fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
75        self.color_generator = generator;
76        self
77    }
78
79    /// Enable or disable colors
80    pub fn with_colors(mut self, use_colors: bool) -> Self {
81        self.use_colors = use_colors;
82        self
83    }
84
85    /// Format a value to a string
86    pub fn format<T: Facet>(&self, value: &T) -> String {
87        let peek = Peek::new(value);
88
89        let mut output = String::new();
90        self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
91            .expect("Formatting failed");
92
93        output
94    }
95
96    /// Format a value to a formatter
97    pub fn format_to<T: Facet>(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        let peek = Peek::new(value);
99        self.format_peek_internal(peek, f, 0, 0, &mut HashMap::new())
100    }
101
102    /// Format a Peek value to a string
103    pub fn format_peek(&self, peek: Peek<'_>) -> String {
104        let mut output = String::new();
105        self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
106            .expect("Formatting failed");
107        output
108    }
109
110    /// Internal method to format a Peek value
111    pub(crate) fn format_peek_internal(
112        &self,
113        peek: Peek<'_>,
114        f: &mut impl Write,
115        format_depth: usize,
116        type_depth: usize,
117        visited: &mut HashMap<*const (), usize>,
118    ) -> fmt::Result {
119        // Create a queue for our stack items
120        let mut stack = VecDeque::new();
121
122        // Push the initial item
123        stack.push_back(StackItem {
124            peek,
125            format_depth,
126            type_depth,
127            state: StackState::Start,
128        });
129
130        // Process items until the stack is empty
131        while let Some(mut item) = stack.pop_back() {
132            match item.state {
133                StackState::Start => {
134                    // Check if we've reached the maximum depth
135                    if let Some(max_depth) = self.max_depth {
136                        if item.format_depth > max_depth {
137                            self.write_punctuation(f, "[")?;
138                            write!(f, "...")?;
139                            continue;
140                        }
141                    }
142
143                    // Get the data pointer for cycle detection
144                    let ptr = unsafe { item.peek.data().as_ptr() };
145
146                    // Check for cycles - if we've seen this pointer before at a different type_depth
147                    if let Some(&ptr_type_depth) = visited.get(&ptr) {
148                        // If the current type_depth is significantly deeper than when we first saw this pointer,
149                        // we have a true cycle, not just a transparent wrapper
150                        if item.type_depth > ptr_type_depth + 1 {
151                            self.write_type_name(f, &item.peek)?;
152                            self.write_punctuation(f, " { ")?;
153                            self.write_comment(
154                                f,
155                                &format!(
156                                    "/* cycle detected at {:p} (first seen at type_depth {}) */",
157                                    ptr, ptr_type_depth
158                                ),
159                            )?;
160                            self.write_punctuation(f, " }")?;
161                            continue;
162                        }
163                    } else {
164                        // First time seeing this pointer, record its type_depth
165                        visited.insert(ptr, item.type_depth);
166                    }
167
168                    // Process based on the peek variant
169                    match item.peek {
170                        Peek::Value(value) => {
171                            self.format_value(value, f)?;
172                        }
173                        Peek::Option(option) => {
174                            // Print the Option name
175                            self.write_type_name(f, &option)?;
176
177                            if option.is_some() {
178                                self.write_punctuation(f, "::Some(")?;
179
180                                if let Some(inner_value) = option.value() {
181                                    // Create a custom stack item for Option::Some value
182                                    let start_item = StackItem {
183                                        peek: inner_value,
184                                        format_depth: item.format_depth,
185                                        type_depth: item.type_depth + 1,
186                                        state: StackState::Start,
187                                    };
188
189                                    // Add a special close parenthesis item
190                                    let close_paren_item = StackItem {
191                                        peek: Peek::Option(option),
192                                        format_depth: item.format_depth,
193                                        type_depth: item.type_depth,
194                                        state: StackState::OptionFinish,
195                                    };
196
197                                    // Process the value first, then handle closing
198                                    stack.push_back(close_paren_item);
199                                    stack.push_back(start_item);
200                                }
201
202                                // Skip to next item
203                                continue;
204                            } else {
205                                self.write_punctuation(f, "::None")?;
206                            }
207                        }
208                        Peek::Struct(struct_) => {
209                            // When recursing into a struct, always increment format_depth
210                            // Only increment type_depth if we're moving to a different address
211                            let new_type_depth =
212                                if core::ptr::eq(unsafe { struct_.data().as_ptr() }, ptr) {
213                                    item.type_depth // Same pointer, don't increment type_depth
214                                } else {
215                                    item.type_depth + 1 // Different pointer, increment type_depth
216                                };
217
218                            // Get struct doc comments from the shape
219                            let doc_comments = struct_.shape().doc;
220                            if !doc_comments.is_empty() {
221                                for line in doc_comments {
222                                    self.write_comment(f, &format!("///{}", line))?;
223                                    writeln!(f)?;
224                                }
225                            }
226
227                            // Print the struct name
228                            self.write_type_name(f, &struct_)?;
229                            self.write_punctuation(f, " {")?;
230
231                            if struct_.field_count() == 0 {
232                                self.write_punctuation(f, "}")?;
233                                continue;
234                            }
235
236                            writeln!(f)?;
237
238                            // Push back the item with the next state to continue processing fields
239                            item.state = StackState::ProcessStructField { field_index: 0 };
240                            item.format_depth += 1;
241                            item.type_depth = new_type_depth;
242                            stack.push_back(item);
243                        }
244                        Peek::List(list) => {
245                            // When recursing into a list, always increment format_depth
246                            // Only increment type_depth if we're moving to a different address
247                            let new_type_depth =
248                                if core::ptr::eq(unsafe { list.data().as_ptr() }, ptr) {
249                                    item.type_depth // Same pointer, don't increment type_depth
250                                } else {
251                                    item.type_depth + 1 // Different pointer, increment type_depth
252                                };
253
254                            // Print the list name
255                            self.write_type_name(f, &list)?;
256
257                            if list.def().t.is_type::<u8>() && self.list_u8_as_bytes {
258                                // Push back the item with the next state to continue processing list items
259                                item.state = StackState::ProcessBytesItem { item_index: 0 };
260                                writeln!(f)?;
261                                write!(f, " ")?;
262
263                                // TODO: write all the bytes here instead?
264                            } else {
265                                // Push back the item with the next state to continue processing list items
266                                item.state = StackState::ProcessListItem { item_index: 0 };
267                                self.write_punctuation(f, " [")?;
268                                writeln!(f)?;
269                            }
270
271                            item.format_depth += 1;
272                            item.type_depth = new_type_depth;
273                            stack.push_back(item);
274                        }
275                        Peek::Map(map) => {
276                            // Print the map name
277                            self.write_type_name(f, &map)?;
278                            self.write_punctuation(f, " {")?;
279                            writeln!(f)?;
280
281                            // Push back the item with the next state to continue processing map
282                            item.state = StackState::ProcessMapEntry;
283                            item.format_depth += 1;
284                            // When recursing into a map, always increment format_depth
285                            // Only increment type_depth if we're moving to a different address
286                            item.type_depth = if core::ptr::eq(unsafe { map.data().as_ptr() }, ptr)
287                            {
288                                item.type_depth // Same pointer, don't increment type_depth
289                            } else {
290                                item.type_depth + 1 // Different pointer, increment type_depth
291                            };
292                            stack.push_back(item);
293                        }
294                        Peek::Enum(enum_) => {
295                            // When recursing into an enum, increment format_depth
296                            // Only increment type_depth if we're moving to a different address
297                            let _new_type_depth =
298                                if core::ptr::eq(unsafe { enum_.data().as_ptr() }, ptr) {
299                                    item.type_depth // Same pointer, don't increment type_depth
300                                } else {
301                                    item.type_depth + 1 // Different pointer, increment type_depth
302                                };
303
304                            // Get the active variant
305                            let variant = enum_.active_variant();
306
307                            // Get enum and variant doc comments
308                            let doc_comments = enum_.shape().doc;
309
310                            // Display doc comments before the type name
311                            for line in doc_comments {
312                                self.write_comment(f, &format!("///{}", line))?;
313                                writeln!(f)?;
314                            }
315
316                            // Show variant docs
317                            for line in variant.doc {
318                                self.write_comment(f, &format!("///{}", line))?;
319                                writeln!(f)?;
320                            }
321
322                            // Print the enum name and separator
323                            self.write_type_name(f, &enum_)?;
324                            self.write_punctuation(f, "::")?;
325
326                            // Variant docs are already handled above
327
328                            // Get the active variant name
329                            let variant_name = enum_.variant_name_active();
330
331                            // Apply color for variant name
332                            if self.use_colors {
333                                ansi::write_bold(f)?;
334                                write!(f, "{}", variant_name)?;
335                                ansi::write_reset(f)?;
336                            } else {
337                                write!(f, "{}", variant_name)?;
338                            }
339
340                            // Process the variant fields based on the variant kind
341                            match enum_.variant_kind_active() {
342                                facet_core::VariantKind::Unit => {
343                                    // Unit variant has no fields, nothing more to print
344                                }
345                                facet_core::VariantKind::Tuple { .. } => {
346                                    // Tuple variant, print the fields like a tuple
347                                    self.write_punctuation(f, "(")?;
348
349                                    // Check if there are any fields to print
350                                    let has_fields = enum_.fields().count() > 0;
351
352                                    if !has_fields {
353                                        self.write_punctuation(f, ")")?;
354                                        continue;
355                                    }
356
357                                    writeln!(f)?;
358
359                                    // Push back item to process fields
360                                    item.state = StackState::ProcessStructField { field_index: 0 };
361                                    item.format_depth += 1;
362                                    stack.push_back(item);
363                                }
364                                facet_core::VariantKind::Struct { .. } => {
365                                    // Struct variant, print the fields like a struct
366                                    self.write_punctuation(f, " {")?;
367
368                                    // Check if there are any fields to print
369                                    let has_fields = enum_.fields().count() > 0;
370
371                                    if !has_fields {
372                                        self.write_punctuation(f, " }")?;
373                                        continue;
374                                    }
375
376                                    writeln!(f)?;
377
378                                    // Push back item to process fields
379                                    item.state = StackState::ProcessStructField { field_index: 0 };
380                                    item.format_depth += 1;
381                                    stack.push_back(item);
382                                }
383                                _ => {
384                                    // Other variant kinds that might be added in the future
385                                    write!(f, " /* unsupported variant kind */")?;
386                                }
387                            }
388                        }
389                        _ => {
390                            write!(f, "unsupported peek variant: {:?}", item.peek)?;
391                        }
392                    }
393                }
394                StackState::ProcessStructField { field_index } => {
395                    // Handle both struct and enum fields
396                    if let Peek::Struct(struct_) = item.peek {
397                        let fields: Vec<_> = struct_.fields_with_metadata().collect();
398
399                        if field_index >= fields.len() {
400                            // All fields processed, write closing brace
401                            write!(
402                                f,
403                                "{:width$}{}",
404                                "",
405                                self.style_punctuation("}"),
406                                width = (item.format_depth - 1) * self.indent_size
407                            )?;
408                            continue;
409                        }
410
411                        let (_, field_name, field_value, field) = &fields[field_index];
412
413                        // Define consistent indentation
414                        let field_indent = "  "; // Use 2 spaces for all fields
415
416                        // Field doc comment
417                        if !field.doc.is_empty() {
418                            // Only add new line if not the first field
419                            if field_index > 0 {
420                                writeln!(f)?;
421                            }
422                            // Hard-code consistent indentation for doc comments
423                            for line in field.doc {
424                                // Use exactly the same indentation as fields (2 spaces)
425                                write!(f, "{}", field_indent)?;
426                                self.write_comment(f, &format!("///{}", line))?;
427                                writeln!(f)?;
428                            }
429                            // Rewrite indentation after doc comments with consistent spacing
430                            write!(f, "{}", field_indent)?;
431                        }
432
433                        // Field name
434                        self.write_field_name(f, field_name)?;
435                        self.write_punctuation(f, ": ")?;
436
437                        // Check if field is sensitive
438                        if field.flags.contains(facet_core::FieldFlags::SENSITIVE) {
439                            // Field value is sensitive, use write_redacted
440                            self.write_redacted(f, "[REDACTED]")?;
441                            self.write_punctuation(f, ",")?;
442                            writeln!(f)?;
443
444                            item.state = StackState::ProcessStructField {
445                                field_index: field_index + 1,
446                            };
447                            stack.push_back(item);
448                        } else {
449                            // Field value is not sensitive, format normally
450                            // Push back current item to continue after formatting field value
451                            item.state = StackState::ProcessStructField {
452                                field_index: field_index + 1,
453                            };
454
455                            let finish_item = StackItem {
456                                peek: *field_value,
457                                format_depth: item.format_depth,
458                                type_depth: item.type_depth + 1,
459                                state: StackState::Finish,
460                            };
461                            let start_item = StackItem {
462                                peek: *field_value,
463                                format_depth: item.format_depth,
464                                type_depth: item.type_depth + 1,
465                                state: StackState::Start,
466                            };
467
468                            stack.push_back(item);
469                            stack.push_back(finish_item);
470                            stack.push_back(start_item);
471                        }
472                    } else if let Peek::Enum(enum_val) = item.peek {
473                        // Since PeekEnum implements Copy, we can use it directly
474
475                        // Get all fields with their metadata
476                        let fields: Vec<_> = enum_val.fields_with_metadata().collect();
477
478                        // Check if we're done processing fields
479                        if field_index >= fields.len() {
480                            // Determine variant kind to use the right closing delimiter
481                            match enum_val.variant_kind_active() {
482                                facet_core::VariantKind::Tuple { .. } => {
483                                    // Close tuple variant with )
484                                    write!(
485                                        f,
486                                        "{:width$}{}",
487                                        "",
488                                        self.style_punctuation(")"),
489                                        width = (item.format_depth - 1) * self.indent_size
490                                    )?;
491                                }
492                                facet_core::VariantKind::Struct { .. } => {
493                                    // Close struct variant with }
494                                    write!(
495                                        f,
496                                        "{:width$}{}",
497                                        "",
498                                        self.style_punctuation("}"),
499                                        width = (item.format_depth - 1) * self.indent_size
500                                    )?;
501                                }
502                                _ => {}
503                            }
504                            continue;
505                        }
506
507                        // Get the current field with metadata
508                        let (_, field_name, field_peek, field) = fields[field_index];
509
510                        // Define consistent indentation
511                        let field_indent = "  "; // Use 2 spaces for all fields
512
513                        // Add field doc comments if available
514                        if !field.doc.is_empty() {
515                            // Only add new line if not the first field
516                            if field_index > 0 {
517                                writeln!(f)?;
518                            }
519                            for line in field.doc {
520                                // Hard-code consistent indentation (2 spaces)
521                                write!(f, "  ")?;
522                                self.write_comment(f, &format!("///{}", line))?;
523                                writeln!(f)?;
524                            }
525                            // Rewrite indentation after doc comments
526                            write!(f, "{}", field_indent)?;
527                        }
528
529                        // For struct variants, print field name
530                        if let facet_core::VariantKind::Struct { .. } =
531                            enum_val.variant_kind_active()
532                        {
533                            self.write_field_name(f, field_name)?;
534                            self.write_punctuation(f, ": ")?;
535                        }
536
537                        // Set up to process the next field after this one
538                        item.state = StackState::ProcessStructField {
539                            field_index: field_index + 1,
540                        };
541
542                        // Create finish and start items for processing the field value
543                        let finish_item = StackItem {
544                            peek: field_peek, // field_peek is already a Peek which is Copy
545                            format_depth: item.format_depth,
546                            type_depth: item.type_depth + 1,
547                            state: StackState::Finish,
548                        };
549                        let start_item = StackItem {
550                            peek: field_peek, // field_peek is already a Peek which is Copy
551                            format_depth: item.format_depth,
552                            type_depth: item.type_depth + 1,
553                            state: StackState::Start,
554                        };
555
556                        // Push items to stack in the right order
557                        stack.push_back(item);
558                        stack.push_back(finish_item);
559                        stack.push_back(start_item);
560                    }
561                }
562                StackState::ProcessListItem { item_index } => {
563                    if let Peek::List(list) = item.peek {
564                        if item_index >= list.len() {
565                            // All items processed, write closing bracket
566                            write!(
567                                f,
568                                "{:width$}",
569                                "",
570                                width = (item.format_depth - 1) * self.indent_size
571                            )?;
572                            self.write_punctuation(f, "]")?;
573                            continue;
574                        }
575
576                        // Indent
577                        write!(
578                            f,
579                            "{:width$}",
580                            "",
581                            width = item.format_depth * self.indent_size
582                        )?;
583
584                        // Push back current item to continue after formatting list item
585                        item.state = StackState::ProcessListItem {
586                            item_index: item_index + 1,
587                        };
588                        let next_format_depth = item.format_depth;
589                        let next_type_depth = item.type_depth + 1;
590                        stack.push_back(item);
591
592                        // Push list item to format first
593                        let list_item = list.iter().nth(item_index).unwrap();
594                        stack.push_back(StackItem {
595                            peek: list_item,
596                            format_depth: next_format_depth,
597                            type_depth: next_type_depth,
598                            state: StackState::Finish,
599                        });
600
601                        // When we push a list item to format, we need to process it from the beginning
602                        stack.push_back(StackItem {
603                            peek: list_item,
604                            format_depth: next_format_depth,
605                            type_depth: next_type_depth,
606                            state: StackState::Start, // Use Start state to properly process the item
607                        });
608                    }
609                }
610                StackState::ProcessBytesItem { item_index } => {
611                    if let Peek::List(list) = item.peek {
612                        if item_index >= list.len() {
613                            // All items processed, write closing bracket
614                            write!(
615                                f,
616                                "{:width$}",
617                                "",
618                                width = (item.format_depth - 1) * self.indent_size
619                            )?;
620                            continue;
621                        }
622
623                        // On the first byte, write the opening byte sequence indicator
624                        if item_index == 0 {
625                            write!(f, " ")?;
626                        }
627
628                        // Only display 16 bytes per line
629                        if item_index > 0 && item_index % 16 == 0 {
630                            writeln!(f)?;
631                            write!(
632                                f,
633                                "{:width$}",
634                                "",
635                                width = item.format_depth * self.indent_size
636                            )?;
637                        } else if item_index > 0 {
638                            write!(f, " ")?;
639                        }
640
641                        // Get the byte
642                        if let Some(Peek::Value(value)) = list.iter().nth(item_index) {
643                            let byte = unsafe { value.data().read::<u8>() };
644
645                            // Generate a color for this byte based on its value
646                            let mut hasher = DefaultHasher::new();
647                            byte.hash(&mut hasher);
648                            let hash = hasher.finish();
649                            let color = self.color_generator.generate_color(hash);
650
651                            // Apply color if needed
652                            if self.use_colors {
653                                color.write_fg(f)?;
654                            }
655
656                            // Display the byte in hex format
657                            write!(f, "{:02x}", byte)?;
658
659                            // Reset color if needed
660                            if self.use_colors {
661                                ansi::write_reset(f)?;
662                            }
663                        } else {
664                            unreachable!()
665                        }
666
667                        // Push back current item to continue after formatting byte
668                        item.state = StackState::ProcessBytesItem {
669                            item_index: item_index + 1,
670                        };
671                        stack.push_back(item);
672                    }
673                }
674                StackState::ProcessMapEntry => {
675                    if let Peek::Map(_) = item.peek {
676                        // TODO: Implement proper map iteration when available in facet_peek
677
678                        // Indent
679                        write!(
680                            f,
681                            "{:width$}",
682                            "",
683                            width = item.format_depth * self.indent_size
684                        )?;
685                        write!(f, "{}", self.style_comment("/* Map contents */"))?;
686                        writeln!(f)?;
687
688                        // Closing brace with proper indentation
689                        write!(
690                            f,
691                            "{:width$}{}",
692                            "",
693                            self.style_punctuation("}"),
694                            width = (item.format_depth - 1) * self.indent_size
695                        )?;
696                    }
697                }
698                StackState::Finish => {
699                    // Add comma and newline for struct fields and list items
700                    self.write_punctuation(f, ",")?;
701                    writeln!(f)?;
702                }
703                StackState::OptionFinish => {
704                    // Just close the Option::Some parenthesis, with no comma
705                    self.write_punctuation(f, ")")?;
706                }
707            }
708        }
709
710        Ok(())
711    }
712
713    /// Format a scalar value
714    fn format_value(&self, value: facet_peek::PeekValue, f: &mut impl Write) -> fmt::Result {
715        // Generate a color for this shape
716        let mut hasher = DefaultHasher::new();
717        value.shape().def.hash(&mut hasher);
718        let hash = hasher.finish();
719        let color = self.color_generator.generate_color(hash);
720
721        // Apply color if needed
722        if self.use_colors {
723            color.write_fg(f)?;
724        }
725
726        // Display the value
727        struct DisplayWrapper<'a>(&'a facet_peek::PeekValue<'a>);
728
729        impl fmt::Display for DisplayWrapper<'_> {
730            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731                if self.0.display(f).is_none() {
732                    // If the value doesn't implement Display, use Debug
733                    if self.0.debug(f).is_none() {
734                        // If the value doesn't implement Debug either, just show the type name
735                        self.0.type_name(f, facet_core::TypeNameOpts::infinite())?;
736                        write!(f, "(⋯)")?;
737                    }
738                }
739                Ok(())
740            }
741        }
742
743        write!(f, "{}", DisplayWrapper(&value))?;
744
745        // Reset color if needed
746        if self.use_colors {
747            ansi::write_reset(f)?;
748        }
749
750        Ok(())
751    }
752
753    /// Write styled type name to formatter
754    fn write_type_name<W: fmt::Write>(
755        &self,
756        f: &mut W,
757        peek: &facet_peek::PeekValue,
758    ) -> fmt::Result {
759        struct TypeNameWriter<'a, 'b: 'a>(&'b facet_peek::PeekValue<'a>);
760
761        impl core::fmt::Display for TypeNameWriter<'_, '_> {
762            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
763                self.0.type_name(f, facet_core::TypeNameOpts::infinite())
764            }
765        }
766        let type_name = TypeNameWriter(peek);
767
768        if self.use_colors {
769            ansi::write_bold(f)?;
770            write!(f, "{}", type_name)?;
771            ansi::write_reset(f)
772        } else {
773            write!(f, "{}", type_name)
774        }
775    }
776
777    /// Style a type name and return it as a string
778    #[allow(dead_code)]
779    fn style_type_name(&self, peek: &facet_peek::PeekValue) -> String {
780        let mut result = String::new();
781        self.write_type_name(&mut result, peek).unwrap();
782        result
783    }
784
785    /// Write styled field name to formatter
786    fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
787        if self.use_colors {
788            ansi::write_rgb(f, 114, 160, 193)?;
789            write!(f, "{}", name)?;
790            ansi::write_reset(f)
791        } else {
792            write!(f, "{}", name)
793        }
794    }
795
796    /// Write styled punctuation to formatter
797    fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
798        if self.use_colors {
799            ansi::write_dim(f)?;
800            write!(f, "{}", text)?;
801            ansi::write_reset(f)
802        } else {
803            write!(f, "{}", text)
804        }
805    }
806
807    /// Style punctuation and return it as a string
808    fn style_punctuation(&self, text: &str) -> String {
809        let mut result = String::new();
810        self.write_punctuation(&mut result, text).unwrap();
811        result
812    }
813
814    /// Write styled comment to formatter
815    fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
816        if self.use_colors {
817            ansi::write_dim(f)?;
818            write!(f, "{}", text)?;
819            ansi::write_reset(f)
820        } else {
821            write!(f, "{}", text)
822        }
823    }
824
825    /// Style a comment and return it as a string
826    fn style_comment(&self, text: &str) -> String {
827        let mut result = String::new();
828        self.write_comment(&mut result, text).unwrap();
829        result
830    }
831
832    /// Write styled redacted value to formatter
833    fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
834        if self.use_colors {
835            ansi::write_rgb(f, 224, 49, 49)?; // Use bright red for redacted values
836            ansi::write_bold(f)?;
837            write!(f, "{}", text)?;
838            ansi::write_reset(f)
839        } else {
840            write!(f, "{}", text)
841        }
842    }
843
844    /// Style a redacted value and return it as a string
845    #[allow(dead_code)]
846    fn style_redacted(&self, text: &str) -> String {
847        let mut result = String::new();
848        self.write_redacted(&mut result, text).unwrap();
849        result
850    }
851}
852
853#[cfg(test)]
854mod tests {
855    use super::*;
856
857    // Basic tests for the PrettyPrinter
858    #[test]
859    fn test_pretty_printer_default() {
860        let printer = PrettyPrinter::default();
861        assert_eq!(printer.indent_size, 2);
862        assert_eq!(printer.max_depth, None);
863        assert!(printer.use_colors);
864    }
865
866    #[test]
867    fn test_pretty_printer_with_methods() {
868        let printer = PrettyPrinter::new()
869            .with_indent_size(4)
870            .with_max_depth(3)
871            .with_colors(false);
872
873        assert_eq!(printer.indent_size, 4);
874        assert_eq!(printer.max_depth, Some(3));
875        assert!(!printer.use_colors);
876    }
877}