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