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}