authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2024-05-29 20:13:11-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2024-06-02 16:46:21-07:00
log337f09e93297f8097e7b5093ea138b60f2c52e5f
tree08dabdb8d902c88de29ab40ab5d3074934e84e8d
parent17dc93934689235ac16b3354c3eb932420a88e85

Add `File.getOrEnableAnsiEscapeSupport` and use it

On Windows, the console mode flag `ENABLE_VIRTUAL_TERMINAL_PROCESSING` determines whether or not ANSI escape codes are parsed/acted on. On the newer Windows Terminal, this flag is set by default, but on the older Windows Console, it is not set by default, but *can* be enabled (since Windows 10 RS1 from June 2016). The new `File.getOrEnableAnsiEscapeSupport` function will get the current status of ANSI escape code support, but will also attempt to enable `ENABLE_VIRTUAL_TERMINAL_PROCESSING` on Windows if necessary which will provide better/more consistent results for things like `std.Progress` and `std.io.tty`. This type of change was not done previously due to a mistaken assumption (on my part) that the console mode would persist after the run of a program. However, it turns out that the console mode is always reset to the default for each program run in a console session.

3 files changed, 32 insertions(+), 6 deletions(-)

lib/std/Progress.zig+1-1
...@@ -377,7 +377,7 @@ pub fn start(options: Options) Node {...@@ -377,7 +377,7 @@ pub fn start(options: Options) Node {
377 }377 }
378 const stderr = std.io.getStdErr();378 const stderr = std.io.getStdErr();
379 global_progress.terminal = stderr;379 global_progress.terminal = stderr;
380 if (stderr.supportsAnsiEscapeCodes()) {380 if (stderr.getOrEnableAnsiEscapeSupport()) {
381 global_progress.terminal_mode = .ansi_escape_codes;381 global_progress.terminal_mode = .ansi_escape_codes;
382 } else if (is_windows and stderr.isTty()) {382 } else if (is_windows and stderr.isTty()) {
383 global_progress.terminal_mode = TerminalMode{ .windows_api = .{383 global_progress.terminal_mode = TerminalMode{ .windows_api = .{
lib/std/fs/File.zig+29-4
...@@ -188,7 +188,7 @@ pub fn sync(self: File) SyncError!void {...@@ -188,7 +188,7 @@ pub fn sync(self: File) SyncError!void {
188}188}
189189
190/// Test whether the file refers to a terminal.190/// Test whether the file refers to a terminal.
191/// See also `supportsAnsiEscapeCodes`.191/// See also `getOrEnableAnsiEscapeSupport` and `supportsAnsiEscapeCodes`.
192pub fn isTty(self: File) bool {192pub fn isTty(self: File) bool {
193 return posix.isatty(self.handle);193 return posix.isatty(self.handle);
194}194}
...@@ -245,8 +245,16 @@ pub fn isCygwinPty(file: File) bool {...@@ -245,8 +245,16 @@ pub fn isCygwinPty(file: File) bool {
245 std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null;245 std.mem.indexOf(u16, name_wide, &[_]u16{ '-', 'p', 't', 'y' }) != null;
246}246}
247247
248/// Test whether ANSI escape codes will be treated as such.248/// Returns whether or not ANSI escape codes will be treated as such,
249pub fn supportsAnsiEscapeCodes(self: File) bool {249/// and attempts to enable support for ANSI escape codes if necessary
250/// (on Windows).
251///
252/// Returns `true` if ANSI escape codes are supported or support was
253/// successfully enabled. Returns false if ANSI escape codes are not
254/// supported or support was unable to be enabled.
255///
256/// See also `supportsAnsiEscapeCodes`.
257pub fn getOrEnableAnsiEscapeSupport(self: File) bool {
250 if (builtin.os.tag == .windows) {258 if (builtin.os.tag == .windows) {
251 var original_console_mode: windows.DWORD = 0;259 var original_console_mode: windows.DWORD = 0;
252260
...@@ -262,7 +270,8 @@ pub fn supportsAnsiEscapeCodes(self: File) bool {...@@ -262,7 +270,8 @@ pub fn supportsAnsiEscapeCodes(self: File) bool {
262 var console_mode = original_console_mode | requested_console_modes;270 var console_mode = original_console_mode | requested_console_modes;
263 if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true;271 if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true;
264272
265 // An application receiving ERROR_INVALID_PARAMETER with one of the newer console mode flags in the bit field should gracefully degrade behavior and try again.273 // An application receiving ERROR_INVALID_PARAMETER with one of the newer console mode
274 // flags in the bit field should gracefully degrade behavior and try again.
266 requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;275 requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
267 console_mode = original_console_mode | requested_console_modes;276 console_mode = original_console_mode | requested_console_modes;
268 if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true;277 if (windows.kernel32.SetConsoleMode(self.handle, console_mode) != 0) return true;
...@@ -270,6 +279,22 @@ pub fn supportsAnsiEscapeCodes(self: File) bool {...@@ -270,6 +279,22 @@ pub fn supportsAnsiEscapeCodes(self: File) bool {
270279
271 return self.isCygwinPty();280 return self.isCygwinPty();
272 }281 }
282 return self.supportsAnsiEscapeCodes();
283}
284
285/// Test whether ANSI escape codes will be treated as such without
286/// attempting to enable support for ANSI escape codes.
287///
288/// See also `getOrEnableAnsiEscapeSupport`.
289pub fn supportsAnsiEscapeCodes(self: File) bool {
290 if (builtin.os.tag == .windows) {
291 var console_mode: windows.DWORD = 0;
292 if (windows.kernel32.GetConsoleMode(self.handle, &console_mode) != 0) {
293 if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return true;
294 }
295
296 return self.isCygwinPty();
297 }
273 if (builtin.os.tag == .wasi) {298 if (builtin.os.tag == .wasi) {
274 // WASI sanitizes stdout when fd is a tty so ANSI escape codes299 // WASI sanitizes stdout when fd is a tty so ANSI escape codes
275 // will not be interpreted as actual cursor commands, and300 // will not be interpreted as actual cursor commands, and
lib/std/io/tty.zig+2-1
...@@ -8,6 +8,7 @@ const native_os = builtin.os.tag;...@@ -8,6 +8,7 @@ const native_os = builtin.os.tag;
8/// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).8/// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).
9/// This includes feature checks for ANSI escape codes and the Windows console API, as well as9/// This includes feature checks for ANSI escape codes and the Windows console API, as well as
10/// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.10/// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.
11/// Will attempt to enable ANSI escape code support if necessary/possible.
11pub fn detectConfig(file: File) Config {12pub fn detectConfig(file: File) Config {
12 const force_color: ?bool = if (builtin.os.tag == .wasi)13 const force_color: ?bool = if (builtin.os.tag == .wasi)
13 null // wasi does not support environment variables14 null // wasi does not support environment variables
...@@ -20,7 +21,7 @@ pub fn detectConfig(file: File) Config {...@@ -20,7 +21,7 @@ pub fn detectConfig(file: File) Config {
2021
21 if (force_color == false) return .no_color;22 if (force_color == false) return .no_color;
2223
23 if (file.supportsAnsiEscapeCodes()) return .escape_codes;24 if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes;
2425
25 if (native_os == .windows and file.isTty()) {26 if (native_os == .windows and file.isTty()) {
26 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;27 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;