aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Io/File
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2025-12-17 15:47:33 -0800
committerAndrew Kelley <andrew@ziglang.org>2025-12-23 22:15:09 -0800
commitaa57793b680b3da05f1d888b4df15807905e57c8 (patch)
treed88a1c6f56796942c9b21aee4bbb88e72045d298 /lib/std/Io/File
parent97f106f949891870433bfcc6b7cf4c2a6709402e (diff)
downloadzig-aa57793b680b3da05f1d888b4df15807905e57c8.tar.gz
zig-aa57793b680b3da05f1d888b4df15807905e57c8.zip
std: rework locking stderr
Diffstat (limited to 'lib/std/Io/File')
-rw-r--r--lib/std/Io/File/Reader.zig34
-rw-r--r--lib/std/Io/File/Writer.zig250
2 files changed, 20 insertions, 264 deletions
diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig
index 8b2074a0aa..792b20a716 100644
--- a/lib/std/Io/File/Reader.zig
+++ b/lib/std/Io/File/Reader.zig
@@ -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
diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig
index 03e4ad8a15..1d0494c674 100644
--- a/lib/std/Io/File/Writer.zig
+++ b/lib/std/Io/File/Writer.zig
@@ -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);
-}