mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-03-08 02:24:33 +01:00
std: rework locking stderr
This commit is contained in:
parent
97f106f949
commit
aa57793b68
10 changed files with 413 additions and 473 deletions
|
|
@ -557,13 +557,6 @@ pub const net = @import("Io/net.zig");
|
|||
userdata: ?*anyopaque,
|
||||
vtable: *const VTable,
|
||||
|
||||
/// This is the global, process-wide protection to coordinate stderr writes.
|
||||
///
|
||||
/// The primary motivation for recursive mutex here is so that a panic while
|
||||
/// stderr mutex is held still dumps the stack trace and other debug
|
||||
/// information.
|
||||
pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
|
||||
|
||||
pub const VTable = struct {
|
||||
/// If it returns `null` it means `result` has been already populated and
|
||||
/// `await` will be a no-op.
|
||||
|
|
@ -719,9 +712,9 @@ pub const VTable = struct {
|
|||
|
||||
processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File,
|
||||
processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize,
|
||||
lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer,
|
||||
tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer,
|
||||
unlockStderrWriter: *const fn (?*anyopaque) void,
|
||||
lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr,
|
||||
tryLockStderr: *const fn (?*anyopaque, buffer: []u8) Cancelable!?LockedStderr,
|
||||
unlockStderr: *const fn (?*anyopaque) void,
|
||||
|
||||
now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
|
||||
sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
|
||||
|
|
@ -763,6 +756,7 @@ pub const UnexpectedError = error{
|
|||
|
||||
pub const Dir = @import("Io/Dir.zig");
|
||||
pub const File = @import("Io/File.zig");
|
||||
pub const Terminal = @import("Io/Terminal.zig");
|
||||
|
||||
pub const Clock = enum {
|
||||
/// A settable system-wide clock that measures real (i.e. wall-clock)
|
||||
|
|
@ -2177,22 +2171,34 @@ pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
|
|||
}
|
||||
}
|
||||
|
||||
pub const LockedStderr = struct {
|
||||
file_writer: *File.Writer,
|
||||
terminal_mode: Terminal.Mode,
|
||||
|
||||
pub fn terminal(ls: LockedStderr) Terminal {
|
||||
return .{
|
||||
.writer = &ls.file_writer.interface,
|
||||
.mode = ls.terminal_mode,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// For doing application-level writes to the standard error stream.
|
||||
/// Coordinates also with debug-level writes that are ignorant of Io interface
|
||||
/// and implementations. When this returns, `stderr_thread_mutex` will be
|
||||
/// locked.
|
||||
/// and implementations. When this returns, `std.process.stderr_thread_mutex`
|
||||
/// will be locked.
|
||||
///
|
||||
/// See also:
|
||||
/// * `tryLockStderrWriter`
|
||||
pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer {
|
||||
return io.vtable.lockStderrWriter(io.userdata, buffer);
|
||||
/// * `tryLockStderr`
|
||||
pub fn lockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!LockedStderr {
|
||||
return io.vtable.lockStderr(io.userdata, buffer, terminal_mode);
|
||||
}
|
||||
|
||||
/// Same as `lockStderrWriter` but uncancelable and non-blocking.
|
||||
pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer {
|
||||
return io.vtable.tryLockStderrWriter(io.userdata, buffer);
|
||||
/// Same as `lockStderr` but non-blocking.
|
||||
pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr {
|
||||
return io.vtable.tryLockStderr(io.userdata, buffer, terminal_mode);
|
||||
}
|
||||
|
||||
pub fn unlockStderrWriter(io: Io) void {
|
||||
return io.vtable.unlockStderrWriter(io.userdata);
|
||||
pub fn unlockStderr(io: Io) void {
|
||||
return io.vtable.unlockStderr(io.userdata);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,24 +64,24 @@ pub const Mode = enum {
|
|||
streaming,
|
||||
positional,
|
||||
/// Avoid syscalls other than `read` and `readv`.
|
||||
streaming_reading,
|
||||
streaming_simple,
|
||||
/// Avoid syscalls other than `pread` and `preadv`.
|
||||
positional_reading,
|
||||
positional_simple,
|
||||
/// Indicates reading cannot continue because of a seek failure.
|
||||
failure,
|
||||
|
||||
pub fn toStreaming(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.positional, .streaming => .streaming,
|
||||
.positional_reading, .streaming_reading => .streaming_reading,
|
||||
.positional_simple, .streaming_simple => .streaming_simple,
|
||||
.failure => .failure,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toReading(m: @This()) @This() {
|
||||
pub fn toSimple(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.positional, .positional_reading => .positional_reading,
|
||||
.streaming, .streaming_reading => .streaming_reading,
|
||||
.positional, .positional_simple => .positional_simple,
|
||||
.streaming, .streaming_simple => .streaming_simple,
|
||||
.failure => .failure,
|
||||
};
|
||||
}
|
||||
|
|
@ -153,10 +153,10 @@ pub fn getSize(r: *Reader) SizeError!u64 {
|
|||
pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
|
||||
const io = r.io;
|
||||
switch (r.mode) {
|
||||
.positional, .positional_reading => {
|
||||
.positional, .positional_simple => {
|
||||
setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
|
||||
},
|
||||
.streaming, .streaming_reading => {
|
||||
.streaming, .streaming_simple => {
|
||||
const seek_err = r.seek_err orelse e: {
|
||||
if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| {
|
||||
setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset));
|
||||
|
|
@ -183,10 +183,10 @@ pub fn seekBy(r: *Reader, offset: i64) SeekError!void {
|
|||
pub fn seekTo(r: *Reader, offset: u64) SeekError!void {
|
||||
const io = r.io;
|
||||
switch (r.mode) {
|
||||
.positional, .positional_reading => {
|
||||
.positional, .positional_simple => {
|
||||
setLogicalPos(r, offset);
|
||||
},
|
||||
.streaming, .streaming_reading => {
|
||||
.streaming, .streaming_simple => {
|
||||
const logical_pos = logicalPos(r);
|
||||
if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos));
|
||||
if (r.seek_err) |err| return err;
|
||||
|
|
@ -225,19 +225,19 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea
|
|||
switch (mode) {
|
||||
.positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) {
|
||||
error.Unimplemented => {
|
||||
r.mode = r.mode.toReading();
|
||||
r.mode = r.mode.toSimple();
|
||||
return 0;
|
||||
},
|
||||
else => |e| return e,
|
||||
},
|
||||
.positional_reading => {
|
||||
.positional_simple => {
|
||||
const dest = limit.slice(try w.writableSliceGreedy(1));
|
||||
var data: [1][]u8 = .{dest};
|
||||
const n = try readVecPositional(r, &data);
|
||||
w.advance(n);
|
||||
return n;
|
||||
},
|
||||
.streaming_reading => {
|
||||
.streaming_simple => {
|
||||
const dest = limit.slice(try w.writableSliceGreedy(1));
|
||||
var data: [1][]u8 = .{dest};
|
||||
const n = try readVecStreaming(r, &data);
|
||||
|
|
@ -251,8 +251,8 @@ pub fn streamMode(r: *Reader, w: *Io.Writer, limit: Io.Limit, mode: Mode) Io.Rea
|
|||
fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
|
||||
const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader));
|
||||
switch (r.mode) {
|
||||
.positional, .positional_reading => return readVecPositional(r, data),
|
||||
.streaming, .streaming_reading => return readVecStreaming(r, data),
|
||||
.positional, .positional_simple => return readVecPositional(r, data),
|
||||
.streaming, .streaming_simple => return readVecStreaming(r, data),
|
||||
.failure => return error.ReadFailed,
|
||||
}
|
||||
}
|
||||
|
|
@ -320,7 +320,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
|
|||
const io = r.io;
|
||||
const file = r.file;
|
||||
switch (r.mode) {
|
||||
.positional, .positional_reading => {
|
||||
.positional, .positional_simple => {
|
||||
const size = r.getSize() catch {
|
||||
r.mode = r.mode.toStreaming();
|
||||
return 0;
|
||||
|
|
@ -330,7 +330,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
|
|||
setLogicalPos(r, logical_pos + delta);
|
||||
return delta;
|
||||
},
|
||||
.streaming, .streaming_reading => {
|
||||
.streaming, .streaming_simple => {
|
||||
// Unfortunately we can't seek forward without knowing the
|
||||
// size because the seek syscalls provided to us will not
|
||||
// return the true end position if a seek would exceed the
|
||||
|
|
|
|||
|
|
@ -18,172 +18,7 @@ write_file_err: ?WriteFileError = null,
|
|||
seek_err: ?SeekError = null,
|
||||
interface: Io.Writer,
|
||||
|
||||
pub const Mode = union(enum) {
|
||||
/// Uses `Io.VTable.fileWriteFileStreaming` if possible. Not a terminal.
|
||||
/// `setColor` does nothing.
|
||||
streaming,
|
||||
/// Uses `Io.VTable.fileWriteFilePositional` if possible. Not a terminal.
|
||||
/// `setColor` does nothing.
|
||||
positional,
|
||||
/// Avoids `Io.VTable.fileWriteFileStreaming`. Not a terminal. `setColor`
|
||||
/// does nothing.
|
||||
streaming_simple,
|
||||
/// Avoids `Io.VTable.fileWriteFilePositional`. Not a terminal. `setColor`
|
||||
/// does nothing.
|
||||
positional_simple,
|
||||
/// It's a terminal. Writes are escaped so as to strip escape sequences.
|
||||
/// Color is enabled.
|
||||
terminal_escaped,
|
||||
/// It's a terminal. Colors are enabled via calling
|
||||
/// SetConsoleTextAttribute. Writes are not escaped.
|
||||
terminal_winapi: TerminalWinapi,
|
||||
/// Indicates writing cannot continue because of a seek failure.
|
||||
failure,
|
||||
|
||||
pub fn toStreaming(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.positional, .streaming => .streaming,
|
||||
.positional_simple, .streaming_simple => .streaming_simple,
|
||||
inline else => |_, x| x,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toSimple(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.positional, .positional_simple => .positional_simple,
|
||||
.streaming, .streaming_simple => .streaming_simple,
|
||||
inline else => |_, x| x,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toUnescaped(m: @This()) @This() {
|
||||
return switch (m) {
|
||||
.terminal_escaped => .streaming_simple,
|
||||
inline else => |_, x| x,
|
||||
};
|
||||
}
|
||||
|
||||
pub const TerminalWinapi = if (!is_windows) noreturn else struct {
|
||||
handle: File.Handle,
|
||||
reset_attributes: u16,
|
||||
};
|
||||
|
||||
/// Detect suitable TTY configuration options for the given file (commonly
|
||||
/// stdout/stderr).
|
||||
///
|
||||
/// Will attempt to enable ANSI escape code support if necessary/possible.
|
||||
pub fn detect(io: Io, file: File, want_color: bool, fallback: Mode) Io.Cancelable!Mode {
|
||||
if (!want_color) return if (try file.isTty(io)) .terminal_escaped else fallback;
|
||||
|
||||
if (file.enableAnsiEscapeCodes(io)) |_| {
|
||||
return .terminal_escaped;
|
||||
} else |err| switch (err) {
|
||||
error.Canceled => return error.Canceled,
|
||||
error.NotTerminalDevice, error.Unexpected => {},
|
||||
}
|
||||
|
||||
if (is_windows and file.isTty(io)) {
|
||||
const windows = std.os.windows;
|
||||
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) {
|
||||
return .{ .terminal_winapi = .{
|
||||
.handle = file.handle,
|
||||
.reset_attributes = info.wAttributes,
|
||||
} };
|
||||
}
|
||||
return .terminal_escaped;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
|
||||
|
||||
pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) SetColorError!void {
|
||||
switch (mode) {
|
||||
.streaming, .positional, .streaming_simple, .positional_simple, .failure => return,
|
||||
.terminal_escaped => {
|
||||
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 io_w.writeAll(color_string);
|
||||
},
|
||||
.terminal_winapi => |ctx| {
|
||||
const windows = std.os.windows;
|
||||
const attributes: windows.WORD = 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 io_w.flush();
|
||||
try windows.SetConsoleTextAttribute(ctx.handle, attributes);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn DecorateArgs(comptime Args: type) type {
|
||||
const fields = @typeInfo(Args).@"struct".fields;
|
||||
var new_fields: [fields.len]type = undefined;
|
||||
for (fields, &new_fields) |old, *new| {
|
||||
if (old.type == std.debug.FormatStackTrace) {
|
||||
new.* = std.debug.FormatStackTrace.Decorated;
|
||||
} else {
|
||||
new.* = old.type;
|
||||
}
|
||||
}
|
||||
return @Tuple(&new_fields);
|
||||
}
|
||||
|
||||
pub fn decorateArgs(file_writer_mode: std.Io.File.Writer.Mode, args: anytype) DecorateArgs(@TypeOf(args)) {
|
||||
var new_args: DecorateArgs(@TypeOf(args)) = undefined;
|
||||
inline for (args, &new_args) |old, *new| {
|
||||
if (@TypeOf(old) == std.debug.FormatStackTrace) {
|
||||
new.* = .{
|
||||
.stack_trace = old.stack_trace,
|
||||
.file_writer_mode = file_writer_mode,
|
||||
};
|
||||
} else {
|
||||
new.* = old;
|
||||
}
|
||||
}
|
||||
return new_args;
|
||||
}
|
||||
};
|
||||
pub const Mode = File.Reader.Mode;
|
||||
|
||||
pub const Error = error{
|
||||
DiskQuota,
|
||||
|
|
@ -277,8 +112,7 @@ pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer
|
|||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
switch (w.mode) {
|
||||
.positional, .positional_simple => return drainPositional(w, data, splat),
|
||||
.streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat),
|
||||
.terminal_escaped => return drainEscaping(w, data, splat),
|
||||
.streaming, .streaming_simple => return drainStreaming(w, data, splat),
|
||||
.failure => return error.WriteFailed,
|
||||
}
|
||||
}
|
||||
|
|
@ -319,38 +153,13 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.
|
|||
return w.interface.consume(n);
|
||||
}
|
||||
|
||||
fn findTerminalEscape(buffer: []const u8) ?usize {
|
||||
return std.mem.findScalar(u8, buffer, 0x1b);
|
||||
}
|
||||
|
||||
fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize {
|
||||
const io = w.io;
|
||||
const header = w.interface.buffered();
|
||||
if (findTerminalEscape(header)) |i| {
|
||||
_ = i;
|
||||
// TODO strip terminal escape sequences here
|
||||
}
|
||||
for (data) |d| {
|
||||
if (findTerminalEscape(d)) |i| {
|
||||
_ = i;
|
||||
// TODO strip terminal escape sequences here
|
||||
}
|
||||
}
|
||||
const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| {
|
||||
w.err = err;
|
||||
return error.WriteFailed;
|
||||
};
|
||||
w.pos += n;
|
||||
return w.interface.consume(n);
|
||||
}
|
||||
|
||||
pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize {
|
||||
const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w));
|
||||
switch (w.mode) {
|
||||
.positional => return sendFilePositional(w, file_reader, limit),
|
||||
.positional_simple => return error.Unimplemented,
|
||||
.streaming => return sendFileStreaming(w, file_reader, limit),
|
||||
.streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented,
|
||||
.streaming_simple => return error.Unimplemented,
|
||||
.failure => return error.WriteFailed,
|
||||
}
|
||||
}
|
||||
|
|
@ -454,60 +263,7 @@ pub fn end(w: *Writer) EndError!void {
|
|||
|
||||
.streaming,
|
||||
.streaming_simple,
|
||||
.terminal_escaped,
|
||||
.terminal_winapi,
|
||||
.failure,
|
||||
=> {},
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
pub fn setColor(w: *Writer, color: Color) Io.Writer.Error!void {
|
||||
return w.mode.setColor(&w.interface, color) catch |err| switch (err) {
|
||||
error.WriteFailed => |e| return e,
|
||||
else => |e| w.err = e,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn disableEscape(w: *Writer) Mode {
|
||||
const prev = w.mode;
|
||||
w.mode = w.mode.toUnescaped();
|
||||
return prev;
|
||||
}
|
||||
|
||||
pub fn restoreEscape(w: *Writer, mode: Mode) void {
|
||||
w.mode = mode;
|
||||
}
|
||||
|
||||
pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Writer.Error!void {
|
||||
const prev_mode = w.disableEscape();
|
||||
defer w.restoreEscape(prev_mode);
|
||||
return w.interface.writeAll(bytes);
|
||||
}
|
||||
|
||||
pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void {
|
||||
const prev_mode = w.disableEscape();
|
||||
defer w.restoreEscape(prev_mode);
|
||||
return w.interface.print(fmt, args);
|
||||
}
|
||||
|
|
|
|||
154
lib/std/Io/Terminal.zig
Normal file
154
lib/std/Io/Terminal.zig
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/// Abstraction for writing to a stream that might support terminal escape
|
||||
/// codes.
|
||||
const Terminal = @This();
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const is_windows = builtin.os.tag == .windows;
|
||||
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const File = std.Io.File;
|
||||
|
||||
writer: *Io.Writer,
|
||||
mode: Mode,
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
pub const Mode = union(enum) {
|
||||
no_color,
|
||||
escape_codes,
|
||||
windows_api: WindowsApi,
|
||||
|
||||
pub const WindowsApi = if (!is_windows) noreturn else struct {
|
||||
handle: File.Handle,
|
||||
reset_attributes: u16,
|
||||
};
|
||||
|
||||
/// Detect suitable TTY configuration options for the given file (commonly
|
||||
/// stdout/stderr).
|
||||
///
|
||||
/// Will attempt to enable ANSI escape code support if necessary/possible.
|
||||
pub fn detect(io: Io, file: File) Io.Cancelable!Mode {
|
||||
if (file.enableAnsiEscapeCodes(io)) |_| {
|
||||
return .escape_codes;
|
||||
} else |err| switch (err) {
|
||||
error.Canceled => return error.Canceled,
|
||||
error.NotTerminalDevice, error.Unexpected => {},
|
||||
}
|
||||
|
||||
if (is_windows and file.isTty(io)) {
|
||||
const windows = std.os.windows;
|
||||
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) {
|
||||
return .{ .terminal_winapi = .{
|
||||
.handle = file.handle,
|
||||
.reset_attributes = info.wAttributes,
|
||||
} };
|
||||
}
|
||||
return .escape_codes;
|
||||
}
|
||||
|
||||
return .no_color;
|
||||
}
|
||||
};
|
||||
|
||||
pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error;
|
||||
|
||||
pub fn setColor(t: Terminal, color: Color) Io.Writer.Error!void {
|
||||
switch (t.mode) {
|
||||
.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 t.writer.writeAll(color_string);
|
||||
},
|
||||
.windows_api => |wa| {
|
||||
const windows = std.os.windows;
|
||||
const attributes: windows.WORD = 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 => wa.reset_attributes,
|
||||
};
|
||||
try t.writer.flush();
|
||||
try windows.SetConsoleTextAttribute(wa.handle, attributes);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disableEscape(t: *Terminal) Mode {
|
||||
const prev = t.mode;
|
||||
t.mode = t.mode.toUnescaped();
|
||||
return prev;
|
||||
}
|
||||
|
||||
pub fn restoreEscape(t: *Terminal, mode: Mode) void {
|
||||
t.mode = mode;
|
||||
}
|
||||
|
||||
pub fn writeAllUnescaped(t: *Terminal, bytes: []const u8) Io.Writer.Error!void {
|
||||
const prev_mode = t.disableEscape();
|
||||
defer t.restoreEscape(prev_mode);
|
||||
return t.interface.writeAll(bytes);
|
||||
}
|
||||
|
||||
pub fn printUnescaped(t: *Terminal, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void {
|
||||
const prev_mode = t.disableEscape();
|
||||
defer t.restoreEscape(prev_mode);
|
||||
return t.interface.print(fmt, args);
|
||||
}
|
||||
|
|
@ -82,8 +82,8 @@ stderr_writer: File.Writer = .{
|
|||
.io = undefined,
|
||||
.interface = Io.File.Writer.initInterface(&.{}),
|
||||
.file = if (is_windows) undefined else .stderr(),
|
||||
.mode = undefined,
|
||||
},
|
||||
stderr_mode: Io.Terminal.Mode = .no_color,
|
||||
stderr_writer_initialized: bool = false,
|
||||
|
||||
pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
|
||||
|
|
@ -755,9 +755,9 @@ pub fn io(t: *Threaded) Io {
|
|||
|
||||
.processExecutableOpen = processExecutableOpen,
|
||||
.processExecutablePath = processExecutablePath,
|
||||
.lockStderrWriter = lockStderrWriter,
|
||||
.tryLockStderrWriter = tryLockStderrWriter,
|
||||
.unlockStderrWriter = unlockStderrWriter,
|
||||
.lockStderr = lockStderr,
|
||||
.tryLockStderr = tryLockStderr,
|
||||
.unlockStderr = unlockStderr,
|
||||
|
||||
.now = now,
|
||||
.sleep = sleep,
|
||||
|
|
@ -887,9 +887,9 @@ pub fn ioBasic(t: *Threaded) Io {
|
|||
|
||||
.processExecutableOpen = processExecutableOpen,
|
||||
.processExecutablePath = processExecutablePath,
|
||||
.lockStderrWriter = lockStderrWriter,
|
||||
.tryLockStderrWriter = tryLockStderrWriter,
|
||||
.unlockStderrWriter = unlockStderrWriter,
|
||||
.lockStderr = lockStderr,
|
||||
.tryLockStderr = tryLockStderr,
|
||||
.unlockStderr = unlockStderr,
|
||||
|
||||
.now = now,
|
||||
.sleep = sleep,
|
||||
|
|
@ -10090,47 +10090,70 @@ fn netLookupFallible(
|
|||
return error.OptionUnsupported;
|
||||
}
|
||||
|
||||
fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer {
|
||||
fn lockStderr(
|
||||
userdata: ?*anyopaque,
|
||||
buffer: []u8,
|
||||
terminal_mode: ?Io.Terminal.Mode,
|
||||
) Io.Cancelable!Io.LockedStderr {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
// Only global mutex since this is Threaded.
|
||||
Io.stderr_thread_mutex.lock();
|
||||
std.process.stderr_thread_mutex.lock();
|
||||
return initLockedStderr(t, buffer, terminal_mode);
|
||||
}
|
||||
|
||||
fn tryLockStderr(
|
||||
userdata: ?*anyopaque,
|
||||
buffer: []u8,
|
||||
terminal_mode: ?Io.Terminal.Mode,
|
||||
) Io.Cancelable!?Io.LockedStderr {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
// Only global mutex since this is Threaded.
|
||||
if (!std.process.stderr_thread_mutex.tryLock()) return null;
|
||||
return try initLockedStderr(t, buffer, terminal_mode);
|
||||
}
|
||||
|
||||
fn initLockedStderr(
|
||||
t: *Threaded,
|
||||
buffer: []u8,
|
||||
terminal_mode: ?Io.Terminal.Mode,
|
||||
) Io.Cancelable!Io.LockedStderr {
|
||||
if (!t.stderr_writer_initialized) {
|
||||
const io_t = ioBasic(t);
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
t.stderr_writer.io = io_t;
|
||||
t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple);
|
||||
t.stderr_writer_initialized = true;
|
||||
t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file);
|
||||
}
|
||||
std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
|
||||
t.stderr_writer.interface.flush() catch {};
|
||||
std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) {
|
||||
error.WriteFailed => switch (t.stderr_writer.err.?) {
|
||||
error.Canceled => |e| return e,
|
||||
else => {},
|
||||
},
|
||||
};
|
||||
t.stderr_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => switch (t.stderr_writer.err.?) {
|
||||
error.Canceled => |e| return e,
|
||||
else => {},
|
||||
},
|
||||
};
|
||||
t.stderr_writer.interface.buffer = buffer;
|
||||
return &t.stderr_writer;
|
||||
return .{
|
||||
.file_writer = &t.stderr_writer,
|
||||
.terminal_mode = t.stderr_mode,
|
||||
};
|
||||
}
|
||||
|
||||
fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer {
|
||||
fn unlockStderr(userdata: ?*anyopaque) void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
// Only global mutex since this is Threaded.
|
||||
if (!Io.stderr_thread_mutex.tryLock()) return null;
|
||||
if (!t.stderr_writer_initialized) {
|
||||
const io_t = ioBasic(t);
|
||||
if (is_windows) t.stderr_writer.file = .stderr();
|
||||
t.stderr_writer.io = io_t;
|
||||
t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch
|
||||
return null;
|
||||
t.stderr_writer_initialized = true;
|
||||
}
|
||||
std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {};
|
||||
t.stderr_writer.interface.flush() catch {};
|
||||
t.stderr_writer.interface.buffer = buffer;
|
||||
return &t.stderr_writer;
|
||||
}
|
||||
|
||||
fn unlockStderrWriter(userdata: ?*anyopaque) void {
|
||||
const t: *Threaded = @ptrCast(@alignCast(userdata));
|
||||
t.stderr_writer.interface.flush() catch {};
|
||||
t.stderr_writer.interface.flush() catch |err| switch (err) {
|
||||
error.WriteFailed => switch (t.stderr_writer.err.?) {
|
||||
error.Canceled => @panic("TODO make this uncancelable"),
|
||||
else => {},
|
||||
},
|
||||
};
|
||||
t.stderr_writer.interface.end = 0;
|
||||
t.stderr_writer.interface.buffer = &.{};
|
||||
Io.stderr_thread_mutex.unlock();
|
||||
std.process.stderr_thread_mutex.unlock();
|
||||
}
|
||||
|
||||
pub const PosixAddress = extern union {
|
||||
|
|
|
|||
|
|
@ -961,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE
|
|||
const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
|
||||
error.EndOfStream => break,
|
||||
error.Unimplemented => {
|
||||
file_reader.mode = file_reader.mode.toReading();
|
||||
file_reader.mode = file_reader.mode.toSimple();
|
||||
remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
|
||||
break;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -565,10 +565,10 @@ fn updateThreadRun(io: Io) void {
|
|||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, _ = computeRedraw(&serialized_buffer);
|
||||
if (io.tryLockStderrWriter(&.{})) |fw| {
|
||||
defer io.unlockStderrWriter();
|
||||
if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
|
||||
defer io.unlockStderr();
|
||||
global_progress.need_clear = true;
|
||||
fw.writeAllUnescaped(buffer) catch return;
|
||||
locked_stderr.file_writer.interface.writeAll(buffer) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -576,18 +576,18 @@ fn updateThreadRun(io: Io) void {
|
|||
const resize_flag = wait(io, global_progress.refresh_rate_ns);
|
||||
|
||||
if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
|
||||
const fw = io.lockStderrWriter(&.{}) catch return;
|
||||
defer io.unlockStderrWriter();
|
||||
return clearWrittenWithEscapeCodes(fw) catch {};
|
||||
const stderr = io.lockStderr(&.{}, null) catch return;
|
||||
defer io.unlockStderr();
|
||||
return clearWrittenWithEscapeCodes(stderr.file_writer) catch {};
|
||||
}
|
||||
|
||||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, _ = computeRedraw(&serialized_buffer);
|
||||
if (io.tryLockStderrWriter(&.{})) |fw| {
|
||||
defer io.unlockStderrWriter();
|
||||
if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
|
||||
defer io.unlockStderr();
|
||||
global_progress.need_clear = true;
|
||||
fw.writeAllUnescaped(buffer) catch return;
|
||||
locked_stderr.file_writer.interface.writeAll(buffer) catch return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -609,11 +609,11 @@ fn windowsApiUpdateThreadRun(io: Io) void {
|
|||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, const nl_n = computeRedraw(&serialized_buffer);
|
||||
if (io.tryLockStderrWriter()) |fw| {
|
||||
defer io.unlockStderrWriter();
|
||||
if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
|
||||
defer io.unlockStderr();
|
||||
windowsApiWriteMarker();
|
||||
global_progress.need_clear = true;
|
||||
fw.writeAllUnescaped(buffer) catch return;
|
||||
locked_stderr.file_writer.interface.writeAll(buffer) catch return;
|
||||
windowsApiMoveToMarker(nl_n) catch return;
|
||||
}
|
||||
}
|
||||
|
|
@ -622,20 +622,20 @@ fn windowsApiUpdateThreadRun(io: Io) void {
|
|||
const resize_flag = wait(io, global_progress.refresh_rate_ns);
|
||||
|
||||
if (@atomicLoad(bool, &global_progress.done, .monotonic)) {
|
||||
_ = io.lockStderrWriter() catch return;
|
||||
defer io.unlockStderrWriter();
|
||||
_ = io.lockStderr(&.{}, null) catch return;
|
||||
defer io.unlockStderr();
|
||||
return clearWrittenWindowsApi() catch {};
|
||||
}
|
||||
|
||||
maybeUpdateSize(resize_flag);
|
||||
|
||||
const buffer, const nl_n = computeRedraw(&serialized_buffer);
|
||||
if (io.tryLockStderrWriter()) |fw| {
|
||||
defer io.unlockStderrWriter();
|
||||
if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| {
|
||||
defer io.unlockStderr();
|
||||
clearWrittenWindowsApi() catch return;
|
||||
windowsApiWriteMarker();
|
||||
global_progress.need_clear = true;
|
||||
fw.writeAllUnescaped(buffer) catch return;
|
||||
locked_stderr.file_writer.interface.writeAll(buffer) catch return;
|
||||
windowsApiMoveToMarker(nl_n) catch return;
|
||||
}
|
||||
}
|
||||
|
|
@ -766,7 +766,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize {
|
|||
|
||||
pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void {
|
||||
if (noop_impl or !global_progress.need_clear) return;
|
||||
try file_writer.writeAllUnescaped(clear ++ progress_remove);
|
||||
try file_writer.interface.writeAll(clear ++ progress_remove);
|
||||
global_progress.need_clear = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -265,29 +265,34 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) {
|
|||
/// separate from the application's `Io` instance.
|
||||
var static_single_threaded_io: Io.Threaded = .init_single_threaded;
|
||||
|
||||
/// Allows the caller to freely write to stderr until `unlockStderrWriter` is called.
|
||||
/// Allows the caller to freely write to stderr until `unlockStderr` is called.
|
||||
///
|
||||
/// During the lock, any `std.Progress` information is cleared from the terminal.
|
||||
///
|
||||
/// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple
|
||||
/// times. The primary motivation is that this allows the panic handler to safely dump the stack
|
||||
/// trace and panic message even if the mutex was held at the panic site.
|
||||
/// The lock is recursive, so it is valid for the same thread to call
|
||||
/// `lockStderr` multiple times, allowing the panic handler to safely
|
||||
/// dump the stack trace and panic message even if the mutex was held at the
|
||||
/// panic site.
|
||||
///
|
||||
/// The returned `Writer` does not need to be manually flushed: flushing is
|
||||
/// performed automatically when the matching `unlockStderrWriter` call occurs.
|
||||
/// performed automatically when the matching `unlockStderr` call occurs.
|
||||
///
|
||||
/// This is a low-level debugging primitive that bypasses the `Io` interface,
|
||||
/// writing directly to stderr using the most basic syscalls available. This
|
||||
/// function does not switch threads, switch stacks, or suspend.
|
||||
///
|
||||
/// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with
|
||||
/// the application's chosen `Io` implementation.
|
||||
pub fn lockStderrWriter(buffer: []u8) *File.Writer {
|
||||
return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable;
|
||||
/// Alternatively, use the higher-level `Io.lockStderr` to integrate with the
|
||||
/// application's chosen `Io` implementation.
|
||||
pub fn lockStderr(buffer: []u8) Io.Terminal {
|
||||
return (static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) {
|
||||
// Impossible to cancel because no calls to cancel using
|
||||
// `static_single_threaded_io` exist.
|
||||
error.Canceled => unreachable,
|
||||
}).terminal();
|
||||
}
|
||||
|
||||
pub fn unlockStderrWriter() void {
|
||||
static_single_threaded_io.ioBasic().unlockStderrWriter();
|
||||
pub fn unlockStderr() void {
|
||||
static_single_threaded_io.ioBasic().unlockStderr();
|
||||
}
|
||||
|
||||
/// Writes to stderr, ignoring errors.
|
||||
|
|
@ -299,14 +304,14 @@ pub fn unlockStderrWriter() void {
|
|||
/// Uses a 64-byte buffer for formatted printing which is flushed before this
|
||||
/// function returns.
|
||||
///
|
||||
/// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to
|
||||
/// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to
|
||||
/// integrate with the application's chosen `Io` implementation.
|
||||
pub fn print(comptime fmt: []const u8, args: anytype) void {
|
||||
nosuspend {
|
||||
var buffer: [64]u8 = undefined;
|
||||
const stderr = lockStderrWriter(&buffer);
|
||||
defer unlockStderrWriter();
|
||||
stderr.interface.print(fmt, stderr.mode.decorateArgs(args)) catch return;
|
||||
const stderr = lockStderr(&buffer);
|
||||
defer unlockStderr();
|
||||
stderr.writer.print(fmt, args) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -322,43 +327,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo {
|
|||
/// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned.
|
||||
/// Obtains the stderr mutex while dumping.
|
||||
pub fn dumpHex(bytes: []const u8) void {
|
||||
const bw, const ttyconf = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
dumpHexFallible(bw, ttyconf, bytes) catch {};
|
||||
const stderr = lockStderr(&.{});
|
||||
defer unlockStderr();
|
||||
dumpHexFallible(stderr, bytes) catch {};
|
||||
}
|
||||
|
||||
/// Prints a hexadecimal view of the bytes, returning any error that occurs.
|
||||
pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !void {
|
||||
pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void {
|
||||
const w = t.writer;
|
||||
var chunks = mem.window(u8, bytes, 16, 16);
|
||||
while (chunks.next()) |window| {
|
||||
// 1. Print the address.
|
||||
const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10;
|
||||
try fwm.setColor(bw, .dim);
|
||||
try t.setColor(.dim);
|
||||
// We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more.
|
||||
// Also, make sure all lines are aligned by padding the address.
|
||||
try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
|
||||
try fwm.setColor(bw, .reset);
|
||||
try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 });
|
||||
try t.setColor(.reset);
|
||||
|
||||
// 2. Print the bytes.
|
||||
for (window, 0..) |byte, index| {
|
||||
try bw.print("{X:0>2} ", .{byte});
|
||||
if (index == 7) try bw.writeByte(' ');
|
||||
try w.print("{X:0>2} ", .{byte});
|
||||
if (index == 7) try w.writeByte(' ');
|
||||
}
|
||||
try bw.writeByte(' ');
|
||||
try w.writeByte(' ');
|
||||
if (window.len < 16) {
|
||||
var missing_columns = (16 - window.len) * 3;
|
||||
if (window.len < 8) missing_columns += 1;
|
||||
try bw.splatByteAll(' ', missing_columns);
|
||||
try w.splatByteAll(' ', missing_columns);
|
||||
}
|
||||
|
||||
// 3. Print the characters.
|
||||
for (window) |byte| {
|
||||
if (std.ascii.isPrint(byte)) {
|
||||
try bw.writeByte(byte);
|
||||
try w.writeByte(byte);
|
||||
} else {
|
||||
// Related: https://github.com/ziglang/zig/issues/7600
|
||||
if (fwm == .terminal_winapi) {
|
||||
try bw.writeByte('.');
|
||||
if (t.mode == .windows_api) {
|
||||
try w.writeByte('.');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -366,14 +372,14 @@ pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !v
|
|||
// We don't want to do this for all control codes because most control codes apart from
|
||||
// the ones that Zig has escape sequences for are likely not very useful to print as symbols.
|
||||
switch (byte) {
|
||||
'\n' => try bw.writeAll("␊"),
|
||||
'\r' => try bw.writeAll("␍"),
|
||||
'\t' => try bw.writeAll("␉"),
|
||||
else => try bw.writeByte('.'),
|
||||
'\n' => try w.writeAll("␊"),
|
||||
'\r' => try w.writeAll("␍"),
|
||||
'\t' => try w.writeAll("␉"),
|
||||
else => try w.writeByte('.'),
|
||||
}
|
||||
}
|
||||
}
|
||||
try bw.writeByte('\n');
|
||||
try w.writeByte('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -545,26 +551,27 @@ pub fn defaultPanic(
|
|||
_ = panicking.fetchAdd(1, .seq_cst);
|
||||
|
||||
trace: {
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
const stderr = lockStderr(&.{});
|
||||
defer unlockStderr();
|
||||
const writer = stderr.writer;
|
||||
|
||||
if (builtin.single_threaded) {
|
||||
stderr.interface.print("panic: ", .{}) catch break :trace;
|
||||
writer.print("panic: ", .{}) catch break :trace;
|
||||
} else {
|
||||
const current_thread_id = std.Thread.getCurrentId();
|
||||
stderr.interface.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
|
||||
writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace;
|
||||
}
|
||||
stderr.interface.print("{s}\n", .{msg}) catch break :trace;
|
||||
writer.print("{s}\n", .{msg}) catch break :trace;
|
||||
|
||||
if (@errorReturnTrace()) |t| if (t.index > 0) {
|
||||
stderr.interface.writeAll("error return context:\n") catch break :trace;
|
||||
writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace;
|
||||
stderr.interface.writeAll("\nstack trace:\n") catch break :trace;
|
||||
writer.writeAll("error return context:\n") catch break :trace;
|
||||
writeStackTrace(t, stderr) catch break :trace;
|
||||
writer.writeAll("\nstack trace:\n") catch break :trace;
|
||||
};
|
||||
writeCurrentStackTrace(.{
|
||||
.first_address = first_trace_addr orelse @returnAddress(),
|
||||
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
|
||||
}, &stderr.interface, stderr.mode) catch break :trace;
|
||||
}, stderr) catch break :trace;
|
||||
}
|
||||
|
||||
waitForOtherThreadToFinishPanicking();
|
||||
|
|
@ -574,8 +581,8 @@ pub fn defaultPanic(
|
|||
// A panic happened while trying to print a previous panic message.
|
||||
// We're still holding the mutex but that's fine as we're going to
|
||||
// call abort().
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
|
||||
const stderr = lockStderr(&.{});
|
||||
stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
|
||||
},
|
||||
else => {}, // Panicked while printing the recursive panic message.
|
||||
}
|
||||
|
|
@ -656,28 +663,29 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf:
|
|||
/// Write the current stack trace to `writer`, annotated with source locations.
|
||||
///
|
||||
/// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing.
|
||||
pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
|
||||
pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void {
|
||||
const writer = t.writer;
|
||||
if (!std.options.allow_stack_tracing) {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
return;
|
||||
}
|
||||
const di_gpa = getDebugInfoAllocator();
|
||||
const di = getSelfDebugInfo() catch |err| switch (err) {
|
||||
error.UnsupportedTarget => {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
return;
|
||||
},
|
||||
};
|
||||
var it: StackIterator = .init(options.context);
|
||||
defer it.deinit();
|
||||
if (!it.stratOk(options.allow_unsafe_unwind)) {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
return;
|
||||
}
|
||||
var total_frames: usize = 0;
|
||||
|
|
@ -701,31 +709,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
|
|||
error.Unexpected => "unexpected error",
|
||||
};
|
||||
if (it.stratOk(options.allow_unsafe_unwind)) {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print(
|
||||
"Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n",
|
||||
.{ module_name, unwind_error.address, caption },
|
||||
);
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
} else {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print(
|
||||
"Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n",
|
||||
.{ module_name, unwind_error.address, caption },
|
||||
);
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
return;
|
||||
}
|
||||
},
|
||||
.end => break,
|
||||
.frame => |ret_addr| {
|
||||
if (total_frames > 10_000) {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print(
|
||||
"Stopping trace after {d} frames (large frame count may indicate broken debug info)\n",
|
||||
.{total_frames},
|
||||
);
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
return;
|
||||
}
|
||||
total_frames += 1;
|
||||
|
|
@ -735,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
|
|||
}
|
||||
// `ret_addr` is the return address, which is *after* the function call.
|
||||
// Subtract 1 to get an address *in* the function call for a better source location.
|
||||
try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
|
||||
try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
|
||||
printed_any_frame = true;
|
||||
},
|
||||
};
|
||||
|
|
@ -743,8 +751,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri
|
|||
}
|
||||
/// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors.
|
||||
pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
const stderr = lockStderr(&.{});
|
||||
defer unlockStderr();
|
||||
writeCurrentStackTrace(.{
|
||||
.first_address = a: {
|
||||
if (options.first_address) |a| break :a a;
|
||||
|
|
@ -753,38 +761,28 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void {
|
|||
},
|
||||
.context = options.context,
|
||||
.allow_unsafe_unwind = options.allow_unsafe_unwind,
|
||||
}, &stderr.interface, stderr.mode) catch |err| switch (err) {
|
||||
}, stderr) catch |err| switch (err) {
|
||||
error.WriteFailed => {},
|
||||
};
|
||||
}
|
||||
|
||||
pub const FormatStackTrace = struct {
|
||||
stack_trace: StackTrace,
|
||||
terminal_mode: Io.Terminal.Mode = .no_color,
|
||||
|
||||
pub const Decorated = struct {
|
||||
stack_trace: StackTrace,
|
||||
file_writer_mode: File.Writer.Mode,
|
||||
|
||||
pub fn format(decorated: Decorated, writer: *Writer) Writer.Error!void {
|
||||
try writer.writeByte('\n');
|
||||
try writeStackTrace(&decorated.stack_trace, writer, decorated.file_writer_mode);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn format(context: FormatStackTrace, writer: *Writer) Writer.Error!void {
|
||||
return Decorated.format(.{
|
||||
.stack_trace = context.stack_trace,
|
||||
.file_writer_mode = .streaming,
|
||||
}, writer);
|
||||
pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void {
|
||||
try writer.writeByte('\n');
|
||||
try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode });
|
||||
}
|
||||
};
|
||||
|
||||
/// Write a previously captured stack trace to `writer`, annotated with source locations.
|
||||
pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void {
|
||||
pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void {
|
||||
const writer = t.writer;
|
||||
if (!std.options.allow_stack_tracing) {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -795,9 +793,9 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.
|
|||
const di_gpa = getDebugInfoAllocator();
|
||||
const di = getSelfDebugInfo() catch |err| switch (err) {
|
||||
error.UnsupportedTarget => {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
|
@ -806,19 +804,19 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.
|
|||
for (st.instruction_addresses[0..captured_frames]) |ret_addr| {
|
||||
// `ret_addr` is the return address, which is *after* the function call.
|
||||
// Subtract 1 to get an address *in* the function call for a better source location.
|
||||
try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm);
|
||||
try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset);
|
||||
}
|
||||
if (n_frames > captured_frames) {
|
||||
fwm.setColor(writer, .bold) catch {};
|
||||
t.setColor(.bold) catch {};
|
||||
try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
}
|
||||
}
|
||||
/// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors.
|
||||
pub fn dumpStackTrace(st: *const StackTrace) void {
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
writeStackTrace(st, &stderr.interface, stderr.mode) catch |err| switch (err) {
|
||||
const stderr = lockStderr(&.{});
|
||||
defer unlockStderr();
|
||||
writeStackTrace(st, stderr) catch |err| switch (err) {
|
||||
error.WriteFailed => {},
|
||||
};
|
||||
}
|
||||
|
|
@ -1117,9 +1115,8 @@ fn printSourceAtAddress(
|
|||
gpa: Allocator,
|
||||
io: Io,
|
||||
debug_info: *SelfInfo,
|
||||
writer: *Writer,
|
||||
t: Io.Terminal,
|
||||
address: usize,
|
||||
fwm: File.Writer.Mode,
|
||||
) Writer.Error!void {
|
||||
const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) {
|
||||
error.MissingDebugInfo,
|
||||
|
|
@ -1127,40 +1124,39 @@ fn printSourceAtAddress(
|
|||
error.InvalidDebugInfo,
|
||||
=> .unknown,
|
||||
error.ReadFailed, error.Unexpected, error.Canceled => s: {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{});
|
||||
t.setColor(.reset) catch {};
|
||||
break :s .unknown;
|
||||
},
|
||||
error.OutOfMemory => s: {
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{});
|
||||
t.setColor(.reset) catch {};
|
||||
break :s .unknown;
|
||||
},
|
||||
};
|
||||
defer if (symbol.source_location) |sl| gpa.free(sl.file_name);
|
||||
return printLineInfo(
|
||||
io,
|
||||
writer,
|
||||
t,
|
||||
symbol.source_location,
|
||||
address,
|
||||
symbol.name orelse "???",
|
||||
symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???",
|
||||
fwm,
|
||||
);
|
||||
}
|
||||
fn printLineInfo(
|
||||
io: Io,
|
||||
writer: *Writer,
|
||||
t: Io.Terminal,
|
||||
source_location: ?SourceLocation,
|
||||
address: usize,
|
||||
symbol_name: []const u8,
|
||||
compile_unit_name: []const u8,
|
||||
fwm: File.Writer.Mode,
|
||||
) Writer.Error!void {
|
||||
nosuspend {
|
||||
fwm.setColor(writer, .bold) catch {};
|
||||
const writer = t.writer;
|
||||
t.setColor(.bold) catch {};
|
||||
|
||||
if (source_location) |*sl| {
|
||||
try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column });
|
||||
|
|
@ -1168,11 +1164,11 @@ fn printLineInfo(
|
|||
try writer.writeAll("???:?:?");
|
||||
}
|
||||
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
try writer.writeAll(": ");
|
||||
fwm.setColor(writer, .dim) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name });
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
try writer.writeAll("\n");
|
||||
|
||||
// Show the matching source code line if possible
|
||||
|
|
@ -1183,9 +1179,9 @@ fn printLineInfo(
|
|||
const space_needed = @as(usize, @intCast(sl.column - 1));
|
||||
|
||||
try writer.splatByteAll(' ', space_needed);
|
||||
fwm.setColor(writer, .green) catch {};
|
||||
t.setColor(.green) catch {};
|
||||
try writer.writeAll("^");
|
||||
fwm.setColor(writer, .reset) catch {};
|
||||
t.setColor(.reset) catch {};
|
||||
}
|
||||
try writer.writeAll("\n");
|
||||
} else |_| {
|
||||
|
|
@ -1554,19 +1550,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
|
|||
_ = panicking.fetchAdd(1, .seq_cst);
|
||||
|
||||
trace: {
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
const stderr = lockStderr(&.{});
|
||||
defer unlockStderr();
|
||||
|
||||
if (addr) |a| {
|
||||
stderr.interface.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
|
||||
stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace;
|
||||
} else {
|
||||
stderr.interface.print("{s} (no address available)\n", .{name}) catch break :trace;
|
||||
stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace;
|
||||
}
|
||||
if (opt_ctx) |context| {
|
||||
writeCurrentStackTrace(.{
|
||||
.context = context,
|
||||
.allow_unsafe_unwind = true, // we're crashing anyway, give it our all!
|
||||
}, &stderr.interface, stderr.mode) catch break :trace;
|
||||
}, stderr) catch break :trace;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -1575,8 +1571,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex
|
|||
// A segfault happened while trying to print a previous panic message.
|
||||
// We're still holding the mutex but that's fine as we're going to
|
||||
// call abort().
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
stderr.interface.writeAll("aborting due to recursive panic\n") catch {};
|
||||
const stderr = lockStderr(&.{});
|
||||
stderr.writer.writeAll("aborting due to recursive panic\n") catch {};
|
||||
},
|
||||
else => {}, // Panicked while printing the recursive panic message.
|
||||
}
|
||||
|
|
@ -1682,21 +1678,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
|
|||
pub fn dump(t: @This()) void {
|
||||
if (!enabled) return;
|
||||
|
||||
const stderr = lockStderrWriter(&.{});
|
||||
defer unlockStderrWriter();
|
||||
const stderr = lockStderr(&.{});
|
||||
defer unlockStderr();
|
||||
const end = @min(t.index, size);
|
||||
for (t.addrs[0..end], 0..) |frames_array, i| {
|
||||
stderr.interface.print("{s}:\n", .{t.notes[i]}) catch return;
|
||||
stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return;
|
||||
var frames_array_mutable = frames_array;
|
||||
const frames = mem.sliceTo(frames_array_mutable[0..], 0);
|
||||
const stack_trace: StackTrace = .{
|
||||
.index = frames.len,
|
||||
.instruction_addresses = frames,
|
||||
};
|
||||
writeStackTrace(&stack_trace, &stderr.interface, stderr.mode) catch return;
|
||||
writeStackTrace(&stack_trace, stderr) catch return;
|
||||
}
|
||||
if (t.index > end) {
|
||||
stderr.interface.print("{d} more traces not shown; consider increasing trace size\n", .{
|
||||
stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{
|
||||
t.index - end,
|
||||
}) catch return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,35 +92,33 @@ pub fn defaultLog(
|
|||
args: anytype,
|
||||
) void {
|
||||
var buffer: [64]u8 = undefined;
|
||||
const stderr = std.debug.lockStderrWriter(&buffer);
|
||||
defer std.debug.unlockStderrWriter();
|
||||
return defaultLogFileWriter(level, scope, format, args, stderr);
|
||||
const stderr = std.debug.lockStderr(&buffer);
|
||||
defer std.debug.unlockStderr();
|
||||
return defaultLogFileTerminal(level, scope, format, args, stderr) catch {};
|
||||
}
|
||||
|
||||
pub fn defaultLogFileWriter(
|
||||
pub fn defaultLogFileTerminal(
|
||||
comptime level: Level,
|
||||
comptime scope: @EnumLiteral(),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
fw: *std.Io.File.Writer,
|
||||
) void {
|
||||
fw.setColor(switch (level) {
|
||||
t: std.Io.Terminal,
|
||||
) std.Io.Writer.Error!void {
|
||||
t.setColor(switch (level) {
|
||||
.err => .red,
|
||||
.warn => .yellow,
|
||||
.info => .green,
|
||||
.debug => .magenta,
|
||||
}) catch {};
|
||||
fw.setColor(.bold) catch {};
|
||||
fw.interface.writeAll(level.asText()) catch return;
|
||||
fw.setColor(.reset) catch {};
|
||||
fw.setColor(.dim) catch {};
|
||||
fw.setColor(.bold) catch {};
|
||||
if (scope != .default) {
|
||||
fw.interface.print("({s})", .{@tagName(scope)}) catch return;
|
||||
}
|
||||
fw.interface.writeAll(": ") catch return;
|
||||
fw.setColor(.reset) catch {};
|
||||
fw.interface.print(format ++ "\n", fw.mode.decorateArgs(args)) catch return;
|
||||
t.setColor(.bold) catch {};
|
||||
try t.writer.writeAll(level.asText());
|
||||
t.setColor(.reset) catch {};
|
||||
t.setColor(.dim) catch {};
|
||||
t.setColor(.bold) catch {};
|
||||
if (scope != .default) try t.writer.print("({t})", .{scope});
|
||||
try t.writer.writeAll(": ");
|
||||
t.setColor(.reset) catch {};
|
||||
try t.writer.print(format ++ "\n", args);
|
||||
}
|
||||
|
||||
/// Returns a scoped logging namespace that logs all messages using the scope
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@ pub const changeCurDirZ = posix.chdirZ;
|
|||
|
||||
pub const GetCwdError = posix.GetCwdError;
|
||||
|
||||
/// This is the global, process-wide protection to coordinate stderr writes.
|
||||
///
|
||||
/// The primary motivation for recursive mutex here is so that a panic while
|
||||
/// stderr mutex is held still dumps the stack trace and other debug
|
||||
/// information.
|
||||
pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init;
|
||||
|
||||
/// The result is a slice of `out_buffer`, from index `0`.
|
||||
/// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/).
|
||||
/// On other platforms, the result is an opaque sequence of bytes with no particular encoding.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue