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