1+ """Terminal color support detection and fallback options."""
2+
3+ import os
4+ import sys
5+ from typing import Dict , Optional , Set , Tuple
6+
7+ # ANSI color codes
8+ ANSI_COLORS = {
9+ 'black' : 30 ,
10+ 'red' : 31 ,
11+ 'green' : 32 ,
12+ 'yellow' : 33 ,
13+ 'blue' : 34 ,
14+ 'magenta' : 35 ,
15+ 'cyan' : 36 ,
16+ 'white' : 37 ,
17+ 'bright_black' : 90 ,
18+ 'bright_red' : 91 ,
19+ 'bright_green' : 92 ,
20+ 'bright_yellow' : 93 ,
21+ 'bright_blue' : 94 ,
22+ 'bright_magenta' : 95 ,
23+ 'bright_cyan' : 96 ,
24+ 'bright_white' : 97 ,
25+ }
26+
27+ class TerminalColorSupport :
28+ """Detect and manage terminal color support."""
29+
30+ def __init__ (self ) -> None :
31+ self ._color_support : Optional [bool ] = None
32+ self ._supported_colors : Set [str ] = set ()
33+ self ._fallback_colors : Dict [str , str ] = {}
34+
35+ def detect_color_support (self ) -> bool :
36+ """Detect if the terminal supports colors."""
37+ if self ._color_support is not None :
38+ return self ._color_support
39+
40+ # Check environment variables
41+ if 'NO_COLOR' in os .environ :
42+ self ._color_support = False
43+ return False
44+
45+ if 'FORCE_COLOR' in os .environ :
46+ self ._color_support = True
47+ return True
48+
49+ # Check if we're in a terminal
50+ if not sys .stdout .isatty ():
51+ self ._color_support = False
52+ return False
53+
54+ # Check terminal type
55+ term = os .environ .get ('TERM' , '' ).lower ()
56+ if term in ('dumb' , 'unknown' ):
57+ self ._color_support = False
58+ return False
59+
60+ # Windows specific checks
61+ if sys .platform == 'win32' :
62+ try :
63+ import ctypes
64+ kernel32 = ctypes .windll .kernel32
65+ if kernel32 .GetConsoleMode (kernel32 .GetStdHandle (- 11 ), None ):
66+ self ._color_support = True
67+ return True
68+ except Exception :
69+ pass
70+
71+ # Default to True for modern terminals
72+ self ._color_support = True
73+ return True
74+
75+ def get_supported_colors (self ) -> Set [str ]:
76+ """Get the set of supported colors."""
77+ if not self ._supported_colors :
78+ if self .detect_color_support ():
79+ # Test each color
80+ for color in ANSI_COLORS :
81+ if self ._test_color (color ):
82+ self ._supported_colors .add (color )
83+
84+ return self ._supported_colors
85+
86+ def _test_color (self , color : str ) -> bool :
87+ """Test if a specific color is supported."""
88+ # Implementation would test actual color support
89+ # For now, return True for all colors if color support is enabled
90+ return self .detect_color_support ()
91+
92+ def get_fallback_color (self , color : str ) -> str :
93+ """Get a fallback color if the requested color is not supported."""
94+ if color in self ._fallback_colors :
95+ return self ._fallback_colors [color ]
96+
97+ # Define fallback mappings
98+ fallbacks = {
99+ 'bright_black' : 'black' ,
100+ 'bright_red' : 'red' ,
101+ 'bright_green' : 'green' ,
102+ 'bright_yellow' : 'yellow' ,
103+ 'bright_blue' : 'blue' ,
104+ 'bright_magenta' : 'magenta' ,
105+ 'bright_cyan' : 'cyan' ,
106+ 'bright_white' : 'white' ,
107+ }
108+
109+ fallback = fallbacks .get (color , 'white' )
110+ self ._fallback_colors [color ] = fallback
111+ return fallback
112+
113+ def get_color_code (self , color : str ) -> Tuple [int , bool ]:
114+ """Get the ANSI color code and whether it's supported."""
115+ if not self .detect_color_support ():
116+ return (0 , False )
117+
118+ if color not in self .get_supported_colors ():
119+ color = self .get_fallback_color (color )
120+
121+ return (ANSI_COLORS [color ], True )
122+
123+ # Global instance
124+ terminal_color = TerminalColorSupport ()
0 commit comments