You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

95 lines
2.8 KiB
Rust

//! 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}]");
}
}
}
}