Commit 0f6fa3f20b
Changed files (11)
lib
src
test
src
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),
lib/std/io/tty.zig
@@ -0,0 +1,121 @@
+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 {
+ red,
+ green,
+ yellow,
+ cyan,
+ 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) {
+ .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);
+ }
+};
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,
@@ -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;
lib/std/Build.zig
@@ -1712,7 +1712,7 @@ fn dumpBadGetPathHelp(
s.name,
});
- const tty_config = std.debug.detectTTYConfig(stderr);
+ 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 {};
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)});
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;
@@ -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,7 +712,7 @@ 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 {
@@ -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| {
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 = {} });
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();
@@ -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 };
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,
};
@@ -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,7 +553,7 @@ 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 {
@@ -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,
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,
};
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;