//! ANSI color helpers for terminal output. //! //! All functions take a `color_enabled` flag; when `false` the string is //! returned unchanged so callers never need to branch on their own. /// Wrap `s` in ANSI escape codes when `color_enabled` is true. /// /// `open` is the escape sequence to start the color and `RESET` (`\x1b[0m`) /// is appended automatically. fn colorize(s: &str, open: &str, color_enabled: bool) -> String { if color_enabled { format!("{open}{s}\x1b[0m") } else { s.to_string() } } /// Orange — used for `[assistant]` labels. /// /// Uses 256-color code 208 (bright orange). pub fn orange(s: &str, color_enabled: bool) -> String { colorize(s, "\x1b[38;5;208m", color_enabled) } /// Grey — used for `[user]` labels. /// /// Uses 256-color code 245 (mid-grey). pub fn grey(s: &str, color_enabled: bool) -> String { colorize(s, "\x1b[38;5;245m", color_enabled) } /// Blue — used for `[tool: X]` labels. /// /// Uses 256-color code 33 (dodger blue). pub fn blue(s: &str, color_enabled: bool) -> String { colorize(s, "\x1b[38;5;33m", color_enabled) } /// Green — used for `[tool_result]` labels. /// /// Uses 256-color code 34 (green). pub fn green(s: &str, color_enabled: bool) -> String { colorize(s, "\x1b[32m", color_enabled) } /// Red — used for `[tool_result (error)]` labels. /// /// Uses standard ANSI red (code 31). pub fn red(s: &str, color_enabled: bool) -> String { colorize(s, "\x1b[31m", color_enabled) } #[cfg(test)] mod tests { use super::*; /// When color is disabled, the string is returned unchanged. #[test] fn color_disabled_returns_plain_string() { assert_eq!(orange("hello", false), "hello"); assert_eq!(grey("hello", false), "hello"); assert_eq!(blue("hello", false), "hello"); assert_eq!(green("hello", false), "hello"); assert_eq!(red("hello", false), "hello"); } /// When color is enabled, output contains the escape code and a reset. #[test] fn color_enabled_wraps_with_escapes() { let out = orange("[assistant]", true); assert!(out.starts_with("\x1b["), "should start with escape"); assert!(out.ends_with("\x1b[0m"), "should end with reset"); assert!(out.contains("[assistant]"), "should contain original text"); } /// Each color function produces distinct output. #[test] fn each_color_function_is_distinct() { let text = "X"; let colors = [ orange(text, true), grey(text, true), blue(text, true), green(text, true), red(text, true), ]; // All pairs must differ. for i in 0..colors.len() { for j in (i + 1)..colors.len() { assert_ne!(colors[i], colors[j], "colors[{i}] == colors[{j}]"); } } } }