@@ -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