vtcode_tui/core_tui/session/
terminal_capabilities.rs1use std::env;
8
9#[cfg(test)]
10mod test_env_overrides {
11 use std::collections::HashMap;
12 use std::sync::{LazyLock, Mutex};
13
14 static OVERRIDES: LazyLock<Mutex<HashMap<String, Option<String>>>> =
15 LazyLock::new(|| Mutex::new(HashMap::new()));
16
17 pub(super) fn get(key: &str) -> Option<Option<String>> {
18 OVERRIDES.lock().ok().and_then(|map| map.get(key).cloned())
19 }
20
21 pub(super) fn set(key: &str, value: Option<&str>) {
22 if let Ok(mut map) = OVERRIDES.lock() {
23 map.insert(key.to_string(), value.map(ToString::to_string));
24 }
25 }
26
27 pub(super) fn clear(key: &str) {
28 if let Ok(mut map) = OVERRIDES.lock() {
29 map.remove(key);
30 }
31 }
32}
33
34fn read_env_var(key: &str) -> Option<String> {
35 #[cfg(test)]
36 if let Some(override_value) = test_env_overrides::get(key) {
37 return override_value;
38 }
39
40 env::var(key).ok()
41}
42
43pub fn supports_unicode_box_drawing() -> bool {
49 if read_env_var("VTCODE_NO_UNICODE").is_some() {
51 return false;
52 }
53
54 if let Some(term) = read_env_var("TERM") {
56 let term_lower = term.to_lowercase();
57
58 if term_lower.contains("unicode")
60 || term_lower.contains("utf")
61 || term_lower.contains("xterm-256color")
62 || term_lower.contains("screen-256color")
63 || term_lower.contains("tmux-256color")
64 || term_lower.contains("alacritty")
65 || term_lower.contains("wezterm")
66 || term_lower.contains("kitty")
67 || term_lower.contains("iterm")
68 || term_lower.contains("hyper")
69 {
70 return true;
71 }
72
73 if term_lower.contains("dumb")
75 || term_lower.contains("basic")
76 || term_lower == "xterm"
77 || term_lower == "screen"
78 {
79 return false;
80 }
81 }
82
83 if let Some(lang) = read_env_var("LANG")
85 && (lang.to_lowercase().contains("utf-8") || lang.to_lowercase().contains("utf8"))
86 {
87 return true;
88 }
89
90 for var in &["LC_ALL", "LC_CTYPE"] {
92 if let Some(locale) = read_env_var(var)
93 && (locale.to_lowercase().contains("utf-8") || locale.to_lowercase().contains("utf8"))
94 {
95 return true;
96 }
97 }
98
99 false
101}
102
103pub fn get_border_type() -> ratatui::widgets::BorderType {
108 if supports_unicode_box_drawing() {
109 ratatui::widgets::BorderType::Rounded
110 } else {
111 ratatui::widgets::BorderType::Plain
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 #[inline]
119 fn set_var(key: &str, value: &str) {
120 test_env_overrides::set(key, Some(value));
121 }
122 #[inline]
123 fn remove_var(key: &str) {
124 test_env_overrides::set(key, None);
125 }
126 #[inline]
127 fn clear_var(key: &str) {
128 test_env_overrides::clear(key);
129 }
130
131 #[test]
132 fn test_supports_unicode_box_drawing() {
133 let original_term = env::var("TERM").ok();
137 let original_lang = env::var("LANG").ok();
138 let original_lc_all = env::var("LC_ALL").ok();
139 let original_no_unicode = env::var("VTCODE_NO_UNICODE").ok();
140
141 set_var("VTCODE_NO_UNICODE", "1");
143 assert!(!supports_unicode_box_drawing());
144 remove_var("VTCODE_NO_UNICODE");
145
146 set_var("TERM", "xterm-256color");
148 assert!(supports_unicode_box_drawing());
149
150 set_var("LANG", "en_US.UTF-8");
152 assert!(supports_unicode_box_drawing());
153
154 set_var("TERM", "dumb");
156 assert!(!supports_unicode_box_drawing());
157
158 remove_var("TERM");
160 remove_var("LANG");
161 remove_var("LC_ALL");
162 assert!(!supports_unicode_box_drawing());
163
164 match original_term {
166 Some(val) => set_var("TERM", &val),
167 None => clear_var("TERM"),
168 }
169 match original_lang {
170 Some(val) => set_var("LANG", &val),
171 None => clear_var("LANG"),
172 }
173 match original_lc_all {
174 Some(val) => set_var("LC_ALL", &val),
175 None => clear_var("LC_ALL"),
176 }
177 match original_no_unicode {
178 Some(val) => set_var("VTCODE_NO_UNICODE", &val),
179 None => clear_var("VTCODE_NO_UNICODE"),
180 }
181 }
182
183 #[test]
184 fn test_get_border_type() {
185 let original_term = env::var("TERM").ok();
187
188 set_var("TERM", "xterm-256color");
190 let border_type = get_border_type();
191 assert!(matches!(border_type, ratatui::widgets::BorderType::Rounded));
192
193 set_var("TERM", "dumb");
195 let border_type = get_border_type();
196 assert!(matches!(border_type, ratatui::widgets::BorderType::Plain));
197
198 match original_term {
200 Some(val) => set_var("TERM", &val),
201 None => clear_var("TERM"),
202 }
203 }
204}