use std::env;
#[cfg(test)]
mod test_env_overrides {
use hashbrown::HashMap;
use std::sync::{LazyLock, Mutex};
static OVERRIDES: LazyLock<Mutex<HashMap<String, Option<String>>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub(super) fn get(key: &str) -> Option<Option<String>> {
OVERRIDES.lock().ok().and_then(|map| map.get(key).cloned())
}
pub(super) fn set(key: &str, value: Option<&str>) {
if let Ok(mut map) = OVERRIDES.lock() {
map.insert(key.to_string(), value.map(ToString::to_string));
}
}
pub(super) fn clear(key: &str) {
if let Ok(mut map) = OVERRIDES.lock() {
map.remove(key);
}
}
}
fn read_env_var(key: &str) -> Option<String> {
#[cfg(test)]
if let Some(override_value) = test_env_overrides::get(key) {
return override_value;
}
env::var(key).ok()
}
#[cfg(test)]
pub(crate) fn set_test_env_override(key: &str, value: Option<&str>) {
test_env_overrides::set(key, value);
}
#[cfg(test)]
pub(crate) fn clear_test_env_override(key: &str) {
test_env_overrides::clear(key);
}
pub fn supports_unicode_box_drawing() -> bool {
if read_env_var("VTCODE_NO_UNICODE").is_some() {
return false;
}
if let Some(term) = read_env_var("TERM") {
let term_lower = term.to_lowercase();
if term_lower.contains("unicode")
|| term_lower.contains("utf")
|| term_lower.contains("xterm-256color")
|| term_lower.contains("screen-256color")
|| term_lower.contains("tmux-256color")
|| term_lower.contains("alacritty")
|| term_lower.contains("wezterm")
|| term_lower.contains("kitty")
|| term_lower.contains("iterm")
|| term_lower.contains("hyper")
{
return true;
}
if term_lower.contains("dumb")
|| term_lower.contains("basic")
|| term_lower == "xterm"
|| term_lower == "screen"
{
return false;
}
}
if let Some(lang) = read_env_var("LANG")
&& (lang.to_lowercase().contains("utf-8") || lang.to_lowercase().contains("utf8"))
{
return true;
}
for var in &["LC_ALL", "LC_CTYPE"] {
if let Some(locale) = read_env_var(var)
&& (locale.to_lowercase().contains("utf-8") || locale.to_lowercase().contains("utf8"))
{
return true;
}
}
false
}
pub fn get_border_type() -> ratatui::widgets::BorderType {
if supports_unicode_box_drawing() {
ratatui::widgets::BorderType::Rounded
} else {
ratatui::widgets::BorderType::Plain
}
}
pub(crate) fn queued_input_edit_uses_shift_left() -> bool {
if read_env_var("TMUX").is_some() {
return true;
}
read_env_var("TERM")
.map(|term| term.to_lowercase().contains("tmux"))
.unwrap_or(false)
}
pub(crate) fn queued_input_edit_hint() -> &'static str {
if queued_input_edit_uses_shift_left() {
if cfg!(target_os = "macos") {
"⇧ + ← edit"
} else {
"Shift + ← edit"
}
} else if cfg!(target_os = "macos") {
"⌥ + ↑ edit"
} else {
"Alt + ↑ edit"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[inline]
fn set_var(key: &str, value: &str) {
test_env_overrides::set(key, Some(value));
}
#[inline]
fn remove_var(key: &str) {
test_env_overrides::set(key, None);
}
#[inline]
fn clear_var(key: &str) {
test_env_overrides::clear(key);
}
#[test]
fn test_supports_unicode_box_drawing() {
let original_term = env::var("TERM").ok();
let original_lang = env::var("LANG").ok();
let original_lc_all = env::var("LC_ALL").ok();
let original_lc_ctype = env::var("LC_CTYPE").ok();
let original_no_unicode = env::var("VTCODE_NO_UNICODE").ok();
set_var("VTCODE_NO_UNICODE", "1");
assert!(!supports_unicode_box_drawing());
remove_var("VTCODE_NO_UNICODE");
set_var("TERM", "xterm-256color");
assert!(supports_unicode_box_drawing());
set_var("LANG", "en_US.UTF-8");
assert!(supports_unicode_box_drawing());
set_var("TERM", "dumb");
assert!(!supports_unicode_box_drawing());
remove_var("TERM");
remove_var("LANG");
remove_var("LC_ALL");
remove_var("LC_CTYPE");
assert!(!supports_unicode_box_drawing());
match original_term {
Some(val) => set_var("TERM", &val),
None => clear_var("TERM"),
}
match original_lang {
Some(val) => set_var("LANG", &val),
None => clear_var("LANG"),
}
match original_lc_all {
Some(val) => set_var("LC_ALL", &val),
None => clear_var("LC_ALL"),
}
match original_lc_ctype {
Some(val) => set_var("LC_CTYPE", &val),
None => clear_var("LC_CTYPE"),
}
match original_no_unicode {
Some(val) => set_var("VTCODE_NO_UNICODE", &val),
None => clear_var("VTCODE_NO_UNICODE"),
}
}
#[test]
fn test_get_border_type() {
let original_term = env::var("TERM").ok();
set_var("TERM", "xterm-256color");
let border_type = get_border_type();
assert!(matches!(border_type, ratatui::widgets::BorderType::Rounded));
set_var("TERM", "dumb");
let border_type = get_border_type();
assert!(matches!(border_type, ratatui::widgets::BorderType::Plain));
match original_term {
Some(val) => set_var("TERM", &val),
None => clear_var("TERM"),
}
}
#[test]
fn queued_input_edit_binding_switches_for_tmux() {
let original_tmux = env::var("TMUX").ok();
let original_term = env::var("TERM").ok();
remove_var("TMUX");
set_var("TERM", "xterm-256color");
assert!(!queued_input_edit_uses_shift_left());
set_var("TMUX", "/tmp/tmux-1000/default,123,0");
assert!(queued_input_edit_uses_shift_left());
remove_var("TMUX");
set_var("TERM", "tmux-256color");
assert!(queued_input_edit_uses_shift_left());
match original_tmux {
Some(val) => set_var("TMUX", &val),
None => clear_var("TMUX"),
}
match original_term {
Some(val) => set_var("TERM", &val),
None => clear_var("TERM"),
}
}
}