master
  1const std = @import("std");
  2const builtin = @import("builtin");
  3const File = std.fs.File;
  4const process = std.process;
  5const windows = std.os.windows;
  6const native_os = builtin.os.tag;
  7
  8/// Deprecated in favor of `Config.detect`.
  9pub fn detectConfig(file: File) Config {
 10    return .detect(file);
 11}
 12
 13pub const Color = enum {
 14    black,
 15    red,
 16    green,
 17    yellow,
 18    blue,
 19    magenta,
 20    cyan,
 21    white,
 22    bright_black,
 23    bright_red,
 24    bright_green,
 25    bright_yellow,
 26    bright_blue,
 27    bright_magenta,
 28    bright_cyan,
 29    bright_white,
 30    dim,
 31    bold,
 32    reset,
 33};
 34
 35/// Provides simple functionality for manipulating the terminal in some way,
 36/// such as coloring text, etc.
 37pub const Config = union(enum) {
 38    no_color,
 39    escape_codes,
 40    windows_api: if (native_os == .windows) WindowsContext else noreturn,
 41
 42    /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).
 43    /// This includes feature checks for ANSI escape codes and the Windows console API, as well as
 44    /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.
 45    /// Will attempt to enable ANSI escape code support if necessary/possible.
 46    pub fn detect(file: File) Config {
 47        const force_color: ?bool = if (builtin.os.tag == .wasi)
 48            null // wasi does not support environment variables
 49        else if (process.hasNonEmptyEnvVarConstant("NO_COLOR"))
 50            false
 51        else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE"))
 52            true
 53        else
 54            null;
 55
 56        if (force_color == false) return .no_color;
 57
 58        if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes;
 59
 60        if (native_os == .windows and file.isTty()) {
 61            var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
 62            if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) {
 63                return if (force_color == true) .escape_codes else .no_color;
 64            }
 65            return .{ .windows_api = .{
 66                .handle = file.handle,
 67                .reset_attributes = info.wAttributes,
 68            } };
 69        }
 70
 71        return if (force_color == true) .escape_codes else .no_color;
 72    }
 73
 74    pub const WindowsContext = struct {
 75        handle: File.Handle,
 76        reset_attributes: u16,
 77    };
 78
 79    pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || std.Io.Writer.Error;
 80
 81    pub fn setColor(conf: Config, w: *std.Io.Writer, color: Color) SetColorError!void {
 82        nosuspend switch (conf) {
 83            .no_color => return,
 84            .escape_codes => {
 85                const color_string = switch (color) {
 86                    .black => "\x1b[30m",
 87                    .red => "\x1b[31m",
 88                    .green => "\x1b[32m",
 89                    .yellow => "\x1b[33m",
 90                    .blue => "\x1b[34m",
 91                    .magenta => "\x1b[35m",
 92                    .cyan => "\x1b[36m",
 93                    .white => "\x1b[37m",
 94                    .bright_black => "\x1b[90m",
 95                    .bright_red => "\x1b[91m",
 96                    .bright_green => "\x1b[92m",
 97                    .bright_yellow => "\x1b[93m",
 98                    .bright_blue => "\x1b[94m",
 99                    .bright_magenta => "\x1b[95m",
100                    .bright_cyan => "\x1b[96m",
101                    .bright_white => "\x1b[97m",
102                    .bold => "\x1b[1m",
103                    .dim => "\x1b[2m",
104                    .reset => "\x1b[0m",
105                };
106                try w.writeAll(color_string);
107            },
108            .windows_api => |ctx| {
109                const attributes = switch (color) {
110                    .black => 0,
111                    .red => windows.FOREGROUND_RED,
112                    .green => windows.FOREGROUND_GREEN,
113                    .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
114                    .blue => windows.FOREGROUND_BLUE,
115                    .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
116                    .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
117                    .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
118                    .bright_black => windows.FOREGROUND_INTENSITY,
119                    .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
120                    .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
121                    .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
122                    .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
123                    .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
124                    .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
125                    .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
126                    // "dim" is not supported using basic character attributes, but let's still make it do *something*.
127                    // This matches the old behavior of TTY.Color before the bright variants were added.
128                    .dim => windows.FOREGROUND_INTENSITY,
129                    .reset => ctx.reset_attributes,
130                };
131                try w.flush();
132                try windows.SetConsoleTextAttribute(ctx.handle, attributes);
133            },
134        };
135    }
136};