agent-tui-common 1.1.0

Shared errors, colors, and telemetry utilities for agent-tui
Documentation
//! Terminal color helpers.

use std::io::IsTerminal;
use std::sync::OnceLock;

static NO_COLOR: OnceLock<bool> = OnceLock::new();

pub fn init(no_color_flag: bool) {
    let _ = NO_COLOR.set(
        no_color_flag || std::env::var("NO_COLOR").is_ok() || !std::io::stdout().is_terminal(),
    );
}

pub fn is_disabled() -> bool {
    *NO_COLOR.get().unwrap_or(&false)
}

mod codes {
    pub const RESET: &str = "\x1b[0m";
    pub const GREEN: &str = "\x1b[32m";
    pub const RED: &str = "\x1b[31m";
    pub const YELLOW: &str = "\x1b[33m";
    pub const CYAN: &str = "\x1b[36m";
    pub const DIM: &str = "\x1b[90m";
    pub const BOLD: &str = "\x1b[1m";
}

pub struct Colors;

fn wrap_with_ansi(text: &str, prefixes: &[&str]) -> String {
    let prefix_len: usize = prefixes.iter().map(|prefix| prefix.len()).sum();
    let mut out = String::with_capacity(prefix_len + text.len() + codes::RESET.len());
    for prefix in prefixes {
        out.push_str(prefix);
    }
    out.push_str(text);
    out.push_str(codes::RESET);
    out
}

impl Colors {
    pub fn success(text: &str) -> String {
        if is_disabled() {
            text.to_string()
        } else {
            wrap_with_ansi(text, &[codes::GREEN])
        }
    }

    pub fn error(text: &str) -> String {
        if is_disabled() {
            text.to_string()
        } else {
            wrap_with_ansi(text, &[codes::RED])
        }
    }

    pub fn info(text: &str) -> String {
        if is_disabled() {
            text.to_string()
        } else {
            wrap_with_ansi(text, &[codes::CYAN])
        }
    }

    pub fn warning(text: &str) -> String {
        if is_disabled() {
            text.to_string()
        } else {
            wrap_with_ansi(text, &[codes::YELLOW])
        }
    }

    pub fn dim(text: &str) -> String {
        if is_disabled() {
            text.to_string()
        } else {
            wrap_with_ansi(text, &[codes::DIM])
        }
    }

    pub fn bold(text: &str) -> String {
        if is_disabled() {
            text.to_string()
        } else {
            wrap_with_ansi(text, &[codes::BOLD])
        }
    }

    pub fn session_id(text: &str) -> String {
        if is_disabled() {
            text.to_string()
        } else {
            wrap_with_ansi(text, &[codes::BOLD, codes::CYAN])
        }
    }
}

#[cfg(test)]
#[path = "color_tests.rs"]
mod tests;