| author | |
| committer | |
| log | aa57793b680b3da05f1d888b4df15807905e57c8 |
| tree | d88a1c6f56796942c9b21aee4bbb88e72045d298 |
| parent | 97f106f949891870433bfcc6b7cf4c2a6709402e |
10 files changed, 409 insertions(+), 469 deletions(-)
lib/std/Io.zig+26-20| ... | ... | @@ -557,13 +557,6 @@ pub const net = @import("Io/net.zig"); |
| 557 | 557 | userdata: ?*anyopaque, |
| 558 | 558 | vtable: *const VTable, |
| 559 | 559 | |
| 560 | /// This is the global, process-wide protection to coordinate stderr writes. | |
| 561 | /// | |
| 562 | /// The primary motivation for recursive mutex here is so that a panic while | |
| 563 | /// stderr mutex is held still dumps the stack trace and other debug | |
| 564 | /// information. | |
| 565 | pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; | |
| 566 | ||
| 567 | 560 | pub const VTable = struct { |
| 568 | 561 | /// If it returns `null` it means `result` has been already populated and |
| 569 | 562 | /// `await` will be a no-op. |
| ... | ... | @@ -719,9 +712,9 @@ pub const VTable = struct { |
| 719 | 712 | |
| 720 | 713 | processExecutableOpen: *const fn (?*anyopaque, File.OpenFlags) std.process.OpenExecutableError!File, |
| 721 | 714 | processExecutablePath: *const fn (?*anyopaque, buffer: []u8) std.process.ExecutablePathError!usize, |
| 722 | lockStderrWriter: *const fn (?*anyopaque, buffer: []u8) Cancelable!*File.Writer, | |
| 723 | tryLockStderrWriter: *const fn (?*anyopaque, buffer: []u8) ?*File.Writer, | |
| 724 | unlockStderrWriter: *const fn (?*anyopaque) void, | |
| 715 | lockStderr: *const fn (?*anyopaque, buffer: []u8, ?Terminal.Mode) Cancelable!LockedStderr, | |
| 716 | tryLockStderr: *const fn (?*anyopaque, buffer: []u8) Cancelable!?LockedStderr, | |
| 717 | unlockStderr: *const fn (?*anyopaque) void, | |
| 725 | 718 | |
| 726 | 719 | now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, |
| 727 | 720 | sleep: *const fn (?*anyopaque, Timeout) SleepError!void, |
| ... | ... | @@ -763,6 +756,7 @@ pub const UnexpectedError = error{ |
| 763 | 756 | |
| 764 | 757 | pub const Dir = @import("Io/Dir.zig"); |
| 765 | 758 | pub const File = @import("Io/File.zig"); |
| 759 | pub const Terminal = @import("Io/Terminal.zig"); | |
| 766 | 760 | |
| 767 | 761 | pub const Clock = enum { |
| 768 | 762 | /// 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)) { |
| 2177 | 2171 | } |
| 2178 | 2172 | } |
| 2179 | 2173 | |
| 2174 | pub const LockedStderr = struct { | |
| 2175 | file_writer: *File.Writer, | |
| 2176 | terminal_mode: Terminal.Mode, | |
| 2177 | ||
| 2178 | pub fn terminal(ls: LockedStderr) Terminal { | |
| 2179 | return .{ | |
| 2180 | .writer = &ls.file_writer.interface, | |
| 2181 | .mode = ls.terminal_mode, | |
| 2182 | }; | |
| 2183 | } | |
| 2184 | }; | |
| 2185 | ||
| 2180 | 2186 | /// For doing application-level writes to the standard error stream. |
| 2181 | 2187 | /// Coordinates also with debug-level writes that are ignorant of Io interface |
| 2182 | /// and implementations. When this returns, `stderr_thread_mutex` will be | |
| 2183 | /// locked. | |
| 2188 | /// and implementations. When this returns, `std.process.stderr_thread_mutex` | |
| 2189 | /// will be locked. | |
| 2184 | 2190 | /// |
| 2185 | 2191 | /// See also: |
| 2186 | /// * `tryLockStderrWriter` | |
| 2187 | pub fn lockStderrWriter(io: Io, buffer: []u8) Cancelable!*File.Writer { | |
| 2188 | return io.vtable.lockStderrWriter(io.userdata, buffer); | |
| 2192 | /// * `tryLockStderr` | |
| 2193 | pub fn lockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!LockedStderr { | |
| 2194 | return io.vtable.lockStderr(io.userdata, buffer, terminal_mode); | |
| 2189 | 2195 | } |
| 2190 | 2196 | |
| 2191 | /// Same as `lockStderrWriter` but uncancelable and non-blocking. | |
| 2192 | pub fn tryLockStderrWriter(io: Io, buffer: []u8) ?*File.Writer { | |
| 2193 | return io.vtable.tryLockStderrWriter(io.userdata, buffer); | |
| 2197 | /// Same as `lockStderr` but non-blocking. | |
| 2198 | pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancelable!?LockedStderr { | |
| 2199 | return io.vtable.tryLockStderr(io.userdata, buffer, terminal_mode); | |
| 2194 | 2200 | } |
| 2195 | 2201 | |
| 2196 | pub fn unlockStderrWriter(io: Io) void { | |
| 2197 | return io.vtable.unlockStderrWriter(io.userdata); | |
| 2202 | pub fn unlockStderr(io: Io) void { | |
| 2203 | return io.vtable.unlockStderr(io.userdata); | |
| 2198 | 2204 | } |
lib/std/Io/File/Reader.zig+17-17| ... | ... | @@ -64,24 +64,24 @@ pub const Mode = enum { |
| 64 | 64 | streaming, |
| 65 | 65 | positional, |
| 66 | 66 | /// Avoid syscalls other than `read` and `readv`. |
| 67 | streaming_reading, | |
| 67 | streaming_simple, | |
| 68 | 68 | /// Avoid syscalls other than `pread` and `preadv`. |
| 69 | positional_reading, | |
| 69 | positional_simple, | |
| 70 | 70 | /// Indicates reading cannot continue because of a seek failure. |
| 71 | 71 | failure, |
| 72 | 72 | |
| 73 | 73 | pub fn toStreaming(m: @This()) @This() { |
| 74 | 74 | return switch (m) { |
| 75 | 75 | .positional, .streaming => .streaming, |
| 76 | .positional_reading, .streaming_reading => .streaming_reading, | |
| 76 | .positional_simple, .streaming_simple => .streaming_simple, | |
| 77 | 77 | .failure => .failure, |
| 78 | 78 | }; |
| 79 | 79 | } |
| 80 | 80 | |
| 81 | pub fn toReading(m: @This()) @This() { | |
| 81 | pub fn toSimple(m: @This()) @This() { | |
| 82 | 82 | return switch (m) { |
| 83 | .positional, .positional_reading => .positional_reading, | |
| 84 | .streaming, .streaming_reading => .streaming_reading, | |
| 83 | .positional, .positional_simple => .positional_simple, | |
| 84 | .streaming, .streaming_simple => .streaming_simple, | |
| 85 | 85 | .failure => .failure, |
| 86 | 86 | }; |
| 87 | 87 | } |
| ... | ... | @@ -153,10 +153,10 @@ pub fn getSize(r: *Reader) SizeError!u64 { |
| 153 | 153 | pub fn seekBy(r: *Reader, offset: i64) SeekError!void { |
| 154 | 154 | const io = r.io; |
| 155 | 155 | switch (r.mode) { |
| 156 | .positional, .positional_reading => { | |
| 156 | .positional, .positional_simple => { | |
| 157 | 157 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); |
| 158 | 158 | }, |
| 159 | .streaming, .streaming_reading => { | |
| 159 | .streaming, .streaming_simple => { | |
| 160 | 160 | const seek_err = r.seek_err orelse e: { |
| 161 | 161 | if (io.vtable.fileSeekBy(io.userdata, r.file, offset)) |_| { |
| 162 | 162 | setLogicalPos(r, @intCast(@as(i64, @intCast(logicalPos(r))) + offset)); |
| ... | ... | @@ -183,10 +183,10 @@ pub fn seekBy(r: *Reader, offset: i64) SeekError!void { |
| 183 | 183 | pub fn seekTo(r: *Reader, offset: u64) SeekError!void { |
| 184 | 184 | const io = r.io; |
| 185 | 185 | switch (r.mode) { |
| 186 | .positional, .positional_reading => { | |
| 186 | .positional, .positional_simple => { | |
| 187 | 187 | setLogicalPos(r, offset); |
| 188 | 188 | }, |
| 189 | .streaming, .streaming_reading => { | |
| 189 | .streaming, .streaming_simple => { | |
| 190 | 190 | const logical_pos = logicalPos(r); |
| 191 | 191 | if (offset >= logical_pos) return seekBy(r, @intCast(offset - logical_pos)); |
| 192 | 192 | 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 |
| 225 | 225 | switch (mode) { |
| 226 | 226 | .positional, .streaming => return w.sendFile(r, limit) catch |write_err| switch (write_err) { |
| 227 | 227 | error.Unimplemented => { |
| 228 | r.mode = r.mode.toReading(); | |
| 228 | r.mode = r.mode.toSimple(); | |
| 229 | 229 | return 0; |
| 230 | 230 | }, |
| 231 | 231 | else => |e| return e, |
| 232 | 232 | }, |
| 233 | .positional_reading => { | |
| 233 | .positional_simple => { | |
| 234 | 234 | const dest = limit.slice(try w.writableSliceGreedy(1)); |
| 235 | 235 | var data: [1][]u8 = .{dest}; |
| 236 | 236 | const n = try readVecPositional(r, &data); |
| 237 | 237 | w.advance(n); |
| 238 | 238 | return n; |
| 239 | 239 | }, |
| 240 | .streaming_reading => { | |
| 240 | .streaming_simple => { | |
| 241 | 241 | const dest = limit.slice(try w.writableSliceGreedy(1)); |
| 242 | 242 | var data: [1][]u8 = .{dest}; |
| 243 | 243 | 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 |
| 251 | 251 | fn readVec(io_reader: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { |
| 252 | 252 | const r: *Reader = @alignCast(@fieldParentPtr("interface", io_reader)); |
| 253 | 253 | switch (r.mode) { |
| 254 | .positional, .positional_reading => return readVecPositional(r, data), | |
| 255 | .streaming, .streaming_reading => return readVecStreaming(r, data), | |
| 254 | .positional, .positional_simple => return readVecPositional(r, data), | |
| 255 | .streaming, .streaming_simple => return readVecStreaming(r, data), | |
| 256 | 256 | .failure => return error.ReadFailed, |
| 257 | 257 | } |
| 258 | 258 | } |
| ... | ... | @@ -320,7 +320,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { |
| 320 | 320 | const io = r.io; |
| 321 | 321 | const file = r.file; |
| 322 | 322 | switch (r.mode) { |
| 323 | .positional, .positional_reading => { | |
| 323 | .positional, .positional_simple => { | |
| 324 | 324 | const size = r.getSize() catch { |
| 325 | 325 | r.mode = r.mode.toStreaming(); |
| 326 | 326 | return 0; |
| ... | ... | @@ -330,7 +330,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { |
| 330 | 330 | setLogicalPos(r, logical_pos + delta); |
| 331 | 331 | return delta; |
| 332 | 332 | }, |
| 333 | .streaming, .streaming_reading => { | |
| 333 | .streaming, .streaming_simple => { | |
| 334 | 334 | // Unfortunately we can't seek forward without knowing the |
| 335 | 335 | // size because the seek syscalls provided to us will not |
| 336 | 336 | // return the true end position if a seek would exceed the |
lib/std/Io/File/Writer.zig+3-247| ... | ... | @@ -18,172 +18,7 @@ write_file_err: ?WriteFileError = null, |
| 18 | 18 | seek_err: ?SeekError = null, |
| 19 | 19 | interface: Io.Writer, |
| 20 | 20 | |
| 21 | pub const Mode = union(enum) { | |
| 22 | /// Uses `Io.VTable.fileWriteFileStreaming` if possible. Not a terminal. | |
| 23 | /// `setColor` does nothing. | |
| 24 | streaming, | |
| 25 | /// Uses `Io.VTable.fileWriteFilePositional` if possible. Not a terminal. | |
| 26 | /// `setColor` does nothing. | |
| 27 | positional, | |
| 28 | /// Avoids `Io.VTable.fileWriteFileStreaming`. Not a terminal. `setColor` | |
| 29 | /// does nothing. | |
| 30 | streaming_simple, | |
| 31 | /// Avoids `Io.VTable.fileWriteFilePositional`. Not a terminal. `setColor` | |
| 32 | /// does nothing. | |
| 33 | positional_simple, | |
| 34 | /// It's a terminal. Writes are escaped so as to strip escape sequences. | |
| 35 | /// Color is enabled. | |
| 36 | terminal_escaped, | |
| 37 | /// It's a terminal. Colors are enabled via calling | |
| 38 | /// SetConsoleTextAttribute. Writes are not escaped. | |
| 39 | terminal_winapi: TerminalWinapi, | |
| 40 | /// Indicates writing cannot continue because of a seek failure. | |
| 41 | failure, | |
| 42 | ||
| 43 | pub fn toStreaming(m: @This()) @This() { | |
| 44 | return switch (m) { | |
| 45 | .positional, .streaming => .streaming, | |
| 46 | .positional_simple, .streaming_simple => .streaming_simple, | |
| 47 | inline else => |_, x| x, | |
| 48 | }; | |
| 49 | } | |
| 50 | ||
| 51 | pub fn toSimple(m: @This()) @This() { | |
| 52 | return switch (m) { | |
| 53 | .positional, .positional_simple => .positional_simple, | |
| 54 | .streaming, .streaming_simple => .streaming_simple, | |
| 55 | inline else => |_, x| x, | |
| 56 | }; | |
| 57 | } | |
| 58 | ||
| 59 | pub fn toUnescaped(m: @This()) @This() { | |
| 60 | return switch (m) { | |
| 61 | .terminal_escaped => .streaming_simple, | |
| 62 | inline else => |_, x| x, | |
| 63 | }; | |
| 64 | } | |
| 65 | ||
| 66 | pub const TerminalWinapi = if (!is_windows) noreturn else struct { | |
| 67 | handle: File.Handle, | |
| 68 | reset_attributes: u16, | |
| 69 | }; | |
| 70 | ||
| 71 | /// Detect suitable TTY configuration options for the given file (commonly | |
| 72 | /// stdout/stderr). | |
| 73 | /// | |
| 74 | /// Will attempt to enable ANSI escape code support if necessary/possible. | |
| 75 | pub fn detect(io: Io, file: File, want_color: bool, fallback: Mode) Io.Cancelable!Mode { | |
| 76 | if (!want_color) return if (try file.isTty(io)) .terminal_escaped else fallback; | |
| 77 | ||
| 78 | if (file.enableAnsiEscapeCodes(io)) |_| { | |
| 79 | return .terminal_escaped; | |
| 80 | } else |err| switch (err) { | |
| 81 | error.Canceled => return error.Canceled, | |
| 82 | error.NotTerminalDevice, error.Unexpected => {}, | |
| 83 | } | |
| 84 | ||
| 85 | if (is_windows and file.isTty(io)) { | |
| 86 | const windows = std.os.windows; | |
| 87 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 88 | if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) { | |
| 89 | return .{ .terminal_winapi = .{ | |
| 90 | .handle = file.handle, | |
| 91 | .reset_attributes = info.wAttributes, | |
| 92 | } }; | |
| 93 | } | |
| 94 | return .terminal_escaped; | |
| 95 | } | |
| 96 | ||
| 97 | return fallback; | |
| 98 | } | |
| 99 | ||
| 100 | pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; | |
| 101 | ||
| 102 | pub fn setColor(mode: Mode, io_w: *Io.Writer, color: Color) SetColorError!void { | |
| 103 | switch (mode) { | |
| 104 | .streaming, .positional, .streaming_simple, .positional_simple, .failure => return, | |
| 105 | .terminal_escaped => { | |
| 106 | const color_string = switch (color) { | |
| 107 | .black => "\x1b[30m", | |
| 108 | .red => "\x1b[31m", | |
| 109 | .green => "\x1b[32m", | |
| 110 | .yellow => "\x1b[33m", | |
| 111 | .blue => "\x1b[34m", | |
| 112 | .magenta => "\x1b[35m", | |
| 113 | .cyan => "\x1b[36m", | |
| 114 | .white => "\x1b[37m", | |
| 115 | .bright_black => "\x1b[90m", | |
| 116 | .bright_red => "\x1b[91m", | |
| 117 | .bright_green => "\x1b[92m", | |
| 118 | .bright_yellow => "\x1b[93m", | |
| 119 | .bright_blue => "\x1b[94m", | |
| 120 | .bright_magenta => "\x1b[95m", | |
| 121 | .bright_cyan => "\x1b[96m", | |
| 122 | .bright_white => "\x1b[97m", | |
| 123 | .bold => "\x1b[1m", | |
| 124 | .dim => "\x1b[2m", | |
| 125 | .reset => "\x1b[0m", | |
| 126 | }; | |
| 127 | try io_w.writeAll(color_string); | |
| 128 | }, | |
| 129 | .terminal_winapi => |ctx| { | |
| 130 | const windows = std.os.windows; | |
| 131 | const attributes: windows.WORD = switch (color) { | |
| 132 | .black => 0, | |
| 133 | .red => windows.FOREGROUND_RED, | |
| 134 | .green => windows.FOREGROUND_GREEN, | |
| 135 | .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, | |
| 136 | .blue => windows.FOREGROUND_BLUE, | |
| 137 | .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, | |
| 138 | .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 139 | .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 140 | .bright_black => windows.FOREGROUND_INTENSITY, | |
| 141 | .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, | |
| 142 | .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 143 | .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 144 | .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 145 | .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 146 | .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 147 | .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 148 | // "dim" is not supported using basic character attributes, but let's still make it do *something*. | |
| 149 | // This matches the old behavior of TTY.Color before the bright variants were added. | |
| 150 | .dim => windows.FOREGROUND_INTENSITY, | |
| 151 | .reset => ctx.reset_attributes, | |
| 152 | }; | |
| 153 | try io_w.flush(); | |
| 154 | try windows.SetConsoleTextAttribute(ctx.handle, attributes); | |
| 155 | }, | |
| 156 | } | |
| 157 | } | |
| 158 | ||
| 159 | fn DecorateArgs(comptime Args: type) type { | |
| 160 | const fields = @typeInfo(Args).@"struct".fields; | |
| 161 | var new_fields: [fields.len]type = undefined; | |
| 162 | for (fields, &new_fields) |old, *new| { | |
| 163 | if (old.type == std.debug.FormatStackTrace) { | |
| 164 | new.* = std.debug.FormatStackTrace.Decorated; | |
| 165 | } else { | |
| 166 | new.* = old.type; | |
| 167 | } | |
| 168 | } | |
| 169 | return @Tuple(&new_fields); | |
| 170 | } | |
| 171 | ||
| 172 | pub fn decorateArgs(file_writer_mode: std.Io.File.Writer.Mode, args: anytype) DecorateArgs(@TypeOf(args)) { | |
| 173 | var new_args: DecorateArgs(@TypeOf(args)) = undefined; | |
| 174 | inline for (args, &new_args) |old, *new| { | |
| 175 | if (@TypeOf(old) == std.debug.FormatStackTrace) { | |
| 176 | new.* = .{ | |
| 177 | .stack_trace = old.stack_trace, | |
| 178 | .file_writer_mode = file_writer_mode, | |
| 179 | }; | |
| 180 | } else { | |
| 181 | new.* = old; | |
| 182 | } | |
| 183 | } | |
| 184 | return new_args; | |
| 185 | } | |
| 186 | }; | |
| 21 | pub const Mode = File.Reader.Mode; | |
| 187 | 22 | |
| 188 | 23 | pub const Error = error{ |
| 189 | 24 | DiskQuota, |
| ... | ... | @@ -277,8 +112,7 @@ pub fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer |
| 277 | 112 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); |
| 278 | 113 | switch (w.mode) { |
| 279 | 114 | .positional, .positional_simple => return drainPositional(w, data, splat), |
| 280 | .streaming, .streaming_simple, .terminal_winapi => return drainStreaming(w, data, splat), | |
| 281 | .terminal_escaped => return drainEscaping(w, data, splat), | |
| 115 | .streaming, .streaming_simple => return drainStreaming(w, data, splat), | |
| 282 | 116 | .failure => return error.WriteFailed, |
| 283 | 117 | } |
| 284 | 118 | } |
| ... | ... | @@ -319,38 +153,13 @@ fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer. |
| 319 | 153 | return w.interface.consume(n); |
| 320 | 154 | } |
| 321 | 155 | |
| 322 | fn findTerminalEscape(buffer: []const u8) ?usize { | |
| 323 | return std.mem.findScalar(u8, buffer, 0x1b); | |
| 324 | } | |
| 325 | ||
| 326 | fn drainEscaping(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { | |
| 327 | const io = w.io; | |
| 328 | const header = w.interface.buffered(); | |
| 329 | if (findTerminalEscape(header)) |i| { | |
| 330 | _ = i; | |
| 331 | // TODO strip terminal escape sequences here | |
| 332 | } | |
| 333 | for (data) |d| { | |
| 334 | if (findTerminalEscape(d)) |i| { | |
| 335 | _ = i; | |
| 336 | // TODO strip terminal escape sequences here | |
| 337 | } | |
| 338 | } | |
| 339 | const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| { | |
| 340 | w.err = err; | |
| 341 | return error.WriteFailed; | |
| 342 | }; | |
| 343 | w.pos += n; | |
| 344 | return w.interface.consume(n); | |
| 345 | } | |
| 346 | ||
| 347 | 156 | pub fn sendFile(io_w: *Io.Writer, file_reader: *Io.File.Reader, limit: Io.Limit) Io.Writer.FileError!usize { |
| 348 | 157 | const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); |
| 349 | 158 | switch (w.mode) { |
| 350 | 159 | .positional => return sendFilePositional(w, file_reader, limit), |
| 351 | 160 | .positional_simple => return error.Unimplemented, |
| 352 | 161 | .streaming => return sendFileStreaming(w, file_reader, limit), |
| 353 | .streaming_simple, .terminal_escaped, .terminal_winapi => return error.Unimplemented, | |
| 162 | .streaming_simple => return error.Unimplemented, | |
| 354 | 163 | .failure => return error.WriteFailed, |
| 355 | 164 | } |
| 356 | 165 | } |
| ... | ... | @@ -454,60 +263,7 @@ pub fn end(w: *Writer) EndError!void { |
| 454 | 263 | |
| 455 | 264 | .streaming, |
| 456 | 265 | .streaming_simple, |
| 457 | .terminal_escaped, | |
| 458 | .terminal_winapi, | |
| 459 | 266 | .failure, |
| 460 | 267 | => {}, |
| 461 | 268 | } |
| 462 | 269 | } |
| 463 | ||
| 464 | pub const Color = enum { | |
| 465 | black, | |
| 466 | red, | |
| 467 | green, | |
| 468 | yellow, | |
| 469 | blue, | |
| 470 | magenta, | |
| 471 | cyan, | |
| 472 | white, | |
| 473 | bright_black, | |
| 474 | bright_red, | |
| 475 | bright_green, | |
| 476 | bright_yellow, | |
| 477 | bright_blue, | |
| 478 | bright_magenta, | |
| 479 | bright_cyan, | |
| 480 | bright_white, | |
| 481 | dim, | |
| 482 | bold, | |
| 483 | reset, | |
| 484 | }; | |
| 485 | ||
| 486 | pub fn setColor(w: *Writer, color: Color) Io.Writer.Error!void { | |
| 487 | return w.mode.setColor(&w.interface, color) catch |err| switch (err) { | |
| 488 | error.WriteFailed => |e| return e, | |
| 489 | else => |e| w.err = e, | |
| 490 | }; | |
| 491 | } | |
| 492 | ||
| 493 | pub fn disableEscape(w: *Writer) Mode { | |
| 494 | const prev = w.mode; | |
| 495 | w.mode = w.mode.toUnescaped(); | |
| 496 | return prev; | |
| 497 | } | |
| 498 | ||
| 499 | pub fn restoreEscape(w: *Writer, mode: Mode) void { | |
| 500 | w.mode = mode; | |
| 501 | } | |
| 502 | ||
| 503 | pub fn writeAllUnescaped(w: *Writer, bytes: []const u8) Io.Writer.Error!void { | |
| 504 | const prev_mode = w.disableEscape(); | |
| 505 | defer w.restoreEscape(prev_mode); | |
| 506 | return w.interface.writeAll(bytes); | |
| 507 | } | |
| 508 | ||
| 509 | pub fn printUnescaped(w: *Writer, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void { | |
| 510 | const prev_mode = w.disableEscape(); | |
| 511 | defer w.restoreEscape(prev_mode); | |
| 512 | return w.interface.print(fmt, args); | |
| 513 | } |
lib/std/Io/Terminal.zig created+154| ... | ... | @@ -0,0 +1,154 @@ |
| 1 | /// Abstraction for writing to a stream that might support terminal escape | |
| 2 | /// codes. | |
| 3 | const Terminal = @This(); | |
| 4 | ||
| 5 | const builtin = @import("builtin"); | |
| 6 | const is_windows = builtin.os.tag == .windows; | |
| 7 | ||
| 8 | const std = @import("std"); | |
| 9 | const Io = std.Io; | |
| 10 | const File = std.Io.File; | |
| 11 | ||
| 12 | writer: *Io.Writer, | |
| 13 | mode: Mode, | |
| 14 | ||
| 15 | pub const Color = enum { | |
| 16 | black, | |
| 17 | red, | |
| 18 | green, | |
| 19 | yellow, | |
| 20 | blue, | |
| 21 | magenta, | |
| 22 | cyan, | |
| 23 | white, | |
| 24 | bright_black, | |
| 25 | bright_red, | |
| 26 | bright_green, | |
| 27 | bright_yellow, | |
| 28 | bright_blue, | |
| 29 | bright_magenta, | |
| 30 | bright_cyan, | |
| 31 | bright_white, | |
| 32 | dim, | |
| 33 | bold, | |
| 34 | reset, | |
| 35 | }; | |
| 36 | ||
| 37 | pub const Mode = union(enum) { | |
| 38 | no_color, | |
| 39 | escape_codes, | |
| 40 | windows_api: WindowsApi, | |
| 41 | ||
| 42 | pub const WindowsApi = if (!is_windows) noreturn else struct { | |
| 43 | handle: File.Handle, | |
| 44 | reset_attributes: u16, | |
| 45 | }; | |
| 46 | ||
| 47 | /// Detect suitable TTY configuration options for the given file (commonly | |
| 48 | /// stdout/stderr). | |
| 49 | /// | |
| 50 | /// Will attempt to enable ANSI escape code support if necessary/possible. | |
| 51 | pub fn detect(io: Io, file: File) Io.Cancelable!Mode { | |
| 52 | if (file.enableAnsiEscapeCodes(io)) |_| { | |
| 53 | return .escape_codes; | |
| 54 | } else |err| switch (err) { | |
| 55 | error.Canceled => return error.Canceled, | |
| 56 | error.NotTerminalDevice, error.Unexpected => {}, | |
| 57 | } | |
| 58 | ||
| 59 | if (is_windows and file.isTty(io)) { | |
| 60 | const windows = std.os.windows; | |
| 61 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | |
| 62 | if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) { | |
| 63 | return .{ .terminal_winapi = .{ | |
| 64 | .handle = file.handle, | |
| 65 | .reset_attributes = info.wAttributes, | |
| 66 | } }; | |
| 67 | } | |
| 68 | return .escape_codes; | |
| 69 | } | |
| 70 | ||
| 71 | return .no_color; | |
| 72 | } | |
| 73 | }; | |
| 74 | ||
| 75 | pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; | |
| 76 | ||
| 77 | pub fn setColor(t: Terminal, color: Color) Io.Writer.Error!void { | |
| 78 | switch (t.mode) { | |
| 79 | .no_color => return, | |
| 80 | .escape_codes => { | |
| 81 | const color_string = switch (color) { | |
| 82 | .black => "\x1b[30m", | |
| 83 | .red => "\x1b[31m", | |
| 84 | .green => "\x1b[32m", | |
| 85 | .yellow => "\x1b[33m", | |
| 86 | .blue => "\x1b[34m", | |
| 87 | .magenta => "\x1b[35m", | |
| 88 | .cyan => "\x1b[36m", | |
| 89 | .white => "\x1b[37m", | |
| 90 | .bright_black => "\x1b[90m", | |
| 91 | .bright_red => "\x1b[91m", | |
| 92 | .bright_green => "\x1b[92m", | |
| 93 | .bright_yellow => "\x1b[93m", | |
| 94 | .bright_blue => "\x1b[94m", | |
| 95 | .bright_magenta => "\x1b[95m", | |
| 96 | .bright_cyan => "\x1b[96m", | |
| 97 | .bright_white => "\x1b[97m", | |
| 98 | .bold => "\x1b[1m", | |
| 99 | .dim => "\x1b[2m", | |
| 100 | .reset => "\x1b[0m", | |
| 101 | }; | |
| 102 | try t.writer.writeAll(color_string); | |
| 103 | }, | |
| 104 | .windows_api => |wa| { | |
| 105 | const windows = std.os.windows; | |
| 106 | const attributes: windows.WORD = switch (color) { | |
| 107 | .black => 0, | |
| 108 | .red => windows.FOREGROUND_RED, | |
| 109 | .green => windows.FOREGROUND_GREEN, | |
| 110 | .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN, | |
| 111 | .blue => windows.FOREGROUND_BLUE, | |
| 112 | .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE, | |
| 113 | .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 114 | .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE, | |
| 115 | .bright_black => windows.FOREGROUND_INTENSITY, | |
| 116 | .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY, | |
| 117 | .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 118 | .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY, | |
| 119 | .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 120 | .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 121 | .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 122 | .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY, | |
| 123 | // "dim" is not supported using basic character attributes, but let's still make it do *something*. | |
| 124 | // This matches the old behavior of TTY.Color before the bright variants were added. | |
| 125 | .dim => windows.FOREGROUND_INTENSITY, | |
| 126 | .reset => wa.reset_attributes, | |
| 127 | }; | |
| 128 | try t.writer.flush(); | |
| 129 | try windows.SetConsoleTextAttribute(wa.handle, attributes); | |
| 130 | }, | |
| 131 | } | |
| 132 | } | |
| 133 | ||
| 134 | pub fn disableEscape(t: *Terminal) Mode { | |
| 135 | const prev = t.mode; | |
| 136 | t.mode = t.mode.toUnescaped(); | |
| 137 | return prev; | |
| 138 | } | |
| 139 | ||
| 140 | pub fn restoreEscape(t: *Terminal, mode: Mode) void { | |
| 141 | t.mode = mode; | |
| 142 | } | |
| 143 | ||
| 144 | pub fn writeAllUnescaped(t: *Terminal, bytes: []const u8) Io.Writer.Error!void { | |
| 145 | const prev_mode = t.disableEscape(); | |
| 146 | defer t.restoreEscape(prev_mode); | |
| 147 | return t.interface.writeAll(bytes); | |
| 148 | } | |
| 149 | ||
| 150 | pub fn printUnescaped(t: *Terminal, comptime fmt: []const u8, args: anytype) Io.Writer.Error!void { | |
| 151 | const prev_mode = t.disableEscape(); | |
| 152 | defer t.restoreEscape(prev_mode); | |
| 153 | return t.interface.print(fmt, args); | |
| 154 | } |
lib/std/Io/Threaded.zig+53-30| ... | ... | @@ -82,8 +82,8 @@ stderr_writer: File.Writer = .{ |
| 82 | 82 | .io = undefined, |
| 83 | 83 | .interface = Io.File.Writer.initInterface(&.{}), |
| 84 | 84 | .file = if (is_windows) undefined else .stderr(), |
| 85 | .mode = undefined, | |
| 86 | 85 | }, |
| 86 | stderr_mode: Io.Terminal.Mode = .no_color, | |
| 87 | 87 | stderr_writer_initialized: bool = false, |
| 88 | 88 | |
| 89 | 89 | pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { |
| ... | ... | @@ -755,9 +755,9 @@ pub fn io(t: *Threaded) Io { |
| 755 | 755 | |
| 756 | 756 | .processExecutableOpen = processExecutableOpen, |
| 757 | 757 | .processExecutablePath = processExecutablePath, |
| 758 | .lockStderrWriter = lockStderrWriter, | |
| 759 | .tryLockStderrWriter = tryLockStderrWriter, | |
| 760 | .unlockStderrWriter = unlockStderrWriter, | |
| 758 | .lockStderr = lockStderr, | |
| 759 | .tryLockStderr = tryLockStderr, | |
| 760 | .unlockStderr = unlockStderr, | |
| 761 | 761 | |
| 762 | 762 | .now = now, |
| 763 | 763 | .sleep = sleep, |
| ... | ... | @@ -887,9 +887,9 @@ pub fn ioBasic(t: *Threaded) Io { |
| 887 | 887 | |
| 888 | 888 | .processExecutableOpen = processExecutableOpen, |
| 889 | 889 | .processExecutablePath = processExecutablePath, |
| 890 | .lockStderrWriter = lockStderrWriter, | |
| 891 | .tryLockStderrWriter = tryLockStderrWriter, | |
| 892 | .unlockStderrWriter = unlockStderrWriter, | |
| 890 | .lockStderr = lockStderr, | |
| 891 | .tryLockStderr = tryLockStderr, | |
| 892 | .unlockStderr = unlockStderr, | |
| 893 | 893 | |
| 894 | 894 | .now = now, |
| 895 | 895 | .sleep = sleep, |
| ... | ... | @@ -10090,47 +10090,70 @@ fn netLookupFallible( |
| 10090 | 10090 | return error.OptionUnsupported; |
| 10091 | 10091 | } |
| 10092 | 10092 | |
| 10093 | fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer { | |
| 10093 | fn lockStderr( | |
| 10094 | userdata: ?*anyopaque, | |
| 10095 | buffer: []u8, | |
| 10096 | terminal_mode: ?Io.Terminal.Mode, | |
| 10097 | ) Io.Cancelable!Io.LockedStderr { | |
| 10094 | 10098 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10095 | 10099 | // Only global mutex since this is Threaded. |
| 10096 | Io.stderr_thread_mutex.lock(); | |
| 10097 | if (!t.stderr_writer_initialized) { | |
| 10098 | const io_t = ioBasic(t); | |
| 10099 | if (is_windows) t.stderr_writer.file = .stderr(); | |
| 10100 | t.stderr_writer.io = io_t; | |
| 10101 | t.stderr_writer.mode = try .detect(io_t, t.stderr_writer.file, true, .streaming_simple); | |
| 10102 | t.stderr_writer_initialized = true; | |
| 10103 | } | |
| 10104 | std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; | |
| 10105 | t.stderr_writer.interface.flush() catch {}; | |
| 10106 | t.stderr_writer.interface.buffer = buffer; | |
| 10107 | return &t.stderr_writer; | |
| 10100 | std.process.stderr_thread_mutex.lock(); | |
| 10101 | return initLockedStderr(t, buffer, terminal_mode); | |
| 10108 | 10102 | } |
| 10109 | 10103 | |
| 10110 | fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer { | |
| 10104 | fn tryLockStderr( | |
| 10105 | userdata: ?*anyopaque, | |
| 10106 | buffer: []u8, | |
| 10107 | terminal_mode: ?Io.Terminal.Mode, | |
| 10108 | ) Io.Cancelable!?Io.LockedStderr { | |
| 10111 | 10109 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10112 | 10110 | // Only global mutex since this is Threaded. |
| 10113 | if (!Io.stderr_thread_mutex.tryLock()) return null; | |
| 10111 | if (!std.process.stderr_thread_mutex.tryLock()) return null; | |
| 10112 | return try initLockedStderr(t, buffer, terminal_mode); | |
| 10113 | } | |
| 10114 | ||
| 10115 | fn initLockedStderr( | |
| 10116 | t: *Threaded, | |
| 10117 | buffer: []u8, | |
| 10118 | terminal_mode: ?Io.Terminal.Mode, | |
| 10119 | ) Io.Cancelable!Io.LockedStderr { | |
| 10114 | 10120 | if (!t.stderr_writer_initialized) { |
| 10115 | 10121 | const io_t = ioBasic(t); |
| 10116 | 10122 | if (is_windows) t.stderr_writer.file = .stderr(); |
| 10117 | 10123 | t.stderr_writer.io = io_t; |
| 10118 | t.stderr_writer.mode = File.Writer.Mode.detect(io_t, t.stderr_writer.file, true, .streaming_simple) catch | |
| 10119 | return null; | |
| 10120 | 10124 | t.stderr_writer_initialized = true; |
| 10125 | t.stderr_mode = terminal_mode orelse try .detect(io_t, t.stderr_writer.file); | |
| 10121 | 10126 | } |
| 10122 | std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; | |
| 10123 | t.stderr_writer.interface.flush() catch {}; | |
| 10127 | std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch |err| switch (err) { | |
| 10128 | error.WriteFailed => switch (t.stderr_writer.err.?) { | |
| 10129 | error.Canceled => |e| return e, | |
| 10130 | else => {}, | |
| 10131 | }, | |
| 10132 | }; | |
| 10133 | t.stderr_writer.interface.flush() catch |err| switch (err) { | |
| 10134 | error.WriteFailed => switch (t.stderr_writer.err.?) { | |
| 10135 | error.Canceled => |e| return e, | |
| 10136 | else => {}, | |
| 10137 | }, | |
| 10138 | }; | |
| 10124 | 10139 | t.stderr_writer.interface.buffer = buffer; |
| 10125 | return &t.stderr_writer; | |
| 10140 | return .{ | |
| 10141 | .file_writer = &t.stderr_writer, | |
| 10142 | .terminal_mode = t.stderr_mode, | |
| 10143 | }; | |
| 10126 | 10144 | } |
| 10127 | 10145 | |
| 10128 | fn unlockStderrWriter(userdata: ?*anyopaque) void { | |
| 10146 | fn unlockStderr(userdata: ?*anyopaque) void { | |
| 10129 | 10147 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10130 | t.stderr_writer.interface.flush() catch {}; | |
| 10148 | t.stderr_writer.interface.flush() catch |err| switch (err) { | |
| 10149 | error.WriteFailed => switch (t.stderr_writer.err.?) { | |
| 10150 | error.Canceled => @panic("TODO make this uncancelable"), | |
| 10151 | else => {}, | |
| 10152 | }, | |
| 10153 | }; | |
| 10131 | 10154 | t.stderr_writer.interface.end = 0; |
| 10132 | 10155 | t.stderr_writer.interface.buffer = &.{}; |
| 10133 | Io.stderr_thread_mutex.unlock(); | |
| 10156 | std.process.stderr_thread_mutex.unlock(); | |
| 10134 | 10157 | } |
| 10135 | 10158 | |
| 10136 | 10159 | pub const PosixAddress = extern union { |
lib/std/Io/Writer.zig+1-1| ... | ... | @@ -961,7 +961,7 @@ pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllE |
| 961 | 961 | const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) { |
| 962 | 962 | error.EndOfStream => break, |
| 963 | 963 | error.Unimplemented => { |
| 964 | file_reader.mode = file_reader.mode.toReading(); | |
| 964 | file_reader.mode = file_reader.mode.toSimple(); | |
| 965 | 965 | remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining)); |
| 966 | 966 | break; |
| 967 | 967 | }, |
lib/std/Progress.zig+18-18| ... | ... | @@ -565,10 +565,10 @@ fn updateThreadRun(io: Io) void { |
| 565 | 565 | maybeUpdateSize(resize_flag); |
| 566 | 566 | |
| 567 | 567 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 568 | if (io.tryLockStderrWriter(&.{})) |fw| { | |
| 569 | defer io.unlockStderrWriter(); | |
| 568 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 569 | defer io.unlockStderr(); | |
| 570 | 570 | global_progress.need_clear = true; |
| 571 | fw.writeAllUnescaped(buffer) catch return; | |
| 571 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 572 | 572 | } |
| 573 | 573 | } |
| 574 | 574 | |
| ... | ... | @@ -576,18 +576,18 @@ fn updateThreadRun(io: Io) void { |
| 576 | 576 | const resize_flag = wait(io, global_progress.refresh_rate_ns); |
| 577 | 577 | |
| 578 | 578 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { |
| 579 | const fw = io.lockStderrWriter(&.{}) catch return; | |
| 580 | defer io.unlockStderrWriter(); | |
| 581 | return clearWrittenWithEscapeCodes(fw) catch {}; | |
| 579 | const stderr = io.lockStderr(&.{}, null) catch return; | |
| 580 | defer io.unlockStderr(); | |
| 581 | return clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; | |
| 582 | 582 | } |
| 583 | 583 | |
| 584 | 584 | maybeUpdateSize(resize_flag); |
| 585 | 585 | |
| 586 | 586 | const buffer, _ = computeRedraw(&serialized_buffer); |
| 587 | if (io.tryLockStderrWriter(&.{})) |fw| { | |
| 588 | defer io.unlockStderrWriter(); | |
| 587 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 588 | defer io.unlockStderr(); | |
| 589 | 589 | global_progress.need_clear = true; |
| 590 | fw.writeAllUnescaped(buffer) catch return; | |
| 590 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 591 | 591 | } |
| 592 | 592 | } |
| 593 | 593 | } |
| ... | ... | @@ -609,11 +609,11 @@ fn windowsApiUpdateThreadRun(io: Io) void { |
| 609 | 609 | maybeUpdateSize(resize_flag); |
| 610 | 610 | |
| 611 | 611 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 612 | if (io.tryLockStderrWriter()) |fw| { | |
| 613 | defer io.unlockStderrWriter(); | |
| 612 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 613 | defer io.unlockStderr(); | |
| 614 | 614 | windowsApiWriteMarker(); |
| 615 | 615 | global_progress.need_clear = true; |
| 616 | fw.writeAllUnescaped(buffer) catch return; | |
| 616 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 617 | 617 | windowsApiMoveToMarker(nl_n) catch return; |
| 618 | 618 | } |
| 619 | 619 | } |
| ... | ... | @@ -622,20 +622,20 @@ fn windowsApiUpdateThreadRun(io: Io) void { |
| 622 | 622 | const resize_flag = wait(io, global_progress.refresh_rate_ns); |
| 623 | 623 | |
| 624 | 624 | if (@atomicLoad(bool, &global_progress.done, .monotonic)) { |
| 625 | _ = io.lockStderrWriter() catch return; | |
| 626 | defer io.unlockStderrWriter(); | |
| 625 | _ = io.lockStderr(&.{}, null) catch return; | |
| 626 | defer io.unlockStderr(); | |
| 627 | 627 | return clearWrittenWindowsApi() catch {}; |
| 628 | 628 | } |
| 629 | 629 | |
| 630 | 630 | maybeUpdateSize(resize_flag); |
| 631 | 631 | |
| 632 | 632 | const buffer, const nl_n = computeRedraw(&serialized_buffer); |
| 633 | if (io.tryLockStderrWriter()) |fw| { | |
| 634 | defer io.unlockStderrWriter(); | |
| 633 | if (io.tryLockStderr(&.{}, null) catch return) |locked_stderr| { | |
| 634 | defer io.unlockStderr(); | |
| 635 | 635 | clearWrittenWindowsApi() catch return; |
| 636 | 636 | windowsApiWriteMarker(); |
| 637 | 637 | global_progress.need_clear = true; |
| 638 | fw.writeAllUnescaped(buffer) catch return; | |
| 638 | locked_stderr.file_writer.interface.writeAll(buffer) catch return; | |
| 639 | 639 | windowsApiMoveToMarker(nl_n) catch return; |
| 640 | 640 | } |
| 641 | 641 | } |
| ... | ... | @@ -766,7 +766,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { |
| 766 | 766 | |
| 767 | 767 | pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error!void { |
| 768 | 768 | if (noop_impl or !global_progress.need_clear) return; |
| 769 | try file_writer.writeAllUnescaped(clear ++ progress_remove); | |
| 769 | try file_writer.interface.writeAll(clear ++ progress_remove); | |
| 770 | 770 | global_progress.need_clear = false; |
| 771 | 771 | } |
| 772 | 772 |
lib/std/debug.zig+114-118| ... | ... | @@ -265,29 +265,34 @@ pub const sys_can_stack_trace = switch (builtin.cpu.arch) { |
| 265 | 265 | /// separate from the application's `Io` instance. |
| 266 | 266 | var static_single_threaded_io: Io.Threaded = .init_single_threaded; |
| 267 | 267 | |
| 268 | /// Allows the caller to freely write to stderr until `unlockStderrWriter` is called. | |
| 268 | /// Allows the caller to freely write to stderr until `unlockStderr` is called. | |
| 269 | 269 | /// |
| 270 | 270 | /// During the lock, any `std.Progress` information is cleared from the terminal. |
| 271 | 271 | /// |
| 272 | /// The lock is recursive, so it is valid for the same thread to call `lockStderrWriter` multiple | |
| 273 | /// times. The primary motivation is that this allows the panic handler to safely dump the stack | |
| 274 | /// trace and panic message even if the mutex was held at the panic site. | |
| 272 | /// The lock is recursive, so it is valid for the same thread to call | |
| 273 | /// `lockStderr` multiple times, allowing the panic handler to safely | |
| 274 | /// dump the stack trace and panic message even if the mutex was held at the | |
| 275 | /// panic site. | |
| 275 | 276 | /// |
| 276 | 277 | /// The returned `Writer` does not need to be manually flushed: flushing is |
| 277 | /// performed automatically when the matching `unlockStderrWriter` call occurs. | |
| 278 | /// performed automatically when the matching `unlockStderr` call occurs. | |
| 278 | 279 | /// |
| 279 | 280 | /// This is a low-level debugging primitive that bypasses the `Io` interface, |
| 280 | 281 | /// writing directly to stderr using the most basic syscalls available. This |
| 281 | 282 | /// function does not switch threads, switch stacks, or suspend. |
| 282 | 283 | /// |
| 283 | /// Alternatively, use the higher-level `Io.lockStderrWriter` to integrate with | |
| 284 | /// the application's chosen `Io` implementation. | |
| 285 | pub fn lockStderrWriter(buffer: []u8) *File.Writer { | |
| 286 | return static_single_threaded_io.ioBasic().lockStderrWriter(buffer) catch unreachable; | |
| 284 | /// Alternatively, use the higher-level `Io.lockStderr` to integrate with the | |
| 285 | /// application's chosen `Io` implementation. | |
| 286 | pub fn lockStderr(buffer: []u8) Io.Terminal { | |
| 287 | return (static_single_threaded_io.ioBasic().lockStderr(buffer, null) catch |err| switch (err) { | |
| 288 | // Impossible to cancel because no calls to cancel using | |
| 289 | // `static_single_threaded_io` exist. | |
| 290 | error.Canceled => unreachable, | |
| 291 | }).terminal(); | |
| 287 | 292 | } |
| 288 | 293 | |
| 289 | pub fn unlockStderrWriter() void { | |
| 290 | static_single_threaded_io.ioBasic().unlockStderrWriter(); | |
| 294 | pub fn unlockStderr() void { | |
| 295 | static_single_threaded_io.ioBasic().unlockStderr(); | |
| 291 | 296 | } |
| 292 | 297 | |
| 293 | 298 | /// Writes to stderr, ignoring errors. |
| ... | ... | @@ -299,14 +304,14 @@ pub fn unlockStderrWriter() void { |
| 299 | 304 | /// Uses a 64-byte buffer for formatted printing which is flushed before this |
| 300 | 305 | /// function returns. |
| 301 | 306 | /// |
| 302 | /// Alternatively, use the higher-level `std.log` or `Io.lockStderrWriter` to | |
| 307 | /// Alternatively, use the higher-level `std.log` or `Io.lockStderr` to | |
| 303 | 308 | /// integrate with the application's chosen `Io` implementation. |
| 304 | 309 | pub fn print(comptime fmt: []const u8, args: anytype) void { |
| 305 | 310 | nosuspend { |
| 306 | 311 | var buffer: [64]u8 = undefined; |
| 307 | const stderr = lockStderrWriter(&buffer); | |
| 308 | defer unlockStderrWriter(); | |
| 309 | stderr.interface.print(fmt, stderr.mode.decorateArgs(args)) catch return; | |
| 312 | const stderr = lockStderr(&buffer); | |
| 313 | defer unlockStderr(); | |
| 314 | stderr.writer.print(fmt, args) catch return; | |
| 310 | 315 | } |
| 311 | 316 | } |
| 312 | 317 | |
| ... | ... | @@ -322,43 +327,44 @@ pub inline fn getSelfDebugInfo() !*SelfInfo { |
| 322 | 327 | /// Tries to print a hexadecimal view of the bytes, unbuffered, and ignores any error returned. |
| 323 | 328 | /// Obtains the stderr mutex while dumping. |
| 324 | 329 | pub fn dumpHex(bytes: []const u8) void { |
| 325 | const bw, const ttyconf = lockStderrWriter(&.{}); | |
| 326 | defer unlockStderrWriter(); | |
| 327 | dumpHexFallible(bw, ttyconf, bytes) catch {}; | |
| 330 | const stderr = lockStderr(&.{}); | |
| 331 | defer unlockStderr(); | |
| 332 | dumpHexFallible(stderr, bytes) catch {}; | |
| 328 | 333 | } |
| 329 | 334 | |
| 330 | 335 | /// Prints a hexadecimal view of the bytes, returning any error that occurs. |
| 331 | pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !void { | |
| 336 | pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void { | |
| 337 | const w = t.writer; | |
| 332 | 338 | var chunks = mem.window(u8, bytes, 16, 16); |
| 333 | 339 | while (chunks.next()) |window| { |
| 334 | 340 | // 1. Print the address. |
| 335 | 341 | const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10; |
| 336 | try fwm.setColor(bw, .dim); | |
| 342 | try t.setColor(.dim); | |
| 337 | 343 | // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. |
| 338 | 344 | // Also, make sure all lines are aligned by padding the address. |
| 339 | try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); | |
| 340 | try fwm.setColor(bw, .reset); | |
| 345 | try w.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); | |
| 346 | try t.setColor(.reset); | |
| 341 | 347 | |
| 342 | 348 | // 2. Print the bytes. |
| 343 | 349 | for (window, 0..) |byte, index| { |
| 344 | try bw.print("{X:0>2} ", .{byte}); | |
| 345 | if (index == 7) try bw.writeByte(' '); | |
| 350 | try w.print("{X:0>2} ", .{byte}); | |
| 351 | if (index == 7) try w.writeByte(' '); | |
| 346 | 352 | } |
| 347 | try bw.writeByte(' '); | |
| 353 | try w.writeByte(' '); | |
| 348 | 354 | if (window.len < 16) { |
| 349 | 355 | var missing_columns = (16 - window.len) * 3; |
| 350 | 356 | if (window.len < 8) missing_columns += 1; |
| 351 | try bw.splatByteAll(' ', missing_columns); | |
| 357 | try w.splatByteAll(' ', missing_columns); | |
| 352 | 358 | } |
| 353 | 359 | |
| 354 | 360 | // 3. Print the characters. |
| 355 | 361 | for (window) |byte| { |
| 356 | 362 | if (std.ascii.isPrint(byte)) { |
| 357 | try bw.writeByte(byte); | |
| 363 | try w.writeByte(byte); | |
| 358 | 364 | } else { |
| 359 | 365 | // Related: https://github.com/ziglang/zig/issues/7600 |
| 360 | if (fwm == .terminal_winapi) { | |
| 361 | try bw.writeByte('.'); | |
| 366 | if (t.mode == .windows_api) { | |
| 367 | try w.writeByte('.'); | |
| 362 | 368 | continue; |
| 363 | 369 | } |
| 364 | 370 | |
| ... | ... | @@ -366,14 +372,14 @@ pub fn dumpHexFallible(bw: *Writer, fwm: File.Writer.Mode, bytes: []const u8) !v |
| 366 | 372 | // We don't want to do this for all control codes because most control codes apart from |
| 367 | 373 | // the ones that Zig has escape sequences for are likely not very useful to print as symbols. |
| 368 | 374 | switch (byte) { |
| 369 | '\n' => try bw.writeAll("␊"), | |
| 370 | '\r' => try bw.writeAll("␍"), | |
| 371 | '\t' => try bw.writeAll("␉"), | |
| 372 | else => try bw.writeByte('.'), | |
| 375 | '\n' => try w.writeAll("␊"), | |
| 376 | '\r' => try w.writeAll("␍"), | |
| 377 | '\t' => try w.writeAll("␉"), | |
| 378 | else => try w.writeByte('.'), | |
| 373 | 379 | } |
| 374 | 380 | } |
| 375 | 381 | } |
| 376 | try bw.writeByte('\n'); | |
| 382 | try w.writeByte('\n'); | |
| 377 | 383 | } |
| 378 | 384 | } |
| 379 | 385 | |
| ... | ... | @@ -545,26 +551,27 @@ pub fn defaultPanic( |
| 545 | 551 | _ = panicking.fetchAdd(1, .seq_cst); |
| 546 | 552 | |
| 547 | 553 | trace: { |
| 548 | const stderr = lockStderrWriter(&.{}); | |
| 549 | defer unlockStderrWriter(); | |
| 554 | const stderr = lockStderr(&.{}); | |
| 555 | defer unlockStderr(); | |
| 556 | const writer = stderr.writer; | |
| 550 | 557 | |
| 551 | 558 | if (builtin.single_threaded) { |
| 552 | stderr.interface.print("panic: ", .{}) catch break :trace; | |
| 559 | writer.print("panic: ", .{}) catch break :trace; | |
| 553 | 560 | } else { |
| 554 | 561 | const current_thread_id = std.Thread.getCurrentId(); |
| 555 | stderr.interface.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; | |
| 562 | writer.print("thread {d} panic: ", .{current_thread_id}) catch break :trace; | |
| 556 | 563 | } |
| 557 | stderr.interface.print("{s}\n", .{msg}) catch break :trace; | |
| 564 | writer.print("{s}\n", .{msg}) catch break :trace; | |
| 558 | 565 | |
| 559 | 566 | if (@errorReturnTrace()) |t| if (t.index > 0) { |
| 560 | stderr.interface.writeAll("error return context:\n") catch break :trace; | |
| 561 | writeStackTrace(t, &stderr.interface, stderr.mode) catch break :trace; | |
| 562 | stderr.interface.writeAll("\nstack trace:\n") catch break :trace; | |
| 567 | writer.writeAll("error return context:\n") catch break :trace; | |
| 568 | writeStackTrace(t, stderr) catch break :trace; | |
| 569 | writer.writeAll("\nstack trace:\n") catch break :trace; | |
| 563 | 570 | }; |
| 564 | 571 | writeCurrentStackTrace(.{ |
| 565 | 572 | .first_address = first_trace_addr orelse @returnAddress(), |
| 566 | 573 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! |
| 567 | }, &stderr.interface, stderr.mode) catch break :trace; | |
| 574 | }, stderr) catch break :trace; | |
| 568 | 575 | } |
| 569 | 576 | |
| 570 | 577 | waitForOtherThreadToFinishPanicking(); |
| ... | ... | @@ -574,8 +581,8 @@ pub fn defaultPanic( |
| 574 | 581 | // A panic happened while trying to print a previous panic message. |
| 575 | 582 | // We're still holding the mutex but that's fine as we're going to |
| 576 | 583 | // call abort(). |
| 577 | const stderr = lockStderrWriter(&.{}); | |
| 578 | stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; | |
| 584 | const stderr = lockStderr(&.{}); | |
| 585 | stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; | |
| 579 | 586 | }, |
| 580 | 587 | else => {}, // Panicked while printing the recursive panic message. |
| 581 | 588 | } |
| ... | ... | @@ -656,28 +663,29 @@ pub noinline fn captureCurrentStackTrace(options: StackUnwindOptions, addr_buf: |
| 656 | 663 | /// Write the current stack trace to `writer`, annotated with source locations. |
| 657 | 664 | /// |
| 658 | 665 | /// See `captureCurrentStackTrace` to capture the trace addresses into a buffer instead of printing. |
| 659 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void { | |
| 666 | pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Terminal) Writer.Error!void { | |
| 667 | const writer = t.writer; | |
| 660 | 668 | if (!std.options.allow_stack_tracing) { |
| 661 | fwm.setColor(writer, .dim) catch {}; | |
| 669 | t.setColor(.dim) catch {}; | |
| 662 | 670 | try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); |
| 663 | fwm.setColor(writer, .reset) catch {}; | |
| 671 | t.setColor(.reset) catch {}; | |
| 664 | 672 | return; |
| 665 | 673 | } |
| 666 | 674 | const di_gpa = getDebugInfoAllocator(); |
| 667 | 675 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 668 | 676 | error.UnsupportedTarget => { |
| 669 | fwm.setColor(writer, .dim) catch {}; | |
| 677 | t.setColor(.dim) catch {}; | |
| 670 | 678 | try writer.print("Cannot print stack trace: debug info unavailable for target\n", .{}); |
| 671 | fwm.setColor(writer, .reset) catch {}; | |
| 679 | t.setColor(.reset) catch {}; | |
| 672 | 680 | return; |
| 673 | 681 | }, |
| 674 | 682 | }; |
| 675 | 683 | var it: StackIterator = .init(options.context); |
| 676 | 684 | defer it.deinit(); |
| 677 | 685 | if (!it.stratOk(options.allow_unsafe_unwind)) { |
| 678 | fwm.setColor(writer, .dim) catch {}; | |
| 686 | t.setColor(.dim) catch {}; | |
| 679 | 687 | try writer.print("Cannot print stack trace: safe unwind unavailable for target\n", .{}); |
| 680 | fwm.setColor(writer, .reset) catch {}; | |
| 688 | t.setColor(.reset) catch {}; | |
| 681 | 689 | return; |
| 682 | 690 | } |
| 683 | 691 | var total_frames: usize = 0; |
| ... | ... | @@ -701,31 +709,31 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 701 | 709 | error.Unexpected => "unexpected error", |
| 702 | 710 | }; |
| 703 | 711 | if (it.stratOk(options.allow_unsafe_unwind)) { |
| 704 | fwm.setColor(writer, .dim) catch {}; | |
| 712 | t.setColor(.dim) catch {}; | |
| 705 | 713 | try writer.print( |
| 706 | 714 | "Unwind error at address `{s}:0x{x}` ({s}), remaining frames may be incorrect\n", |
| 707 | 715 | .{ module_name, unwind_error.address, caption }, |
| 708 | 716 | ); |
| 709 | fwm.setColor(writer, .reset) catch {}; | |
| 717 | t.setColor(.reset) catch {}; | |
| 710 | 718 | } else { |
| 711 | fwm.setColor(writer, .dim) catch {}; | |
| 719 | t.setColor(.dim) catch {}; | |
| 712 | 720 | try writer.print( |
| 713 | 721 | "Unwind error at address `{s}:0x{x}` ({s}), stopping trace early\n", |
| 714 | 722 | .{ module_name, unwind_error.address, caption }, |
| 715 | 723 | ); |
| 716 | fwm.setColor(writer, .reset) catch {}; | |
| 724 | t.setColor(.reset) catch {}; | |
| 717 | 725 | return; |
| 718 | 726 | } |
| 719 | 727 | }, |
| 720 | 728 | .end => break, |
| 721 | 729 | .frame => |ret_addr| { |
| 722 | 730 | if (total_frames > 10_000) { |
| 723 | fwm.setColor(writer, .dim) catch {}; | |
| 731 | t.setColor(.dim) catch {}; | |
| 724 | 732 | try writer.print( |
| 725 | 733 | "Stopping trace after {d} frames (large frame count may indicate broken debug info)\n", |
| 726 | 734 | .{total_frames}, |
| 727 | 735 | ); |
| 728 | fwm.setColor(writer, .reset) catch {}; | |
| 736 | t.setColor(.reset) catch {}; | |
| 729 | 737 | return; |
| 730 | 738 | } |
| 731 | 739 | total_frames += 1; |
| ... | ... | @@ -735,7 +743,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 735 | 743 | } |
| 736 | 744 | // `ret_addr` is the return address, which is *after* the function call. |
| 737 | 745 | // Subtract 1 to get an address *in* the function call for a better source location. |
| 738 | try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm); | |
| 746 | try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset); | |
| 739 | 747 | printed_any_frame = true; |
| 740 | 748 | }, |
| 741 | 749 | }; |
| ... | ... | @@ -743,8 +751,8 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, writer: *Wri |
| 743 | 751 | } |
| 744 | 752 | /// A thin wrapper around `writeCurrentStackTrace` which writes to stderr and ignores write errors. |
| 745 | 753 | pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 746 | const stderr = lockStderrWriter(&.{}); | |
| 747 | defer unlockStderrWriter(); | |
| 754 | const stderr = lockStderr(&.{}); | |
| 755 | defer unlockStderr(); | |
| 748 | 756 | writeCurrentStackTrace(.{ |
| 749 | 757 | .first_address = a: { |
| 750 | 758 | if (options.first_address) |a| break :a a; |
| ... | ... | @@ -753,38 +761,28 @@ pub fn dumpCurrentStackTrace(options: StackUnwindOptions) void { |
| 753 | 761 | }, |
| 754 | 762 | .context = options.context, |
| 755 | 763 | .allow_unsafe_unwind = options.allow_unsafe_unwind, |
| 756 | }, &stderr.interface, stderr.mode) catch |err| switch (err) { | |
| 764 | }, stderr) catch |err| switch (err) { | |
| 757 | 765 | error.WriteFailed => {}, |
| 758 | 766 | }; |
| 759 | 767 | } |
| 760 | 768 | |
| 761 | 769 | pub const FormatStackTrace = struct { |
| 762 | 770 | stack_trace: StackTrace, |
| 771 | terminal_mode: Io.Terminal.Mode = .no_color, | |
| 763 | 772 | |
| 764 | pub const Decorated = struct { | |
| 765 | stack_trace: StackTrace, | |
| 766 | file_writer_mode: File.Writer.Mode, | |
| 767 | ||
| 768 | pub fn format(decorated: Decorated, writer: *Writer) Writer.Error!void { | |
| 769 | try writer.writeByte('\n'); | |
| 770 | try writeStackTrace(&decorated.stack_trace, writer, decorated.file_writer_mode); | |
| 771 | } | |
| 772 | }; | |
| 773 | ||
| 774 | pub fn format(context: FormatStackTrace, writer: *Writer) Writer.Error!void { | |
| 775 | return Decorated.format(.{ | |
| 776 | .stack_trace = context.stack_trace, | |
| 777 | .file_writer_mode = .streaming, | |
| 778 | }, writer); | |
| 773 | pub fn format(fst: FormatStackTrace, writer: *Writer) Writer.Error!void { | |
| 774 | try writer.writeByte('\n'); | |
| 775 | try writeStackTrace(&fst.stack_trace, .{ .writer = writer, .mode = fst.terminal_mode }); | |
| 779 | 776 | } |
| 780 | 777 | }; |
| 781 | 778 | |
| 782 | 779 | /// Write a previously captured stack trace to `writer`, annotated with source locations. |
| 783 | pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer.Mode) Writer.Error!void { | |
| 780 | pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void { | |
| 781 | const writer = t.writer; | |
| 784 | 782 | if (!std.options.allow_stack_tracing) { |
| 785 | fwm.setColor(writer, .dim) catch {}; | |
| 783 | t.setColor(.dim) catch {}; | |
| 786 | 784 | try writer.print("Cannot print stack trace: stack tracing is disabled\n", .{}); |
| 787 | fwm.setColor(writer, .reset) catch {}; | |
| 785 | t.setColor(.reset) catch {}; | |
| 788 | 786 | return; |
| 789 | 787 | } |
| 790 | 788 | |
| ... | ... | @@ -795,9 +793,9 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer. |
| 795 | 793 | const di_gpa = getDebugInfoAllocator(); |
| 796 | 794 | const di = getSelfDebugInfo() catch |err| switch (err) { |
| 797 | 795 | error.UnsupportedTarget => { |
| 798 | fwm.setColor(writer, .dim) catch {}; | |
| 796 | t.setColor(.dim) catch {}; | |
| 799 | 797 | try writer.print("Cannot print stack trace: debug info unavailable for target\n\n", .{}); |
| 800 | fwm.setColor(writer, .reset) catch {}; | |
| 798 | t.setColor(.reset) catch {}; | |
| 801 | 799 | return; |
| 802 | 800 | }, |
| 803 | 801 | }; |
| ... | ... | @@ -806,19 +804,19 @@ pub fn writeStackTrace(st: *const StackTrace, writer: *Writer, fwm: File.Writer. |
| 806 | 804 | for (st.instruction_addresses[0..captured_frames]) |ret_addr| { |
| 807 | 805 | // `ret_addr` is the return address, which is *after* the function call. |
| 808 | 806 | // Subtract 1 to get an address *in* the function call for a better source location. |
| 809 | try printSourceAtAddress(di_gpa, io, di, writer, ret_addr -| StackIterator.ra_call_offset, fwm); | |
| 807 | try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset); | |
| 810 | 808 | } |
| 811 | 809 | if (n_frames > captured_frames) { |
| 812 | fwm.setColor(writer, .bold) catch {}; | |
| 810 | t.setColor(.bold) catch {}; | |
| 813 | 811 | try writer.print("({d} additional stack frames skipped...)\n", .{n_frames - captured_frames}); |
| 814 | fwm.setColor(writer, .reset) catch {}; | |
| 812 | t.setColor(.reset) catch {}; | |
| 815 | 813 | } |
| 816 | 814 | } |
| 817 | 815 | /// A thin wrapper around `writeStackTrace` which writes to stderr and ignores write errors. |
| 818 | 816 | pub fn dumpStackTrace(st: *const StackTrace) void { |
| 819 | const stderr = lockStderrWriter(&.{}); | |
| 820 | defer unlockStderrWriter(); | |
| 821 | writeStackTrace(st, &stderr.interface, stderr.mode) catch |err| switch (err) { | |
| 817 | const stderr = lockStderr(&.{}); | |
| 818 | defer unlockStderr(); | |
| 819 | writeStackTrace(st, stderr) catch |err| switch (err) { | |
| 822 | 820 | error.WriteFailed => {}, |
| 823 | 821 | }; |
| 824 | 822 | } |
| ... | ... | @@ -1117,9 +1115,8 @@ fn printSourceAtAddress( |
| 1117 | 1115 | gpa: Allocator, |
| 1118 | 1116 | io: Io, |
| 1119 | 1117 | debug_info: *SelfInfo, |
| 1120 | writer: *Writer, | |
| 1118 | t: Io.Terminal, | |
| 1121 | 1119 | address: usize, |
| 1122 | fwm: File.Writer.Mode, | |
| 1123 | 1120 | ) Writer.Error!void { |
| 1124 | 1121 | const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { |
| 1125 | 1122 | error.MissingDebugInfo, |
| ... | ... | @@ -1127,40 +1124,39 @@ fn printSourceAtAddress( |
| 1127 | 1124 | error.InvalidDebugInfo, |
| 1128 | 1125 | => .unknown, |
| 1129 | 1126 | error.ReadFailed, error.Unexpected, error.Canceled => s: { |
| 1130 | fwm.setColor(writer, .dim) catch {}; | |
| 1131 | try writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); | |
| 1132 | fwm.setColor(writer, .reset) catch {}; | |
| 1127 | t.setColor(.dim) catch {}; | |
| 1128 | try t.writer.print("Failed to read debug info from filesystem, trace may be incomplete\n\n", .{}); | |
| 1129 | t.setColor(.reset) catch {}; | |
| 1133 | 1130 | break :s .unknown; |
| 1134 | 1131 | }, |
| 1135 | 1132 | error.OutOfMemory => s: { |
| 1136 | fwm.setColor(writer, .dim) catch {}; | |
| 1137 | try writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); | |
| 1138 | fwm.setColor(writer, .reset) catch {}; | |
| 1133 | t.setColor(.dim) catch {}; | |
| 1134 | try t.writer.print("Ran out of memory loading debug info, trace may be incomplete\n\n", .{}); | |
| 1135 | t.setColor(.reset) catch {}; | |
| 1139 | 1136 | break :s .unknown; |
| 1140 | 1137 | }, |
| 1141 | 1138 | }; |
| 1142 | 1139 | defer if (symbol.source_location) |sl| gpa.free(sl.file_name); |
| 1143 | 1140 | return printLineInfo( |
| 1144 | 1141 | io, |
| 1145 | writer, | |
| 1142 | t, | |
| 1146 | 1143 | symbol.source_location, |
| 1147 | 1144 | address, |
| 1148 | 1145 | symbol.name orelse "???", |
| 1149 | 1146 | symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", |
| 1150 | fwm, | |
| 1151 | 1147 | ); |
| 1152 | 1148 | } |
| 1153 | 1149 | fn printLineInfo( |
| 1154 | 1150 | io: Io, |
| 1155 | writer: *Writer, | |
| 1151 | t: Io.Terminal, | |
| 1156 | 1152 | source_location: ?SourceLocation, |
| 1157 | 1153 | address: usize, |
| 1158 | 1154 | symbol_name: []const u8, |
| 1159 | 1155 | compile_unit_name: []const u8, |
| 1160 | fwm: File.Writer.Mode, | |
| 1161 | 1156 | ) Writer.Error!void { |
| 1162 | 1157 | nosuspend { |
| 1163 | fwm.setColor(writer, .bold) catch {}; | |
| 1158 | const writer = t.writer; | |
| 1159 | t.setColor(.bold) catch {}; | |
| 1164 | 1160 | |
| 1165 | 1161 | if (source_location) |*sl| { |
| 1166 | 1162 | try writer.print("{s}:{d}:{d}", .{ sl.file_name, sl.line, sl.column }); |
| ... | ... | @@ -1168,11 +1164,11 @@ fn printLineInfo( |
| 1168 | 1164 | try writer.writeAll("???:?:?"); |
| 1169 | 1165 | } |
| 1170 | 1166 | |
| 1171 | fwm.setColor(writer, .reset) catch {}; | |
| 1167 | t.setColor(.reset) catch {}; | |
| 1172 | 1168 | try writer.writeAll(": "); |
| 1173 | fwm.setColor(writer, .dim) catch {}; | |
| 1169 | t.setColor(.dim) catch {}; | |
| 1174 | 1170 | try writer.print("0x{x} in {s} ({s})", .{ address, symbol_name, compile_unit_name }); |
| 1175 | fwm.setColor(writer, .reset) catch {}; | |
| 1171 | t.setColor(.reset) catch {}; | |
| 1176 | 1172 | try writer.writeAll("\n"); |
| 1177 | 1173 | |
| 1178 | 1174 | // Show the matching source code line if possible |
| ... | ... | @@ -1183,9 +1179,9 @@ fn printLineInfo( |
| 1183 | 1179 | const space_needed = @as(usize, @intCast(sl.column - 1)); |
| 1184 | 1180 | |
| 1185 | 1181 | try writer.splatByteAll(' ', space_needed); |
| 1186 | fwm.setColor(writer, .green) catch {}; | |
| 1182 | t.setColor(.green) catch {}; | |
| 1187 | 1183 | try writer.writeAll("^"); |
| 1188 | fwm.setColor(writer, .reset) catch {}; | |
| 1184 | t.setColor(.reset) catch {}; | |
| 1189 | 1185 | } |
| 1190 | 1186 | try writer.writeAll("\n"); |
| 1191 | 1187 | } else |_| { |
| ... | ... | @@ -1554,19 +1550,19 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex |
| 1554 | 1550 | _ = panicking.fetchAdd(1, .seq_cst); |
| 1555 | 1551 | |
| 1556 | 1552 | trace: { |
| 1557 | const stderr = lockStderrWriter(&.{}); | |
| 1558 | defer unlockStderrWriter(); | |
| 1553 | const stderr = lockStderr(&.{}); | |
| 1554 | defer unlockStderr(); | |
| 1559 | 1555 | |
| 1560 | 1556 | if (addr) |a| { |
| 1561 | stderr.interface.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; | |
| 1557 | stderr.writer.print("{s} at address 0x{x}\n", .{ name, a }) catch break :trace; | |
| 1562 | 1558 | } else { |
| 1563 | stderr.interface.print("{s} (no address available)\n", .{name}) catch break :trace; | |
| 1559 | stderr.writer.print("{s} (no address available)\n", .{name}) catch break :trace; | |
| 1564 | 1560 | } |
| 1565 | 1561 | if (opt_ctx) |context| { |
| 1566 | 1562 | writeCurrentStackTrace(.{ |
| 1567 | 1563 | .context = context, |
| 1568 | 1564 | .allow_unsafe_unwind = true, // we're crashing anyway, give it our all! |
| 1569 | }, &stderr.interface, stderr.mode) catch break :trace; | |
| 1565 | }, stderr) catch break :trace; | |
| 1570 | 1566 | } |
| 1571 | 1567 | } |
| 1572 | 1568 | }, |
| ... | ... | @@ -1575,8 +1571,8 @@ pub fn defaultHandleSegfault(addr: ?usize, name: []const u8, opt_ctx: ?CpuContex |
| 1575 | 1571 | // A segfault happened while trying to print a previous panic message. |
| 1576 | 1572 | // We're still holding the mutex but that's fine as we're going to |
| 1577 | 1573 | // call abort(). |
| 1578 | const stderr = lockStderrWriter(&.{}); | |
| 1579 | stderr.interface.writeAll("aborting due to recursive panic\n") catch {}; | |
| 1574 | const stderr = lockStderr(&.{}); | |
| 1575 | stderr.writer.writeAll("aborting due to recursive panic\n") catch {}; | |
| 1580 | 1576 | }, |
| 1581 | 1577 | else => {}, // Panicked while printing the recursive panic message. |
| 1582 | 1578 | } |
| ... | ... | @@ -1682,21 +1678,21 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize |
| 1682 | 1678 | pub fn dump(t: @This()) void { |
| 1683 | 1679 | if (!enabled) return; |
| 1684 | 1680 | |
| 1685 | const stderr = lockStderrWriter(&.{}); | |
| 1686 | defer unlockStderrWriter(); | |
| 1681 | const stderr = lockStderr(&.{}); | |
| 1682 | defer unlockStderr(); | |
| 1687 | 1683 | const end = @min(t.index, size); |
| 1688 | 1684 | for (t.addrs[0..end], 0..) |frames_array, i| { |
| 1689 | stderr.interface.print("{s}:\n", .{t.notes[i]}) catch return; | |
| 1685 | stderr.writer.print("{s}:\n", .{t.notes[i]}) catch return; | |
| 1690 | 1686 | var frames_array_mutable = frames_array; |
| 1691 | 1687 | const frames = mem.sliceTo(frames_array_mutable[0..], 0); |
| 1692 | 1688 | const stack_trace: StackTrace = .{ |
| 1693 | 1689 | .index = frames.len, |
| 1694 | 1690 | .instruction_addresses = frames, |
| 1695 | 1691 | }; |
| 1696 | writeStackTrace(&stack_trace, &stderr.interface, stderr.mode) catch return; | |
| 1692 | writeStackTrace(&stack_trace, stderr) catch return; | |
| 1697 | 1693 | } |
| 1698 | 1694 | if (t.index > end) { |
| 1699 | stderr.interface.print("{d} more traces not shown; consider increasing trace size\n", .{ | |
| 1695 | stderr.writer.print("{d} more traces not shown; consider increasing trace size\n", .{ | |
| 1700 | 1696 | t.index - end, |
| 1701 | 1697 | }) catch return; |
| 1702 | 1698 | } |
lib/std/log.zig+16-18| ... | ... | @@ -92,35 +92,33 @@ pub fn defaultLog( |
| 92 | 92 | args: anytype, |
| 93 | 93 | ) void { |
| 94 | 94 | var buffer: [64]u8 = undefined; |
| 95 | const stderr = std.debug.lockStderrWriter(&buffer); | |
| 96 | defer std.debug.unlockStderrWriter(); | |
| 97 | return defaultLogFileWriter(level, scope, format, args, stderr); | |
| 95 | const stderr = std.debug.lockStderr(&buffer); | |
| 96 | defer std.debug.unlockStderr(); | |
| 97 | return defaultLogFileTerminal(level, scope, format, args, stderr) catch {}; | |
| 98 | 98 | } |
| 99 | 99 | |
| 100 | pub fn defaultLogFileWriter( | |
| 100 | pub fn defaultLogFileTerminal( | |
| 101 | 101 | comptime level: Level, |
| 102 | 102 | comptime scope: @EnumLiteral(), |
| 103 | 103 | comptime format: []const u8, |
| 104 | 104 | args: anytype, |
| 105 | fw: *std.Io.File.Writer, | |
| 106 | ) void { | |
| 107 | fw.setColor(switch (level) { | |
| 105 | t: std.Io.Terminal, | |
| 106 | ) std.Io.Writer.Error!void { | |
| 107 | t.setColor(switch (level) { | |
| 108 | 108 | .err => .red, |
| 109 | 109 | .warn => .yellow, |
| 110 | 110 | .info => .green, |
| 111 | 111 | .debug => .magenta, |
| 112 | 112 | }) catch {}; |
| 113 | fw.setColor(.bold) catch {}; | |
| 114 | fw.interface.writeAll(level.asText()) catch return; | |
| 115 | fw.setColor(.reset) catch {}; | |
| 116 | fw.setColor(.dim) catch {}; | |
| 117 | fw.setColor(.bold) catch {}; | |
| 118 | if (scope != .default) { | |
| 119 | fw.interface.print("({s})", .{@tagName(scope)}) catch return; | |
| 120 | } | |
| 121 | fw.interface.writeAll(": ") catch return; | |
| 122 | fw.setColor(.reset) catch {}; | |
| 123 | fw.interface.print(format ++ "\n", fw.mode.decorateArgs(args)) catch return; | |
| 113 | t.setColor(.bold) catch {}; | |
| 114 | try t.writer.writeAll(level.asText()); | |
| 115 | t.setColor(.reset) catch {}; | |
| 116 | t.setColor(.dim) catch {}; | |
| 117 | t.setColor(.bold) catch {}; | |
| 118 | if (scope != .default) try t.writer.print("({t})", .{scope}); | |
| 119 | try t.writer.writeAll(": "); | |
| 120 | t.setColor(.reset) catch {}; | |
| 121 | try t.writer.print(format ++ "\n", args); | |
| 124 | 122 | } |
| 125 | 123 | |
| 126 | 124 | /// Returns a scoped logging namespace that logs all messages using the scope |
lib/std/process.zig+7| ... | ... | @@ -21,6 +21,13 @@ pub const changeCurDirZ = posix.chdirZ; |
| 21 | 21 | |
| 22 | 22 | pub const GetCwdError = posix.GetCwdError; |
| 23 | 23 | |
| 24 | /// This is the global, process-wide protection to coordinate stderr writes. | |
| 25 | /// | |
| 26 | /// The primary motivation for recursive mutex here is so that a panic while | |
| 27 | /// stderr mutex is held still dumps the stack trace and other debug | |
| 28 | /// information. | |
| 29 | pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; | |
| 30 | ||
| 24 | 31 | /// The result is a slice of `out_buffer`, from index `0`. |
| 25 | 32 | /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). |
| 26 | 33 | /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. |