Skip to main content

ralph_workflow/json_parser/delta_display/
formatter.rs

1// Delta display formatter.
2//
3// Contains the DeltaDisplayFormatter for consistent styling across parsers.
4
5/// Delta display formatter
6///
7/// Formats delta content for user display with consistent styling across all parsers.
8pub struct DeltaDisplayFormatter {
9    /// Whether to mark partial content visually
10    mark_partial: bool,
11}
12
13impl DeltaDisplayFormatter {
14    /// Create a new formatter with default settings
15    pub const fn new() -> Self {
16        Self { mark_partial: true }
17    }
18
19    /// Format thinking content specifically.
20    ///
21    /// Thinking content has special formatting to distinguish it from regular text.
22    ///
23    /// # Terminal Mode Behavior
24    ///
25    /// - `TerminalMode::Full` / `TerminalMode::Basic`: May include ANSI colors.
26    /// - `TerminalMode::None`: MUST be plain text (no ANSI sequences).
27    pub fn format_thinking(
28        &self,
29        content: &str,
30        prefix: &str,
31        colors: Colors,
32        terminal_mode: crate::json_parser::terminal::TerminalMode,
33    ) -> String {
34        use crate::json_parser::terminal::TerminalMode;
35
36        match terminal_mode {
37            TerminalMode::None => {
38                // Plain-text guarantee: never emit ANSI in None mode, even if colors are enabled.
39                format!("[{prefix}] Thinking: {content}\n")
40            }
41            TerminalMode::Full | TerminalMode::Basic => {
42                if self.mark_partial {
43                    format!(
44                        "{}[{}]{} {}Thinking: {}{}{}\n",
45                        colors.dim(),
46                        prefix,
47                        colors.reset(),
48                        colors.dim(),
49                        colors.cyan(),
50                        content,
51                        colors.reset()
52                    )
53                } else {
54                    format!(
55                        "{}[{}]{} {}Thinking: {}{}{}\n",
56                        colors.dim(),
57                        prefix,
58                        colors.reset(),
59                        colors.cyan(),
60                        colors.reset(),
61                        content,
62                        colors.reset()
63                    )
64                }
65            }
66        }
67    }
68
69    /// Format tool input specifically
70    ///
71    /// Tool input is shown with appropriate styling.
72    ///
73    /// # Terminal Mode Behavior
74    ///
75    /// - **Full mode (TTY):** Renders the full `[prefix]   └─ content` pattern
76    ///   for each delta, providing real-time feedback with clarity about which
77    ///   agent's tool is being invoked.
78    ///
79    /// - **Basic/None modes (non-TTY):** Suppresses per-delta output to prevent
80    ///   repeated prefixed lines in logs and CI output. Tool input is accumulated
81    ///   and rendered ONCE at completion boundaries (`message_stop`).
82    ///
83    /// # CCS Spam Prevention (Bug Fix)
84    ///
85    /// This implementation prevents repeated "[ccs/glm]" and "[ccs/codex]" prefixed
86    /// lines for tool input deltas in non-TTY modes. The fix is validated with
87    /// comprehensive regression tests that simulate real-world streaming scenarios.
88    ///
89    /// See comprehensive regression tests:
90    /// - `tests/integration_tests/ccs_delta_spam_systematic_reproduction.rs` (NEW: systematic reproduction test)
91    /// - `tests/integration_tests/ccs_all_delta_types_spam_reproduction.rs` (comprehensive coverage)
92    /// - `tests/integration_tests/ccs_nuclear_spam_test.rs` (tool input with 500+ deltas)
93    /// - `tests/integration_tests/ccs_streaming_spam_all_deltas.rs` (all delta types including tool input)
94    ///
95    /// # Future Enhancement
96    ///
97    /// For streaming tool inputs with multiple deltas in Full mode, consider suppressing
98    /// the `[prefix]` on continuation lines to reduce visual noise:
99    /// - First tool input line: `[prefix] Tool: name`
100    /// - Continuation: `           └─ more input` (aligned, no prefix)
101    ///
102    /// This would require tracking whether the prefix has been displayed
103    /// for the current tool block, likely via the streaming session state.
104    pub fn format_tool_input(
105        &self,
106        content: &str,
107        prefix: &str,
108        colors: Colors,
109        terminal_mode: crate::json_parser::terminal::TerminalMode,
110    ) -> String {
111        use crate::json_parser::terminal::TerminalMode;
112
113        match terminal_mode {
114            TerminalMode::Full => {
115                // In Full mode, render tool input deltas as they arrive for real-time feedback
116                if self.mark_partial {
117                    format!(
118                        "{}[{}]{} {}  └─ {}{}{}\n",
119                        colors.dim(),
120                        prefix,
121                        colors.reset(),
122                        colors.dim(),
123                        colors.reset(),
124                        content,
125                        colors.reset()
126                    )
127                } else {
128                    format!(
129                        "{}[{}]{} {}  └─ {}{}\n",
130                        colors.dim(),
131                        prefix,
132                        colors.reset(),
133                        colors.reset(),
134                        content,
135                        colors.reset()
136                    )
137                }
138            }
139            TerminalMode::Basic | TerminalMode::None => {
140                // SUPPRESS per-delta tool input in non-TTY modes.
141                // Tool input will be rendered ONCE at tool completion or message_stop.
142                String::new()
143            }
144        }
145    }
146}
147
148impl Default for DeltaDisplayFormatter {
149    fn default() -> Self {
150        Self::new()
151    }
152}