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};