Skip to content

Commit e2b9e1a

Browse files
feat: improve terminal color detection with multi-method approach
- Implement robust truecolor detection using multiple environment variables - Check COLORTERM for 'truecolor' or '24bit' (most reliable) - Check TERM for known truecolor terminals (kitty, alacritty, wezterm) - Check TERM_PROGRAM for terminal apps (iTerm2, VSCode, etc.) - Fall back to supports_color crate detection as last resort - Add comprehensive comments explaining detection rationale - Follows termstandard/colors recommendations for robustness This improves compatibility across different terminal emulators.
1 parent c44d374 commit e2b9e1a

1 file changed

Lines changed: 84 additions & 3 deletions

File tree

src/tui/terminal_compat.rs

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ impl TerminalCapabilities {
2222
.map(|v| v == "Apple_Terminal")
2323
.unwrap_or(false);
2424

25-
let supports_rgb = on(Stream::Stdout)
26-
.map(|level| level.has_16m)
27-
.unwrap_or(false);
25+
// Detect RGB/truecolor support using multiple methods for robustness.
26+
// The supports_color crate can miss truecolor in some terminals,
27+
// so we check environment variables first per termstandard/colors recommendations.
28+
let supports_rgb = Self::detect_truecolor_support();
2829

2930
let macos_version = Self::detect_macos_version();
3031

@@ -78,6 +79,61 @@ impl TerminalCapabilities {
7879
None
7980
}
8081

82+
/// Detect truecolor (24-bit RGB) support using multiple methods.
83+
///
84+
/// Per termstandard/colors recommendations, we check in this order:
85+
/// 1. COLORTERM env var for "truecolor" or "24bit" (most reliable)
86+
/// 2. TERM env var for known truecolor terminals or suffixes
87+
/// 3. Fall back to supports_color crate detection
88+
fn detect_truecolor_support() -> bool {
89+
// Method 1: Check COLORTERM environment variable (primary standard)
90+
// VTE, Konsole, iTerm2, Kitty, Alacritty all set this
91+
if let Ok(colorterm) = std::env::var("COLORTERM") {
92+
if colorterm == "truecolor" || colorterm == "24bit" {
93+
return true;
94+
}
95+
}
96+
97+
// Method 2: Check TERM for known truecolor-capable terminals or suffixes
98+
if let Ok(term) = std::env::var("TERM") {
99+
let term_lower = term.to_lowercase();
100+
// Check for explicit truecolor/direct suffixes
101+
if term_lower.ends_with("-truecolor")
102+
|| term_lower.ends_with("-direct")
103+
|| term_lower.ends_with("direct")
104+
{
105+
return true;
106+
}
107+
// Check for known truecolor-capable terminal types
108+
if term_lower.contains("kitty")
109+
|| term_lower.contains("alacritty")
110+
|| term_lower.contains("wezterm")
111+
{
112+
return true;
113+
}
114+
}
115+
116+
// Method 3: Check TERM_PROGRAM for known truecolor apps
117+
// (iTerm2 is already handled by supports_color, but be explicit)
118+
if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
119+
let prog_lower = term_program.to_lowercase();
120+
if prog_lower.contains("iterm")
121+
|| prog_lower.contains("kitty")
122+
|| prog_lower.contains("alacritty")
123+
|| prog_lower.contains("wezterm")
124+
|| prog_lower.contains("hyper")
125+
|| prog_lower.contains("vscode")
126+
{
127+
return true;
128+
}
129+
}
130+
131+
// Method 4: Fall back to supports_color crate detection
132+
on(Stream::Stdout)
133+
.map(|level| level.has_16m)
134+
.unwrap_or(false)
135+
}
136+
81137
/// Get a user-friendly warning message
82138
pub fn warning_message(&self) -> Option<String> {
83139
if !self.should_warn {
@@ -110,4 +166,29 @@ mod tests {
110166
// Just ensure it doesn't panic
111167
println!("Detected capabilities: {:?}", caps);
112168
}
169+
170+
#[test]
171+
fn test_truecolor_detection_uses_env_vars() {
172+
// This test verifies that the detection method runs without panicking.
173+
// Full testing of env var logic would require mocking, but we verify
174+
// the detection integrates properly with the capabilities struct.
175+
let caps = TerminalCapabilities::detect();
176+
177+
// The detection should return a valid color mode regardless of environment
178+
assert!(
179+
caps.recommended_color_mode == ColorMode::Rgb
180+
|| caps.recommended_color_mode == ColorMode::Indexed256
181+
);
182+
}
183+
184+
#[test]
185+
fn test_color_mode_enum() {
186+
// Verify ColorMode variants are distinct
187+
assert_ne!(ColorMode::Rgb, ColorMode::Indexed256);
188+
189+
// Verify Copy trait works
190+
let mode = ColorMode::Rgb;
191+
let mode_copy = mode;
192+
assert_eq!(mode, mode_copy);
193+
}
113194
}

0 commit comments

Comments
 (0)