Skip to main content

vtcode_tui/core_tui/session/
terminal_capabilities.rs

1//! Terminal capability detection for optimal rendering
2//!
3//! This module provides utilities to detect terminal capabilities such as
4//! Unicode support, color support, and other features to ensure optimal
5//! rendering across different terminal environments.
6
7use std::env;
8
9/// Detects if the current terminal supports Unicode box drawing characters
10///
11/// This function checks various environment variables and terminal settings
12/// to determine if Unicode characters can be safely displayed without
13/// appearing as broken ANSI sequences.
14pub fn supports_unicode_box_drawing() -> bool {
15    // Check if explicitly disabled via environment variable
16    if env::var("VTCODE_NO_UNICODE").is_ok() {
17        return false;
18    }
19
20    // Check terminal type - many terminals support Unicode
21    if let Ok(term) = env::var("TERM") {
22        let term_lower = term.to_lowercase();
23
24        // Modern terminals that definitely support Unicode
25        if term_lower.contains("unicode")
26            || term_lower.contains("utf")
27            || term_lower.contains("xterm-256color")
28            || term_lower.contains("screen-256color")
29            || term_lower.contains("tmux-256color")
30            || term_lower.contains("alacritty")
31            || term_lower.contains("wezterm")
32            || term_lower.contains("kitty")
33            || term_lower.contains("iterm")
34            || term_lower.contains("hyper")
35        {
36            return true;
37        }
38
39        // Older or basic terminal types that likely don't support Unicode well
40        if term_lower.contains("dumb")
41            || term_lower.contains("basic")
42            || term_lower == "xterm"
43            || term_lower == "screen"
44        {
45            return false;
46        }
47    }
48
49    // Check LANG environment variable for UTF-8 locale
50    if let Ok(lang) = env::var("LANG")
51        && (lang.to_lowercase().contains("utf-8") || lang.to_lowercase().contains("utf8"))
52    {
53        return true;
54    }
55
56    // Check LC_ALL and LC_CTYPE for UTF-8
57    for var in &["LC_ALL", "LC_CTYPE"] {
58        if let Ok(locale) = env::var(var)
59            && (locale.to_lowercase().contains("utf-8") || locale.to_lowercase().contains("utf8"))
60        {
61            return true;
62        }
63    }
64
65    // Default to plain ASCII for safety - prevents broken Unicode display
66    false
67}
68
69/// Gets the appropriate border type based on terminal capabilities
70///
71/// Returns `BorderType::Rounded` if Unicode is supported, otherwise
72/// returns `BorderType::Plain` for maximum compatibility.
73pub fn get_border_type() -> ratatui::widgets::BorderType {
74    if supports_unicode_box_drawing() {
75        ratatui::widgets::BorderType::Rounded
76    } else {
77        ratatui::widgets::BorderType::Plain
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    #[inline]
85    fn set_var(key: &str, value: &str) {
86        unsafe { env::set_var(key, value) };
87    }
88    #[inline]
89    fn remove_var(key: &str) {
90        unsafe { env::remove_var(key) };
91    }
92
93    #[test]
94    fn test_supports_unicode_box_drawing() {
95        // Test with different environment variable combinations
96
97        // Save original values
98        let original_term = env::var("TERM").ok();
99        let original_lang = env::var("LANG").ok();
100        let original_lc_all = env::var("LC_ALL").ok();
101        let original_no_unicode = env::var("VTCODE_NO_UNICODE").ok();
102
103        // Test with VTCODE_NO_UNICODE set (should disable Unicode)
104        set_var("VTCODE_NO_UNICODE", "1");
105        assert!(!supports_unicode_box_drawing());
106        remove_var("VTCODE_NO_UNICODE");
107
108        // Test with modern terminal
109        set_var("TERM", "xterm-256color");
110        assert!(supports_unicode_box_drawing());
111
112        // Test with UTF-8 locale
113        set_var("LANG", "en_US.UTF-8");
114        assert!(supports_unicode_box_drawing());
115
116        // Test with basic terminal
117        set_var("TERM", "dumb");
118        assert!(!supports_unicode_box_drawing());
119
120        // Test with no locale info (should default to false for safety)
121        remove_var("TERM");
122        remove_var("LANG");
123        remove_var("LC_ALL");
124        assert!(!supports_unicode_box_drawing());
125
126        // Restore original values
127        match original_term {
128            Some(val) => set_var("TERM", &val),
129            None => remove_var("TERM"),
130        }
131        match original_lang {
132            Some(val) => set_var("LANG", &val),
133            None => remove_var("LANG"),
134        }
135        match original_lc_all {
136            Some(val) => set_var("LC_ALL", &val),
137            None => remove_var("LC_ALL"),
138        }
139        match original_no_unicode {
140            Some(val) => set_var("VTCODE_NO_UNICODE", &val),
141            None => remove_var("VTCODE_NO_UNICODE"),
142        }
143    }
144
145    #[test]
146    fn test_get_border_type() {
147        // Save original TERM
148        let original_term = env::var("TERM").ok();
149
150        // Test with Unicode-supporting terminal
151        set_var("TERM", "xterm-256color");
152        let border_type = get_border_type();
153        assert!(matches!(border_type, ratatui::widgets::BorderType::Rounded));
154
155        // Test with basic terminal
156        set_var("TERM", "dumb");
157        let border_type = get_border_type();
158        assert!(matches!(border_type, ratatui::widgets::BorderType::Plain));
159
160        // Restore original TERM
161        match original_term {
162            Some(val) => set_var("TERM", &val),
163            None => remove_var("TERM"),
164        }
165    }
166}