@@ -40,36 +40,95 @@ public static bool IsValidTerminal(string term)
4040 return ! string . IsNullOrWhiteSpace ( term ) && _regexes . Any ( regex => regex . IsMatch ( term ) ) ;
4141 }
4242
43+ /// <summary>
44+ /// Detect whether current environment supports ANSI sequences.
45+ /// Returns tuple (SupportsAnsi, LegacyConsole).
46+ /// - SupportsAnsi: true when we can emit ANSI sequences and they will be interpreted.
47+ /// - LegacyConsole: true when environment is a legacy Windows console that doesn't support ANSI.
48+ /// </summary>
4349 public static ( bool SupportsAnsi , bool LegacyConsole ) Detect ( )
4450 {
4551 // Running on Windows?
4652 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
4753 {
48- // Running under ConEmu?
54+ // ConEmu explicit opt-in
4955 string ? conEmu = Environment . GetEnvironmentVariable ( "ConEmuANSI" ) ;
5056 if ( ! string . IsNullOrEmpty ( conEmu ) && conEmu . Equals ( "On" , StringComparison . OrdinalIgnoreCase ) )
5157 {
5258 return ( true , false ) ;
5359 }
5460
55- bool supportsAnsi = Windows . SupportsAnsi ( out bool legacyConsole ) ;
56- return ( supportsAnsi , legacyConsole ) ;
61+ // ANSICON (older ANSI emulator), Windows Terminal (WT_SESSION), and similar env hints
62+ if ( ! string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "ANSICON" ) ) ||
63+ ! string . IsNullOrEmpty ( Environment . GetEnvironmentVariable ( "WT_SESSION" ) ) ||
64+ IsKnownTerminalProgram ( ) )
65+ {
66+ // Even if these env vars are present, we should still try to enable console mode if possible.
67+ bool supports = Windows . SupportsAnsi ( out bool legacyConsole ) ;
68+ return ( supports , legacyConsole ) ;
69+ }
70+
71+ // Otherwise query Windows console directly (stdout preferred).
72+ bool supportsAnsi = Windows . SupportsAnsi ( out bool legacy ) ;
73+ return ( supportsAnsi , legacy ) ;
5774 }
75+ // Non-Windows platforms: decide from TERM/COLORTERM
5876 return DetectFromTerm ( ) ;
5977 }
6078
6179 private static ( bool SupportsAnsi , bool LegacyConsole ) DetectFromTerm ( )
6280 {
63- // Check if the terminal is of type ANSI/VT100/xterm compatible.
81+ // If output is redirected, ANSI sequences probably won't be useful.
82+ if ( Console . IsOutputRedirected && Console . IsErrorRedirected )
83+ {
84+ return ( false , true ) ;
85+ }
86+
87+ // Common variable that indicates color support (truecolor/24bit or general color)
88+ string ? colorterm = Environment . GetEnvironmentVariable ( "COLORTERM" ) ;
89+ if ( ! string . IsNullOrWhiteSpace ( colorterm ) )
90+ {
91+ // If COLORTERM is present, assume ANSI/VT sequences are supported on non-Windows
92+ return ( true , false ) ;
93+ }
94+
95+ // TERM-based detection (xterm, screen, linux, cygwin, etc).
6496 string ? term = Environment . GetEnvironmentVariable ( "TERM" ) ;
6597 if ( ! string . IsNullOrWhiteSpace ( term ) && _regexes . Any ( regex => regex . IsMatch ( term ) ) )
6698 {
6799 return ( true , false ) ;
68100 }
69101
102+ // Fallback: if TERM_PROGRAM suggests a modern terminal (vscode, iTerm, Apple_Terminal)
103+ if ( IsKnownTerminalProgram ( ) )
104+ {
105+ return ( true , false ) ;
106+ }
107+
70108 return ( false , true ) ;
71109 }
72110
111+ private static bool IsKnownTerminalProgram ( )
112+ {
113+ string ? termProgram = Environment . GetEnvironmentVariable ( "TERM_PROGRAM" ) ;
114+ if ( string . IsNullOrWhiteSpace ( termProgram ) )
115+ {
116+ return false ;
117+ }
118+
119+ // Some known modern terminal identifiers
120+ string [ ] known =
121+ [
122+ "vscode" , // VS Code integrated terminal
123+ "vscode-insiders" ,
124+ "iTerm.app" , // iTerm2 on macOS
125+ "Apple_Terminal" ,
126+ "WindowsTerminal" ,
127+ ] ;
128+
129+ return known . Any ( k => termProgram . Contains ( k , StringComparison . OrdinalIgnoreCase ) ) ;
130+ }
131+
73132 [ GeneratedRegex ( "^xterm" ) ]
74133 private static partial Regex XtermRegex ( ) ;
75134
@@ -121,12 +180,12 @@ private static (bool SupportsAnsi, bool LegacyConsole) DetectFromTerm()
121180 [ GeneratedRegex ( "alacritty" ) ]
122181 private static partial Regex AlacrittyRegex ( ) ;
123182
124- unsafe private static partial class Windows
183+ private static partial class Windows
125184 {
185+ private const int STD_OUTPUT_HANDLE = - 11 ;
126186 private const int STD_ERROR_HANDLE = - 12 ;
127187
128188 private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 ;
129-
130189 private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008 ;
131190
132191 [ LibraryImport ( "kernel32.dll" ) ]
@@ -149,39 +208,54 @@ public static bool SupportsAnsi(out bool isLegacy)
149208
150209 try
151210 {
152- nint @out = GetStdHandle ( STD_ERROR_HANDLE ) ;
153- if ( ! GetConsoleMode ( @out , out uint mode ) )
211+ // If output is redirected to a file/pipe, the console APIs will fail. Respect redirection.
212+ if ( Console . IsOutputRedirected && Console . IsErrorRedirected )
154213 {
155- // Could not get console mode, try TERM (set in cygwin, WSL-Shell).
156- ( bool ansiFromTerm , bool legacyFromTerm ) = DetectFromTerm ( ) ;
157-
158- isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy ;
159- return ansiFromTerm ;
214+ isLegacy = false ;
215+ return false ;
160216 }
161217
162- if ( ( mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING ) == 0 )
218+ // Prefer stdout handle, then stderr.
219+ nint handle = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
220+ if ( handle == nint . Zero || ! GetConsoleMode ( handle , out uint mode ) )
163221 {
164- isLegacy = true ;
165-
166- // Try enable ANSI support.
167- mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN ;
168- if ( ! SetConsoleMode ( @out , mode ) )
222+ // try stderr as fallback
223+ handle = GetStdHandle ( STD_ERROR_HANDLE ) ;
224+ if ( handle == nint . Zero || ! GetConsoleMode ( handle , out mode ) )
169225 {
170- // Enabling failed.
171- return false ;
226+ // Could not get console mode. Fall back to TERM detection (WSL, MSYS, Cygwin)
227+ ( bool ansiFromTerm , bool legacyFromTerm ) = DetectFromTerm ( ) ;
228+ isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy ;
229+ return ansiFromTerm ;
172230 }
231+ }
173232
174- isLegacy = false ;
233+ // If virtual terminal processing already enabled, we support ANSI
234+ if ( ( mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0 )
235+ {
236+ return true ;
237+ }
238+
239+ // Attempt to enable virtual terminal processing.
240+ uint newMode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN ;
241+ if ( ! SetConsoleMode ( handle , newMode ) )
242+ {
243+ // Enabling failed: treat as legacy Windows console.
244+ isLegacy = true ;
245+ return false ;
175246 }
176247
248+ // Successfully enabled.
249+ isLegacy = false ;
177250 return true ;
178251 }
179252 catch
180253 {
181- // All we know here is that we don't support ANSI.
254+ // Unknown failure; assume no ANSI support.
255+ isLegacy = true ;
182256 return false ;
183257 }
184258 }
185259 }
186260 }
187- }
261+ }
0 commit comments