diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2025-12-09 22:10:12 -0800 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2025-12-23 22:15:09 -0800 |
| commit | ffcbd48a1220ce6d652ee762001d88baa385de49 (patch) | |
| tree | e1ea279b4cd0b8991df04d8a799589f72dbffd86 /lib/std/Io/Threaded.zig | |
| parent | 78d262d96ee6200c7a6bc0a41fe536d263c24d92 (diff) | |
| download | zig-ffcbd48a1220ce6d652ee762001d88baa385de49.tar.gz zig-ffcbd48a1220ce6d652ee762001d88baa385de49.zip | |
std: rework TTY detection and printing
This commit sketches an idea for how to deal with detection of file
streams as being terminals.
When a File stream is a terminal, writes through the stream should have
their escapes stripped unless the programmer explicitly enables terminal
escapes. Furthermore, the programmer needs a convenient API for
intentionally outputting escapes into the stream. In particular it
should be possible to set colors that are silently discarded when the
stream is not a terminal.
This commit makes `Io.File.Writer` track the terminal mode in the
already-existing `mode` field, making it the appropriate place to
implement escape stripping.
`Io.lockStderrWriter` returns a `*Io.File.Writer` with terminal
detection already done by default. This is a higher-level application
layer stream for writing to stderr.
Meanwhile, `std.debug.lockStderrWriter` also returns a `*Io.File.Writer`
but a lower-level one that is hard-coded to use a static single-threaded
`std.Io.Threaded` instance. This is the same instance that is used for
collecting debug information and iterating the unwind info.
Diffstat (limited to 'lib/std/Io/Threaded.zig')
| -rw-r--r-- | lib/std/Io/Threaded.zig | 47 |
1 files changed, 34 insertions, 13 deletions
diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9d6d9f979e..c2a3e15f15 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -77,7 +77,13 @@ use_sendfile: UseSendfile = .default, use_copy_file_range: UseCopyFileRange = .default, use_fcopyfile: UseFcopyfile = .default, -stderr_writer: Io.Writer, +stderr_writer: File.Writer = .{ + .io = undefined, + .interface = Io.File.Writer.initInterface(&.{}), + .file = if (is_windows) undefined else .stderr(), + .mode = undefined, +}, +stderr_writer_initialized: bool = false, pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, @@ -737,6 +743,9 @@ pub fn io(t: *Threaded) Io { .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, + .lockStderrWriter = lockStderrWriter, + .tryLockStderrWriter = tryLockStderrWriter, + .unlockStderrWriter = unlockStderrWriter, .now = now, .sleep = sleep, @@ -864,6 +873,9 @@ pub fn ioBasic(t: *Threaded) Io { .processExecutableOpen = processExecutableOpen, .processExecutablePath = processExecutablePath, + .lockStderrWriter = lockStderrWriter, + .tryLockStderrWriter = tryLockStderrWriter, + .unlockStderrWriter = unlockStderrWriter, .now = now, .sleep = sleep, @@ -9516,33 +9528,42 @@ fn netLookupFallible( return error.OptionUnsupported; } -fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*Io.Writer { +fn lockStderrWriter(userdata: ?*anyopaque, buffer: []u8) Io.Cancelable!*File.Writer { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. Io.stderr_thread_mutex.lock(); - if (is_windows) t.stderr_writer.file = .stderr(); + if (!t.stderr_writer_initialized) { + if (is_windows) t.stderr_writer.file = .stderr(); + t.stderr_writer.mode = try .detect(ioBasic(t), t.stderr_writer.file, true, .streaming_simple); + t.stderr_writer_initialized = true; + } std.Progress.clearWrittenWithEscapeCodes(&t.stderr_writer) catch {}; - t.stderr_writer.flush() catch {}; - t.stderr_writer.buffer = buffer; + t.stderr_writer.interface.flush() catch {}; + t.stderr_writer.interface.buffer = buffer; return &t.stderr_writer; } -fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*Io.Writer { +fn tryLockStderrWriter(userdata: ?*anyopaque, buffer: []u8) ?*File.Writer { const t: *Threaded = @ptrCast(@alignCast(userdata)); // Only global mutex since this is Threaded. if (!Io.stderr_thread_mutex.tryLock()) return null; - std.Progress.clearWrittenWithEscapeCodes(t.io()) catch {}; - if (is_windows) t.stderr_writer.file = .stderr(); - t.stderr_writer.flush() catch {}; - t.stderr_writer.buffer = buffer; + if (!t.stderr_writer_initialized) { + if (is_windows) t.stderr_writer.file = .stderr(); + t.stderr_writer.mode = File.Writer.Mode.detect(ioBasic(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.flush() catch {}; - t.stderr_writer.end = 0; - t.stderr_writer.buffer = &.{}; + t.stderr_writer.interface.flush() catch {}; + t.stderr_writer.interface.end = 0; + t.stderr_writer.interface.buffer = &.{}; Io.stderr_thread_mutex.unlock(); } |
