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}