mirror of
https://github.com/ziglang/zig.git
synced 2026-01-21 06:45:24 +00:00
Merge pull request #15806 from linusg/std-io-tty
std: Move TTY from std.debug to std.io and add missing colors
This commit is contained in:
commit
7cb2e653a2
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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)});
|
||||
|
||||
@ -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| {
|
||||
|
||||
@ -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 = {} });
|
||||
|
||||
|
||||
126
lib/std/io/tty.zig
Normal file
126
lib/std/io/tty.zig
Normal file
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user