Skip to main content

nuviz_cli/terminal/
capability.rs

1use std::env;
2
3/// Color depth supported by the terminal.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ColorDepth {
6    TrueColor,
7    Colors256,
8    Colors16,
9}
10
11/// Detected terminal capabilities.
12#[derive(Debug, Clone)]
13pub struct TerminalCapabilities {
14    pub supports_kitty_graphics: bool,
15    pub supports_iterm2: bool,
16    pub supports_sixel: bool,
17    pub color_depth: ColorDepth,
18    #[allow(dead_code)]
19    pub unicode_support: bool,
20    #[allow(dead_code)]
21    pub terminal_name: Option<String>,
22}
23
24/// Detect terminal capabilities from environment variables.
25///
26/// This is detection only — actual image rendering is Phase 3.
27pub fn detect_capabilities() -> TerminalCapabilities {
28    let term_program = env::var("TERM_PROGRAM").ok();
29    let term = env::var("TERM").ok();
30    let colorterm = env::var("COLORTERM").ok();
31    let lang = env::var("LANG").ok();
32
33    let term_program_lower = term_program.as_deref().unwrap_or("").to_lowercase();
34
35    // Kitty graphics protocol
36    let supports_kitty = term_program_lower == "kitty"
37        || term_program_lower == "wezterm"
38        || term_program_lower == "ghostty"
39        || env::var("KITTY_WINDOW_ID").is_ok();
40
41    // iTerm2 inline images
42    let supports_iterm2 = term_program_lower.contains("iterm")
43        || term_program_lower == "wezterm"
44        || env::var("ITERM_SESSION_ID").is_ok();
45
46    // Sixel support (heuristic)
47    let supports_sixel = term
48        .as_deref()
49        .map(|t| t.contains("xterm") || t.contains("foot") || t.contains("mlterm"))
50        .unwrap_or(false)
51        || term_program_lower == "wezterm";
52
53    // Color depth
54    let color_depth = if colorterm.as_deref() == Some("truecolor")
55        || colorterm.as_deref() == Some("24bit")
56        || supports_kitty
57    {
58        ColorDepth::TrueColor
59    } else if term.as_deref().is_some_and(|t| t.contains("256color")) {
60        ColorDepth::Colors256
61    } else {
62        ColorDepth::Colors16
63    };
64
65    // Unicode support
66    let unicode_support = lang
67        .as_deref()
68        .map(|l| l.contains("UTF-8") || l.contains("utf-8") || l.contains("UTF8"))
69        .unwrap_or(false)
70        || supports_kitty;
71
72    TerminalCapabilities {
73        supports_kitty_graphics: supports_kitty,
74        supports_iterm2,
75        supports_sixel,
76        color_depth,
77        unicode_support,
78        terminal_name: term_program,
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_detect_returns_valid_struct() {
88        let caps = detect_capabilities();
89        // Just verify it doesn't panic and returns something sensible
90        let _ = caps.color_depth;
91        let _ = caps.supports_kitty_graphics;
92        let _ = caps.supports_sixel;
93        let _ = caps.supports_iterm2;
94        let _ = caps.unicode_support;
95    }
96
97    #[test]
98    fn test_color_depth_variants() {
99        // Verify enum variants exist and can be compared
100        assert_ne!(ColorDepth::TrueColor, ColorDepth::Colors256);
101        assert_ne!(ColorDepth::Colors256, ColorDepth::Colors16);
102    }
103}