diff --git a/lib/build_runner.zig b/lib/build_runner.zig index 42903b82f32a..a09ec2cf1f81 100644 --- a/lib/build_runner.zig +++ b/lib/build_runner.zig @@ -333,7 +333,7 @@ const Run = struct { claimed_rss: usize, enable_summary: ?bool, - ttyconf: std.debug.TTY.Config, + ttyconf: std.io.tty.Config, stderr: std.fs.File, }; @@ -476,9 +476,9 @@ fn runStepNames( if (run.enable_summary != false) { const total_count = success_count + failure_count + pending_count + skipped_count; - ttyconf.setColor(stderr, .Cyan) catch {}; + ttyconf.setColor(stderr, .cyan) catch {}; stderr.writeAll("Build Summary:") catch {}; - ttyconf.setColor(stderr, .Reset) catch {}; + ttyconf.setColor(stderr, .reset) catch {}; stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {}; if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {}; if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {}; @@ -489,9 +489,9 @@ fn runStepNames( if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {}; if (run.enable_summary == null) { - ttyconf.setColor(stderr, .Dim) catch {}; + ttyconf.setColor(stderr, .dim) catch {}; stderr.writeAll(" (disable with -fno-summary)") catch {}; - ttyconf.setColor(stderr, .Reset) catch {}; + ttyconf.setColor(stderr, .reset) catch {}; } stderr.writeAll("\n") catch {}; @@ -535,7 +535,7 @@ const PrintNode = struct { last: bool = false, }; -fn printPrefix(node: *PrintNode, stderr: std.fs.File, ttyconf: std.debug.TTY.Config) !void { +fn printPrefix(node: *PrintNode, stderr: std.fs.File, ttyconf: std.io.tty.Config) !void { const parent = node.parent orelse return; if (parent.parent == null) return; try printPrefix(parent, stderr, ttyconf); @@ -553,14 +553,14 @@ fn printTreeStep( b: *std.Build, s: *Step, stderr: std.fs.File, - ttyconf: std.debug.TTY.Config, + ttyconf: std.io.tty.Config, parent_node: *PrintNode, step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void), ) !void { const first = step_stack.swapRemove(s); try printPrefix(parent_node, stderr, ttyconf); - if (!first) try ttyconf.setColor(stderr, .Dim); + if (!first) try ttyconf.setColor(stderr, .dim); if (parent_node.parent != null) { if (parent_node.last) { try stderr.writeAll(switch (ttyconf) { @@ -586,28 +586,28 @@ fn printTreeStep( .running => unreachable, .dependency_failure => { - try ttyconf.setColor(stderr, .Dim); + try ttyconf.setColor(stderr, .dim); try stderr.writeAll(" transitive failure\n"); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); }, .success => { - try ttyconf.setColor(stderr, .Green); + try ttyconf.setColor(stderr, .green); if (s.result_cached) { try stderr.writeAll(" cached"); } else if (s.test_results.test_count > 0) { const pass_count = s.test_results.passCount(); try stderr.writer().print(" {d} passed", .{pass_count}); if (s.test_results.skip_count > 0) { - try ttyconf.setColor(stderr, .Yellow); + try ttyconf.setColor(stderr, .yellow); try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count}); } } else { try stderr.writeAll(" success"); } - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); if (s.result_duration_ns) |ns| { - try ttyconf.setColor(stderr, .Dim); + try ttyconf.setColor(stderr, .dim); if (ns >= std.time.ns_per_min) { try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min}); } else if (ns >= std.time.ns_per_s) { @@ -619,11 +619,11 @@ fn printTreeStep( } else { try stderr.writer().print(" {d}ns", .{ns}); } - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } if (s.result_peak_rss != 0) { const rss = s.result_peak_rss; - try ttyconf.setColor(stderr, .Dim); + try ttyconf.setColor(stderr, .dim); if (rss >= 1000_000_000) { try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000}); } else if (rss >= 1000_000) { @@ -633,57 +633,57 @@ fn printTreeStep( } else { try stderr.writer().print(" MaxRSS:{d}B", .{rss}); } - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } try stderr.writeAll("\n"); }, .skipped => { - try ttyconf.setColor(stderr, .Yellow); + try ttyconf.setColor(stderr, .yellow); try stderr.writeAll(" skipped\n"); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); }, .failure => { if (s.result_error_bundle.errorMessageCount() > 0) { - try ttyconf.setColor(stderr, .Red); + try ttyconf.setColor(stderr, .red); try stderr.writer().print(" {d} errors\n", .{ s.result_error_bundle.errorMessageCount(), }); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } else if (!s.test_results.isSuccess()) { try stderr.writer().print(" {d}/{d} passed", .{ s.test_results.passCount(), s.test_results.test_count, }); if (s.test_results.fail_count > 0) { try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .Red); + try ttyconf.setColor(stderr, .red); try stderr.writer().print("{d} failed", .{ s.test_results.fail_count, }); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } if (s.test_results.skip_count > 0) { try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .Yellow); + try ttyconf.setColor(stderr, .yellow); try stderr.writer().print("{d} skipped", .{ s.test_results.skip_count, }); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } if (s.test_results.leak_count > 0) { try stderr.writeAll(", "); - try ttyconf.setColor(stderr, .Red); + try ttyconf.setColor(stderr, .red); try stderr.writer().print("{d} leaked", .{ s.test_results.leak_count, }); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } try stderr.writeAll("\n"); } else { - try ttyconf.setColor(stderr, .Red); + try ttyconf.setColor(stderr, .red); try stderr.writeAll(" failure\n"); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } }, } @@ -703,7 +703,7 @@ fn printTreeStep( s.dependencies.items.len, }); } - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } } @@ -819,13 +819,13 @@ fn workerMakeOneStep( for (s.result_error_msgs.items) |msg| { // Sometimes it feels like you just can't catch a break. Finally, // with Zig, you can. - ttyconf.setColor(stderr, .Bold) catch break; + ttyconf.setColor(stderr, .bold) catch break; stderr.writeAll(s.owner.dep_prefix) catch break; stderr.writeAll(s.name) catch break; stderr.writeAll(": ") catch break; - ttyconf.setColor(stderr, .Red) catch break; + ttyconf.setColor(stderr, .red) catch break; stderr.writeAll("error: ") catch break; - ttyconf.setColor(stderr, .Reset) catch break; + ttyconf.setColor(stderr, .reset) catch break; stderr.writeAll(msg) catch break; stderr.writeAll("\n") catch break; } @@ -1026,15 +1026,15 @@ fn cleanExit() void { const Color = enum { auto, off, on }; -fn get_tty_conf(color: Color, stderr: std.fs.File) std.debug.TTY.Config { +fn get_tty_conf(color: Color, stderr: std.fs.File) std.io.tty.Config { return switch (color) { - .auto => std.debug.detectTTYConfig(stderr), + .auto => std.io.tty.detectConfig(stderr), .on => .escape_codes, .off => .no_color, }; } -fn renderOptions(ttyconf: std.debug.TTY.Config) std.zig.ErrorBundle.RenderOptions { +fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions { return .{ .ttyconf = ttyconf, .include_source_line = ttyconf != .no_color, diff --git a/lib/std/Build.zig b/lib/std/Build.zig index d97a5c5d7aac..bb642b5e66c8 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1712,10 +1712,10 @@ fn dumpBadGetPathHelp( s.name, }); - const tty_config = std.debug.detectTTYConfig(stderr); - tty_config.setColor(w, .Red) catch {}; + const tty_config = std.io.tty.detectConfig(stderr); + tty_config.setColor(w, .red) catch {}; try stderr.writeAll(" The step was created by this stack trace:\n"); - tty_config.setColor(w, .Reset) catch {}; + tty_config.setColor(w, .reset) catch {}; const debug_info = std.debug.getSelfDebugInfo() catch |err| { try w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); @@ -1727,9 +1727,9 @@ fn dumpBadGetPathHelp( return; }; if (asking_step) |as| { - tty_config.setColor(w, .Red) catch {}; + tty_config.setColor(w, .red) catch {}; try stderr.writeAll(" The step that is missing a dependency on the above step was created by this stack trace:\n"); - tty_config.setColor(w, .Reset) catch {}; + tty_config.setColor(w, .reset) catch {}; std.debug.writeStackTrace(as.getStackTrace(), w, ally, debug_info, tty_config) catch |err| { try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}); @@ -1737,9 +1737,9 @@ fn dumpBadGetPathHelp( }; } - tty_config.setColor(w, .Red) catch {}; + tty_config.setColor(w, .red) catch {}; try stderr.writeAll(" Hope that helps. Proceeding to panic.\n"); - tty_config.setColor(w, .Reset) catch {}; + tty_config.setColor(w, .reset) catch {}; } /// Allocates a new string for assigning a value to a named macro. diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 40c88df2b945..a0d7a6a296ff 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -237,7 +237,7 @@ pub fn dump(step: *Step) void { const stderr = std.io.getStdErr(); const w = stderr.writer(); - const tty_config = std.debug.detectTTYConfig(stderr); + const tty_config = std.io.tty.detectConfig(stderr); const debug_info = std.debug.getSelfDebugInfo() catch |err| { w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{ @errorName(err), diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 56fab05d88bf..710aaefd5a08 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -51,7 +51,7 @@ pub const StackTrace = struct { const debug_info = std.debug.getSelfDebugInfo() catch |err| { return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}); }; - const tty_config = std.debug.detectTTYConfig(std.io.getStdErr()); + const tty_config = std.io.tty.detectConfig(std.io.getStdErr()); try writer.writeAll("\n"); std.debug.writeStackTrace(self, writer, arena.allocator(), debug_info, tty_config) catch |err| { try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)}); diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 005c2b540428..08407023d6b8 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -5,7 +5,6 @@ const mem = std.mem; const io = std.io; const os = std.os; const fs = std.fs; -const process = std.process; const testing = std.testing; const elf = std.elf; const DW = std.dwarf; @@ -109,31 +108,6 @@ pub fn getSelfDebugInfo() !*DebugInfo { } } -pub fn detectTTYConfig(file: std.fs.File) TTY.Config { - if (builtin.os.tag == .wasi) { - // Per https://github.com/WebAssembly/WASI/issues/162 ANSI codes - // aren't currently supported. - return .no_color; - } else if (process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) { - return .escape_codes; - } else if (process.hasEnvVarConstant("NO_COLOR")) { - return .no_color; - } else if (file.supportsAnsiEscapeCodes()) { - return .escape_codes; - } else if (native_os == .windows and file.isTty()) { - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) { - // TODO: Should this return an error instead? - return .no_color; - } - return .{ .windows_api = .{ - .handle = file.handle, - .reset_attributes = info.wAttributes, - } }; - } - return .no_color; -} - /// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. /// TODO multithreaded awareness pub fn dumpCurrentStackTrace(start_addr: ?usize) void { @@ -154,7 +128,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; - writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| { + writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(io.getStdErr()), start_addr) catch |err| { stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; return; }; @@ -182,7 +156,7 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; - const tty_config = detectTTYConfig(io.getStdErr()); + const tty_config = io.tty.detectConfig(io.getStdErr()); if (native_os == .windows) { writeCurrentStackTraceWindows(stderr, debug_info, tty_config, ip) catch return; return; @@ -265,7 +239,7 @@ pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void { stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return; return; }; - writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig(io.getStdErr())) catch |err| { + writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, io.tty.detectConfig(io.getStdErr())) catch |err| { stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return; return; }; @@ -403,7 +377,7 @@ pub fn writeStackTrace( out_stream: anytype, allocator: mem.Allocator, debug_info: *DebugInfo, - tty_config: TTY.Config, + tty_config: io.tty.Config, ) !void { _ = allocator; if (builtin.strip_debug_info) return error.MissingDebugInfo; @@ -421,9 +395,9 @@ pub fn writeStackTrace( if (stack_trace.index > stack_trace.instruction_addresses.len) { const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len; - tty_config.setColor(out_stream, .Bold) catch {}; + tty_config.setColor(out_stream, .bold) catch {}; try out_stream.print("({d} additional stack frames skipped...)\n", .{dropped_frames}); - tty_config.setColor(out_stream, .Reset) catch {}; + tty_config.setColor(out_stream, .reset) catch {}; } } @@ -562,7 +536,7 @@ pub const StackIterator = struct { pub fn writeCurrentStackTrace( out_stream: anytype, debug_info: *DebugInfo, - tty_config: TTY.Config, + tty_config: io.tty.Config, start_addr: ?usize, ) !void { if (native_os == .windows) { @@ -634,7 +608,7 @@ pub noinline fn walkStackWindows(addresses: []usize) usize { pub fn writeCurrentStackTraceWindows( out_stream: anytype, debug_info: *DebugInfo, - tty_config: TTY.Config, + tty_config: io.tty.Config, start_addr: ?usize, ) !void { var addr_buf: [1024]usize = undefined; @@ -651,95 +625,6 @@ pub fn writeCurrentStackTraceWindows( } } -/// Provides simple functionality for manipulating the terminal in some way, -/// for debugging purposes, such as coloring text, etc. -pub const TTY = struct { - pub const Color = enum { - Red, - Green, - Yellow, - Cyan, - White, - Dim, - Bold, - Reset, - }; - - pub const Config = union(enum) { - no_color, - escape_codes, - windows_api: if (native_os == .windows) WindowsContext else void, - - pub const WindowsContext = struct { - handle: File.Handle, - reset_attributes: u16, - }; - - pub fn setColor(conf: Config, out_stream: anytype, color: Color) !void { - nosuspend switch (conf) { - .no_color => return, - .escape_codes => { - const color_string = switch (color) { - .Red => "\x1b[31;1m", - .Green => "\x1b[32;1m", - .Yellow => "\x1b[33;1m", - .Cyan => "\x1b[36;1m", - .White => "\x1b[37;1m", - .Bold => "\x1b[1m", - .Dim => "\x1b[2m", - .Reset => "\x1b[0m", - }; - try out_stream.writeAll(color_string); - }, - .windows_api => |ctx| if (native_os == .windows) { - const attributes = switch (color) { - .Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, - .Green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, - .Yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, - .Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .White, .Bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, - .Dim => windows.FOREGROUND_INTENSITY, - .Reset => ctx.reset_attributes, - }; - try windows.SetConsoleTextAttribute(ctx.handle, attributes); - } else { - unreachable; - }, - }; - } - - pub fn writeDEC(conf: Config, writer: anytype, codepoint: u8) !void { - const bytes = switch (conf) { - .no_color, .windows_api => switch (codepoint) { - 0x50...0x5e => @as(*const [1]u8, &codepoint), - 0x6a => "+", // ┘ - 0x6b => "+", // ┐ - 0x6c => "+", // ┌ - 0x6d => "+", // └ - 0x6e => "+", // ┼ - 0x71 => "-", // ─ - 0x74 => "+", // ├ - 0x75 => "+", // ┤ - 0x76 => "+", // ┴ - 0x77 => "+", // ┬ - 0x78 => "|", // │ - else => " ", // TODO - }, - .escape_codes => switch (codepoint) { - // Here we avoid writing the DEC beginning sequence and - // ending sequence in separate syscalls by putting the - // beginning and ending sequence into the same string - // literals, to prevent terminals ending up in bad states - // in case a crash happens between syscalls. - inline 0x50...0x7f => |x| "\x1B\x28\x30" ++ [1]u8{x} ++ "\x1B\x28\x42", - else => unreachable, - }, - }; - return writer.writeAll(bytes); - } - }; -}; - fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol { var min: usize = 0; var max: usize = symbols.len - 1; @@ -785,7 +670,7 @@ test "machoSearchSymbols" { try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?); } -fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: TTY.Config) !void { +fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { const module_name = debug_info.getModuleNameForAddress(address); return printLineInfo( out_stream, @@ -798,7 +683,7 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz ); } -pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: TTY.Config) !void { +pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void { const module = debug_info.getModuleForAddress(address) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config), else => return err, @@ -827,11 +712,11 @@ fn printLineInfo( address: usize, symbol_name: []const u8, compile_unit_name: []const u8, - tty_config: TTY.Config, + tty_config: io.tty.Config, comptime printLineFromFile: anytype, ) !void { nosuspend { - try tty_config.setColor(out_stream, .Bold); + try tty_config.setColor(out_stream, .bold); if (line_info) |*li| { try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column }); @@ -839,11 +724,11 @@ fn printLineInfo( try out_stream.writeAll("???:?:?"); } - try tty_config.setColor(out_stream, .Reset); + try tty_config.setColor(out_stream, .reset); try out_stream.writeAll(": "); - try tty_config.setColor(out_stream, .Dim); + try tty_config.setColor(out_stream, .dim); try out_stream.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); - try tty_config.setColor(out_stream, .Reset); + try tty_config.setColor(out_stream, .reset); try out_stream.writeAll("\n"); // Show the matching source code line if possible @@ -854,9 +739,9 @@ fn printLineInfo( const space_needed = @intCast(usize, li.column - 1); try out_stream.writeByteNTimes(' ', space_needed); - try tty_config.setColor(out_stream, .Green); + try tty_config.setColor(out_stream, .green); try out_stream.writeAll("^"); - try tty_config.setColor(out_stream, .Reset); + try tty_config.setColor(out_stream, .reset); } try out_stream.writeAll("\n"); } else |err| switch (err) { @@ -2193,7 +2078,7 @@ test "manage resources correctly" { const writer = std.io.null_writer; var di = try openSelfDebugInfo(testing.allocator); defer di.deinit(); - try printSourceAtAddress(&di, writer, showMyTrace(), detectTTYConfig(std.io.getStdErr())); + try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr())); } noinline fn showMyTrace() usize { @@ -2253,7 +2138,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize pub fn dump(t: @This()) void { if (!enabled) return; - const tty_config = detectTTYConfig(std.io.getStdErr()); + const tty_config = io.tty.detectConfig(std.io.getStdErr()); const stderr = io.getStdErr().writer(); const end = @min(t.index, size); const debug_info = getSelfDebugInfo() catch |err| { diff --git a/lib/std/io.zig b/lib/std/io.zig index d95997f85388..f6d893c7dd1d 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -155,6 +155,8 @@ pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAt pub const StreamSource = @import("io/stream_source.zig").StreamSource; +pub const tty = @import("io/tty.zig"); + /// A Writer that doesn't write to anything. pub const null_writer = @as(NullWriter, .{ .context = {} }); diff --git a/lib/std/io/tty.zig b/lib/std/io/tty.zig new file mode 100644 index 000000000000..04a84dc375c8 --- /dev/null +++ b/lib/std/io/tty.zig @@ -0,0 +1,126 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const File = std.fs.File; +const process = std.process; +const windows = std.os.windows; +const native_os = builtin.os.tag; + +/// Detect suitable TTY configuration options for the given file (commonly stdout/stderr). +/// This includes feature checks for ANSI escape codes and the Windows console API, as well as +/// respecting the `NO_COLOR` environment variable. +pub fn detectConfig(file: File) Config { + if (builtin.os.tag == .wasi) { + // Per https://github.com/WebAssembly/WASI/issues/162 ANSI codes + // aren't currently supported. + return .no_color; + } else if (process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) { + return .escape_codes; + } else if (process.hasEnvVarConstant("NO_COLOR")) { + return .no_color; + } else if (file.supportsAnsiEscapeCodes()) { + return .escape_codes; + } else if (native_os == .windows and file.isTty()) { + var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; + if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) { + // TODO: Should this return an error instead? + return .no_color; + } + return .{ .windows_api = .{ + .handle = file.handle, + .reset_attributes = info.wAttributes, + } }; + } + return .no_color; +} + +pub const Color = enum { + black, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white, + dim, + bold, + reset, +}; + +/// Provides simple functionality for manipulating the terminal in some way, +/// such as coloring text, etc. +pub const Config = union(enum) { + no_color, + escape_codes, + windows_api: if (native_os == .windows) WindowsContext else void, + + pub const WindowsContext = struct { + handle: File.Handle, + reset_attributes: u16, + }; + + pub fn setColor(conf: Config, out_stream: anytype, color: Color) !void { + nosuspend switch (conf) { + .no_color => return, + .escape_codes => { + const color_string = switch (color) { + .black => "\x1b[30m", + .red => "\x1b[31m", + .green => "\x1b[32m", + .yellow => "\x1b[33m", + .blue => "\x1b[34m", + .magenta => "\x1b[35m", + .cyan => "\x1b[36m", + .white => "\x1b[37m", + .bright_black => "\x1b[90m", + .bright_red => "\x1b[91m", + .bright_green => "\x1b[92m", + .bright_yellow => "\x1b[93m", + .bright_blue => "\x1b[94m", + .bright_magenta => "\x1b[95m", + .bright_cyan => "\x1b[96m", + .bright_white => "\x1b[97m", + .bold => "\x1b[1m", + .dim => "\x1b[2m", + .reset => "\x1b[0m", + }; + try out_stream.writeAll(color_string); + }, + .windows_api => |ctx| if (native_os == .windows) { + const attributes = switch (color) { + .black => 0, + .red => windows.FOREGROUND_RED, + .green => windows.FOREGROUND_GREEN, + .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, + .blue => windows.FOREGROUND_BLUE, + .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, + .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, + .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, + .bright_black => windows.FOREGROUND_INTENSITY, + .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, + .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, + .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, + .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, + // "dim" is not supported using basic character attributes, but let's still make it do *something*. + // This matches the old behavior of TTY.Color before the bright variants were added. + .dim => windows.FOREGROUND_INTENSITY, + .reset => ctx.reset_attributes, + }; + try windows.SetConsoleTextAttribute(ctx.handle, attributes); + } else { + unreachable; + }, + }; + } +}; diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 2857ebdbd31b..7986c50eaf48 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -279,7 +279,7 @@ test "expectApproxEqRel" { /// This function is intended to be used only in tests. When the two slices are not /// equal, prints diagnostics to stderr to show exactly how they are not equal (with /// the differences highlighted in red), then returns a test failure error. -/// The colorized output is optional and controlled by the return of `std.debug.detectTTYConfig()`. +/// The colorized output is optional and controlled by the return of `std.io.tty.detectConfig()`. /// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead. pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void { if (expected.ptr == actual.ptr and expected.len == actual.len) { @@ -312,7 +312,7 @@ pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)]; const actual_truncated = window_start + actual_window.len < actual.len; - const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()); + const ttyconf = std.io.tty.detectConfig(std.io.getStdErr()); var differ = if (T == u8) BytesDiffer{ .expected = expected_window, .actual = actual_window, @@ -379,7 +379,7 @@ fn SliceDiffer(comptime T: type) type { start_index: usize, expected: []const T, actual: []const T, - ttyconf: std.debug.TTY.Config, + ttyconf: std.io.tty.Config, const Self = @This(); @@ -387,9 +387,9 @@ fn SliceDiffer(comptime T: type) type { for (self.expected, 0..) |value, i| { var full_index = self.start_index + i; const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true; - if (diff) try self.ttyconf.setColor(writer, .Red); + if (diff) try self.ttyconf.setColor(writer, .red); try writer.print("[{}]: {any}\n", .{ full_index, value }); - if (diff) try self.ttyconf.setColor(writer, .Reset); + if (diff) try self.ttyconf.setColor(writer, .reset); } } }; @@ -398,7 +398,7 @@ fn SliceDiffer(comptime T: type) type { const BytesDiffer = struct { expected: []const u8, actual: []const u8, - ttyconf: std.debug.TTY.Config, + ttyconf: std.io.tty.Config, pub fn write(self: BytesDiffer, writer: anytype) !void { var expected_iterator = ChunkIterator{ .bytes = self.expected }; @@ -427,9 +427,9 @@ const BytesDiffer = struct { } fn writeByteDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, byte: u8, diff: bool) !void { - if (diff) try self.ttyconf.setColor(writer, .Red); + if (diff) try self.ttyconf.setColor(writer, .red); try writer.print(fmt, .{byte}); - if (diff) try self.ttyconf.setColor(writer, .Reset); + if (diff) try self.ttyconf.setColor(writer, .reset); } const ChunkIterator = struct { diff --git a/lib/std/zig/ErrorBundle.zig b/lib/std/zig/ErrorBundle.zig index ffe748203e82..46b57998076b 100644 --- a/lib/std/zig/ErrorBundle.zig +++ b/lib/std/zig/ErrorBundle.zig @@ -148,7 +148,7 @@ pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 { } pub const RenderOptions = struct { - ttyconf: std.debug.TTY.Config, + ttyconf: std.io.tty.Config, include_reference_trace: bool = true, include_source_line: bool = true, include_log_text: bool = true, @@ -163,7 +163,7 @@ pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void { pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void { for (eb.getMessages()) |err_msg| { - try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0); + try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .red, 0); } if (options.include_log_text) { @@ -181,7 +181,7 @@ fn renderErrorMessageToWriter( err_msg_index: MessageIndex, stderr: anytype, kind: []const u8, - color: std.debug.TTY.Color, + color: std.io.tty.Color, indent: usize, ) anyerror!void { const ttyconf = options.ttyconf; @@ -191,7 +191,7 @@ fn renderErrorMessageToWriter( if (err_msg.src_loc != .none) { const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc)); try counting_stderr.writeByteNTimes(' ', indent); - try ttyconf.setColor(stderr, .Bold); + try ttyconf.setColor(stderr, .bold); try counting_stderr.print("{s}:{d}:{d}: ", .{ eb.nullTerminatedString(src.data.src_path), src.data.line + 1, @@ -203,17 +203,17 @@ fn renderErrorMessageToWriter( // This is the length of the part before the error message: // e.g. "file.zig:4:5: error: " const prefix_len = @intCast(usize, counting_stderr.context.bytes_written); - try ttyconf.setColor(stderr, .Reset); - try ttyconf.setColor(stderr, .Bold); + try ttyconf.setColor(stderr, .reset); + try ttyconf.setColor(stderr, .bold); if (err_msg.count == 1) { try writeMsg(eb, err_msg, stderr, prefix_len); try stderr.writeByte('\n'); } else { try writeMsg(eb, err_msg, stderr, prefix_len); - try ttyconf.setColor(stderr, .Dim); + try ttyconf.setColor(stderr, .dim); try stderr.print(" ({d} times)\n", .{err_msg.count}); } - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); if (src.data.source_line != 0 and options.include_source_line) { const line = eb.nullTerminatedString(src.data.source_line); for (line) |b| switch (b) { @@ -226,19 +226,19 @@ fn renderErrorMessageToWriter( // -1 since span.main includes the caret const after_caret = src.data.span_end - src.data.span_main -| 1; try stderr.writeByteNTimes(' ', src.data.column - before_caret); - try ttyconf.setColor(stderr, .Green); + try ttyconf.setColor(stderr, .green); try stderr.writeByteNTimes('~', before_caret); try stderr.writeByte('^'); try stderr.writeByteNTimes('~', after_caret); try stderr.writeByte('\n'); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent); + try renderErrorMessageToWriter(eb, options, note, stderr, "note", .cyan, indent); } if (src.data.reference_trace_len > 0 and options.include_reference_trace) { - try ttyconf.setColor(stderr, .Reset); - try ttyconf.setColor(stderr, .Dim); + try ttyconf.setColor(stderr, .reset); + try ttyconf.setColor(stderr, .dim); try stderr.print("referenced by:\n", .{}); var ref_index = src.end; for (0..src.data.reference_trace_len) |_| { @@ -266,25 +266,25 @@ fn renderErrorMessageToWriter( } } try stderr.writeByte('\n'); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); } } else { try ttyconf.setColor(stderr, color); try stderr.writeByteNTimes(' ', indent); try stderr.writeAll(kind); try stderr.writeAll(": "); - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); const msg = eb.nullTerminatedString(err_msg.msg); if (err_msg.count == 1) { try stderr.print("{s}\n", .{msg}); } else { try stderr.print("{s}", .{msg}); - try ttyconf.setColor(stderr, .Dim); + try ttyconf.setColor(stderr, .dim); try stderr.print(" ({d} times)\n", .{err_msg.count}); } - try ttyconf.setColor(stderr, .Reset); + try ttyconf.setColor(stderr, .reset); for (eb.getNotes(err_msg_index)) |note| { - try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4); + try renderErrorMessageToWriter(eb, options, note, stderr, "note", .cyan, indent + 4); } } } diff --git a/src/main.zig b/src/main.zig index 650741e5e4c7..afda88cebde9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6044,9 +6044,9 @@ const ClangSearchSanitizer = struct { }; }; -fn get_tty_conf(color: Color) std.debug.TTY.Config { +fn get_tty_conf(color: Color) std.io.tty.Config { return switch (color) { - .auto => std.debug.detectTTYConfig(std.io.getStdErr()), + .auto => std.io.tty.detectConfig(std.io.getStdErr()), .on => .escape_codes, .off => .no_color, }; diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 63dd2fd3da48..08568d0dd699 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -1354,7 +1354,7 @@ fn runOneCase( defer all_errors.deinit(allocator); if (all_errors.errorMessageCount() > 0) { all_errors.renderToStdErr(.{ - .ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()), + .ttyconf = std.io.tty.detectConfig(std.io.getStdErr()), }); // TODO print generated C code return error.UnexpectedCompileErrors;