facet_pretty/
printer.rs

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