Skip to main content

tui_dispatch_debug/debug/
format.rs

1use std::fmt::Debug;
2
3pub fn debug_string<T>(value: &T) -> String
4where
5    T: Debug,
6{
7    debug_string_compact(value)
8}
9
10pub fn debug_string_compact<T>(value: &T) -> String
11where
12    T: Debug,
13{
14    format!("{:?}", value)
15}
16
17pub fn debug_string_pretty<T>(value: &T) -> String
18where
19    T: Debug,
20{
21    format!("{:#?}", value)
22}
23
24/// Reformat a compact Debug-style string with indentation and newlines.
25///
26/// Takes a single-line string like `{\"a\", \"b\", \"c\"}` and produces:
27/// ```text
28/// {
29///     "a",
30///     "b",
31///     "c",
32/// }
33/// ```
34///
35/// Handles nested `{}`, `[]`, `()` and preserves quoted strings.
36pub fn pretty_reformat(input: &str) -> String {
37    let mut out = String::with_capacity(input.len() * 2);
38    let mut indent: usize = 0;
39    let mut chars = input.chars().peekable();
40    let indent_str = "    ";
41
42    while let Some(ch) = chars.next() {
43        match ch {
44            // String literal — copy verbatim
45            '"' => {
46                out.push('"');
47                loop {
48                    match chars.next() {
49                        Some('\\') => {
50                            out.push('\\');
51                            if let Some(esc) = chars.next() {
52                                out.push(esc);
53                            }
54                        }
55                        Some('"') => {
56                            out.push('"');
57                            break;
58                        }
59                        Some(c) => out.push(c),
60                        None => break,
61                    }
62                }
63            }
64            // Char literal — copy verbatim. Debug chars look like 'x', '\n', '\'', '\u{7f}'.
65            '\'' => {
66                out.push('\'');
67                loop {
68                    match chars.next() {
69                        Some('\\') => {
70                            out.push('\\');
71                            if let Some(esc) = chars.next() {
72                                out.push(esc);
73                            }
74                        }
75                        Some('\'') => {
76                            out.push('\'');
77                            break;
78                        }
79                        Some(c) => out.push(c),
80                        None => break,
81                    }
82                }
83            }
84            '{' | '[' | '(' => {
85                // Peek: if the matching close is next (empty container), keep inline
86                let close = match ch {
87                    '{' => '}',
88                    '[' => ']',
89                    _ => ')',
90                };
91                if chars.peek() == Some(&close) {
92                    out.push(ch);
93                    out.push(chars.next().unwrap());
94                } else {
95                    indent += 1;
96                    out.push(ch);
97                    out.push('\n');
98                    for _ in 0..indent {
99                        out.push_str(indent_str);
100                    }
101                }
102            }
103            '}' | ']' | ')' => {
104                indent = indent.saturating_sub(1);
105                // Trim trailing whitespace on current line
106                let trimmed = out.trim_end_matches(' ');
107                out.truncate(trimmed.len());
108                if !out.ends_with('\n') {
109                    out.push('\n');
110                }
111                for _ in 0..indent {
112                    out.push_str(indent_str);
113                }
114                out.push(ch);
115            }
116            ',' => {
117                out.push(',');
118                // If we're at indent > 0, break the line
119                if indent > 0 {
120                    out.push('\n');
121                    for _ in 0..indent {
122                        out.push_str(indent_str);
123                    }
124                } else {
125                    out.push(' ');
126                }
127            }
128            // Skip spaces after commas / braces (we handle our own whitespace)
129            ' ' if out.ends_with('\n') || out.ends_with(indent_str) => {}
130            _ => {
131                out.push(ch);
132            }
133        }
134    }
135
136    out
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn pretty_reformat_simple_set() {
145        let input = r#"{"a", "b", "c"}"#;
146        let result = pretty_reformat(input);
147        assert!(result.contains('\n'));
148        assert!(result.contains("    \"a\","));
149        assert!(result.contains("    \"b\","));
150    }
151
152    #[test]
153    fn pretty_reformat_nested() {
154        let input = "Foo{x: [1, 2], y: Bar{z: 3}}";
155        let result = pretty_reformat(input);
156        assert!(result.contains('\n'));
157        // Check nesting increases indent
158        assert!(result.contains("        z: 3"));
159    }
160
161    #[test]
162    fn pretty_reformat_empty_containers() {
163        assert_eq!(pretty_reformat("{}"), "{}");
164        assert_eq!(pretty_reformat("[]"), "[]");
165        assert_eq!(pretty_reformat("()"), "()");
166    }
167
168    #[test]
169    fn pretty_reformat_preserves_strings() {
170        let input = r#"{"hello, world", "foo{bar}"}"#;
171        let result = pretty_reformat(input);
172        assert!(result.contains(r#""hello, world""#));
173        assert!(result.contains(r#""foo{bar}""#));
174    }
175
176    #[test]
177    fn pretty_reformat_plain_value() {
178        assert_eq!(pretty_reformat("42"), "42");
179        assert_eq!(pretty_reformat("true"), "true");
180    }
181
182    #[test]
183    fn pretty_reformat_preserves_char_literals() {
184        // Char literals with structural chars inside must not be parsed as structure.
185        assert_eq!(pretty_reformat("Some('}')"), "Some(\n    '}'\n)");
186
187        let input = "Foo { x: Some('}'), y: 2 }";
188        let result = pretty_reformat(input);
189        assert!(result.contains("        '}'"));
190        assert!(result.contains("    y: 2"));
191    }
192
193    #[test]
194    fn pretty_reformat_preserves_escaped_char() {
195        let input = r#"Foo { c: '\'', n: '\n' }"#;
196        let result = pretty_reformat(input);
197        assert!(result.contains(r#"'\''"#));
198        assert!(result.contains(r#"'\n'"#));
199    }
200}