From 10bce7f66f62cd1b556c75727993c8ae6d204e25 Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Tue, 20 Jan 2026 10:00:37 +0530 Subject: [PATCH] Remove ANSI formatting codes from g3-core Move terminal formatting responsibility to g3-cli layer: - format_str_replace_summary(): Remove ANSI codes, add colorize_str_replace_summary() helper in CLI to apply green/red colors for insertions/deletions - format_timing_footer(): Remove dimming ANSI codes (now plain text) - str_replace tool result: Remove ANSI codes from success message Remaining acceptable ANSI usage in g3-core: - iTerm2 inline image protocol (terminal-specific escape sequence) - Image metadata dimming (direct print, would need larger refactor) - Terminal beep for stale TODO warning (audio, not visual) - ANSI stripping utility in research.rs (not output) This continues the separation of concerns: g3-core handles logic, g3-cli handles all terminal formatting. --- crates/g3-cli/src/ui_writer_impl.rs | 29 +++++++++++++++++++++++++--- crates/g3-core/src/streaming.rs | 10 +++++----- crates/g3-core/src/tools/file_ops.rs | 2 +- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/crates/g3-cli/src/ui_writer_impl.rs b/crates/g3-cli/src/ui_writer_impl.rs index 0776478..b252e0b 100644 --- a/crates/g3-cli/src/ui_writer_impl.rs +++ b/crates/g3-cli/src/ui_writer_impl.rs @@ -15,6 +15,22 @@ mod ansi { pub const RED: &str = "\x1b[31m"; } +/// Colorize a str_replace summary (e.g., "+5 | -3" -> green "+5" | red "-3") +fn colorize_str_replace_summary(summary: &str) -> String { + // Parse patterns like "+5 | -3", "+5", "-3" + if summary.contains(" | ") { + let parts: Vec<&str> = summary.split(" | ").collect(); + if parts.len() == 2 { + return format!("\x1b[32m{}\x1b[0m \x1b[2m|\x1b[0m \x1b[31m{}\x1b[0m", parts[0], parts[1]); + } + } else if summary.starts_with('+') { + return format!("\x1b[32m{}\x1b[0m", summary); + } else if summary.starts_with('-') { + return format!("\x1b[31m{}\x1b[0m", summary); + } + summary.to_string() +} + /// ANSI color codes for tool names const TOOL_COLOR_NORMAL: &str = "\x1b[32m"; const TOOL_COLOR_NORMAL_BOLD: &str = "\x1b[1;32m"; @@ -523,6 +539,13 @@ impl UiWriter for ConsoleUiWriter { // Color for tool name let tool_color = if is_agent_mode { TOOL_COLOR_AGENT } else { TOOL_COLOR_NORMAL }; + // Colorize summary for str_replace (green insertions, red deletions) + let display_summary = if tool_name == "str_replace" { + colorize_str_replace_summary(summary) + } else { + summary.to_string() + }; + // Print compact single line if is_continuation { // Continuation line for consecutive read_file on same file: @@ -530,7 +553,7 @@ impl UiWriter for ConsoleUiWriter { println!( " \x1b[2m└─ reading further\x1b[0m\x1b[35m{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m", range_suffix, - summary, + display_summary, tokens_delta, duration_str ); @@ -539,14 +562,14 @@ impl UiWriter for ConsoleUiWriter { // Pad to align with longest compact tool (str_replace = 11 chars) println!( " \x1b[2m●\x1b[0m {}{:<11}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m", - tool_color, tool_name, summary, tokens_delta, duration_str + tool_color, tool_name, display_summary, tokens_delta, duration_str ); } else { // Tools with file path: " ● tool_name | path [range] | summary | tokens ◉ time" // Pad to align with longest compact tool (str_replace = 11 chars) println!( " \x1b[2m●\x1b[0m {}{:<11}\x1b[0m \x1b[2m|\x1b[0m \x1b[35m{}{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m", - tool_color, tool_name, display_arg, range_suffix, summary, tokens_delta, duration_str + tool_color, tool_name, display_arg, range_suffix, display_summary, tokens_delta, duration_str ); } diff --git a/crates/g3-core/src/streaming.rs b/crates/g3-core/src/streaming.rs index 9eac742..4ffca01 100644 --- a/crates/g3-core/src/streaming.rs +++ b/crates/g3-core/src/streaming.rs @@ -142,11 +142,11 @@ pub fn format_timing_footer( // Add token usage info if available (dimmed) if let Some(tokens) = turn_tokens { format!( - "{} \x1b[2m{} ◉ | {:.0}%\x1b[0m", + "{} {} ◉ | {:.0}%", timing, tokens, context_percentage ) } else { - format!("{} \x1b[2m{:.0}%\x1b[0m", timing, context_percentage) + format!("{} {:.0}%", timing, context_percentage) } } @@ -326,11 +326,11 @@ pub fn format_write_file_result(tool_result: &str) -> String { /// Format a str_replace result summary. pub fn format_str_replace_summary(insertions: i32, deletions: i32) -> String { if insertions > 0 && deletions > 0 { - format!("\x1b[32m+{}\x1b[0m \x1b[2m|\x1b[0m \x1b[31m-{}\x1b[0m", insertions, deletions) + format!("+{} | -{}", insertions, deletions) } else if insertions > 0 { - format!("\x1b[32m+{}\x1b[0m", insertions) + format!("+{}", insertions) } else { - format!("\x1b[31m-{}\x1b[0m", deletions) + format!("-{}", deletions) } } diff --git a/crates/g3-core/src/tools/file_ops.rs b/crates/g3-core/src/tools/file_ops.rs index 6032221..5eecb85 100644 --- a/crates/g3-core/src/tools/file_ops.rs +++ b/crates/g3-core/src/tools/file_ops.rs @@ -582,7 +582,7 @@ pub async fn execute_str_replace( // Write the result back to the file match std::fs::write(&file_path, &result) { - Ok(()) => Ok(format!("✅ \x1b[32m+{} insertions\x1b[0m | \x1b[31m-{} deletions\x1b[0m", insertions, deletions)), + Ok(()) => Ok(format!("✅ +{} insertions | -{} deletions", insertions, deletions)), Err(e) => Ok(format!("❌ Failed to write to file '{}': {}", file_path, e)), } }