authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-05-24 16:03:44+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-05-24 16:03:44+03:00
log7cb2e653a20698b4b8050705bcb72afeea9df63b
treee8d6161a0258448cd6fcb6bad9f81fc31ad56c8a
parentb2a514b3d2d2d880a41a26e798fb3c9ee014c229
parent93d9c9bf319e78045c70d1e6260b15e3319c82b9
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #15806 from linusg/std-io-tty

std: Move TTY from std.debug to std.io and add missing colors

11 files changed, 221 insertions(+), 208 deletions(-)

lib/build_runner.zig+36-36
......@@ -333,7 +333,7 @@ const Run = struct {
333333
334334 claimed_rss: usize,
335335 enable_summary: ?bool,
336 ttyconf: std.debug.TTY.Config,
336 ttyconf: std.io.tty.Config,
337337 stderr: std.fs.File,
338338};
339339
......@@ -476,9 +476,9 @@ fn runStepNames(
476476
477477 if (run.enable_summary != false) {
478478 const total_count = success_count + failure_count + pending_count + skipped_count;
479 ttyconf.setColor(stderr, .Cyan) catch {};
479 ttyconf.setColor(stderr, .cyan) catch {};
480480 stderr.writeAll("Build Summary:") catch {};
481 ttyconf.setColor(stderr, .Reset) catch {};
481 ttyconf.setColor(stderr, .reset) catch {};
482482 stderr.writer().print(" {d}/{d} steps succeeded", .{ success_count, total_count }) catch {};
483483 if (skipped_count > 0) stderr.writer().print("; {d} skipped", .{skipped_count}) catch {};
484484 if (failure_count > 0) stderr.writer().print("; {d} failed", .{failure_count}) catch {};
......@@ -489,9 +489,9 @@ fn runStepNames(
489489 if (test_leak_count > 0) stderr.writer().print("; {d} leaked", .{test_leak_count}) catch {};
490490
491491 if (run.enable_summary == null) {
492 ttyconf.setColor(stderr, .Dim) catch {};
492 ttyconf.setColor(stderr, .dim) catch {};
493493 stderr.writeAll(" (disable with -fno-summary)") catch {};
494 ttyconf.setColor(stderr, .Reset) catch {};
494 ttyconf.setColor(stderr, .reset) catch {};
495495 }
496496 stderr.writeAll("\n") catch {};
497497
......@@ -535,7 +535,7 @@ const PrintNode = struct {
535535 last: bool = false,
536536};
537537
538fn printPrefix(node: *PrintNode, stderr: std.fs.File, ttyconf: std.debug.TTY.Config) !void {
538fn printPrefix(node: *PrintNode, stderr: std.fs.File, ttyconf: std.io.tty.Config) !void {
539539 const parent = node.parent orelse return;
540540 if (parent.parent == null) return;
541541 try printPrefix(parent, stderr, ttyconf);
......@@ -553,14 +553,14 @@ fn printTreeStep(
553553 b: *std.Build,
554554 s: *Step,
555555 stderr: std.fs.File,
556 ttyconf: std.debug.TTY.Config,
556 ttyconf: std.io.tty.Config,
557557 parent_node: *PrintNode,
558558 step_stack: *std.AutoArrayHashMapUnmanaged(*Step, void),
559559) !void {
560560 const first = step_stack.swapRemove(s);
561561 try printPrefix(parent_node, stderr, ttyconf);
562562
563 if (!first) try ttyconf.setColor(stderr, .Dim);
563 if (!first) try ttyconf.setColor(stderr, .dim);
564564 if (parent_node.parent != null) {
565565 if (parent_node.last) {
566566 try stderr.writeAll(switch (ttyconf) {
......@@ -586,28 +586,28 @@ fn printTreeStep(
586586 .running => unreachable,
587587
588588 .dependency_failure => {
589 try ttyconf.setColor(stderr, .Dim);
589 try ttyconf.setColor(stderr, .dim);
590590 try stderr.writeAll(" transitive failure\n");
591 try ttyconf.setColor(stderr, .Reset);
591 try ttyconf.setColor(stderr, .reset);
592592 },
593593
594594 .success => {
595 try ttyconf.setColor(stderr, .Green);
595 try ttyconf.setColor(stderr, .green);
596596 if (s.result_cached) {
597597 try stderr.writeAll(" cached");
598598 } else if (s.test_results.test_count > 0) {
599599 const pass_count = s.test_results.passCount();
600600 try stderr.writer().print(" {d} passed", .{pass_count});
601601 if (s.test_results.skip_count > 0) {
602 try ttyconf.setColor(stderr, .Yellow);
602 try ttyconf.setColor(stderr, .yellow);
603603 try stderr.writer().print(" {d} skipped", .{s.test_results.skip_count});
604604 }
605605 } else {
606606 try stderr.writeAll(" success");
607607 }
608 try ttyconf.setColor(stderr, .Reset);
608 try ttyconf.setColor(stderr, .reset);
609609 if (s.result_duration_ns) |ns| {
610 try ttyconf.setColor(stderr, .Dim);
610 try ttyconf.setColor(stderr, .dim);
611611 if (ns >= std.time.ns_per_min) {
612612 try stderr.writer().print(" {d}m", .{ns / std.time.ns_per_min});
613613 } else if (ns >= std.time.ns_per_s) {
......@@ -619,11 +619,11 @@ fn printTreeStep(
619619 } else {
620620 try stderr.writer().print(" {d}ns", .{ns});
621621 }
622 try ttyconf.setColor(stderr, .Reset);
622 try ttyconf.setColor(stderr, .reset);
623623 }
624624 if (s.result_peak_rss != 0) {
625625 const rss = s.result_peak_rss;
626 try ttyconf.setColor(stderr, .Dim);
626 try ttyconf.setColor(stderr, .dim);
627627 if (rss >= 1000_000_000) {
628628 try stderr.writer().print(" MaxRSS:{d}G", .{rss / 1000_000_000});
629629 } else if (rss >= 1000_000) {
......@@ -633,57 +633,57 @@ fn printTreeStep(
633633 } else {
634634 try stderr.writer().print(" MaxRSS:{d}B", .{rss});
635635 }
636 try ttyconf.setColor(stderr, .Reset);
636 try ttyconf.setColor(stderr, .reset);
637637 }
638638 try stderr.writeAll("\n");
639639 },
640640
641641 .skipped => {
642 try ttyconf.setColor(stderr, .Yellow);
642 try ttyconf.setColor(stderr, .yellow);
643643 try stderr.writeAll(" skipped\n");
644 try ttyconf.setColor(stderr, .Reset);
644 try ttyconf.setColor(stderr, .reset);
645645 },
646646
647647 .failure => {
648648 if (s.result_error_bundle.errorMessageCount() > 0) {
649 try ttyconf.setColor(stderr, .Red);
649 try ttyconf.setColor(stderr, .red);
650650 try stderr.writer().print(" {d} errors\n", .{
651651 s.result_error_bundle.errorMessageCount(),
652652 });
653 try ttyconf.setColor(stderr, .Reset);
653 try ttyconf.setColor(stderr, .reset);
654654 } else if (!s.test_results.isSuccess()) {
655655 try stderr.writer().print(" {d}/{d} passed", .{
656656 s.test_results.passCount(), s.test_results.test_count,
657657 });
658658 if (s.test_results.fail_count > 0) {
659659 try stderr.writeAll(", ");
660 try ttyconf.setColor(stderr, .Red);
660 try ttyconf.setColor(stderr, .red);
661661 try stderr.writer().print("{d} failed", .{
662662 s.test_results.fail_count,
663663 });
664 try ttyconf.setColor(stderr, .Reset);
664 try ttyconf.setColor(stderr, .reset);
665665 }
666666 if (s.test_results.skip_count > 0) {
667667 try stderr.writeAll(", ");
668 try ttyconf.setColor(stderr, .Yellow);
668 try ttyconf.setColor(stderr, .yellow);
669669 try stderr.writer().print("{d} skipped", .{
670670 s.test_results.skip_count,
671671 });
672 try ttyconf.setColor(stderr, .Reset);
672 try ttyconf.setColor(stderr, .reset);
673673 }
674674 if (s.test_results.leak_count > 0) {
675675 try stderr.writeAll(", ");
676 try ttyconf.setColor(stderr, .Red);
676 try ttyconf.setColor(stderr, .red);
677677 try stderr.writer().print("{d} leaked", .{
678678 s.test_results.leak_count,
679679 });
680 try ttyconf.setColor(stderr, .Reset);
680 try ttyconf.setColor(stderr, .reset);
681681 }
682682 try stderr.writeAll("\n");
683683 } else {
684 try ttyconf.setColor(stderr, .Red);
684 try ttyconf.setColor(stderr, .red);
685685 try stderr.writeAll(" failure\n");
686 try ttyconf.setColor(stderr, .Reset);
686 try ttyconf.setColor(stderr, .reset);
687687 }
688688 },
689689 }
......@@ -703,7 +703,7 @@ fn printTreeStep(
703703 s.dependencies.items.len,
704704 });
705705 }
706 try ttyconf.setColor(stderr, .Reset);
706 try ttyconf.setColor(stderr, .reset);
707707 }
708708}
709709
......@@ -819,13 +819,13 @@ fn workerMakeOneStep(
819819 for (s.result_error_msgs.items) |msg| {
820820 // Sometimes it feels like you just can't catch a break. Finally,
821821 // with Zig, you can.
822 ttyconf.setColor(stderr, .Bold) catch break;
822 ttyconf.setColor(stderr, .bold) catch break;
823823 stderr.writeAll(s.owner.dep_prefix) catch break;
824824 stderr.writeAll(s.name) catch break;
825825 stderr.writeAll(": ") catch break;
826 ttyconf.setColor(stderr, .Red) catch break;
826 ttyconf.setColor(stderr, .red) catch break;
827827 stderr.writeAll("error: ") catch break;
828 ttyconf.setColor(stderr, .Reset) catch break;
828 ttyconf.setColor(stderr, .reset) catch break;
829829 stderr.writeAll(msg) catch break;
830830 stderr.writeAll("\n") catch break;
831831 }
......@@ -1026,15 +1026,15 @@ fn cleanExit() void {
10261026
10271027const Color = enum { auto, off, on };
10281028
1029fn get_tty_conf(color: Color, stderr: std.fs.File) std.debug.TTY.Config {
1029fn get_tty_conf(color: Color, stderr: std.fs.File) std.io.tty.Config {
10301030 return switch (color) {
1031 .auto => std.debug.detectTTYConfig(stderr),
1031 .auto => std.io.tty.detectConfig(stderr),
10321032 .on => .escape_codes,
10331033 .off => .no_color,
10341034 };
10351035}
10361036
1037fn renderOptions(ttyconf: std.debug.TTY.Config) std.zig.ErrorBundle.RenderOptions {
1037fn renderOptions(ttyconf: std.io.tty.Config) std.zig.ErrorBundle.RenderOptions {
10381038 return .{
10391039 .ttyconf = ttyconf,
10401040 .include_source_line = ttyconf != .no_color,
lib/std/Build.zig+7-7
......@@ -1712,10 +1712,10 @@ fn dumpBadGetPathHelp(
17121712 s.name,
17131713 });
17141714
1715 const tty_config = std.debug.detectTTYConfig(stderr);
1716 tty_config.setColor(w, .Red) catch {};
1715 const tty_config = std.io.tty.detectConfig(stderr);
1716 tty_config.setColor(w, .red) catch {};
17171717 try stderr.writeAll(" The step was created by this stack trace:\n");
1718 tty_config.setColor(w, .Reset) catch {};
1718 tty_config.setColor(w, .reset) catch {};
17191719
17201720 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
17211721 try w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
......@@ -1727,9 +1727,9 @@ fn dumpBadGetPathHelp(
17271727 return;
17281728 };
17291729 if (asking_step) |as| {
1730 tty_config.setColor(w, .Red) catch {};
1730 tty_config.setColor(w, .red) catch {};
17311731 try stderr.writeAll(" The step that is missing a dependency on the above step was created by this stack trace:\n");
1732 tty_config.setColor(w, .Reset) catch {};
1732 tty_config.setColor(w, .reset) catch {};
17331733
17341734 std.debug.writeStackTrace(as.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
17351735 try stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)});
......@@ -1737,9 +1737,9 @@ fn dumpBadGetPathHelp(
17371737 };
17381738 }
17391739
1740 tty_config.setColor(w, .Red) catch {};
1740 tty_config.setColor(w, .red) catch {};
17411741 try stderr.writeAll(" Hope that helps. Proceeding to panic.\n");
1742 tty_config.setColor(w, .Reset) catch {};
1742 tty_config.setColor(w, .reset) catch {};
17431743}
17441744
17451745/// Allocates a new string for assigning a value to a named macro.
lib/std/Build/Step.zig+1-1
......@@ -237,7 +237,7 @@ pub fn dump(step: *Step) void {
237237
238238 const stderr = std.io.getStdErr();
239239 const w = stderr.writer();
240 const tty_config = std.debug.detectTTYConfig(stderr);
240 const tty_config = std.io.tty.detectConfig(stderr);
241241 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
242242 w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
243243 @errorName(err),
lib/std/builtin.zig+1-1
......@@ -51,7 +51,7 @@ pub const StackTrace = struct {
5151 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
5252 return writer.print("\nUnable to print stack trace: Unable to open debug info: {s}\n", .{@errorName(err)});
5353 };
54 const tty_config = std.debug.detectTTYConfig(std.io.getStdErr());
54 const tty_config = std.io.tty.detectConfig(std.io.getStdErr());
5555 try writer.writeAll("\n");
5656 std.debug.writeStackTrace(self, writer, arena.allocator(), debug_info, tty_config) catch |err| {
5757 try writer.print("Unable to print stack trace: {s}\n", .{@errorName(err)});
lib/std/debug.zig+19-134
......@@ -5,7 +5,6 @@ const mem = std.mem;
55const io = std.io;
66const os = std.os;
77const fs = std.fs;
8const process = std.process;
98const testing = std.testing;
109const elf = std.elf;
1110const DW = std.dwarf;
......@@ -109,31 +108,6 @@ pub fn getSelfDebugInfo() !*DebugInfo {
109108 }
110109}
111110
112pub fn detectTTYConfig(file: std.fs.File) TTY.Config {
113 if (builtin.os.tag == .wasi) {
114 // Per https://github.com/WebAssembly/WASI/issues/162 ANSI codes
115 // aren't currently supported.
116 return .no_color;
117 } else if (process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
118 return .escape_codes;
119 } else if (process.hasEnvVarConstant("NO_COLOR")) {
120 return .no_color;
121 } else if (file.supportsAnsiEscapeCodes()) {
122 return .escape_codes;
123 } else if (native_os == .windows and file.isTty()) {
124 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
125 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
126 // TODO: Should this return an error instead?
127 return .no_color;
128 }
129 return .{ .windows_api = .{
130 .handle = file.handle,
131 .reset_attributes = info.wAttributes,
132 } };
133 }
134 return .no_color;
135}
136
137111/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned.
138112/// TODO multithreaded awareness
139113pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
......@@ -154,7 +128,7 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
154128 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
155129 return;
156130 };
157 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {
131 writeCurrentStackTrace(stderr, debug_info, io.tty.detectConfig(io.getStdErr()), start_addr) catch |err| {
158132 stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
159133 return;
160134 };
......@@ -182,7 +156,7 @@ pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
182156 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
183157 return;
184158 };
185 const tty_config = detectTTYConfig(io.getStdErr());
159 const tty_config = io.tty.detectConfig(io.getStdErr());
186160 if (native_os == .windows) {
187161 writeCurrentStackTraceWindows(stderr, debug_info, tty_config, ip) catch return;
188162 return;
......@@ -265,7 +239,7 @@ pub fn dumpStackTrace(stack_trace: std.builtin.StackTrace) void {
265239 stderr.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{@errorName(err)}) catch return;
266240 return;
267241 };
268 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig(io.getStdErr())) catch |err| {
242 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, io.tty.detectConfig(io.getStdErr())) catch |err| {
269243 stderr.print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch return;
270244 return;
271245 };
......@@ -403,7 +377,7 @@ pub fn writeStackTrace(
403377 out_stream: anytype,
404378 allocator: mem.Allocator,
405379 debug_info: *DebugInfo,
406 tty_config: TTY.Config,
380 tty_config: io.tty.Config,
407381) !void {
408382 _ = allocator;
409383 if (builtin.strip_debug_info) return error.MissingDebugInfo;
......@@ -421,9 +395,9 @@ pub fn writeStackTrace(
421395 if (stack_trace.index > stack_trace.instruction_addresses.len) {
422396 const dropped_frames = stack_trace.index - stack_trace.instruction_addresses.len;
423397
424 tty_config.setColor(out_stream, .Bold) catch {};
398 tty_config.setColor(out_stream, .bold) catch {};
425399 try out_stream.print("({d} additional stack frames skipped...)\n", .{dropped_frames});
426 tty_config.setColor(out_stream, .Reset) catch {};
400 tty_config.setColor(out_stream, .reset) catch {};
427401 }
428402}
429403
......@@ -562,7 +536,7 @@ pub const StackIterator = struct {
562536pub fn writeCurrentStackTrace(
563537 out_stream: anytype,
564538 debug_info: *DebugInfo,
565 tty_config: TTY.Config,
539 tty_config: io.tty.Config,
566540 start_addr: ?usize,
567541) !void {
568542 if (native_os == .windows) {
......@@ -634,7 +608,7 @@ pub noinline fn walkStackWindows(addresses: []usize) usize {
634608pub fn writeCurrentStackTraceWindows(
635609 out_stream: anytype,
636610 debug_info: *DebugInfo,
637 tty_config: TTY.Config,
611 tty_config: io.tty.Config,
638612 start_addr: ?usize,
639613) !void {
640614 var addr_buf: [1024]usize = undefined;
......@@ -651,95 +625,6 @@ pub fn writeCurrentStackTraceWindows(
651625 }
652626}
653627
654/// Provides simple functionality for manipulating the terminal in some way,
655/// for debugging purposes, such as coloring text, etc.
656pub const TTY = struct {
657 pub const Color = enum {
658 Red,
659 Green,
660 Yellow,
661 Cyan,
662 White,
663 Dim,
664 Bold,
665 Reset,
666 };
667
668 pub const Config = union(enum) {
669 no_color,
670 escape_codes,
671 windows_api: if (native_os == .windows) WindowsContext else void,
672
673 pub const WindowsContext = struct {
674 handle: File.Handle,
675 reset_attributes: u16,
676 };
677
678 pub fn setColor(conf: Config, out_stream: anytype, color: Color) !void {
679 nosuspend switch (conf) {
680 .no_color => return,
681 .escape_codes => {
682 const color_string = switch (color) {
683 .Red => "\x1b[31;1m",
684 .Green => "\x1b[32;1m",
685 .Yellow => "\x1b[33;1m",
686 .Cyan => "\x1b[36;1m",
687 .White => "\x1b[37;1m",
688 .Bold => "\x1b[1m",
689 .Dim => "\x1b[2m",
690 .Reset => "\x1b[0m",
691 };
692 try out_stream.writeAll(color_string);
693 },
694 .windows_api => |ctx| if (native_os == .windows) {
695 const attributes = switch (color) {
696 .Red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
697 .Green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
698 .Yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
699 .Cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
700 .White, .Bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
701 .Dim => windows.FOREGROUND_INTENSITY,
702 .Reset => ctx.reset_attributes,
703 };
704 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
705 } else {
706 unreachable;
707 },
708 };
709 }
710
711 pub fn writeDEC(conf: Config, writer: anytype, codepoint: u8) !void {
712 const bytes = switch (conf) {
713 .no_color, .windows_api => switch (codepoint) {
714 0x50...0x5e => @as(*const [1]u8, &codepoint),
715 0x6a => "+", // ┘
716 0x6b => "+", // ┐
717 0x6c => "+", // ┌
718 0x6d => "+", // └
719 0x6e => "+", // ┼
720 0x71 => "-", // ─
721 0x74 => "+", // ├
722 0x75 => "+", // ┤
723 0x76 => "+", // ┴
724 0x77 => "+", // ┬
725 0x78 => "|", // │
726 else => " ", // TODO
727 },
728 .escape_codes => switch (codepoint) {
729 // Here we avoid writing the DEC beginning sequence and
730 // ending sequence in separate syscalls by putting the
731 // beginning and ending sequence into the same string
732 // literals, to prevent terminals ending up in bad states
733 // in case a crash happens between syscalls.
734 inline 0x50...0x7f => |x| "\x1B\x28\x30" ++ [1]u8{x} ++ "\x1B\x28\x42",
735 else => unreachable,
736 },
737 };
738 return writer.writeAll(bytes);
739 }
740 };
741};
742
743628fn machoSearchSymbols(symbols: []const MachoSymbol, address: usize) ?*const MachoSymbol {
744629 var min: usize = 0;
745630 var max: usize = symbols.len - 1;
......@@ -785,7 +670,7 @@ test "machoSearchSymbols" {
785670 try testing.expectEqual(&symbols[2], machoSearchSymbols(&symbols, 5000).?);
786671}
787672
788fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: TTY.Config) !void {
673fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
789674 const module_name = debug_info.getModuleNameForAddress(address);
790675 return printLineInfo(
791676 out_stream,
......@@ -798,7 +683,7 @@ fn printUnknownSource(debug_info: *DebugInfo, out_stream: anytype, address: usiz
798683 );
799684}
800685
801pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: TTY.Config) !void {
686pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: anytype, address: usize, tty_config: io.tty.Config) !void {
802687 const module = debug_info.getModuleForAddress(address) catch |err| switch (err) {
803688 error.MissingDebugInfo, error.InvalidDebugInfo => return printUnknownSource(debug_info, out_stream, address, tty_config),
804689 else => return err,
......@@ -827,11 +712,11 @@ fn printLineInfo(
827712 address: usize,
828713 symbol_name: []const u8,
829714 compile_unit_name: []const u8,
830 tty_config: TTY.Config,
715 tty_config: io.tty.Config,
831716 comptime printLineFromFile: anytype,
832717) !void {
833718 nosuspend {
834 try tty_config.setColor(out_stream, .Bold);
719 try tty_config.setColor(out_stream, .bold);
835720
836721 if (line_info) |*li| {
837722 try out_stream.print("{s}:{d}:{d}", .{ li.file_name, li.line, li.column });
......@@ -839,11 +724,11 @@ fn printLineInfo(
839724 try out_stream.writeAll("???:?:?");
840725 }
841726
842 try tty_config.setColor(out_stream, .Reset);
727 try tty_config.setColor(out_stream, .reset);
843728 try out_stream.writeAll(": ");
844 try tty_config.setColor(out_stream, .Dim);
729 try tty_config.setColor(out_stream, .dim);
845730 try out_stream.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
846 try tty_config.setColor(out_stream, .Reset);
731 try tty_config.setColor(out_stream, .reset);
847732 try out_stream.writeAll("\n");
848733
849734 // Show the matching source code line if possible
......@@ -854,9 +739,9 @@ fn printLineInfo(
854739 const space_needed = @intCast(usize, li.column - 1);
855740
856741 try out_stream.writeByteNTimes(' ', space_needed);
857 try tty_config.setColor(out_stream, .Green);
742 try tty_config.setColor(out_stream, .green);
858743 try out_stream.writeAll("^");
859 try tty_config.setColor(out_stream, .Reset);
744 try tty_config.setColor(out_stream, .reset);
860745 }
861746 try out_stream.writeAll("\n");
862747 } else |err| switch (err) {
......@@ -2193,7 +2078,7 @@ test "manage resources correctly" {
21932078 const writer = std.io.null_writer;
21942079 var di = try openSelfDebugInfo(testing.allocator);
21952080 defer di.deinit();
2196 try printSourceAtAddress(&di, writer, showMyTrace(), detectTTYConfig(std.io.getStdErr()));
2081 try printSourceAtAddress(&di, writer, showMyTrace(), io.tty.detectConfig(std.io.getStdErr()));
21972082}
21982083
21992084noinline fn showMyTrace() usize {
......@@ -2253,7 +2138,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
22532138 pub fn dump(t: @This()) void {
22542139 if (!enabled) return;
22552140
2256 const tty_config = detectTTYConfig(std.io.getStdErr());
2141 const tty_config = io.tty.detectConfig(std.io.getStdErr());
22572142 const stderr = io.getStdErr().writer();
22582143 const end = @min(t.index, size);
22592144 const debug_info = getSelfDebugInfo() catch |err| {
lib/std/io.zig+2
......@@ -155,6 +155,8 @@ pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAt
155155
156156pub const StreamSource = @import("io/stream_source.zig").StreamSource;
157157
158pub const tty = @import("io/tty.zig");
159
158160/// A Writer that doesn't write to anything.
159161pub const null_writer = @as(NullWriter, .{ .context = {} });
160162
lib/std/io/tty.zig created+126
......@@ -0,0 +1,126 @@
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/// 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 as
10/// respecting the `NO_COLOR` environment variable.
11pub fn detectConfig(file: File) Config {
12 if (builtin.os.tag == .wasi) {
13 // Per https://github.com/WebAssembly/WASI/issues/162 ANSI codes
14 // aren't currently supported.
15 return .no_color;
16 } else if (process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
17 return .escape_codes;
18 } else if (process.hasEnvVarConstant("NO_COLOR")) {
19 return .no_color;
20 } else if (file.supportsAnsiEscapeCodes()) {
21 return .escape_codes;
22 } else if (native_os == .windows and file.isTty()) {
23 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
24 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) {
25 // TODO: Should this return an error instead?
26 return .no_color;
27 }
28 return .{ .windows_api = .{
29 .handle = file.handle,
30 .reset_attributes = info.wAttributes,
31 } };
32 }
33 return .no_color;
34}
35
36pub const Color = enum {
37 black,
38 red,
39 green,
40 yellow,
41 blue,
42 magenta,
43 cyan,
44 white,
45 bright_black,
46 bright_red,
47 bright_green,
48 bright_yellow,
49 bright_blue,
50 bright_magenta,
51 bright_cyan,
52 bright_white,
53 dim,
54 bold,
55 reset,
56};
57
58/// Provides simple functionality for manipulating the terminal in some way,
59/// such as coloring text, etc.
60pub const Config = union(enum) {
61 no_color,
62 escape_codes,
63 windows_api: if (native_os == .windows) WindowsContext else void,
64
65 pub const WindowsContext = struct {
66 handle: File.Handle,
67 reset_attributes: u16,
68 };
69
70 pub fn setColor(conf: Config, out_stream: anytype, color: Color) !void {
71 nosuspend switch (conf) {
72 .no_color => return,
73 .escape_codes => {
74 const color_string = switch (color) {
75 .black => "\x1b[30m",
76 .red => "\x1b[31m",
77 .green => "\x1b[32m",
78 .yellow => "\x1b[33m",
79 .blue => "\x1b[34m",
80 .magenta => "\x1b[35m",
81 .cyan => "\x1b[36m",
82 .white => "\x1b[37m",
83 .bright_black => "\x1b[90m",
84 .bright_red => "\x1b[91m",
85 .bright_green => "\x1b[92m",
86 .bright_yellow => "\x1b[93m",
87 .bright_blue => "\x1b[94m",
88 .bright_magenta => "\x1b[95m",
89 .bright_cyan => "\x1b[96m",
90 .bright_white => "\x1b[97m",
91 .bold => "\x1b[1m",
92 .dim => "\x1b[2m",
93 .reset => "\x1b[0m",
94 };
95 try out_stream.writeAll(color_string);
96 },
97 .windows_api => |ctx| if (native_os == .windows) {
98 const attributes = switch (color) {
99 .black => 0,
100 .red => windows.FOREGROUND_RED,
101 .green => windows.FOREGROUND_GREEN,
102 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
103 .blue => windows.FOREGROUND_BLUE,
104 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
105 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
106 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
107 .bright_black => windows.FOREGROUND_INTENSITY,
108 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
109 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
110 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
111 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
112 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
113 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
114 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
115 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
116 // This matches the old behavior of TTY.Color before the bright variants were added.
117 .dim => windows.FOREGROUND_INTENSITY,
118 .reset => ctx.reset_attributes,
119 };
120 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
121 } else {
122 unreachable;
123 },
124 };
125 }
126};
lib/std/testing.zig+8-8
......@@ -279,7 +279,7 @@ test "expectApproxEqRel" {
279279/// This function is intended to be used only in tests. When the two slices are not
280280/// equal, prints diagnostics to stderr to show exactly how they are not equal (with
281281/// the differences highlighted in red), then returns a test failure error.
282/// The colorized output is optional and controlled by the return of `std.debug.detectTTYConfig()`.
282/// The colorized output is optional and controlled by the return of `std.io.tty.detectConfig()`.
283283/// If your inputs are UTF-8 encoded strings, consider calling `expectEqualStrings` instead.
284284pub fn expectEqualSlices(comptime T: type, expected: []const T, actual: []const T) !void {
285285 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
312312 const actual_window = actual[window_start..@min(actual.len, window_start + max_window_size)];
313313 const actual_truncated = window_start + actual_window.len < actual.len;
314314
315 const ttyconf = std.debug.detectTTYConfig(std.io.getStdErr());
315 const ttyconf = std.io.tty.detectConfig(std.io.getStdErr());
316316 var differ = if (T == u8) BytesDiffer{
317317 .expected = expected_window,
318318 .actual = actual_window,
......@@ -379,7 +379,7 @@ fn SliceDiffer(comptime T: type) type {
379379 start_index: usize,
380380 expected: []const T,
381381 actual: []const T,
382 ttyconf: std.debug.TTY.Config,
382 ttyconf: std.io.tty.Config,
383383
384384 const Self = @This();
385385
......@@ -387,9 +387,9 @@ fn SliceDiffer(comptime T: type) type {
387387 for (self.expected, 0..) |value, i| {
388388 var full_index = self.start_index + i;
389389 const diff = if (i < self.actual.len) !std.meta.eql(self.actual[i], value) else true;
390 if (diff) try self.ttyconf.setColor(writer, .Red);
390 if (diff) try self.ttyconf.setColor(writer, .red);
391391 try writer.print("[{}]: {any}\n", .{ full_index, value });
392 if (diff) try self.ttyconf.setColor(writer, .Reset);
392 if (diff) try self.ttyconf.setColor(writer, .reset);
393393 }
394394 }
395395 };
......@@ -398,7 +398,7 @@ fn SliceDiffer(comptime T: type) type {
398398const BytesDiffer = struct {
399399 expected: []const u8,
400400 actual: []const u8,
401 ttyconf: std.debug.TTY.Config,
401 ttyconf: std.io.tty.Config,
402402
403403 pub fn write(self: BytesDiffer, writer: anytype) !void {
404404 var expected_iterator = ChunkIterator{ .bytes = self.expected };
......@@ -427,9 +427,9 @@ const BytesDiffer = struct {
427427 }
428428
429429 fn writeByteDiff(self: BytesDiffer, writer: anytype, comptime fmt: []const u8, byte: u8, diff: bool) !void {
430 if (diff) try self.ttyconf.setColor(writer, .Red);
430 if (diff) try self.ttyconf.setColor(writer, .red);
431431 try writer.print(fmt, .{byte});
432 if (diff) try self.ttyconf.setColor(writer, .Reset);
432 if (diff) try self.ttyconf.setColor(writer, .reset);
433433 }
434434
435435 const ChunkIterator = struct {
lib/std/zig/ErrorBundle.zig+18-18
......@@ -148,7 +148,7 @@ pub fn nullTerminatedString(eb: ErrorBundle, index: usize) [:0]const u8 {
148148}
149149
150150pub const RenderOptions = struct {
151 ttyconf: std.debug.TTY.Config,
151 ttyconf: std.io.tty.Config,
152152 include_reference_trace: bool = true,
153153 include_source_line: bool = true,
154154 include_log_text: bool = true,
......@@ -163,7 +163,7 @@ pub fn renderToStdErr(eb: ErrorBundle, options: RenderOptions) void {
163163
164164pub fn renderToWriter(eb: ErrorBundle, options: RenderOptions, writer: anytype) anyerror!void {
165165 for (eb.getMessages()) |err_msg| {
166 try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .Red, 0);
166 try renderErrorMessageToWriter(eb, options, err_msg, writer, "error", .red, 0);
167167 }
168168
169169 if (options.include_log_text) {
......@@ -181,7 +181,7 @@ fn renderErrorMessageToWriter(
181181 err_msg_index: MessageIndex,
182182 stderr: anytype,
183183 kind: []const u8,
184 color: std.debug.TTY.Color,
184 color: std.io.tty.Color,
185185 indent: usize,
186186) anyerror!void {
187187 const ttyconf = options.ttyconf;
......@@ -191,7 +191,7 @@ fn renderErrorMessageToWriter(
191191 if (err_msg.src_loc != .none) {
192192 const src = eb.extraData(SourceLocation, @enumToInt(err_msg.src_loc));
193193 try counting_stderr.writeByteNTimes(' ', indent);
194 try ttyconf.setColor(stderr, .Bold);
194 try ttyconf.setColor(stderr, .bold);
195195 try counting_stderr.print("{s}:{d}:{d}: ", .{
196196 eb.nullTerminatedString(src.data.src_path),
197197 src.data.line + 1,
......@@ -203,17 +203,17 @@ fn renderErrorMessageToWriter(
203203 // This is the length of the part before the error message:
204204 // e.g. "file.zig:4:5: error: "
205205 const prefix_len = @intCast(usize, counting_stderr.context.bytes_written);
206 try ttyconf.setColor(stderr, .Reset);
207 try ttyconf.setColor(stderr, .Bold);
206 try ttyconf.setColor(stderr, .reset);
207 try ttyconf.setColor(stderr, .bold);
208208 if (err_msg.count == 1) {
209209 try writeMsg(eb, err_msg, stderr, prefix_len);
210210 try stderr.writeByte('\n');
211211 } else {
212212 try writeMsg(eb, err_msg, stderr, prefix_len);
213 try ttyconf.setColor(stderr, .Dim);
213 try ttyconf.setColor(stderr, .dim);
214214 try stderr.print(" ({d} times)\n", .{err_msg.count});
215215 }
216 try ttyconf.setColor(stderr, .Reset);
216 try ttyconf.setColor(stderr, .reset);
217217 if (src.data.source_line != 0 and options.include_source_line) {
218218 const line = eb.nullTerminatedString(src.data.source_line);
219219 for (line) |b| switch (b) {
......@@ -226,19 +226,19 @@ fn renderErrorMessageToWriter(
226226 // -1 since span.main includes the caret
227227 const after_caret = src.data.span_end - src.data.span_main -| 1;
228228 try stderr.writeByteNTimes(' ', src.data.column - before_caret);
229 try ttyconf.setColor(stderr, .Green);
229 try ttyconf.setColor(stderr, .green);
230230 try stderr.writeByteNTimes('~', before_caret);
231231 try stderr.writeByte('^');
232232 try stderr.writeByteNTimes('~', after_caret);
233233 try stderr.writeByte('\n');
234 try ttyconf.setColor(stderr, .Reset);
234 try ttyconf.setColor(stderr, .reset);
235235 }
236236 for (eb.getNotes(err_msg_index)) |note| {
237 try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent);
237 try renderErrorMessageToWriter(eb, options, note, stderr, "note", .cyan, indent);
238238 }
239239 if (src.data.reference_trace_len > 0 and options.include_reference_trace) {
240 try ttyconf.setColor(stderr, .Reset);
241 try ttyconf.setColor(stderr, .Dim);
240 try ttyconf.setColor(stderr, .reset);
241 try ttyconf.setColor(stderr, .dim);
242242 try stderr.print("referenced by:\n", .{});
243243 var ref_index = src.end;
244244 for (0..src.data.reference_trace_len) |_| {
......@@ -266,25 +266,25 @@ fn renderErrorMessageToWriter(
266266 }
267267 }
268268 try stderr.writeByte('\n');
269 try ttyconf.setColor(stderr, .Reset);
269 try ttyconf.setColor(stderr, .reset);
270270 }
271271 } else {
272272 try ttyconf.setColor(stderr, color);
273273 try stderr.writeByteNTimes(' ', indent);
274274 try stderr.writeAll(kind);
275275 try stderr.writeAll(": ");
276 try ttyconf.setColor(stderr, .Reset);
276 try ttyconf.setColor(stderr, .reset);
277277 const msg = eb.nullTerminatedString(err_msg.msg);
278278 if (err_msg.count == 1) {
279279 try stderr.print("{s}\n", .{msg});
280280 } else {
281281 try stderr.print("{s}", .{msg});
282 try ttyconf.setColor(stderr, .Dim);
282 try ttyconf.setColor(stderr, .dim);
283283 try stderr.print(" ({d} times)\n", .{err_msg.count});
284284 }
285 try ttyconf.setColor(stderr, .Reset);
285 try ttyconf.setColor(stderr, .reset);
286286 for (eb.getNotes(err_msg_index)) |note| {
287 try renderErrorMessageToWriter(eb, options, note, stderr, "note", .Cyan, indent + 4);
287 try renderErrorMessageToWriter(eb, options, note, stderr, "note", .cyan, indent + 4);
288288 }
289289 }
290290}
src/main.zig+2-2
......@@ -6044,9 +6044,9 @@ const ClangSearchSanitizer = struct {
60446044 };
60456045};
60466046
6047fn get_tty_conf(color: Color) std.debug.TTY.Config {
6047fn get_tty_conf(color: Color) std.io.tty.Config {
60486048 return switch (color) {
6049 .auto => std.debug.detectTTYConfig(std.io.getStdErr()),
6049 .auto => std.io.tty.detectConfig(std.io.getStdErr()),
60506050 .on => .escape_codes,
60516051 .off => .no_color,
60526052 };
test/src/Cases.zig+1-1
......@@ -1354,7 +1354,7 @@ fn runOneCase(
13541354 defer all_errors.deinit(allocator);
13551355 if (all_errors.errorMessageCount() > 0) {
13561356 all_errors.renderToStdErr(.{
1357 .ttyconf = std.debug.detectTTYConfig(std.io.getStdErr()),
1357 .ttyconf = std.io.tty.detectConfig(std.io.getStdErr()),
13581358 });
13591359 // TODO print generated C code
13601360 return error.UnexpectedCompileErrors;