llm 1.3.8

A Rust library unifying multiple LLM backends.
Documentation
use std::env;

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ColorLevel {
    TrueColor,
    Ansi256,
    Ansi16,
    Ansi8,
    None,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum AnimationLevel {
    Shimmer,
    Spinner,
    Static,
}

#[derive(Debug, Clone)]
pub struct TerminalCapabilities {
    pub color_level: ColorLevel,
    pub supports_mouse: bool,
    pub supports_bracketed_paste: bool,
    pub slow_terminal: bool,
}

impl TerminalCapabilities {
    pub fn detect() -> Self {
        let env = TerminalEnv::from_env();
        Self::from_env(env)
    }

    pub fn animation_level(&self) -> AnimationLevel {
        if self.slow_terminal || self.color_level == ColorLevel::None {
            return AnimationLevel::Static;
        }
        match self.color_level {
            ColorLevel::TrueColor => AnimationLevel::Shimmer,
            ColorLevel::Ansi256 | ColorLevel::Ansi16 => AnimationLevel::Spinner,
            ColorLevel::Ansi8 | ColorLevel::None => AnimationLevel::Static,
        }
    }

    fn from_env(env: TerminalEnv) -> Self {
        let color_level = detect_color_level(&env);
        let supports_mouse = supports_mouse(&env);
        let supports_bracketed_paste = supports_bracketed_paste(&env);
        let slow_terminal = is_slow_terminal(&env, color_level);
        Self {
            color_level,
            supports_mouse,
            supports_bracketed_paste,
            slow_terminal,
        }
    }
}

#[derive(Debug, Clone)]
struct TerminalEnv {
    term: Option<String>,
    colorterm: Option<String>,
    no_color: bool,
    ssh: bool,
}

impl TerminalEnv {
    fn from_env() -> Self {
        let term = env::var("TERM").ok();
        let colorterm = env::var("COLORTERM").ok();
        let no_color = env::var("NO_COLOR").is_ok();
        let ssh = env::var("SSH_CONNECTION").is_ok()
            || env::var("SSH_CLIENT").is_ok()
            || env::var("SSH_TTY").is_ok();
        Self {
            term,
            colorterm,
            no_color,
            ssh,
        }
    }
}

fn detect_color_level(env: &TerminalEnv) -> ColorLevel {
    if env.no_color {
        return ColorLevel::None;
    }
    let term = env.term.as_deref().unwrap_or_default();
    if term == "dumb" {
        return ColorLevel::None;
    }
    if has_truecolor(env.colorterm.as_deref()) {
        return ColorLevel::TrueColor;
    }
    if term.contains("256color") {
        return ColorLevel::Ansi256;
    }
    if term.contains("color") {
        return ColorLevel::Ansi16;
    }
    ColorLevel::Ansi8
}

fn has_truecolor(value: Option<&str>) -> bool {
    value
        .map(|v| v.to_lowercase())
        .map(|v| v.contains("truecolor") || v.contains("24bit"))
        .unwrap_or(false)
}

fn supports_mouse(env: &TerminalEnv) -> bool {
    let term = env.term.as_deref().unwrap_or_default();
    term != "dumb"
}

fn supports_bracketed_paste(env: &TerminalEnv) -> bool {
    let term = env.term.as_deref().unwrap_or_default();
    term != "dumb"
}

fn is_slow_terminal(env: &TerminalEnv, color_level: ColorLevel) -> bool {
    if !env.ssh {
        return false;
    }
    matches!(
        color_level,
        ColorLevel::Ansi8 | ColorLevel::Ansi16 | ColorLevel::None
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    fn env_with(term: Option<&str>, colorterm: Option<&str>, ssh: bool) -> TerminalEnv {
        TerminalEnv {
            term: term.map(|v| v.to_string()),
            colorterm: colorterm.map(|v| v.to_string()),
            no_color: false,
            ssh,
        }
    }

    #[test]
    fn detects_truecolor() {
        let env = env_with(Some("xterm-256color"), Some("truecolor"), false);
        let caps = TerminalCapabilities::from_env(env);
        assert_eq!(caps.color_level, ColorLevel::TrueColor);
        assert_eq!(caps.animation_level(), AnimationLevel::Shimmer);
    }

    #[test]
    fn detects_ansi256() {
        let env = env_with(Some("xterm-256color"), None, false);
        let caps = TerminalCapabilities::from_env(env);
        assert_eq!(caps.color_level, ColorLevel::Ansi256);
        assert_eq!(caps.animation_level(), AnimationLevel::Spinner);
    }

    #[test]
    fn ssh_limits_animation() {
        let env = env_with(Some("xterm-color"), None, true);
        let caps = TerminalCapabilities::from_env(env);
        assert_eq!(caps.color_level, ColorLevel::Ansi16);
        assert!(caps.slow_terminal);
        assert_eq!(caps.animation_level(), AnimationLevel::Static);
    }
}