Skip to main content

agent_tui_common/common/
color.rs

1//! Terminal color helpers.
2
3use std::io::IsTerminal;
4use std::sync::OnceLock;
5
6static NO_COLOR: OnceLock<bool> = OnceLock::new();
7
8pub fn init(no_color_flag: bool) {
9    let _ = NO_COLOR.set(
10        no_color_flag || std::env::var("NO_COLOR").is_ok() || !std::io::stdout().is_terminal(),
11    );
12}
13
14pub fn is_disabled() -> bool {
15    *NO_COLOR.get().unwrap_or(&false)
16}
17
18mod codes {
19    pub const RESET: &str = "\x1b[0m";
20    pub const GREEN: &str = "\x1b[32m";
21    pub const RED: &str = "\x1b[31m";
22    pub const YELLOW: &str = "\x1b[33m";
23    pub const CYAN: &str = "\x1b[36m";
24    pub const DIM: &str = "\x1b[90m";
25    pub const BOLD: &str = "\x1b[1m";
26}
27
28pub struct Colors;
29
30fn wrap_with_ansi(text: &str, prefixes: &[&str]) -> String {
31    let prefix_len: usize = prefixes.iter().map(|prefix| prefix.len()).sum();
32    let mut out = String::with_capacity(prefix_len + text.len() + codes::RESET.len());
33    for prefix in prefixes {
34        out.push_str(prefix);
35    }
36    out.push_str(text);
37    out.push_str(codes::RESET);
38    out
39}
40
41impl Colors {
42    pub fn success(text: &str) -> String {
43        if is_disabled() {
44            text.to_string()
45        } else {
46            wrap_with_ansi(text, &[codes::GREEN])
47        }
48    }
49
50    pub fn error(text: &str) -> String {
51        if is_disabled() {
52            text.to_string()
53        } else {
54            wrap_with_ansi(text, &[codes::RED])
55        }
56    }
57
58    pub fn info(text: &str) -> String {
59        if is_disabled() {
60            text.to_string()
61        } else {
62            wrap_with_ansi(text, &[codes::CYAN])
63        }
64    }
65
66    pub fn warning(text: &str) -> String {
67        if is_disabled() {
68            text.to_string()
69        } else {
70            wrap_with_ansi(text, &[codes::YELLOW])
71        }
72    }
73
74    pub fn dim(text: &str) -> String {
75        if is_disabled() {
76            text.to_string()
77        } else {
78            wrap_with_ansi(text, &[codes::DIM])
79        }
80    }
81
82    pub fn bold(text: &str) -> String {
83        if is_disabled() {
84            text.to_string()
85        } else {
86            wrap_with_ansi(text, &[codes::BOLD])
87        }
88    }
89
90    pub fn session_id(text: &str) -> String {
91        if is_disabled() {
92            text.to_string()
93        } else {
94            wrap_with_ansi(text, &[codes::BOLD, codes::CYAN])
95        }
96    }
97}
98
99#[cfg(test)]
100#[path = "color_tests.rs"]
101mod tests;