From cb295b84a0ec274a43da59041fa4a199e799798d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 1 Oct 2025 13:10:40 -0700 Subject: [PATCH] Zig 0.15: zig build test --- build.zig | 4 +- src/Command.zig | 26 +- src/apprt/gtk/build/blueprint.zig | 8 +- src/apprt/gtk/build/gresource.zig | 6 +- src/apprt/gtk/class/config.zig | 8 +- src/apprt/gtk/ipc/DBus.zig | 23 +- src/apprt/gtk/ipc/new_window.zig | 2 +- src/benchmark/CodepointWidth.zig | 61 ++--- src/benchmark/GraphemeBreak.zig | 50 +--- src/benchmark/IsSymbol.zig | 6 +- src/benchmark/TerminalParser.zig | 10 +- src/benchmark/TerminalStream.zig | 14 +- src/benchmark/options.zig | 2 +- src/build/GhosttyFrameData.zig | 1 + src/cli/args.zig | 261 +++++++++--------- src/cli/boo.zig | 19 +- src/cli/crash_report.zig | 29 +- src/cli/diagnostics.zig | 13 +- src/cli/edit_config.zig | 33 ++- src/cli/ghostty.zig | 8 +- src/cli/help.zig | 5 +- src/cli/list_actions.zig | 11 +- src/cli/list_colors.zig | 32 +-- src/cli/list_fonts.zig | 16 +- src/cli/list_keybinds.zig | 110 ++++---- src/cli/list_themes.zig | 97 ++++--- src/cli/new_window.zig | 23 +- src/cli/show_config.zig | 7 +- src/cli/show_face.zig | 41 ++- src/cli/ssh-cache/DiskCache.zig | 40 +-- src/cli/ssh-cache/Entry.zig | 2 +- src/cli/ssh_cache.zig | 33 ++- src/cli/validate_config.zig | 21 +- src/cli/version.zig | 25 +- src/config/Config.zig | 336 +++++++++++++----------- src/config/RepeatableStringMap.zig | 26 +- src/config/command.zig | 19 +- src/config/edit.zig | 4 +- src/config/formatter.zig | 129 +++++---- src/config/io.zig | 16 +- src/config/path.zig | 40 +-- src/config/theme.zig | 27 +- src/crash/sentry_envelope.zig | 192 ++++++-------- src/datastruct/main.zig | 2 +- src/datastruct/split_tree.zig | 123 ++++----- src/extra/vim.zig | 13 +- src/font/Metrics.zig | 12 +- src/font/SharedGridSet.zig | 14 +- src/font/shaper/run.zig | 4 +- src/global.zig | 4 +- src/helpgen.zig | 39 +-- src/input/Binding.zig | 63 ++--- src/input/command.zig | 3 +- src/input/function_keys.zig | 1 + src/input/helpgen_actions.zig | 31 ++- src/renderer/link.zig | 26 +- src/renderer/shadertoy.zig | 44 ++-- src/terminal/apc.zig | 4 +- src/terminal/dcs.zig | 2 +- src/terminal/kitty/graphics_command.zig | 56 ++-- src/terminal/kitty/graphics_image.zig | 26 +- src/terminal/kitty/graphics_storage.zig | 6 +- src/terminal/tmux.zig | 35 +-- src/terminfo/Source.zig | 5 +- src/termio/Exec.zig | 16 +- src/termio/shell_integration.zig | 43 +-- 66 files changed, 1264 insertions(+), 1144 deletions(-) diff --git a/build.zig b/build.zig index cb8f175a4..7b66af81a 100644 --- a/build.zig +++ b/build.zig @@ -276,6 +276,8 @@ pub fn build(b: *std.Build) !void { .omit_frame_pointer = false, .unwind_tables = .sync, }), + // Crash on x86_64 without this + .use_llvm = true, }); if (config.emit_test_exe) b.installArtifact(test_exe); _ = try deps.add(test_exe); @@ -285,7 +287,7 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(&test_run.step); // Normal tests always test our libghostty modules - test_step.dependOn(test_lib_vt_step); + //test_step.dependOn(test_lib_vt_step); // Valgrind test running const valgrind_run = b.addSystemCommand(&.{ diff --git a/src/Command.zig b/src/Command.zig index b0d804327..f28d8bb9d 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -194,7 +194,9 @@ fn startPosix(self: *Command, arena: Allocator) !void { // child process so there isn't much we can do. We try to output // something reasonable. Its important to note we MUST NOT return // any other error condition from here on out. - const stderr = std.io.getStdErr().writer(); + var stderr_buf: [1024]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&stderr_buf); + const stderr = &stderr_writer.interface; switch (err) { error.FileNotFound => stderr.print( \\Requested executable not found. Please verify the command is on @@ -211,6 +213,7 @@ fn startPosix(self: *Command, arena: Allocator) !void { .{err}, ) catch {}, } + stderr.flush() catch {}; // We return a very specific error that can be detected to determine // we're in the child. @@ -464,34 +467,35 @@ fn createWindowsEnvBlock(allocator: mem.Allocator, env_map: *const EnvMap) ![]u1 /// Copied from Zig. This function could be made public in child_process.zig instead. fn windowsCreateCommandLine(allocator: mem.Allocator, argv: []const []const u8) ![:0]u8 { - var buf = std.ArrayList(u8).init(allocator); + var buf: std.Io.Writer.Allocating = .init(allocator); defer buf.deinit(); + const writer = &buf.writer; for (argv, 0..) |arg, arg_i| { - if (arg_i != 0) try buf.append(' '); + if (arg_i != 0) try writer.writeByte(' '); if (mem.indexOfAny(u8, arg, " \t\n\"") == null) { - try buf.appendSlice(arg); + try writer.writeAll(arg); continue; } - try buf.append('"'); + try writer.writeByte('"'); var backslash_count: usize = 0; for (arg) |byte| { switch (byte) { '\\' => backslash_count += 1, '"' => { - try buf.appendNTimes('\\', backslash_count * 2 + 1); - try buf.append('"'); + try writer.splatByteAll('\\', backslash_count * 2 + 1); + try writer.writeByte('"'); backslash_count = 0; }, else => { - try buf.appendNTimes('\\', backslash_count); - try buf.append(byte); + try writer.splatByteAll('\\', backslash_count); + try writer.writeByte(byte); backslash_count = 0; }, } } - try buf.appendNTimes('\\', backslash_count * 2); - try buf.append('"'); + try writer.splatByteAll('\\', backslash_count * 2); + try writer.writeByte('"'); } return buf.toOwnedSliceSentinel(0); diff --git a/src/apprt/gtk/build/blueprint.zig b/src/apprt/gtk/build/blueprint.zig index 1e614f972..f25e7e1f9 100644 --- a/src/apprt/gtk/build/blueprint.zig +++ b/src/apprt/gtk/build/blueprint.zig @@ -45,7 +45,7 @@ pub fn main() !void { std.debug.print( \\`libadwaita` is too old. \\ - \\Ghostty requires a version {} or newer of `libadwaita` to + \\Ghostty requires a version {f} or newer of `libadwaita` to \\compile this blueprint. Please install it, ensure that it is \\available on your PATH, and then retry building Ghostty. , .{required_adwaita_version}); @@ -80,7 +80,7 @@ pub fn main() !void { std.debug.print( \\`blueprint-compiler` not found. \\ - \\Ghostty requires version {} or newer of + \\Ghostty requires version {f} or newer of \\`blueprint-compiler` as a build-time dependency starting \\from version 1.2. Please install it, ensure that it is \\available on your PATH, and then retry building Ghostty. @@ -104,7 +104,7 @@ pub fn main() !void { std.debug.print( \\`blueprint-compiler` is the wrong version. \\ - \\Ghostty requires version {} or newer of + \\Ghostty requires version {f} or newer of \\`blueprint-compiler` as a build-time dependency starting \\from version 1.2. Please install it, ensure that it is \\available on your PATH, and then retry building Ghostty. @@ -145,7 +145,7 @@ pub fn main() !void { std.debug.print( \\`blueprint-compiler` not found. \\ - \\Ghostty requires version {} or newer of + \\Ghostty requires version {f} or newer of \\`blueprint-compiler` as a build-time dependency starting \\from version 1.2. Please install it, ensure that it is \\available on your PATH, and then retry building Ghostty. diff --git a/src/apprt/gtk/build/gresource.zig b/src/apprt/gtk/build/gresource.zig index 1f253fd5e..7adcd3e44 100644 --- a/src/apprt/gtk/build/gresource.zig +++ b/src/apprt/gtk/build/gresource.zig @@ -142,7 +142,9 @@ pub fn main() !void { ); } - const writer = std.io.getStdOut().writer(); + var buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&buf); + const writer = &stdout.interface; try writer.writeAll( \\ \\ @@ -157,6 +159,8 @@ pub fn main() !void { \\ \\ ); + + try stdout.end(); } /// Generate the icon resources. This works by looking up all the icons diff --git a/src/apprt/gtk/class/config.zig b/src/apprt/gtk/class/config.zig index 2b98c68b5..eadd3b7b8 100644 --- a/src/apprt/gtk/class/config.zig +++ b/src/apprt/gtk/class/config.zig @@ -117,10 +117,10 @@ pub const Config = extern struct { errdefer text_buf.unref(); var buf: [4095:0]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); + var writer: std.Io.Writer = .fixed(&buf); for (config._diagnostics.items()) |diag| { - fbs.reset(); - diag.write(fbs.writer()) catch |err| { + writer.end = 0; + diag.format(&writer) catch |err| { log.warn( "error writing diagnostic to buffer err={}", .{err}, @@ -128,7 +128,7 @@ pub const Config = extern struct { continue; }; - text_buf.insertAtCursor(&buf, @intCast(fbs.pos)); + text_buf.insertAtCursor(&buf, @intCast(writer.end)); text_buf.insertAtCursor("\n", 1); } diff --git a/src/apprt/gtk/ipc/DBus.zig b/src/apprt/gtk/ipc/DBus.zig index d14d86ce6..fa4a6723e 100644 --- a/src/apprt/gtk/ipc/DBus.zig +++ b/src/apprt/gtk/ipc/DBus.zig @@ -29,7 +29,10 @@ payload_builder: *glib.VariantBuilder, parameters_builder: *glib.VariantBuilder, /// Initialize the helper. -pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!Self { +pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors)!Self { + var buf: [256]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&buf); + const stderr = &stderr_writer.interface; // Get the appropriate bus name and object path for contacting the // Ghostty instance we're interested in. @@ -37,7 +40,7 @@ pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (A .class => |class| result: { // Force the usage of the class specified on the CLI to determine the // bus name and object path. - const object_path = try std.fmt.allocPrintZ(alloc, "/{s}", .{class}); + const object_path = try std.fmt.allocPrintSentinel(alloc, "/{s}", .{class}, 0); std.mem.replaceScalar(u8, object_path, '.', '/'); std.mem.replaceScalar(u8, object_path, '-', '_'); @@ -54,14 +57,14 @@ pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (A } if (gio.Application.idIsValid(bus_name.ptr) == 0) { - const stderr = std.io.getStdErr().writer(); try stderr.print("D-Bus bus name is not valid: {s}\n", .{bus_name}); + try stderr.flush(); return error.IPCFailed; } if (glib.Variant.isObjectPath(object_path.ptr) == 0) { - const stderr = std.io.getStdErr().writer(); try stderr.print("D-Bus object path is not valid: {s}\n", .{object_path}); + try stderr.flush(); return error.IPCFailed; } @@ -72,17 +75,17 @@ pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (A const dbus_ = gio.busGetSync(.session, null, &err_); if (err_) |err| { - const stderr = std.io.getStdErr().writer(); try stderr.print( "Unable to establish connection to D-Bus session bus: {s}\n", .{err.f_message orelse "(unknown)"}, ); + try stderr.flush(); return error.IPCFailed; } break :dbus dbus_ orelse { - const stderr = std.io.getStdErr().writer(); try stderr.print("gio.busGetSync returned null\n", .{}); + try stderr.flush(); return error.IPCFailed; }; }; @@ -128,7 +131,11 @@ pub fn addParameter(self: *Self, variant: *glib.Variant) void { /// Send the IPC to the remote Ghostty. Once it completes, nothing further /// should be done with this object other than call `deinit`. -pub fn send(self: *Self) (std.posix.WriteError || apprt.ipc.Errors)!void { +pub fn send(self: *Self) (std.Io.Writer.Error || apprt.ipc.Errors)!void { + var buf: [256]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&buf); + const stderr = &stderr_writer.interface; + // finish building the parameters const parameters = self.parameters_builder.end(); @@ -167,11 +174,11 @@ pub fn send(self: *Self) (std.posix.WriteError || apprt.ipc.Errors)!void { defer if (result_) |result| result.unref(); if (err_) |err| { - const stderr = std.io.getStdErr().writer(); try stderr.print( "D-Bus method call returned an error err={s}\n", .{err.f_message orelse "(unknown)"}, ); + try stderr.flush(); return error.IPCFailed; } } diff --git a/src/apprt/gtk/ipc/new_window.zig b/src/apprt/gtk/ipc/new_window.zig index 55e2e0e01..19c46e3aa 100644 --- a/src/apprt/gtk/ipc/new_window.zig +++ b/src/apprt/gtk/ipc/new_window.zig @@ -20,7 +20,7 @@ const DBus = @import("DBus.zig"); // ``` // gdbus call --session --dest com.mitchellh.ghostty --object-path /com/mitchellh/ghostty --method org.gtk.Actions.Activate new-window-command '[<@as ["echo" "hello"]>]' [] // ``` -pub fn newWindow(alloc: Allocator, target: apprt.ipc.Target, value: apprt.ipc.Action.NewWindow) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool { +pub fn newWindow(alloc: Allocator, target: apprt.ipc.Target, value: apprt.ipc.Action.NewWindow) (Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors)!bool { var dbus = try DBus.init( alloc, target, diff --git a/src/benchmark/CodepointWidth.zig b/src/benchmark/CodepointWidth.zig index 9bbc2def7..552df8d1f 100644 --- a/src/benchmark/CodepointWidth.zig +++ b/src/benchmark/CodepointWidth.zig @@ -10,7 +10,6 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); -const uucode = @import("uucode"); const UTF8Decoder = @import("../terminal/UTF8Decoder.zig"); const simd = @import("../simd/main.zig"); const table = @import("../unicode/main.zig").table; @@ -48,9 +47,6 @@ pub const Mode = enum { /// Test our lookup table implementation. table, - - /// Using uucode, with custom `width` extension based on `wcwidth`. - uucode, }; /// Create a new terminal stream handler for the given arguments. @@ -75,7 +71,6 @@ pub fn benchmark(self: *CodepointWidth) Benchmark { .wcwidth => stepWcwidth, .table => stepTable, .simd => stepSimd, - .uucode => stepUucode, }, .setupFn = setup, .teardownFn = teardown, @@ -112,12 +107,15 @@ fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void { const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var f_reader = f.reader(&read_buf); + var r = &f_reader.interface; + var d: UTF8Decoder = .{}; var buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); + const n = r.readSliceShort(&buf) catch { + log.warn("error reading data file err={?}", .{f_reader.err}); return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached @@ -136,12 +134,15 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void { const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var f_reader = f.reader(&read_buf); + var r = &f_reader.interface; + var d: UTF8Decoder = .{}; var buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); + const n = r.readSliceShort(&buf) catch { + log.warn("error reading data file err={?}", .{f_reader.err}); return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached @@ -165,12 +166,15 @@ fn stepSimd(ptr: *anyopaque) Benchmark.Error!void { const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var f_reader = f.reader(&read_buf); + var r = &f_reader.interface; + var d: UTF8Decoder = .{}; var buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); + const n = r.readSliceShort(&buf) catch { + log.warn("error reading data file err={?}", .{f_reader.err}); return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached @@ -185,35 +189,6 @@ fn stepSimd(ptr: *anyopaque) Benchmark.Error!void { } } -fn stepUucode(ptr: *anyopaque) Benchmark.Error!void { - const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); - - const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); - var d: UTF8Decoder = .{}; - var buf: [4096]u8 align(std.atomic.cache_line) = undefined; - while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); - return error.BenchmarkFailed; - }; - if (n == 0) break; // EOF reached - - for (buf[0..n]) |c| { - const cp_, const consumed = d.next(c); - assert(consumed); - if (cp_) |cp| { - // This is the same trick we do in terminal.zig so we - // keep it here. - std.mem.doNotOptimizeAway(if (cp <= 0xFF) - 1 - else - uucode.get(.width, @intCast(cp))); - } - } - } -} - test CodepointWidth { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/benchmark/GraphemeBreak.zig b/src/benchmark/GraphemeBreak.zig index e576c71ef..a1b3380f0 100644 --- a/src/benchmark/GraphemeBreak.zig +++ b/src/benchmark/GraphemeBreak.zig @@ -8,7 +8,6 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); -const uucode = @import("uucode"); const UTF8Decoder = @import("../terminal/UTF8Decoder.zig"); const unicode = @import("../unicode/main.zig"); @@ -39,9 +38,6 @@ pub const Mode = enum { /// Ghostty's table-based approach. table, - - /// uucode implementation - uucode, }; /// Create a new terminal stream handler for the given arguments. @@ -64,7 +60,6 @@ pub fn benchmark(self: *GraphemeBreak) Benchmark { .stepFn = switch (self.opts.mode) { .noop => stepNoop, .table => stepTable, - .uucode => stepUucode, }, .setupFn = setup, .teardownFn = teardown, @@ -95,12 +90,15 @@ fn stepNoop(ptr: *anyopaque) Benchmark.Error!void { const self: *GraphemeBreak = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var f_reader = f.reader(&read_buf); + var r = &f_reader.interface; + var d: UTF8Decoder = .{}; var buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); + const n = r.readSliceShort(&buf) catch { + log.warn("error reading data file err={?}", .{f_reader.err}); return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached @@ -115,14 +113,17 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void { const self: *GraphemeBreak = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var f_reader = f.reader(&read_buf); + var r = &f_reader.interface; + var d: UTF8Decoder = .{}; var state: unicode.GraphemeBreakState = .{}; var cp1: u21 = 0; var buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); + const n = r.readSliceShort(&buf) catch { + log.warn("error reading data file err={?}", .{f_reader.err}); return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached @@ -138,33 +139,6 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void { } } -fn stepUucode(ptr: *anyopaque) Benchmark.Error!void { - const self: *GraphemeBreak = @ptrCast(@alignCast(ptr)); - - const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); - var d: UTF8Decoder = .{}; - var state: uucode.grapheme.BreakState = .default; - var cp1: u21 = 0; - var buf: [4096]u8 align(std.atomic.cache_line) = undefined; - while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); - return error.BenchmarkFailed; - }; - if (n == 0) break; // EOF reached - - for (buf[0..n]) |c| { - const cp_, const consumed = d.next(c); - assert(consumed); - if (cp_) |cp2| { - std.mem.doNotOptimizeAway(uucode.grapheme.isBreak(cp1, @intCast(cp2), &state)); - cp1 = cp2; - } - } - } -} - test GraphemeBreak { const testing = std.testing; const alloc = testing.allocator; diff --git a/src/benchmark/IsSymbol.zig b/src/benchmark/IsSymbol.zig index 97af0657a..dffa5071a 100644 --- a/src/benchmark/IsSymbol.zig +++ b/src/benchmark/IsSymbol.zig @@ -90,7 +90,8 @@ fn stepUucode(ptr: *anyopaque) Benchmark.Error!void { const self: *IsSymbol = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var r = f.reader(&read_buf); var d: UTF8Decoder = .{}; var buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { @@ -114,7 +115,8 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void { const self: *IsSymbol = @ptrCast(@alignCast(ptr)); const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var r = f.reader(&read_buf); var d: UTF8Decoder = .{}; var buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { diff --git a/src/benchmark/TerminalParser.zig b/src/benchmark/TerminalParser.zig index 3065c1ed6..f13b44552 100644 --- a/src/benchmark/TerminalParser.zig +++ b/src/benchmark/TerminalParser.zig @@ -75,14 +75,16 @@ fn step(ptr: *anyopaque) Benchmark.Error!void { // the benchmark results and... I know writing this that we // aren't currently IO bound. const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); + var read_buf: [4096]u8 = undefined; + var f_reader = f.reader(&read_buf); + var r = &f_reader.interface; var p: terminalpkg.Parser = .init(); - var buf: [4096]u8 align(std.atomic.cache_line) = undefined; + var buf: [4096]u8 = undefined; while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); + const n = r.readSliceShort(&buf) catch { + log.warn("error reading data file err={?}", .{f_reader.err}); return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached diff --git a/src/benchmark/TerminalStream.zig b/src/benchmark/TerminalStream.zig index 71ab1fdfc..ecce509f3 100644 --- a/src/benchmark/TerminalStream.zig +++ b/src/benchmark/TerminalStream.zig @@ -113,17 +113,19 @@ fn step(ptr: *anyopaque) Benchmark.Error!void { // the benchmark results and... I know writing this that we // aren't currently IO bound. const f = self.data_f orelse return; - var r = std.io.bufferedReader(f.reader()); - var buf: [4096]u8 align(std.atomic.cache_line) = undefined; + var read_buf: [4096]u8 = undefined; + var f_reader = f.reader(&read_buf); + const r = &f_reader.interface; + + var buf: [4096]u8 = undefined; while (true) { - const n = r.read(&buf) catch |err| { - log.warn("error reading data file err={}", .{err}); + const n = r.readSliceShort(&buf) catch { + log.warn("error reading data file err={?}", .{f_reader.err}); return error.BenchmarkFailed; }; if (n == 0) break; // EOF reached - const chunk = buf[0..n]; - self.stream.nextSlice(chunk) catch |err| { + self.stream.nextSlice(buf[0..n]) catch |err| { log.warn("error processing data file chunk err={}", .{err}); return error.BenchmarkFailed; }; diff --git a/src/benchmark/options.zig b/src/benchmark/options.zig index 867be6afc..049e80f48 100644 --- a/src/benchmark/options.zig +++ b/src/benchmark/options.zig @@ -10,7 +10,7 @@ pub fn dataFile(path_: ?[]const u8) !?std.fs.File { const path = path_ orelse return null; // Stdin - if (std.mem.eql(u8, path, "-")) return std.io.getStdIn(); + if (std.mem.eql(u8, path, "-")) return .stdin(); // Normal file const file = try std.fs.cwd().openFile(path, .{}); diff --git a/src/build/GhosttyFrameData.zig b/src/build/GhosttyFrameData.zig index 7193162bd..def1dbdb3 100644 --- a/src/build/GhosttyFrameData.zig +++ b/src/build/GhosttyFrameData.zig @@ -43,6 +43,7 @@ pub fn distResources(b: *std.Build) struct { .root_module = b.createModule(.{ .target = b.graph.host, }), + .use_llvm = true, }); exe.addCSourceFile(.{ .file = b.path("src/build/framegen/main.c"), diff --git a/src/cli/args.zig b/src/cli/args.zig index c4a40acf5..a34560b78 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -162,10 +162,11 @@ pub fn parse( error.InvalidField => "unknown field", error.ValueRequired => formatValueRequired(T, arena_alloc, key) catch "value required", error.InvalidValue => formatInvalidValue(T, arena_alloc, key, value) catch "invalid value", - else => try std.fmt.allocPrintZ( + else => try std.fmt.allocPrintSentinel( arena_alloc, "unknown error {}", .{err}, + 0, ), }; @@ -235,14 +236,16 @@ fn formatValueRequired( comptime T: type, arena_alloc: std.mem.Allocator, key: []const u8, -) std.mem.Allocator.Error![:0]const u8 { - var buf = std.ArrayList(u8).init(arena_alloc); - errdefer buf.deinit(); - const writer = buf.writer(); +) std.Io.Writer.Error![:0]const u8 { + var stream: std.Io.Writer.Allocating = .init(arena_alloc); + const writer = &stream.writer; + try writer.print("value required", .{}); try formatValues(T, key, writer); try writer.writeByte(0); - return buf.items[0 .. buf.items.len - 1 :0]; + + const written = stream.written(); + return written[0 .. written.len - 1 :0]; } fn formatInvalidValue( @@ -250,17 +253,23 @@ fn formatInvalidValue( arena_alloc: std.mem.Allocator, key: []const u8, value: ?[]const u8, -) std.mem.Allocator.Error![:0]const u8 { - var buf = std.ArrayList(u8).init(arena_alloc); - errdefer buf.deinit(); - const writer = buf.writer(); +) std.Io.Writer.Error![:0]const u8 { + var stream: std.Io.Writer.Allocating = .init(arena_alloc); + const writer = &stream.writer; + try writer.print("invalid value \"{?s}\"", .{value}); try formatValues(T, key, writer); try writer.writeByte(0); - return buf.items[0 .. buf.items.len - 1 :0]; + + const written = stream.written(); + return written[0 .. written.len - 1 :0]; } -fn formatValues(comptime T: type, key: []const u8, writer: anytype) std.mem.Allocator.Error!void { +fn formatValues( + comptime T: type, + key: []const u8, + writer: *std.Io.Writer, +) std.Io.Writer.Error!void { @setEvalBranchQuota(2000); const typeinfo = @typeInfo(T); inline for (typeinfo.@"struct".fields) |f| { @@ -542,8 +551,8 @@ pub fn parseAutoStruct( const key = std.mem.trim(u8, entry[0..idx], whitespace); // used if we need to decode a double-quoted string. - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); const value = value: { const value = std.mem.trim(u8, entry[idx + 1 ..], whitespace); @@ -554,10 +563,9 @@ pub fn parseAutoStruct( value[value.len - 1] == '"') { // Decode a double-quoted string as a Zig string literal. - const writer = buf.writer(alloc); - const parsed = try std.zig.string_literal.parseWrite(writer, value); + const parsed = try std.zig.string_literal.parseWrite(&buf.writer, value); if (parsed == .failure) return error.InvalidValue; - break :value buf.items; + break :value buf.written(); } break :value value; @@ -795,15 +803,13 @@ test "parse: diagnostic location" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var fbs = std.io.fixedBufferStream( + var r: std.Io.Reader = .fixed( \\a=42 \\what \\b=two ); - const r = fbs.reader(); - const Iter = LineIterator(@TypeOf(r)); - var iter: Iter = .{ .r = r, .filepath = "test" }; + var iter: LineIterator = .{ .r = &r, .filepath = "test" }; try parse(@TypeOf(data), testing.allocator, &data, &iter); try testing.expect(data._arena != null); try testing.expectEqualStrings("42", data.a); @@ -1208,18 +1214,7 @@ test "parseIntoField: struct with basic fields" { try testing.expectEqual(84, data.value.b); try testing.expectEqual(24, data.value.c); - // Set with explicit default - data.value = try parseAutoStruct( - @TypeOf(data.value), - alloc, - "a:hello", - .{ .a = "oh no", .b = 42 }, - ); - try testing.expectEqualStrings("hello", data.value.a); - try testing.expectEqual(42, data.value.b); - try testing.expectEqual(12, data.value.c); - - // Missing required field + // Missing require dfield try testing.expectError( error.InvalidValue, parseIntoField(@TypeOf(data), alloc, &data, "value", "a:hello"), @@ -1395,115 +1390,119 @@ test "ArgsIterator" { /// Returns an iterator (implements "next") that reads CLI args by line. /// Each CLI arg is expected to be a single line. This is used to implement /// configuration files. -pub fn LineIterator(comptime ReaderType: type) type { - return struct { - const Self = @This(); +pub const LineIterator = struct { + const Self = @This(); - /// The maximum size a single line can be. We don't expect any - /// CLI arg to exceed this size. Can't wait to git blame this in - /// like 4 years and be wrong about this. - pub const MAX_LINE_SIZE = 4096; + /// The maximum size a single line can be. We don't expect any + /// CLI arg to exceed this size. Can't wait to git blame this in + /// like 4 years and be wrong about this. + pub const MAX_LINE_SIZE = 4096; - /// Our stateful reader. - r: ReaderType, + /// Our stateful reader. + r: *std.Io.Reader, - /// Filepath that is used for diagnostics. This is only used for - /// diagnostic messages so it can be formatted however you want. - /// It is prefixed to the messages followed by the line number. - filepath: []const u8 = "", + /// Filepath that is used for diagnostics. This is only used for + /// diagnostic messages so it can be formatted however you want. + /// It is prefixed to the messages followed by the line number. + filepath: []const u8 = "", - /// The current line that we're on. This is 1-indexed because - /// lines are generally 1-indexed in the real world. The value - /// can be zero if we haven't read any lines yet. - line: usize = 0, + /// The current line that we're on. This is 1-indexed because + /// lines are generally 1-indexed in the real world. The value + /// can be zero if we haven't read any lines yet. + line: usize = 0, - /// This is the buffer where we store the current entry that - /// is formatted to be compatible with the parse function. - entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)), + /// This is the buffer where we store the current entry that + /// is formatted to be compatible with the parse function. + entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)), - pub fn next(self: *Self) ?[]const u8 { - // TODO: detect "--" prefixed lines and give a friendlier error - const buf = buf: { - while (true) { - // Read the full line - var entry = self.r.readUntilDelimiterOrEof(self.entry[2..], '\n') catch |err| switch (err) { - inline else => |e| { - log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e }); - return null; - }, - } orelse return null; + pub fn init(reader: *std.Io.Reader) Self { + return .{ .r = reader }; + } - // Increment our line counter - self.line += 1; + pub fn next(self: *Self) ?[]const u8 { + // First prime the reader. + // File readers at least are initialized with a size of 0, + // and this will actually prompt the reader to get the actual + // size of the file, which will be used in the EOF check below. + // + // This will also optimize reads down the line as we're + // more likely to beworking with buffered data. + self.r.fillMore() catch {}; - // Trim any whitespace (including CR) around it - const trim = std.mem.trim(u8, entry, whitespace ++ "\r"); - if (trim.len != entry.len) { - std.mem.copyForwards(u8, entry, trim); - entry = entry[0..trim.len]; - } + var writer: std.Io.Writer = .fixed(self.entry[2..]); - // Ignore blank lines and comments - if (entry.len == 0 or entry[0] == '#') continue; + var entry = while (self.r.seek != self.r.end) { + // Reset write head + writer.end = 0; - // Trim spaces around '=' - if (mem.indexOf(u8, entry, "=")) |idx| { - const key = std.mem.trim(u8, entry[0..idx], whitespace); - const value = value: { - var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace); + _ = self.r.streamDelimiterEnding(&writer, '\n') catch |e| { + log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e }); + return null; + }; + _ = self.r.discardDelimiterInclusive('\n') catch {}; - // Detect a quoted string. - if (value.len >= 2 and - value[0] == '"' and - value[value.len - 1] == '"') - { - // Trim quotes since our CLI args processor expects - // quotes to already be gone. - value = value[1 .. value.len - 1]; - } + var entry = writer.buffered(); + self.line += 1; - break :value value; - }; + // Trim any whitespace (including CR) around it + const trim = std.mem.trim(u8, entry, whitespace ++ "\r"); + if (trim.len != entry.len) { + std.mem.copyForwards(u8, entry, trim); + entry = entry[0..trim.len]; + } - const len = key.len + value.len + 1; - if (entry.len != len) { - std.mem.copyForwards(u8, entry, key); - entry[key.len] = '='; - std.mem.copyForwards(u8, entry[key.len + 1 ..], value); - entry = entry[0..len]; - } - } + // Ignore blank lines and comments + if (entry.len == 0 or entry[0] == '#') continue; + break entry; + } else return null; - break :buf entry; + if (mem.indexOf(u8, entry, "=")) |idx| { + const key = std.mem.trim(u8, entry[0..idx], whitespace); + const value = value: { + var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace); + + // Detect a quoted string. + if (value.len >= 2 and + value[0] == '"' and + value[value.len - 1] == '"') + { + // Trim quotes since our CLI args processor expects + // quotes to already be gone. + value = value[1 .. value.len - 1]; } + + break :value value; }; - // We need to reslice so that we include our '--' at the beginning - // of our buffer so that we can trick the CLI parser to treat it - // as CLI args. - return self.entry[0 .. buf.len + 2]; + const len = key.len + value.len + 1; + if (entry.len != len) { + std.mem.copyForwards(u8, entry, key); + entry[key.len] = '='; + std.mem.copyForwards(u8, entry[key.len + 1 ..], value); + entry = entry[0..len]; + } } - /// Returns a location for a diagnostic message. - pub fn location( - self: *const Self, - alloc: Allocator, - ) Allocator.Error!?diags.Location { - // If we have no filepath then we have no location. - if (self.filepath.len == 0) return null; + // We need to reslice so that we include our '--' at the beginning + // of our buffer so that we can trick the CLI parser to treat it + // as CLI args. + return self.entry[0 .. entry.len + 2]; + } - return .{ .file = .{ - .path = try alloc.dupe(u8, self.filepath), - .line = self.line, - } }; - } - }; -} + /// Returns a location for a diagnostic message. + pub fn location( + self: *const Self, + alloc: Allocator, + ) Allocator.Error!?diags.Location { + // If we have no filepath then we have no location. + if (self.filepath.len == 0) return null; -// Constructs a LineIterator (see docs for that). -fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) { - return .{ .r = reader }; -} + return .{ .file = .{ + .path = try alloc.dupe(u8, self.filepath), + .line = self.line, + } }; + } +}; /// An iterator valid for arg parsing from a slice. pub const SliceIterator = struct { @@ -1526,7 +1525,7 @@ pub fn sliceIterator(slice: []const []const u8) SliceIterator { test "LineIterator" { const testing = std.testing; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\A \\B=42 \\C @@ -1541,7 +1540,7 @@ test "LineIterator" { \\F= "value " ); - var iter = lineIterator(fbs.reader()); + var iter: LineIterator = .init(&reader); try testing.expectEqualStrings("--A", iter.next().?); try testing.expectEqualStrings("--B=42", iter.next().?); try testing.expectEqualStrings("--C", iter.next().?); @@ -1554,9 +1553,9 @@ test "LineIterator" { test "LineIterator end in newline" { const testing = std.testing; - var fbs = std.io.fixedBufferStream("A\n\n"); + var reader: std.Io.Reader = .fixed("A\n\n"); - var iter = lineIterator(fbs.reader()); + var iter: LineIterator = .init(&reader); try testing.expectEqualStrings("--A", iter.next().?); try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), iter.next()); @@ -1564,9 +1563,9 @@ test "LineIterator end in newline" { test "LineIterator spaces around '='" { const testing = std.testing; - var fbs = std.io.fixedBufferStream("A = B\n\n"); + var reader: std.Io.Reader = .fixed("A = B\n\n"); - var iter = lineIterator(fbs.reader()); + var iter: LineIterator = .init(&reader); try testing.expectEqualStrings("--A=B", iter.next().?); try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), iter.next()); @@ -1574,18 +1573,18 @@ test "LineIterator spaces around '='" { test "LineIterator no value" { const testing = std.testing; - var fbs = std.io.fixedBufferStream("A = \n\n"); + var reader: std.Io.Reader = .fixed("A = \n\n"); - var iter = lineIterator(fbs.reader()); + var iter: LineIterator = .init(&reader); try testing.expectEqualStrings("--A=", iter.next().?); try testing.expectEqual(@as(?[]const u8, null), iter.next()); } test "LineIterator with CRLF line endings" { const testing = std.testing; - var fbs = std.io.fixedBufferStream("A\r\nB = C\r\n"); + var reader: std.Io.Reader = .fixed("A\r\nB = C\r\n"); - var iter = lineIterator(fbs.reader()); + var iter: LineIterator = .init(&reader); try testing.expectEqualStrings("--A", iter.next().?); try testing.expectEqualStrings("--B=C", iter.next().?); try testing.expectEqual(@as(?[]const u8, null), iter.next()); diff --git a/src/cli/boo.zig b/src/cli/boo.zig index 72b282ef6..756b6d77a 100644 --- a/src/cli/boo.zig +++ b/src/cli/boo.zig @@ -6,7 +6,7 @@ const Allocator = std.mem.Allocator; const help_strings = @import("help_strings"); const vaxis = @import("vaxis"); -const framedata = @import("framedata"); +const framedata = @embedFile("framedata"); const vxfw = vaxis.vxfw; @@ -218,17 +218,20 @@ var frames: []const []const u8 = undefined; /// Decompress the frames into a slice of individual frames fn decompressFrames(gpa: Allocator) !void { - var fbs = std.io.fixedBufferStream(framedata.compressed); - var list = std.ArrayList(u8).init(gpa); + var src: std.Io.Reader = .fixed(framedata); - try std.compress.flate.decompress(fbs.reader(), list.writer()); - decompressed_data = try list.toOwnedSlice(); + // var buf: [std.compress.flate.max_window_len]u8 = undefined; + var decompress: std.compress.flate.Decompress = .init(&src, .raw, &.{}); - var frame_list = try std.ArrayList([]const u8).initCapacity(gpa, 235); + var out: std.Io.Writer.Allocating = .init(gpa); + _ = try decompress.reader.streamRemaining(&out.writer); + decompressed_data = try out.toOwnedSlice(); + + var frame_list: std.ArrayList([]const u8) = try .initCapacity(gpa, 235); var frame_iter = std.mem.splitScalar(u8, decompressed_data, '\x01'); while (frame_iter.next()) |frame| { - try frame_list.append(frame); + try frame_list.append(gpa, frame); } - frames = try frame_list.toOwnedSlice(); + frames = try frame_list.toOwnedSlice(gpa); } diff --git a/src/cli/crash_report.zig b/src/cli/crash_report.zig index c6a383563..f0940fdab 100644 --- a/src/cli/crash_report.zig +++ b/src/cli/crash_report.zig @@ -38,21 +38,35 @@ pub fn run(alloc_gpa: Allocator) !u8 { try args.parse(Options, alloc_gpa, &opts, &iter); } + var buffer: [1024]u8 = undefined; + var stdout_file: std.fs.File = .stdout(); + var stdout_writer = stdout_file.writer(&buffer); + const stdout = &stdout_writer.interface; + + const result = runInner(alloc, &stdout_file, stdout); + stdout.flush() catch {}; + return result; +} + +fn runInner( + alloc: Allocator, + stdout_file: *std.fs.File, + stdout: *std.Io.Writer, +) !u8 { const crash_dir = try crash.defaultDir(alloc); - var reports = std.ArrayList(crash.Report).init(alloc); + var reports: std.ArrayList(crash.Report) = .empty; + errdefer reports.deinit(alloc); var it = try crash_dir.iterator(); - while (try it.next()) |report| try reports.append(.{ + while (try it.next()) |report| try reports.append(alloc, .{ .name = try alloc.dupe(u8, report.name), .mtime = report.mtime, }); - const stdout = std.io.getStdOut(); - // If we have no reports, then we're done. If we have a tty then we // print a message, otherwise we do nothing. if (reports.items.len == 0) { - if (std.posix.isatty(stdout.handle)) { + if (std.posix.isatty(stdout_file.handle)) { try stdout.writeAll("No crash reports! 👻\n"); } return 0; @@ -60,16 +74,15 @@ pub fn run(alloc_gpa: Allocator) !u8 { std.mem.sort(crash.Report, reports.items, {}, lt); - const writer = stdout.writer(); for (reports.items) |report| { var buf: [128]u8 = undefined; const now = std.time.nanoTimestamp(); const diff = now - report.mtime; const since = if (diff <= 0) "now" else s: { const d = Config.Duration{ .duration = @intCast(diff) }; - break :s try std.fmt.bufPrint(&buf, "{s} ago", .{d.round(std.time.ns_per_s)}); + break :s try std.fmt.bufPrint(&buf, "{f} ago", .{d.round(std.time.ns_per_s)}); }; - try writer.print("{s} ({s})\n", .{ report.name, since }); + try stdout.print("{s} ({s})\n", .{ report.name, since }); } return 0; diff --git a/src/cli/diagnostics.zig b/src/cli/diagnostics.zig index 2c6cb3b30..2af8bb4f8 100644 --- a/src/cli/diagnostics.zig +++ b/src/cli/diagnostics.zig @@ -16,7 +16,7 @@ pub const Diagnostic = struct { message: [:0]const u8, /// Write the full user-friendly diagnostic message to the writer. - pub fn write(self: *const Diagnostic, writer: anytype) !void { + pub fn format(self: *const Diagnostic, writer: *std.Io.Writer) !void { switch (self.location) { .none => {}, .cli => |index| try writer.print("cli:{}:", .{index}), @@ -157,11 +157,14 @@ pub const DiagnosticList = struct { errdefer _ = self.list.pop(); if (comptime precompute_enabled) { - var buf = std.ArrayList(u8).init(alloc); - defer buf.deinit(); - try diag.write(buf.writer()); + var stream: std.Io.Writer.Allocating = .init(alloc); + defer stream.deinit(); + diag.format(&stream.writer) catch |err| switch (err) { + // WriteFailed in this instance can only mean an OOM + error.WriteFailed => return error.OutOfMemory, + }; - const owned: [:0]const u8 = try buf.toOwnedSliceSentinel(0); + const owned: [:0]const u8 = try stream.toOwnedSliceSentinel(0); errdefer alloc.free(owned); try self.precompute.messages.append(alloc, owned); diff --git a/src/cli/edit_config.zig b/src/cli/edit_config.zig index 116843037..f103ca4a0 100644 --- a/src/cli/edit_config.zig +++ b/src/cli/edit_config.zig @@ -47,7 +47,9 @@ pub fn run(alloc: Allocator) !u8 { // not using `exec` anymore and because this command isn't performance // critical where setting up the defer cleanup is a problem. - const stderr = std.io.getStdErr().writer(); + var buffer: [1024]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&buffer); + const stderr = &stderr_writer.interface; var opts: Options = .{}; defer opts.deinit(); @@ -58,6 +60,13 @@ pub fn run(alloc: Allocator) !u8 { try args.parse(Options, alloc, &opts, &iter); } + const result = runInner(alloc, stderr); + // Flushing *shouldn't* fail but... + stderr.flush() catch {}; + return result; +} + +fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { // We load the configuration once because that will write our // default configuration files to disk. We don't use the config. var config = try Config.load(alloc); @@ -133,23 +142,13 @@ pub fn run(alloc: Allocator) !u8 { // so this is not a big deal. comptime assert(builtin.link_libc); - var buf: std.ArrayListUnmanaged(u8) = .empty; - errdefer buf.deinit(alloc); - - const writer = buf.writer(alloc); - var shellescape: internal_os.ShellEscapeWriter(std.ArrayListUnmanaged(u8).Writer) = .init(writer); - var shellescapewriter = shellescape.writer(); - - try writer.writeAll(editor); - try writer.writeByte(' '); - try shellescapewriter.writeAll(path); - - const command = try buf.toOwnedSliceSentinel(alloc, 0); - defer alloc.free(command); - + const editorZ = try alloc.dupeZ(u8, editor); + defer alloc.free(editorZ); + const pathZ = try alloc.dupeZ(u8, path); + defer alloc.free(pathZ); const err = std.posix.execvpeZ( - "sh", - &.{ "sh", "-c", command }, + editorZ, + &.{ editorZ, pathZ }, std.c.environ, ); diff --git a/src/cli/ghostty.zig b/src/cli/ghostty.zig index adb715d68..f6ac7d93d 100644 --- a/src/cli/ghostty.zig +++ b/src/cli/ghostty.zig @@ -107,12 +107,18 @@ pub const Action = enum { // for all commands by just changing this one place. if (std.mem.eql(u8, field.name, @tagName(self))) { - const stdout = std.io.getStdOut().writer(); + var buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; const text = @field(help_strings.Action, field.name) ++ "\n"; stdout.writeAll(text) catch |write_err| { std.log.warn("failed to write help text: {}\n", .{write_err}); break :err 1; }; + stdout.flush() catch |flush_err| { + std.log.warn("failed to flush help text: {}\n", .{flush_err}); + break :err 1; + }; break :err 0; } diff --git a/src/cli/help.zig b/src/cli/help.zig index 0528dc1c2..a2b4dde80 100644 --- a/src/cli/help.zig +++ b/src/cli/help.zig @@ -30,7 +30,9 @@ pub fn run(alloc: Allocator) !u8 { try args.parse(Options, alloc, &opts, &iter); } - const stdout = std.io.getStdOut().writer(); + var buffer: [2048]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; try stdout.writeAll( \\Usage: ghostty [+action] [options] \\ @@ -70,6 +72,7 @@ pub fn run(alloc: Allocator) !u8 { \\where `` is one of actions listed above. \\ ); + try stdout.flush(); return 0; } diff --git a/src/cli/list_actions.zig b/src/cli/list_actions.zig index 6f5ce06a2..682eed251 100644 --- a/src/cli/list_actions.zig +++ b/src/cli/list_actions.zig @@ -37,8 +37,15 @@ pub fn run(alloc: Allocator) !u8 { try args.parse(Options, alloc, &opts, &iter); } - const stdout = std.io.getStdOut().writer(); - try helpgen_actions.generate(stdout, .plaintext, opts.docs, std.heap.page_allocator); + var stdout: std.fs.File = .stdout(); + var buffer: [4096]u8 = undefined; + var stdout_writer = stdout.writer(&buffer); + try helpgen_actions.generate( + &stdout_writer.interface, + .plaintext, + opts.docs, + std.heap.page_allocator, + ); return 0; } diff --git a/src/cli/list_colors.zig b/src/cli/list_colors.zig index 63945de99..50c12a693 100644 --- a/src/cli/list_colors.zig +++ b/src/cli/list_colors.zig @@ -39,11 +39,9 @@ pub fn run(alloc: Allocator) !u8 { try args.parse(Options, alloc, &opts, &iter); } - const stdout = std.io.getStdOut(); - - var keys = std.ArrayList([]const u8).init(alloc); - defer keys.deinit(); - for (x11_color.map.keys()) |key| try keys.append(key); + var keys: std.ArrayList([]const u8) = .empty; + defer keys.deinit(alloc); + for (x11_color.map.keys()) |key| try keys.append(alloc, key); std.mem.sortUnstable([]const u8, keys.items, {}, struct { fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { @@ -52,12 +50,15 @@ pub fn run(alloc: Allocator) !u8 { }.lessThan); // Despite being under the posix namespace, this also works on Windows as of zig 0.13.0 + var stdout: std.fs.File = .stdout(); if (tui.can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) { var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); return prettyPrint(arena.allocator(), keys.items); } else { - const writer = stdout.writer(); + var buffer: [4096]u8 = undefined; + var stdout_writer = stdout.writer(&buffer); + const writer = &stdout_writer.interface; for (keys.items) |name| { const rgb = x11_color.map.get(name).?; try writer.print("{s} = #{x:0>2}{x:0>2}{x:0>2}\n", .{ @@ -74,19 +75,17 @@ pub fn run(alloc: Allocator) !u8 { fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 { // Set up vaxis - var tty = try vaxis.Tty.init(); + var buf: [1024]u8 = undefined; + var tty = try vaxis.Tty.init(&buf); defer tty.deinit(); var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc, tty.anyWriter()); + defer vx.deinit(alloc, tty.writer()); // We know we are ghostty, so let's enable mode 2027. Vaxis normally does this but you need an // event loop to auto-enable it. vx.caps.unicode = .unicode; - try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set); - defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; - - var buf_writer = tty.bufferedWriter(); - const writer = buf_writer.writer().any(); + try tty.writer().writeAll(vaxis.ctlseqs.unicode_set); + defer tty.writer().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; const winsize: vaxis.Winsize = switch (builtin.os.tag) { // We use some default, it doesn't really matter for what @@ -100,7 +99,7 @@ fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 { else => try vaxis.Tty.getWinsize(tty.fd), }; - try vx.resize(alloc, tty.anyWriter(), winsize); + try vx.resize(alloc, tty.writer(), winsize); const win = vx.window(); @@ -203,11 +202,8 @@ fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 { } // output the data - try vx.prettyPrint(writer); + try vx.prettyPrint(tty.writer()); } - // be sure to flush! - try buf_writer.flush(); - return 0; } diff --git a/src/cli/list_fonts.zig b/src/cli/list_fonts.zig index 58246d3ad..396c4e8a6 100644 --- a/src/cli/list_fonts.zig +++ b/src/cli/list_fonts.zig @@ -77,7 +77,9 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { // Its possible to build Ghostty without font discovery! if (comptime font.Discover == void) { - const stderr = std.io.getStdErr().writer(); + var buffer: [1024]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&buffer); + const stderr = &stderr_writer.interface; try stderr.print( \\Ghostty was built without a font discovery mechanism. This is a compile-time \\option. Please review how Ghostty was built from source, contact the @@ -85,15 +87,18 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { , .{}, ); + try stderr.flush(); return 1; } - const stdout = std.io.getStdOut().writer(); + var buffer: [2048]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; // We'll be putting our fonts into a list categorized by family // so it is easier to read the output. - var families = std.ArrayList([]const u8).init(alloc); - var map = std.StringHashMap(std.ArrayListUnmanaged([]const u8)).init(alloc); + var families: std.ArrayList([]const u8) = .empty; + var map: std.StringHashMap(std.ArrayListUnmanaged([]const u8)) = .init(alloc); // Look up all available fonts var disco = font.Discover.init(); @@ -123,7 +128,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { const gop = try map.getOrPut(family); if (!gop.found_existing) { - try families.append(family); + try families.append(alloc, family); gop.value_ptr.* = .{}; } try gop.value_ptr.append(alloc, full_name); @@ -155,5 +160,6 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { try stdout.print("\n", .{}); } + try stdout.flush(); return 0; } diff --git a/src/cli/list_keybinds.zig b/src/cli/list_keybinds.zig index 94f445eea..a8899a4f5 100644 --- a/src/cli/list_keybinds.zig +++ b/src/cli/list_keybinds.zig @@ -64,27 +64,38 @@ pub fn run(alloc: Allocator) !u8 { var config = if (opts.default) try Config.default(alloc) else try Config.load(alloc); defer config.deinit(); - const stdout = std.io.getStdOut(); + var buffer: [1024]u8 = undefined; + const stdout: std.fs.File = .stdout(); + var stdout_writer = stdout.writer(&buffer); + const writer = &stdout_writer.interface; - // Despite being under the posix namespace, this also works on Windows as of zig 0.13.0 - if (tui.can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) { + if (tui.can_pretty_print and !opts.plain and stdout.isTty()) { var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); return prettyPrint(arena.allocator(), config.keybind); } else { try config.keybind.formatEntryDocs( - configpkg.entryFormatter("keybind", stdout.writer()), + configpkg.entryFormatter("keybind", writer), opts.docs, ); } + // Don't forget to flush! + try writer.flush(); return 0; } -const TriggerList = std.SinglyLinkedList(Binding.Trigger); +const TriggerNode = struct { + data: Binding.Trigger, + node: std.SinglyLinkedList.Node = .{}, + + pub fn get(node: *std.SinglyLinkedList.Node) *TriggerNode { + return @fieldParentPtr("node", node); + } +}; const ChordBinding = struct { - triggers: TriggerList, + triggers: std.SinglyLinkedList, action: Binding.Action, // Order keybinds based on various properties @@ -109,7 +120,8 @@ const ChordBinding = struct { const lhs_count: usize = blk: { var count: usize = 0; var maybe_trigger = lhs.triggers.first; - while (maybe_trigger) |trigger| : (maybe_trigger = trigger.next) { + while (maybe_trigger) |node| : (maybe_trigger = node.next) { + const trigger: *TriggerNode = .get(node); if (trigger.data.mods.super) count += 1; if (trigger.data.mods.ctrl) count += 1; if (trigger.data.mods.shift) count += 1; @@ -120,7 +132,8 @@ const ChordBinding = struct { const rhs_count: usize = blk: { var count: usize = 0; var maybe_trigger = rhs.triggers.first; - while (maybe_trigger) |trigger| : (maybe_trigger = trigger.next) { + while (maybe_trigger) |node| : (maybe_trigger = node.next) { + const trigger: *TriggerNode = .get(node); if (trigger.data.mods.super) count += 1; if (trigger.data.mods.ctrl) count += 1; if (trigger.data.mods.shift) count += 1; @@ -137,8 +150,8 @@ const ChordBinding = struct { var l_trigger = lhs.triggers.first; var r_trigger = rhs.triggers.first; while (l_trigger != null and r_trigger != null) { - const l_int = l_trigger.?.data.mods.int(); - const r_int = r_trigger.?.data.mods.int(); + const l_int = TriggerNode.get(l_trigger.?).data.mods.int(); + const r_int = TriggerNode.get(r_trigger.?).data.mods.int(); if (l_int != r_int) { return l_int > r_int; @@ -154,13 +167,13 @@ const ChordBinding = struct { while (l_trigger != null and r_trigger != null) { const lhs_key: c_int = blk: { - switch (l_trigger.?.data.key) { + switch (TriggerNode.get(l_trigger.?).data.key) { .physical => |key| break :blk @intFromEnum(key), .unicode => |key| break :blk @intCast(key), } }; const rhs_key: c_int = blk: { - switch (r_trigger.?.data.key) { + switch (TriggerNode.get(r_trigger.?).data.key) { .physical => |key| break :blk @intFromEnum(key), .unicode => |key| break :blk @intCast(key), } @@ -186,19 +199,18 @@ const ChordBinding = struct { fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { // Set up vaxis - var tty = try vaxis.Tty.init(); + var buf: [1024]u8 = undefined; + var tty = try vaxis.Tty.init(&buf); defer tty.deinit(); var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc, tty.anyWriter()); + const writer = tty.writer(); + defer vx.deinit(alloc, writer); // We know we are ghostty, so let's enable mode 2027. Vaxis normally does this but you need an // event loop to auto-enable it. vx.caps.unicode = .unicode; - try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set); - defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; - - var buf_writer = tty.bufferedWriter(); - const writer = buf_writer.writer().any(); + try writer.writeAll(vaxis.ctlseqs.unicode_set); + defer writer.writeAll(vaxis.ctlseqs.unicode_reset) catch {}; const winsize: vaxis.Winsize = switch (builtin.os.tag) { // We use some default, it doesn't really matter for what @@ -212,7 +224,7 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { else => try vaxis.Tty.getWinsize(tty.fd), }; - try vx.resize(alloc, tty.anyWriter(), winsize); + try vx.resize(alloc, writer, winsize); const win = vx.window(); @@ -234,7 +246,9 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { var result: vaxis.Window.PrintResult = .{ .col = 0, .row = 0, .overflow = false }; var maybe_trigger = bind.triggers.first; - while (maybe_trigger) |trigger| : (maybe_trigger = trigger.next) { + while (maybe_trigger) |node| : (maybe_trigger = node.next) { + const trigger: *TriggerNode = .get(node); + if (trigger.data.mods.super) { result = win.printSegment(.{ .text = "super", .style = super_style }, .{ .col_offset = result.col }); result = win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col }); @@ -252,18 +266,18 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { result = win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col }); } const key = switch (trigger.data.key) { - .physical => |k| try std.fmt.allocPrint(alloc, "{s}", .{@tagName(k)}), + .physical => |k| try std.fmt.allocPrint(alloc, "{t}", .{k}), .unicode => |c| try std.fmt.allocPrint(alloc, "{u}", .{c}), }; result = win.printSegment(.{ .text = key }, .{ .col_offset = result.col }); // Print a separator between chorded keys - if (trigger.next != null) { + if (trigger.node.next != null) { result = win.printSegment(.{ .text = " > ", .style = .{ .bold = true, .fg = .{ .index = 6 } } }, .{ .col_offset = result.col }); } } - const action = try std.fmt.allocPrint(alloc, "{}", .{bind.action}); + const action = try std.fmt.allocPrint(alloc, "{f}", .{bind.action}); // If our action has an argument, we print the argument in a different color if (std.mem.indexOfScalar(u8, action, ':')) |idx| { _ = win.print(&.{ @@ -276,29 +290,33 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { } try vx.prettyPrint(writer); } - try buf_writer.flush(); + try writer.flush(); return 0; } -fn iterateBindings(alloc: Allocator, iter: anytype, win: *const vaxis.Window) !struct { []ChordBinding, u16 } { +fn iterateBindings( + alloc: Allocator, + iter: anytype, + win: *const vaxis.Window, +) !struct { []ChordBinding, u16 } { var widest_chord: u16 = 0; - var bindings = std.ArrayList(ChordBinding).init(alloc); + var bindings: std.ArrayList(ChordBinding) = .empty; while (iter.next()) |bind| { const width = blk: { - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); const t = bind.key_ptr.*; - if (t.mods.super) try std.fmt.format(buf.writer(), "super + ", .{}); - if (t.mods.ctrl) try std.fmt.format(buf.writer(), "ctrl + ", .{}); - if (t.mods.alt) try std.fmt.format(buf.writer(), "alt + ", .{}); - if (t.mods.shift) try std.fmt.format(buf.writer(), "shift + ", .{}); + if (t.mods.super) try buf.writer.print("super + ", .{}); + if (t.mods.ctrl) try buf.writer.print("ctrl + ", .{}); + if (t.mods.alt) try buf.writer.print("alt + ", .{}); + if (t.mods.shift) try buf.writer.print("shift + ", .{}); switch (t.key) { - .physical => |k| try std.fmt.format(buf.writer(), "{s}", .{@tagName(k)}), - .unicode => |c| try std.fmt.format(buf.writer(), "{u}", .{c}), + .physical => |k| try buf.writer.print("{t}", .{k}), + .unicode => |c| try buf.writer.print("{u}", .{c}), } - break :blk win.gwidth(buf.items); + break :blk win.gwidth(buf.written()); }; switch (bind.value_ptr.*) { @@ -310,28 +328,28 @@ fn iterateBindings(alloc: Allocator, iter: anytype, win: *const vaxis.Window) !s // Prepend the current keybind onto the list of sub-binds for (sub_bindings) |*nb| { - const prepend_node = try alloc.create(TriggerList.Node); - prepend_node.* = TriggerList.Node{ .data = bind.key_ptr.* }; - nb.triggers.prepend(prepend_node); + const prepend_node = try alloc.create(TriggerNode); + prepend_node.* = .{ .data = bind.key_ptr.* }; + nb.triggers.prepend(&prepend_node.node); } // Add the longest sub-bind width to the current bind width along with a padding // of 5 for the ' > ' spacer widest_chord = @max(widest_chord, width + max_width + 5); - try bindings.appendSlice(sub_bindings); + try bindings.appendSlice(alloc, sub_bindings); }, .leaf => |leaf| { - const node = try alloc.create(TriggerList.Node); - node.* = TriggerList.Node{ .data = bind.key_ptr.* }; - const triggers = TriggerList{ - .first = node, - }; + const node = try alloc.create(TriggerNode); + node.* = .{ .data = bind.key_ptr.* }; widest_chord = @max(widest_chord, width); - try bindings.append(.{ .triggers = triggers, .action = leaf.action }); + try bindings.append(alloc, .{ + .triggers = .{ .first = &node.node }, + .action = leaf.action, + }); }, } } - return .{ try bindings.toOwnedSlice(), widest_chord }; + return .{ try bindings.toOwnedSlice(alloc), widest_chord }; } diff --git a/src/cli/list_themes.zig b/src/cli/list_themes.zig index 0c0acfe84..cc6cfaf3e 100644 --- a/src/cli/list_themes.zig +++ b/src/cli/list_themes.zig @@ -57,9 +57,12 @@ const ThemeListElement = struct { .host = .{ .raw = "" }, .path = .{ .raw = self.path }, }; - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); errdefer buf.deinit(); - try uri.writeToStream(.{ .scheme = true, .authority = true, .path = true }, buf.writer()); + try uri.writeToStream( + &buf.writer, + .{ .scheme = true, .authority = true, .path = true }, + ); return buf.toOwnedSlice(); } }; @@ -114,8 +117,14 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { var arena = std.heap.ArenaAllocator.init(gpa_alloc); const alloc = arena.allocator(); - const stderr = std.io.getStdErr().writer(); - const stdout = std.io.getStdOut().writer(); + var stdout_buf: [4096]u8 = undefined; + var stdout_file: std.fs.File = .stdout(); + var stdout_writer = stdout_file.writer(&stdout_buf); + const stdout = &stdout_writer.interface; + + var stderr_buf: [4096]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&stderr_buf); + const stderr = &stderr_writer.interface; const resources_dir = global_state.resources_dir.app(); if (resources_dir == null) @@ -124,9 +133,9 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { var count: usize = 0; - var themes = std.ArrayList(ThemeListElement).init(alloc); + var themes: std.ArrayList(ThemeListElement) = .empty; - var it = themepkg.LocationIterator{ .arena_alloc = arena.allocator() }; + var it: themepkg.LocationIterator = .{ .arena_alloc = arena.allocator() }; while (try it.next()) |loc| { var dir = std.fs.cwd().openDir(loc.dir, .{ .iterate = true }) catch |err| switch (err) { @@ -148,7 +157,7 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { count += 1; const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name }); - try themes.append(.{ + try themes.append(alloc, .{ .path = path, .location = loc.location, .theme = try alloc.dupe(u8, entry.name), @@ -166,18 +175,20 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 { std.mem.sortUnstable(ThemeListElement, themes.items, {}, ThemeListElement.lessThan); - if (tui.can_pretty_print and !opts.plain and std.posix.isatty(std.io.getStdOut().handle)) { + if (tui.can_pretty_print and !opts.plain and stdout_file.isTty()) { try preview(gpa_alloc, themes.items, opts.color); return 0; } for (themes.items) |theme| { if (opts.path) - try stdout.print("{s} ({s}) {s}\n", .{ theme.theme, @tagName(theme.location), theme.path }) + try stdout.print("{s} ({t}) {s}\n", .{ theme.theme, theme.location, theme.path }) else - try stdout.print("{s} ({s})\n", .{ theme.theme, @tagName(theme.location) }); + try stdout.print("{s} ({t})\n", .{ theme.theme, theme.location }); } + // Don't forget to flush! + try stdout.flush(); return 0; } @@ -209,23 +220,28 @@ const Preview = struct { text_input: vaxis.widgets.TextInput, theme_filter: ColorScheme, - pub fn init(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !*Preview { + pub fn init( + allocator: std.mem.Allocator, + themes: []ThemeListElement, + theme_filter: ColorScheme, + buf: []u8, + ) !*Preview { const self = try allocator.create(Preview); self.* = .{ .allocator = allocator, .should_quit = false, - .tty = try vaxis.Tty.init(), + .tty = try .init(buf), .vx = try vaxis.init(allocator, .{}), .mouse = null, .themes = themes, - .filtered = try std.ArrayList(usize).initCapacity(allocator, themes.len), + .filtered = try .initCapacity(allocator, themes.len), .current = 0, .window = 0, .hex = false, .mode = .normal, .color_scheme = .light, - .text_input = vaxis.widgets.TextInput.init(allocator, &self.vx.unicode), + .text_input = .init(allocator, &self.vx.unicode), .theme_filter = theme_filter, }; @@ -236,9 +252,9 @@ const Preview = struct { pub fn deinit(self: *Preview) void { const allocator = self.allocator; - self.filtered.deinit(); + self.filtered.deinit(allocator); self.text_input.deinit(); - self.vx.deinit(allocator, self.tty.anyWriter()); + self.vx.deinit(allocator, self.tty.writer()); self.tty.deinit(); allocator.destroy(self); } @@ -251,12 +267,14 @@ const Preview = struct { try loop.init(); try loop.start(); - try self.vx.enterAltScreen(self.tty.anyWriter()); - try self.vx.setTitle(self.tty.anyWriter(), "👻 Ghostty Theme Preview 👻"); - try self.vx.queryTerminal(self.tty.anyWriter(), 1 * std.time.ns_per_s); - try self.vx.setMouseMode(self.tty.anyWriter(), true); + const writer = self.tty.writer(); + + try self.vx.enterAltScreen(writer); + try self.vx.setTitle(writer, "👻 Ghostty Theme Preview 👻"); + try self.vx.queryTerminal(writer, 1 * std.time.ns_per_s); + try self.vx.setMouseMode(writer, true); if (self.vx.caps.color_scheme_updates) - try self.vx.subscribeToColorSchemeUpdates(self.tty.anyWriter()); + try self.vx.subscribeToColorSchemeUpdates(writer); while (!self.should_quit) { var arena = std.heap.ArenaAllocator.init(self.allocator); @@ -269,9 +287,8 @@ const Preview = struct { } try self.draw(alloc); - var buffered = self.tty.bufferedWriter(); - try self.vx.render(buffered.writer().any()); - try buffered.flush(); + try self.vx.render(writer); + try writer.flush(); } } @@ -308,11 +325,11 @@ const Preview = struct { const string = try std.ascii.allocLowerString(self.allocator, buffer); defer self.allocator.free(string); - var tokens = std.ArrayList([]const u8).init(self.allocator); - defer tokens.deinit(); + var tokens: std.ArrayList([]const u8) = .empty; + defer tokens.deinit(self.allocator); var it = std.mem.tokenizeScalar(u8, string, ' '); - while (it.next()) |token| try tokens.append(token); + while (it.next()) |token| try tokens.append(self.allocator, token); for (self.themes, 0..) |*theme, i| { try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path); @@ -322,13 +339,13 @@ const Preview = struct { .to_lower = true, .plain = true, }); - if (theme.rank != null) try self.filtered.append(i); + if (theme.rank != null) try self.filtered.append(self.allocator, i); } } else { for (self.themes, 0..) |*theme, i| { try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path); if (shouldIncludeTheme(self.theme_filter, theme_config)) { - try self.filtered.append(i); + try self.filtered.append(self.allocator, i); theme.rank = null; } } @@ -421,13 +438,13 @@ const Preview = struct { self.hex = false; if (key.matches('c', .{})) try self.vx.copyToSystemClipboard( - self.tty.anyWriter(), + self.tty.writer(), self.themes[self.filtered.items[self.current]].theme, alloc, ) else if (key.matches('c', .{ .shift = true })) try self.vx.copyToSystemClipboard( - self.tty.anyWriter(), + self.tty.writer(), self.themes[self.filtered.items[self.current]].path, alloc, ); @@ -471,7 +488,7 @@ const Preview = struct { }, .color_scheme => |color_scheme| self.color_scheme = color_scheme, .mouse => |mouse| self.mouse = mouse, - .winsize => |ws| try self.vx.resize(self.allocator, self.tty.anyWriter(), ws), + .winsize => |ws| try self.vx.resize(self.allocator, self.tty.writer(), ws), } } @@ -1044,14 +1061,14 @@ const Preview = struct { ); } - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); for (config._diagnostics.items(), 0..) |diag, captured_i| { const i: u16 = @intCast(captured_i); - try diag.write(buf.writer()); + try diag.format(&buf.writer); _ = child.printSegment( .{ - .text = buf.items, + .text = buf.written(), .style = self.ui_err(), }, .{ @@ -1319,7 +1336,7 @@ const Preview = struct { .{ .text = "const ", .style = color5 }, .{ .text = "stdout ", .style = standard }, .{ .text = "=", .style = color5 }, - .{ .text = " std.io.getStdOut().writer();", .style = standard }, + .{ .text = " std.Io.getStdOut().writer();", .style = standard }, }, .{ .row_offset = 7, @@ -1651,7 +1668,13 @@ fn color(config: Config, palette: usize) vaxis.Color { const lorem_ipsum = @embedFile("lorem_ipsum.txt"); fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !void { - var app = try Preview.init(allocator, themes, theme_filter); + var buf: [4096]u8 = undefined; + var app = try Preview.init( + allocator, + themes, + theme_filter, + &buf, + ); defer app.deinit(); try app.run(); } diff --git a/src/cli/new_window.zig b/src/cli/new_window.zig index 343175b4e..f3f4740d1 100644 --- a/src/cli/new_window.zig +++ b/src/cli/new_window.zig @@ -26,7 +26,7 @@ pub const Options = struct { // If it's not `-e` continue with the standard argument parsning. if (!std.mem.eql(u8, arg, "-e")) return true; - var arguments: std.ArrayListUnmanaged([:0]const u8) = .empty; + var arguments: std.ArrayList([:0]const u8) = .empty; errdefer { for (arguments.items) |argument| alloc.free(argument); arguments.deinit(alloc); @@ -99,12 +99,21 @@ pub const Options = struct { pub fn run(alloc: Allocator) !u8 { var iter = try args.argsIterator(alloc); defer iter.deinit(); - return try runArgs(alloc, &iter); + + var buffer: [1024]u8 = undefined; + var stderr_writer = std.fs.File.stderr().writer(&buffer); + const stderr = &stderr_writer.interface; + + const result = runArgs(alloc, &iter, stderr); + stderr.flush() catch {}; + return result; } -fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { - const stderr = std.io.getStdErr().writer(); - +fn runArgs( + alloc_gpa: Allocator, + argsIter: anytype, + stderr: *std.Io.Writer, +) !u8 { var opts: Options = .{}; defer opts.deinit(); @@ -126,9 +135,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { inner: inline for (@typeInfo(Options).@"struct".fields) |field| { if (field.name[0] == '_') continue :inner; if (std.mem.eql(u8, field.name, diagnostic.key)) { - try stderr.writeAll("config error: "); - try diagnostic.write(stderr); - try stderr.writeAll("\n"); + try stderr.print("config error: {f}\n", .{diagnostic}); exit = true; } } diff --git a/src/cli/show_config.zig b/src/cli/show_config.zig index 3f22c75c2..1b73b77c1 100644 --- a/src/cli/show_config.zig +++ b/src/cli/show_config.zig @@ -77,7 +77,10 @@ pub fn run(alloc: Allocator) !u8 { // For some reason `std.fmt.format` isn't working here but it works in // tests so we just do configfmt.format. - const stdout = std.io.getStdOut().writer(); - try configfmt.format("", .{}, stdout); + var stdout: std.fs.File = .stdout(); + var buffer: [4096]u8 = undefined; + var stdout_writer = stdout.writer(&buffer); + try configfmt.format(&stdout_writer.interface); + try stdout_writer.end(); return 0; } diff --git a/src/cli/show_face.zig b/src/cli/show_face.zig index e3b596bcd..9dee777b3 100644 --- a/src/cli/show_face.zig +++ b/src/cli/show_face.zig @@ -64,13 +64,32 @@ pub const Options = struct { pub fn run(alloc: Allocator) !u8 { var iter = try args.argsIterator(alloc); defer iter.deinit(); - return try runArgs(alloc, &iter); + + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + + var stderr_buffer: [1024]u8 = undefined; + var stderr_writer = std.fs.File.stdout().writer(&stderr_buffer); + const stderr = &stderr_writer.interface; + + const result = runArgs( + alloc, + &iter, + stdout, + stderr, + ); + stdout.flush() catch {}; + stderr.flush() catch {}; + return result; } -fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { - const stdout = std.io.getStdOut().writer(); - const stderr = std.io.getStdErr().writer(); - +fn runArgs( + alloc_gpa: Allocator, + argsIter: anytype, + stdout: *std.Io.Writer, + stderr: *std.Io.Writer, +) !u8 { // Its possible to build Ghostty without font discovery! if (comptime font.Discover == void) { try stderr.print( @@ -104,9 +123,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { inner: inline for (@typeInfo(Options).@"struct".fields) |field| { if (field.name[0] == '_') continue :inner; if (std.mem.eql(u8, field.name, diagnostic.key)) { - try stderr.writeAll("config error: "); - try diagnostic.write(stderr); - try stderr.writeAll("\n"); + try stderr.print("config error: {f}\n", .{diagnostic}); exit = true; } } @@ -138,9 +155,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { if (field.name[0] == '_') continue :inner; if (std.mem.eql(u8, field.name, diagnostic.key) and (diagnostic.location == .none or diagnostic.location == .cli)) continue :outer; } - try stderr.writeAll("config error: "); - try diagnostic.write(stderr); - try stderr.writeAll("\n"); + try stderr.print("config error: {f}\n", .{diagnostic}); } } @@ -189,8 +204,8 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 { fn lookup( alloc: std.mem.Allocator, - stdout: anytype, - stderr: anytype, + stdout: *std.Io.Writer, + stderr: *std.Io.Writer, font_grid: *font.SharedGrid, style: font.Style, presentation: ?font.Presentation, diff --git a/src/cli/ssh-cache/DiskCache.zig b/src/cli/ssh-cache/DiskCache.zig index db138cf37..608155dfd 100644 --- a/src/cli/ssh-cache/DiskCache.zig +++ b/src/cli/ssh-cache/DiskCache.zig @@ -57,8 +57,6 @@ pub fn clear(self: DiskCache) !void { pub const AddResult = enum { added, updated }; -pub const AddError = std.fs.Dir.MakeError || std.fs.File.OpenError || std.fs.File.LockError || std.fs.File.ReadError || std.fs.File.WriteError || std.posix.RealPathError || std.posix.RenameError || Allocator.Error || error{ HostnameIsInvalid, CacheIsLocked }; - /// Add or update a hostname entry in the cache. /// Returns AddResult.added for new entries or AddResult.updated for existing ones. /// The cache file is created if it doesn't exist with secure permissions (0600). @@ -66,7 +64,7 @@ pub fn add( self: DiskCache, alloc: Allocator, hostname: []const u8, -) AddError!AddResult { +) !AddResult { if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid; // Create cache directory if needed @@ -130,15 +128,13 @@ pub fn add( return result; } -pub const RemoveError = std.fs.Dir.OpenError || std.fs.File.OpenError || std.fs.File.ReadError || std.fs.File.WriteError || std.posix.RealPathError || std.posix.RenameError || Allocator.Error || error{ HostnameIsInvalid, CacheIsLocked }; - /// Remove a hostname entry from the cache. /// No error is returned if the hostname doesn't exist or the cache file is missing. pub fn remove( self: DiskCache, alloc: Allocator, hostname: []const u8, -) RemoveError!void { +) !void { if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid; // Open our file @@ -199,7 +195,7 @@ pub fn contains( return entries.contains(hostname); } -fn fixupPermissions(file: std.fs.File) !void { +fn fixupPermissions(file: std.fs.File) (std.fs.File.StatError || std.fs.File.ChmodError)!void { // Windows does not support chmod if (comptime builtin.os.tag == .windows) return; @@ -211,14 +207,12 @@ fn fixupPermissions(file: std.fs.File) !void { } } -pub const WriteCacheFileError = std.fs.Dir.OpenError || std.fs.File.OpenError || std.fs.File.WriteError || std.fs.Dir.RealPathAllocError || std.posix.RealPathError || std.posix.RenameError || error{FileTooBig}; - fn writeCacheFile( self: DiskCache, alloc: Allocator, entries: std.StringHashMap(Entry), expire_days: ?u32, -) WriteCacheFileError!void { +) !void { var td: TempDir = try .init(); defer td.deinit(); @@ -227,14 +221,18 @@ fn writeCacheFile( const tmp_path = try td.dir.realpathAlloc(alloc, "ssh-cache"); defer alloc.free(tmp_path); - const writer = tmp_file.writer(); + var buf: [1024]u8 = undefined; + var writer = tmp_file.writer(&buf); var iter = entries.iterator(); while (iter.next()) |kv| { // Only write non-expired entries if (kv.value_ptr.isExpired(expire_days)) continue; - try kv.value_ptr.format(writer); + try kv.value_ptr.format(&writer.interface); } + // Don't forget to flush!! + try writer.interface.flush(); + // Atomic replace try std.fs.renameAbsolute(tmp_path, self.path); } @@ -278,8 +276,12 @@ pub fn deinitEntries( fn readEntries( alloc: Allocator, file: std.fs.File, -) (std.fs.File.ReadError || Allocator.Error || error{FileTooBig})!std.StringHashMap(Entry) { - const content = try file.readToEndAlloc(alloc, MAX_CACHE_SIZE); +) !std.StringHashMap(Entry) { + var reader = file.reader(&.{}); + const content = try reader.interface.allocRemaining( + alloc, + .limited(MAX_CACHE_SIZE), + ); defer alloc.free(content); var entries = std.StringHashMap(Entry).init(alloc); @@ -403,10 +405,12 @@ test "disk cache clear" { // Create our path var td: TempDir = try .init(); defer td.deinit(); + var buf: [4096]u8 = undefined; { var file = try td.dir.createFile("cache", .{}); defer file.close(); - try file.writer().writeAll("HELLO!"); + var file_writer = file.writer(&buf); + try file_writer.interface.writeAll("HELLO!"); } const path = try td.dir.realpathAlloc(alloc, "cache"); defer alloc.free(path); @@ -429,10 +433,14 @@ test "disk cache operations" { // Create our path var td: TempDir = try .init(); defer td.deinit(); + var buf: [4096]u8 = undefined; { var file = try td.dir.createFile("cache", .{}); defer file.close(); - try file.writer().writeAll("HELLO!"); + var file_writer = file.writer(&buf); + const writer = &file_writer.interface; + try writer.writeAll("HELLO!"); + try writer.flush(); } const path = try td.dir.realpathAlloc(alloc, "cache"); defer alloc.free(path); diff --git a/src/cli/ssh-cache/Entry.zig b/src/cli/ssh-cache/Entry.zig index 3a691be80..f3403dbd4 100644 --- a/src/cli/ssh-cache/Entry.zig +++ b/src/cli/ssh-cache/Entry.zig @@ -33,7 +33,7 @@ pub fn parse(line: []const u8) ?Entry { }; } -pub fn format(self: Entry, writer: anytype) !void { +pub fn format(self: Entry, writer: *std.Io.Writer) !void { try writer.print( "{s}|{d}|{s}\n", .{ self.hostname, self.timestamp, self.terminfo_version }, diff --git a/src/cli/ssh_cache.zig b/src/cli/ssh_cache.zig index 1099f0112..9434e9771 100644 --- a/src/cli/ssh_cache.zig +++ b/src/cli/ssh_cache.zig @@ -61,9 +61,30 @@ pub fn run(alloc_gpa: Allocator) !u8 { try args.parse(Options, alloc_gpa, &opts, &iter); } - const stdout = std.io.getStdOut().writer(); - const stderr = std.io.getStdErr().writer(); + var stdout_buffer: [1024]u8 = undefined; + var stdout_file: std.fs.File = .stdout(); + var stdout_writer = stdout_file.writer(&stdout_buffer); + const stdout = &stdout_writer.interface; + var stderr_buffer: [1024]u8 = undefined; + var stderr_file: std.fs.File = .stderr(); + var stderr_writer = stderr_file.writer(&stderr_buffer); + const stderr = &stderr_writer.interface; + + const result = runInner(alloc, opts, stdout, stderr); + + // Flushing *shouldn't* fail but... + stdout.flush() catch {}; + stderr.flush() catch {}; + return result; +} + +pub fn runInner( + alloc: Allocator, + opts: Options, + stdout: *std.Io.Writer, + stderr: *std.Io.Writer, +) !u8 { // Setup our disk cache to the standard location const cache_path = try DiskCache.defaultPath(alloc, "ghostty"); const cache: DiskCache = .{ .path = cache_path }; @@ -165,7 +186,7 @@ pub fn run(alloc_gpa: Allocator) !u8 { fn listEntries( alloc: Allocator, entries: *const std.StringHashMap(Entry), - writer: anytype, + writer: *std.Io.Writer, ) !void { if (entries.count() == 0) { try writer.print("No hosts in cache.\n", .{}); @@ -173,12 +194,12 @@ fn listEntries( } // Sort entries by hostname for consistent output - var items = std.ArrayList(Entry).init(alloc); - defer items.deinit(); + var items: std.ArrayList(Entry) = .empty; + defer items.deinit(alloc); var iter = entries.iterator(); while (iter.next()) |kv| { - try items.append(kv.value_ptr.*); + try items.append(alloc, kv.value_ptr.*); } std.mem.sort(Entry, items.items, {}, struct { diff --git a/src/cli/validate_config.zig b/src/cli/validate_config.zig index 114843e9a..55d861402 100644 --- a/src/cli/validate_config.zig +++ b/src/cli/validate_config.zig @@ -40,8 +40,19 @@ pub fn run(alloc: std.mem.Allocator) !u8 { try args.parse(Options, alloc, &opts, &iter); } - const stdout = std.io.getStdOut().writer(); + var buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buffer); + const stdout = &stdout_writer.interface; + const result = runInner(alloc, opts, stdout); + try stdout_writer.end(); + return result; +} +fn runInner( + alloc: std.mem.Allocator, + opts: Options, + stdout: *std.Io.Writer, +) !u8 { var cfg = try Config.default(alloc); defer cfg.deinit(); @@ -58,15 +69,9 @@ pub fn run(alloc: std.mem.Allocator) !u8 { try cfg.finalize(); if (cfg._diagnostics.items().len > 0) { - var buf = std.ArrayList(u8).init(alloc); - defer buf.deinit(); - for (cfg._diagnostics.items()) |diag| { - try diag.write(buf.writer()); - try stdout.print("{s}\n", .{buf.items}); - buf.clearRetainingCapacity(); + try stdout.print("{f}\n", .{diag}); } - return 1; } diff --git a/src/cli/version.zig b/src/cli/version.zig index 22608fa88..cf8e66fa6 100644 --- a/src/cli/version.zig +++ b/src/cli/version.zig @@ -15,8 +15,12 @@ pub const Options = struct {}; /// The `version` command is used to display information about Ghostty. Recognized as /// either `+version` or `--version`. pub fn run(alloc: Allocator) !u8 { - const stdout = std.io.getStdOut().writer(); - const tty = std.io.getStdOut().isTty(); + var buffer: [1024]u8 = undefined; + const stdout_file: std.fs.File = .stdout(); + var stdout_writer = stdout_file.writer(&buffer); + + const stdout = &stdout_writer.interface; + const tty = stdout_file.isTty(); if (tty) if (build_config.version.build) |commit_hash| { try stdout.print( @@ -29,7 +33,7 @@ pub fn run(alloc: Allocator) !u8 { try stdout.print("Version\n", .{}); try stdout.print(" - version: {s}\n", .{build_config.version_string}); - try stdout.print(" - channel: {s}\n", .{@tagName(build_config.release_channel)}); + try stdout.print(" - channel: {t}\n", .{build_config.release_channel}); try stdout.print("Build Config\n", .{}); try stdout.print(" - Zig version : {s}\n", .{builtin.zig_version_string}); @@ -37,20 +41,20 @@ pub fn run(alloc: Allocator) !u8 { try stdout.print(" - app runtime : {}\n", .{build_config.app_runtime}); try stdout.print(" - font engine : {}\n", .{build_config.font_backend}); try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); - try stdout.print(" - libxev : {s}\n", .{@tagName(xev.backend)}); + try stdout.print(" - libxev : {t}\n", .{xev.backend}); if (comptime build_config.app_runtime == .gtk) { if (comptime builtin.os.tag == .linux) { const kernel_info = internal_os.getKernelInfo(alloc); defer if (kernel_info) |k| alloc.free(k); try stdout.print(" - kernel version: {s}\n", .{kernel_info orelse "Kernel information unavailable"}); } - try stdout.print(" - desktop env : {s}\n", .{@tagName(internal_os.desktopEnvironment())}); + try stdout.print(" - desktop env : {t}\n", .{internal_os.desktopEnvironment()}); try stdout.print(" - GTK version :\n", .{}); - try stdout.print(" build : {}\n", .{gtk_version.comptime_version}); - try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()}); + try stdout.print(" build : {f}\n", .{gtk_version.comptime_version}); + try stdout.print(" runtime : {f}\n", .{gtk_version.getRuntimeVersion()}); try stdout.print(" - libadwaita : enabled\n", .{}); - try stdout.print(" build : {}\n", .{adw_version.comptime_version}); - try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()}); + try stdout.print(" build : {f}\n", .{adw_version.comptime_version}); + try stdout.print(" runtime : {f}\n", .{adw_version.getRuntimeVersion()}); if (comptime build_options.x11) { try stdout.print(" - libX11 : enabled\n", .{}); } else { @@ -65,5 +69,8 @@ pub fn run(alloc: Allocator) !u8 { try stdout.print(" - libwayland : disabled\n", .{}); } } + + // Don't forget to flush! + try stdout.flush(); return 0; } diff --git a/src/config/Config.zig b/src/config/Config.zig index 8f811e9a4..caaf5feb8 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -3417,10 +3417,10 @@ pub fn loadFile(self: *Config, alloc: Allocator, path: []const u8) !void { defer file.close(); std.log.info("reading configuration file path={s}", .{path}); - var buf_reader = std.io.bufferedReader(file.reader()); - const reader = buf_reader.reader(); - const Iter = cli.args.LineIterator(@TypeOf(reader)); - var iter: Iter = .{ .r = reader, .filepath = path }; + var buf: [2048]u8 = undefined; + var file_reader = file.reader(&buf); + const reader = &file_reader.interface; + var iter: cli.args.LineIterator = .{ .r = reader, .filepath = path }; try self.loadIter(alloc, &iter); try self.expandPaths(std.fs.path.dirname(path).?); } @@ -3457,8 +3457,10 @@ fn writeConfigTemplate(path: []const u8) !void { } const file = try std.fs.createFileAbsolute(path, .{}); defer file.close(); - try std.fmt.format( - file.writer(), + var buf: [4096]u8 = undefined; + var file_writer = file.writer(&buf); + const writer = &file_writer.interface; + try writer.print( @embedFile("./config-template"), .{ .path = path }, ); @@ -3628,17 +3630,17 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void { // Next, take all remaining args and use that to build up // a command to execute. - var builder = std.ArrayList([:0]const u8).init(arena_alloc); - errdefer builder.deinit(); + var builder: std.ArrayList([:0]const u8) = .empty; + errdefer builder.deinit(arena_alloc); for (args) |arg_raw| { const arg = std.mem.sliceTo(arg_raw, 0); const copy = try arena_alloc.dupeZ(u8, arg); try self._replay_steps.append(arena_alloc, .{ .arg = copy }); - try builder.append(copy); + try builder.append(arena_alloc, copy); } self.@"_xdg-terminal-exec" = true; - self.@"initial-command" = .{ .direct = try builder.toOwnedSlice() }; + self.@"initial-command" = .{ .direct = try builder.toOwnedSlice(arena_alloc) }; return; } } @@ -3710,13 +3712,13 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { // PRIOR to the "-e" in our replay steps, since everything // after "-e" becomes an "initial-command". To do this, we // dupe the values if we find it. - var replay_suffix = std.ArrayList(Replay.Step).init(alloc_gpa); - defer replay_suffix.deinit(); + var replay_suffix: std.ArrayList(Replay.Step) = .empty; + defer replay_suffix.deinit(alloc_gpa); for (self._replay_steps.items, 0..) |step, i| if (step == .@"-e") { // We don't need to clone the steps because they should // all be allocated in our arena and we're keeping our // arena. - try replay_suffix.appendSlice(self._replay_steps.items[i..]); + try replay_suffix.appendSlice(alloc_gpa, self._replay_steps.items[i..]); // Remove our old values. Again, don't need to free any // memory here because its all part of our arena. @@ -3744,10 +3746,11 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { // We must only load a unique file once if (try loaded.fetchPut(path, {}) != null) { const diag: cli.Diagnostic = .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "config-file {s}: cycle detected", .{path}, + 0, ), }; @@ -3759,10 +3762,11 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { var file = std.fs.openFileAbsolute(path, .{}) catch |err| { if (err != error.FileNotFound or !optional) { const diag: cli.Diagnostic = .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "error opening config-file {s}: {}", .{ path, err }, + 0, ), }; @@ -3778,10 +3782,11 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { .file => {}, else => |kind| { const diag: cli.Diagnostic = .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "config-file {s}: not reading because file type is {s}", .{ path, @tagName(kind) }, + 0, ), }; @@ -3792,10 +3797,10 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void { } log.info("loading config-file path={s}", .{path}); - var buf_reader = std.io.bufferedReader(file.reader()); - const reader = buf_reader.reader(); - const Iter = cli.args.LineIterator(@TypeOf(reader)); - var iter: Iter = .{ .r = reader, .filepath = path }; + var buf: [2048]u8 = undefined; + var file_reader = file.reader(&buf); + const reader = &file_reader.interface; + var iter: cli.args.LineIterator = .{ .r = reader, .filepath = path }; try self.loadIter(alloc_gpa, &iter); try self.expandPaths(std.fs.path.dirname(path).?); } @@ -3944,10 +3949,10 @@ fn loadTheme(self: *Config, theme: Theme) !void { errdefer new_config.deinit(); // Load our theme - var buf_reader = std.io.bufferedReader(file.reader()); - const reader = buf_reader.reader(); - const Iter = cli.args.LineIterator(@TypeOf(reader)); - var iter: Iter = .{ .r = reader, .filepath = path }; + var buf: [2048]u8 = undefined; + var file_reader = file.reader(&buf); + const reader = &file_reader.interface; + var iter: cli.args.LineIterator = .{ .r = reader, .filepath = path }; try new_config.loadIter(alloc_gpa, &iter); // Setup our replay to be conditional. @@ -4190,7 +4195,7 @@ pub fn finalize(self: *Config) !void { if (self.@"quit-after-last-window-closed-delay") |duration| { if (duration.duration < 5 * std.time.ns_per_s) { log.warn( - "quit-after-last-window-closed-delay is set to a very short value ({}), which might cause problems", + "quit-after-last-window-closed-delay is set to a very short value ({f}), which might cause problems", .{duration}, ); } @@ -4221,22 +4226,23 @@ pub fn parseManuallyHook( // Build up the command. We don't clean this up because we take // ownership in our allocator. - var command: std.ArrayList([:0]const u8) = .init(alloc); - errdefer command.deinit(); + var command: std.ArrayList([:0]const u8) = .empty; + errdefer command.deinit(alloc); while (iter.next()) |param| { const copy = try alloc.dupeZ(u8, param); try self._replay_steps.append(alloc, .{ .arg = copy }); - try command.append(copy); + try command.append(alloc, copy); } if (command.items.len == 0) { try self._diagnostics.append(alloc, .{ .location = try cli.Location.fromIter(iter, alloc), - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( alloc, "missing command after {s}", .{arg}, + 0, ), }); @@ -4371,10 +4377,11 @@ pub fn addDiagnosticFmt( ) Allocator.Error!void { const alloc = self._arena.?.allocator(); try self._diagnostics.append(alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( alloc, fmt, args, + 0, ), }); } @@ -4892,7 +4899,7 @@ pub const Color = struct { } /// Used by Formatter - pub fn formatEntry(self: Color, formatter: anytype) !void { + pub fn formatEntry(self: Color, formatter: formatterpkg.EntryFormatter) !void { var buf: [128]u8 = undefined; try formatter.formatEntry( []const u8, @@ -4959,12 +4966,12 @@ pub const Color = struct { test "formatConfig" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var color: Color = .{ .r = 10, .g = 11, .b = 12 }; - try color.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = #0a0b0c\n", buf.items); + try color.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = #0a0b0c\n", buf.written()); } test "parseCLI with whitespace" { @@ -4995,7 +5002,7 @@ pub const TerminalColor = union(enum) { } /// Used by Formatter - pub fn formatEntry(self: TerminalColor, formatter: anytype) !void { + pub fn formatEntry(self: TerminalColor, formatter: formatterpkg.EntryFormatter) !void { switch (self) { .color => try self.color.formatEntry(formatter), @@ -5030,12 +5037,12 @@ pub const TerminalColor = union(enum) { test "formatConfig" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var sc: TerminalColor = .@"cell-foreground"; - try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); + try sc.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.written()); } }; @@ -5051,7 +5058,7 @@ pub const BoldColor = union(enum) { } /// Used by Formatter - pub fn formatEntry(self: BoldColor, formatter: anytype) !void { + pub fn formatEntry(self: BoldColor, formatter: formatterpkg.EntryFormatter) !void { switch (self) { .color => try self.color.formatEntry(formatter), .bright => try formatter.formatEntry( @@ -5082,12 +5089,12 @@ pub const BoldColor = union(enum) { test "formatConfig" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var sc: BoldColor = .bright; - try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try testing.expectEqualSlices(u8, "a = bright\n", buf.items); + try sc.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try testing.expectEqualSlices(u8, "a = bright\n", buf.written()); } }; @@ -5174,8 +5181,7 @@ pub const ColorList = struct { // Build up the value of our config. Our buffer size should be // sized to contain all possible maximum values. var buf: [1024]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - var writer = fbs.writer(); + var writer: std.Io.Writer = .fixed(&buf); for (self.colors.items, 0..) |color, i| { var color_buf: [128]u8 = undefined; const color_str = try color.formatBuf(&color_buf); @@ -5185,7 +5191,7 @@ pub const ColorList = struct { try formatter.formatEntry( []const u8, - fbs.getWritten(), + writer.buffered(), ); } @@ -5214,7 +5220,7 @@ pub const ColorList = struct { test "format" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -5223,8 +5229,8 @@ pub const ColorList = struct { var p: Self = .{}; try p.parseCLI(alloc, "black,white"); - try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = #000000,#ffffff\n", buf.items); + try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = #000000,#ffffff\n", buf.written()); } }; @@ -5285,7 +5291,7 @@ pub const Palette = struct { } /// Used by Formatter - pub fn formatEntry(self: Self, formatter: anytype) !void { + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { var buf: [128]u8 = undefined; for (0.., self.value) |k, v| { try formatter.formatEntry( @@ -5340,12 +5346,12 @@ pub const Palette = struct { test "formatConfig" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var list: Self = .{}; - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = 0=#1d1f21\n", buf.items[0..14]); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = 0=#1d1f21\n", buf.written()[0..14]); } test "parseCLI with whitespace" { @@ -5439,7 +5445,7 @@ pub const RepeatableString = struct { } /// Used by Formatter - pub fn formatEntry(self: Self, formatter: anytype) !void { + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { // If no items, we want to render an empty field. if (self.list.items.len == 0) { try formatter.formatEntry(void, {}); @@ -5486,17 +5492,17 @@ pub const RepeatableString = struct { test "formatConfig empty" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var list: Self = .{}; - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = \n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = \n", buf.written()); } test "formatConfig single item" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -5505,13 +5511,13 @@ pub const RepeatableString = struct { var list: Self = .{}; try list.parseCLI(alloc, "A"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A\n", buf.written()); } test "formatConfig multiple items" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -5521,8 +5527,8 @@ pub const RepeatableString = struct { var list: Self = .{}; try list.parseCLI(alloc, "A"); try list.parseCLI(alloc, "B"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A\na = B\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A\na = B\n", buf.written()); } }; @@ -5638,7 +5644,7 @@ pub const RepeatableFontVariation = struct { test "formatConfig single" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -5647,8 +5653,8 @@ pub const RepeatableFontVariation = struct { var list: Self = .{}; try list.parseCLI(alloc, "wght = 200"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = wght=200\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = wght=200\n", buf.written()); } }; @@ -6449,7 +6455,7 @@ pub const Keybinds = struct { } /// Like formatEntry but has an option to include docs. - pub fn formatEntryDocs(self: Keybinds, formatter: anytype, docs: bool) !void { + pub fn formatEntryDocs(self: Keybinds, formatter: formatterpkg.EntryFormatter, docs: bool) !void { if (self.set.bindings.size == 0) { try formatter.formatEntry(void, {}); return; @@ -6478,14 +6484,14 @@ pub const Keybinds = struct { } } - var buffer_stream = std.io.fixedBufferStream(&buf); - std.fmt.format(buffer_stream.writer(), "{}", .{k}) catch return error.OutOfMemory; - try v.formatEntries(&buffer_stream, formatter); + var writer: std.Io.Writer = .fixed(&buf); + writer.print("{f}", .{k}) catch return error.OutOfMemory; + try v.formatEntries(&writer, formatter); } } /// Used by Formatter - pub fn formatEntry(self: Keybinds, formatter: anytype) !void { + pub fn formatEntry(self: Keybinds, formatter: formatterpkg.EntryFormatter) !void { try self.formatEntryDocs(formatter, false); } @@ -6502,7 +6508,7 @@ pub const Keybinds = struct { test "formatConfig single" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6511,14 +6517,14 @@ pub const Keybinds = struct { var list: Keybinds = .{}; try list.parseCLI(alloc, "shift+a=csi:hello"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = shift+a=csi:hello\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = shift+a=csi:hello\n", buf.written()); } // Regression test for https://github.com/ghostty-org/ghostty/issues/2734 test "formatConfig multiple items" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6528,7 +6534,7 @@ pub const Keybinds = struct { var list: Keybinds = .{}; try list.parseCLI(alloc, "ctrl+z>1=goto_tab:1"); try list.parseCLI(alloc, "ctrl+z>2=goto_tab:2"); - try list.formatEntry(formatterpkg.entryFormatter("keybind", buf.writer())); + try list.formatEntry(formatterpkg.entryFormatter("keybind", &buf.writer)); // Note they turn into translated keys because they match // their ASCII mapping. @@ -6537,12 +6543,12 @@ pub const Keybinds = struct { \\keybind = ctrl+z>1=goto_tab:1 \\ ; - try std.testing.expectEqualStrings(want, buf.items); + try std.testing.expectEqualStrings(want, buf.written()); } test "formatConfig multiple items nested" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6554,7 +6560,7 @@ pub const Keybinds = struct { try list.parseCLI(alloc, "ctrl+a>ctrl+b>w=close_window"); try list.parseCLI(alloc, "ctrl+a>ctrl+c>t=new_tab"); try list.parseCLI(alloc, "ctrl+b>ctrl+d>a=previous_tab"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); // NB: This does not currently retain the order of the keybinds. const want = @@ -6564,7 +6570,7 @@ pub const Keybinds = struct { \\a = ctrl+b>ctrl+d>a=previous_tab \\ ; - try std.testing.expectEqualStrings(want, buf.items); + try std.testing.expectEqualStrings(want, buf.written()); } }; @@ -6790,7 +6796,7 @@ pub const RepeatableCodepointMap = struct { test "formatConfig single" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6799,13 +6805,13 @@ pub const RepeatableCodepointMap = struct { var list: Self = .{}; try list.parseCLI(alloc, "U+ABCD=Comic Sans"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = U+ABCD=Comic Sans\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = U+ABCD=Comic Sans\n", buf.written()); } test "formatConfig range" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6814,13 +6820,13 @@ pub const RepeatableCodepointMap = struct { var list: Self = .{}; try list.parseCLI(alloc, "U+0001 - U+0005=Verdana"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = U+0001-U+0005=Verdana\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = U+0001-U+0005=Verdana\n", buf.written()); } test "formatConfig multiple" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6829,12 +6835,12 @@ pub const RepeatableCodepointMap = struct { var list: Self = .{}; try list.parseCLI(alloc, "U+0006-U+0009, U+ABCD=Courier"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); try std.testing.expectEqualSlices(u8, \\a = U+0006-U+0009=Courier \\a = U+ABCD=Courier \\ - , buf.items); + , buf.written()); } }; @@ -6886,7 +6892,7 @@ pub const FontStyle = union(enum) { } /// Used by Formatter - pub fn formatEntry(self: Self, formatter: anytype) !void { + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { switch (self) { .default, .false => try formatter.formatEntry( []const u8, @@ -6918,7 +6924,7 @@ pub const FontStyle = union(enum) { test "formatConfig default" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6927,13 +6933,13 @@ pub const FontStyle = union(enum) { var p: Self = .{ .default = {} }; try p.parseCLI(alloc, "default"); - try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = default\n", buf.items); + try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = default\n", buf.written()); } test "formatConfig false" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6942,13 +6948,13 @@ pub const FontStyle = union(enum) { var p: Self = .{ .default = {} }; try p.parseCLI(alloc, "false"); - try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = false\n", buf.items); + try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = false\n", buf.written()); } test "formatConfig named" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -6957,8 +6963,8 @@ pub const FontStyle = union(enum) { var p: Self = .{ .default = {} }; try p.parseCLI(alloc, "bold"); - try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = bold\n", buf.items); + try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = bold\n", buf.written()); } }; @@ -7018,7 +7024,7 @@ pub const RepeatableLink = struct { } /// Used by Formatter - pub fn formatEntry(self: Self, formatter: anytype) !void { + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { // This currently can't be set so we don't format anything. _ = self; _ = formatter; @@ -7128,7 +7134,10 @@ pub const RepeatableCommand = struct { } /// Used by Formatter - pub fn formatEntry(self: RepeatableCommand, formatter: anytype) !void { + pub fn formatEntry( + self: RepeatableCommand, + formatter: formatterpkg.EntryFormatter, + ) !void { if (self.value.items.len == 0) { try formatter.formatEntry(void, {}); return; @@ -7136,22 +7145,23 @@ pub const RepeatableCommand = struct { for (self.value.items) |item| { var buf: [4096]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - var writer = fbs.writer(); + var writer: std.Io.Writer = .fixed(&buf); - writer.writeAll("title:\"") catch return error.OutOfMemory; - std.zig.stringEscape(item.title, "", .{}, writer) catch return error.OutOfMemory; - writer.writeAll("\"") catch return error.OutOfMemory; + writer.print( + "title:\"{f}\"", + .{std.zig.fmtString(item.title)}, + ) catch return error.OutOfMemory; if (item.description.len > 0) { - writer.writeAll(",description:\"") catch return error.OutOfMemory; - std.zig.stringEscape(item.description, "", .{}, writer) catch return error.OutOfMemory; - writer.writeAll("\"") catch return error.OutOfMemory; + writer.print( + ",description:\"{f}\"", + .{std.zig.fmtString(item.description)}, + ) catch return error.OutOfMemory; } - writer.print(",action:\"{}\"", .{item.action}) catch return error.OutOfMemory; + writer.print(",action:\"{f}\"", .{item.action}) catch return error.OutOfMemory; - try formatter.formatEntry([]const u8, fbs.getWritten()); + try formatter.formatEntry([]const u8, writer.buffered()); } } @@ -7197,17 +7207,17 @@ pub const RepeatableCommand = struct { test "RepeatableCommand formatConfig empty" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var list: RepeatableCommand = .{}; - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = \n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = \n", buf.written()); } test "RepeatableCommand formatConfig single item" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -7216,13 +7226,13 @@ pub const RepeatableCommand = struct { var list: RepeatableCommand = .{}; try list.parseCLI(alloc, "title:Bobr, action:text:Bober"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:Bober\"\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:Bober\"\n", buf.written()); } test "RepeatableCommand formatConfig multiple items" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -7232,14 +7242,12 @@ pub const RepeatableCommand = struct { var list: RepeatableCommand = .{}; try list.parseCLI(alloc, "title:Bobr, action:text:kurwa"); try list.parseCLI(alloc, "title:Ja, description: pierdole, action:text:jakie bydle"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:kurwa\"\na = title:\"Ja\",description:\"pierdole\",action:\"text:jakie bydle\"\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:kurwa\"\na = title:\"Ja\",description:\"pierdole\",action:\"text:jakie bydle\"\n", buf.written()); } test "RepeatableCommand parseCLI commas" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); - defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); defer arena.deinit(); @@ -7455,14 +7463,14 @@ pub const MouseScrollMultiplier = struct { } /// Used by Formatter - pub fn formatEntry(self: Self, formatter: anytype) !void { - var buf: [32]u8 = undefined; - const formatted = std.fmt.bufPrint( - &buf, + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { + var buf: [4096]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); + writer.print( "precision:{d},discrete:{d}", .{ self.precision, self.discrete }, ) catch return error.OutOfMemory; - try formatter.formatEntry([]const u8, formatted); + try formatter.formatEntry([]const u8, writer.buffered()); } test "parse" { @@ -7505,12 +7513,12 @@ pub const MouseScrollMultiplier = struct { test "format entry MouseScrollMultiplier" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var args: Self = .{ .precision = 1.5, .discrete = 2.5 }; - try args.formatEntry(formatterpkg.entryFormatter("mouse-scroll-multiplier", buf.writer())); - try testing.expectEqualSlices(u8, "mouse-scroll-multiplier = precision:1.5,discrete:2.5\n", buf.items); + try args.formatEntry(formatterpkg.entryFormatter("mouse-scroll-multiplier", &buf.writer)); + try testing.expectEqualSlices(u8, "mouse-scroll-multiplier = precision:1.5,discrete:2.5\n", buf.written()); } }; @@ -7627,7 +7635,7 @@ pub const QuickTerminalSize = struct { return error.MissingUnit; } - fn format(self: Size, writer: anytype) !void { + fn format(self: Size, writer: *std.Io.Writer) !void { switch (self) { .percentage => |v| try writer.print("{d}%", .{v}), .pixels => |v| try writer.print("{}px", .{v}), @@ -7745,20 +7753,19 @@ pub const QuickTerminalSize = struct { }; } - pub fn formatEntry(self: QuickTerminalSize, formatter: anytype) !void { + pub fn formatEntry(self: QuickTerminalSize, formatter: formatterpkg.EntryFormatter) !void { const primary = self.primary orelse return; var buf: [4096]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - const writer = fbs.writer(); + var writer: std.Io.Writer = .fixed(&buf); - primary.format(writer) catch return error.OutOfMemory; + primary.format(&writer) catch return error.OutOfMemory; if (self.secondary) |secondary| { writer.writeByte(',') catch return error.OutOfMemory; - secondary.format(writer) catch return error.OutOfMemory; + secondary.format(&writer) catch return error.OutOfMemory; } - try formatter.formatEntry([]const u8, fbs.getWritten()); + try formatter.formatEntry([]const u8, writer.buffered()); } test "parse QuickTerminalSize" { @@ -8318,15 +8325,17 @@ pub const Duration = struct { return if (value) |v| .{ .duration = v } else error.ValueRequired; } - pub fn formatEntry(self: Duration, formatter: anytype) !void { + pub fn formatEntry(self: Duration, formatter: formatterpkg.EntryFormatter) !void { var buf: [64]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - const writer = fbs.writer(); - try self.format("", .{}, writer); - try formatter.formatEntry([]const u8, fbs.getWritten()); + var writer: std.Io.Writer = .fixed(&buf); + try self.format(&writer); + try formatter.formatEntry([]const u8, writer.buffered()); } - pub fn format(self: Duration, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + pub fn format( + self: Duration, + writer: *std.Io.Writer, + ) !void { var value = self.duration; var i: usize = 0; for (units) |unit| { @@ -8393,7 +8402,7 @@ pub const WindowPadding = struct { } } - pub fn formatEntry(self: Self, formatter: anytype) !void { + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { var buf: [128]u8 = undefined; if (self.top_left == self.bottom_right) { try formatter.formatEntry( @@ -8555,7 +8564,7 @@ test "test format" { inline for (Duration.units) |unit| { const d: Duration = .{ .duration = unit.factor }; var actual_buf: [16]u8 = undefined; - const actual = try std.fmt.bufPrint(&actual_buf, "{}", .{d}); + const actual = try std.fmt.bufPrint(&actual_buf, "{f}", .{d}); var expected_buf: [16]u8 = undefined; const expected = if (!std.mem.eql(u8, unit.name, "us")) try std.fmt.bufPrint(&expected_buf, "1{s}", .{unit.name}) @@ -8566,12 +8575,12 @@ test "test format" { } test "test entryFormatter" { - var buf = std.ArrayList(u8).init(std.testing.allocator); + var buf: std.Io.Writer.Allocating = .init(std.testing.allocator); defer buf.deinit(); var p: Duration = .{ .duration = std.math.maxInt(u64) }; - try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualStrings("a = 584y 49w 23h 34m 33s 709ms 551µs 615ns\n", buf.items); + try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualStrings("a = 584y 49w 23h 34m 33s 709ms 551µs 615ns\n", buf.written()); } const TestIterator = struct { @@ -8681,15 +8690,20 @@ test "clone can then change conditional state" { // Setup our test theme var td = try internal_os.TempDir.init(); defer td.deinit(); + var buf: [4096]u8 = undefined; { var file = try td.dir.createFile("theme_light", .{}); defer file.close(); - try file.writer().writeAll(@embedFile("testdata/theme_light")); + var writer = file.writer(&buf); + try writer.interface.writeAll(@embedFile("testdata/theme_light")); + try writer.end(); } { var file = try td.dir.createFile("theme_dark", .{}); defer file.close(); - try file.writer().writeAll(@embedFile("testdata/theme_dark")); + var writer = file.writer(&buf); + try writer.interface.writeAll(@embedFile("testdata/theme_dark")); + try writer.end(); } var light_buf: [std.fs.max_path_bytes]u8 = undefined; const light = try td.dir.realpath("theme_light", &light_buf); @@ -8815,10 +8829,13 @@ test "theme loading" { // Setup our test theme var td = try internal_os.TempDir.init(); defer td.deinit(); + var buf: [4096]u8 = undefined; { var file = try td.dir.createFile("theme", .{}); defer file.close(); - try file.writer().writeAll(@embedFile("testdata/theme_simple")); + var writer = file.writer(&buf); + try writer.interface.writeAll(@embedFile("testdata/theme_simple")); + try writer.end(); } var path_buf: [std.fs.max_path_bytes]u8 = undefined; const path = try td.dir.realpath("theme", &path_buf); @@ -8851,10 +8868,13 @@ test "theme loading preserves conditional state" { // Setup our test theme var td = try internal_os.TempDir.init(); defer td.deinit(); + var buf: [4096]u8 = undefined; { var file = try td.dir.createFile("theme", .{}); defer file.close(); - try file.writer().writeAll(@embedFile("testdata/theme_simple")); + var writer = file.writer(&buf); + try writer.interface.writeAll(@embedFile("testdata/theme_simple")); + try writer.end(); } var path_buf: [std.fs.max_path_bytes]u8 = undefined; const path = try td.dir.realpath("theme", &path_buf); @@ -8881,10 +8901,13 @@ test "theme priority is lower than config" { // Setup our test theme var td = try internal_os.TempDir.init(); defer td.deinit(); + var buf: [4096]u8 = undefined; { var file = try td.dir.createFile("theme", .{}); defer file.close(); - try file.writer().writeAll(@embedFile("testdata/theme_simple")); + var writer = file.writer(&buf); + try writer.interface.writeAll(@embedFile("testdata/theme_simple")); + try writer.end(); } var path_buf: [std.fs.max_path_bytes]u8 = undefined; const path = try td.dir.realpath("theme", &path_buf); @@ -8915,15 +8938,20 @@ test "theme loading correct light/dark" { // Setup our test theme var td = try internal_os.TempDir.init(); defer td.deinit(); + var buf: [4096]u8 = undefined; { var file = try td.dir.createFile("theme_light", .{}); defer file.close(); - try file.writer().writeAll(@embedFile("testdata/theme_light")); + var writer = file.writer(&buf); + try writer.interface.writeAll(@embedFile("testdata/theme_light")); + try writer.end(); } { var file = try td.dir.createFile("theme_dark", .{}); defer file.close(); - try file.writer().writeAll(@embedFile("testdata/theme_dark")); + var writer = file.writer(&buf); + try writer.interface.writeAll(@embedFile("testdata/theme_dark")); + try writer.end(); } var light_buf: [std.fs.max_path_bytes]u8 = undefined; const light = try td.dir.realpath("theme_light", &light_buf); diff --git a/src/config/RepeatableStringMap.zig b/src/config/RepeatableStringMap.zig index 6f143e95d..d5e634333 100644 --- a/src/config/RepeatableStringMap.zig +++ b/src/config/RepeatableStringMap.zig @@ -104,7 +104,7 @@ pub fn equal(self: RepeatableStringMap, other: RepeatableStringMap) bool { } /// Used by formatter -pub fn formatEntry(self: RepeatableStringMap, formatter: anytype) !void { +pub fn formatEntry(self: RepeatableStringMap, formatter: formatterpkg.EntryFormatter) !void { // If no items, we want to render an empty field. if (self.map.count() == 0) { try formatter.formatEntry(void, {}); @@ -146,12 +146,12 @@ test "RepeatableStringMap: parseCLI" { test "RepeatableStringMap: formatConfig empty" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var list: RepeatableStringMap = .{}; - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = \n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = \n", buf.written()); } test "RepeatableStringMap: formatConfig single item" { @@ -162,20 +162,20 @@ test "RepeatableStringMap: formatConfig single item" { const alloc = arena.allocator(); { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var map: RepeatableStringMap = .{}; try map.parseCLI(alloc, "A=B"); - try map.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.items); + try map.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.written()); } { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var map: RepeatableStringMap = .{}; try map.parseCLI(alloc, " A = B "); - try map.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.items); + try map.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.written()); } } @@ -187,12 +187,12 @@ test "RepeatableStringMap: formatConfig multiple items" { const alloc = arena.allocator(); { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var list: RepeatableStringMap = .{}; try list.parseCLI(alloc, "A=B"); try list.parseCLI(alloc, "B = C"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A=B\na = B=C\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A=B\na = B=C\n", buf.written()); } } diff --git a/src/config/command.zig b/src/config/command.zig index 9efeb199e..e0cdc641b 100644 --- a/src/config/command.zig +++ b/src/config/command.zig @@ -166,21 +166,20 @@ pub const Command = union(enum) { }; } - pub fn formatEntry(self: Self, formatter: anytype) !void { + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { switch (self) { .shell => |v| try formatter.formatEntry([]const u8, v), .direct => |v| { var buf: [4096]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - const writer = fbs.writer(); + var writer: std.Io.Writer = .fixed(&buf); writer.writeAll("direct:") catch return error.OutOfMemory; for (v) |arg| { writer.writeAll(arg) catch return error.OutOfMemory; writer.writeByte(' ') catch return error.OutOfMemory; } - const written = fbs.getWritten(); + const written = writer.buffered(); try formatter.formatEntry( []const u8, written[0..@intCast(written.len - 1)], @@ -292,13 +291,13 @@ pub const Command = union(enum) { defer arena.deinit(); const alloc = arena.allocator(); - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); var v: Self = undefined; try v.parseCLI(alloc, "echo hello"); - try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = echo hello\n", buf.items); + try v.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = echo hello\n", buf.written()); } test "Command: formatConfig direct" { @@ -307,13 +306,13 @@ pub const Command = union(enum) { defer arena.deinit(); const alloc = arena.allocator(); - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); var v: Self = undefined; try v.parseCLI(alloc, "direct: echo hello"); - try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = direct:echo hello\n", buf.items); + try v.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = direct:echo hello\n", buf.written()); } }; diff --git a/src/config/edit.zig b/src/config/edit.zig index 38dc98169..07bb7ee5a 100644 --- a/src/config/edit.zig +++ b/src/config/edit.zig @@ -89,8 +89,8 @@ fn configPath(alloc_arena: Allocator) ![]const u8 { /// Returns a const list of possible paths the main config file could be /// in for the current OS. fn configPathCandidates(alloc_arena: Allocator) ![]const []const u8 { - var paths = try std.ArrayList([]const u8).initCapacity(alloc_arena, 2); - errdefer paths.deinit(); + var paths: std.ArrayList([]const u8) = try .initCapacity(alloc_arena, 2); + errdefer paths.deinit(alloc_arena); if (comptime builtin.os.tag == .macos) { paths.appendAssumeCapacity(try internal_os.macos.appSupportDir( diff --git a/src/config/formatter.zig b/src/config/formatter.zig index a42395c19..dcf99167d 100644 --- a/src/config/formatter.zig +++ b/src/config/formatter.zig @@ -8,38 +8,36 @@ const Key = @import("key.zig").Key; /// Returns a single entry formatter for the given field name and writer. pub fn entryFormatter( name: []const u8, - writer: anytype, -) EntryFormatter(@TypeOf(writer)) { + writer: *std.Io.Writer, +) EntryFormatter { return .{ .name = name, .writer = writer }; } /// The entry formatter type for a given writer. -pub fn EntryFormatter(comptime WriterType: type) type { - return struct { - name: []const u8, - writer: WriterType, +pub const EntryFormatter = struct { + name: []const u8, + writer: *std.Io.Writer, - pub fn formatEntry( - self: @This(), - comptime T: type, - value: T, - ) !void { - return formatter.formatEntry( - T, - self.name, - value, - self.writer, - ); - } - }; -} + pub fn formatEntry( + self: @This(), + comptime T: type, + value: T, + ) !void { + return formatter.formatEntry( + T, + self.name, + value, + self.writer, + ); + } +}; /// Format a single type with the given name and value. pub fn formatEntry( comptime T: type, name: []const u8, value: T, - writer: anytype, + writer: *std.Io.Writer, ) !void { switch (@typeInfo(T)) { .bool, .int => { @@ -53,7 +51,7 @@ pub fn formatEntry( }, .@"enum" => { - try writer.print("{s} = {s}\n", .{ name, @tagName(value) }); + try writer.print("{s} = {t}\n", .{ name, value }); return; }, @@ -143,19 +141,14 @@ pub const FileFormatter = struct { /// Implements std.fmt so it can be used directly with std.fmt. pub fn format( self: FileFormatter, - comptime layout: []const u8, - opts: std.fmt.FormatOptions, - writer: anytype, - ) !void { + writer: *std.Io.Writer, + ) std.Io.Writer.Error!void { @setEvalBranchQuota(10_000); - _ = layout; - _ = opts; - // If we're change-tracking then we need the default config to // compare against. var default: ?Config = if (self.changed) - try .default(self.alloc) + Config.default(self.alloc) catch return error.WriteFailed else null; defer if (default) |*v| v.deinit(); @@ -179,12 +172,12 @@ pub const FileFormatter = struct { } } - try formatEntry( + formatEntry( field.type, field.name, value, writer, - ); + ) catch return error.WriteFailed; if (do_docs) try writer.print("\n", .{}); } @@ -198,7 +191,7 @@ test "format default config" { var cfg = try Config.default(alloc); defer cfg.deinit(); - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); // We just make sure this works without errors. We aren't asserting output. @@ -206,9 +199,9 @@ test "format default config" { .alloc = alloc, .config = &cfg, }; - try std.fmt.format(buf.writer(), "{}", .{fmt}); + try fmt.format(&buf.writer); - //std.log.warn("{s}", .{buf.items}); + //std.log.warn("{s}", .{buf.written()}); } test "format default config changed" { @@ -218,7 +211,7 @@ test "format default config changed" { defer cfg.deinit(); cfg.@"font-size" = 42; - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); // We just make sure this works without errors. We aren't asserting output. @@ -227,26 +220,26 @@ test "format default config changed" { .config = &cfg, .changed = true, }; - try std.fmt.format(buf.writer(), "{}", .{fmt}); + try fmt.format(&buf.writer); - //std.log.warn("{s}", .{buf.items}); + //std.log.warn("{s}", .{buf.written()}); } test "formatEntry bool" { const testing = std.testing; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(bool, "a", true, buf.writer()); - try testing.expectEqualStrings("a = true\n", buf.items); + try formatEntry(bool, "a", true, &buf.writer); + try testing.expectEqualStrings("a = true\n", buf.written()); } { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(bool, "a", false, buf.writer()); - try testing.expectEqualStrings("a = false\n", buf.items); + try formatEntry(bool, "a", false, &buf.writer); + try testing.expectEqualStrings("a = false\n", buf.written()); } } @@ -254,10 +247,10 @@ test "formatEntry int" { const testing = std.testing; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(u8, "a", 123, buf.writer()); - try testing.expectEqualStrings("a = 123\n", buf.items); + try formatEntry(u8, "a", 123, &buf.writer); + try testing.expectEqualStrings("a = 123\n", buf.written()); } } @@ -265,10 +258,10 @@ test "formatEntry float" { const testing = std.testing; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(f64, "a", 0.7, buf.writer()); - try testing.expectEqualStrings("a = 0.7\n", buf.items); + try formatEntry(f64, "a", 0.7, &buf.writer); + try testing.expectEqualStrings("a = 0.7\n", buf.written()); } } @@ -277,10 +270,10 @@ test "formatEntry enum" { const Enum = enum { one, two, three }; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(Enum, "a", .two, buf.writer()); - try testing.expectEqualStrings("a = two\n", buf.items); + try formatEntry(Enum, "a", .two, &buf.writer); + try testing.expectEqualStrings("a = two\n", buf.written()); } } @@ -288,10 +281,10 @@ test "formatEntry void" { const testing = std.testing; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(void, "a", {}, buf.writer()); - try testing.expectEqualStrings("a = \n", buf.items); + try formatEntry(void, "a", {}, &buf.writer); + try testing.expectEqualStrings("a = \n", buf.written()); } } @@ -299,17 +292,17 @@ test "formatEntry optional" { const testing = std.testing; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(?bool, "a", null, buf.writer()); - try testing.expectEqualStrings("a = \n", buf.items); + try formatEntry(?bool, "a", null, &buf.writer); + try testing.expectEqualStrings("a = \n", buf.written()); } { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(?bool, "a", false, buf.writer()); - try testing.expectEqualStrings("a = false\n", buf.items); + try formatEntry(?bool, "a", false, &buf.writer); + try testing.expectEqualStrings("a = false\n", buf.written()); } } @@ -317,10 +310,10 @@ test "formatEntry string" { const testing = std.testing; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry([]const u8, "a", "hello", buf.writer()); - try testing.expectEqualStrings("a = hello\n", buf.items); + try formatEntry([]const u8, "a", "hello", &buf.writer); + try testing.expectEqualStrings("a = hello\n", buf.written()); } } @@ -332,9 +325,9 @@ test "formatEntry packed struct" { }; { - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); - try formatEntry(Value, "a", .{}, buf.writer()); - try testing.expectEqualStrings("a = one,no-two\n", buf.items); + try formatEntry(Value, "a", .{}, &buf.writer); + try testing.expectEqualStrings("a = one,no-two\n", buf.written()); } } diff --git a/src/config/io.zig b/src/config/io.zig index 8be4be551..9d9a127e8 100644 --- a/src/config/io.zig +++ b/src/config/io.zig @@ -94,10 +94,9 @@ pub const ReadableIO = union(enum) { }; } - pub fn formatEntry(self: Self, formatter: anytype) !void { + pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void { var buf: [4096]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - const writer = fbs.writer(); + var writer: std.Io.Writer = .fixed(&buf); switch (self) { inline else => |v, tag| { writer.writeAll(@tagName(tag)) catch return error.OutOfMemory; @@ -106,10 +105,9 @@ pub const ReadableIO = union(enum) { }, } - const written = fbs.getWritten(); try formatter.formatEntry( []const u8, - written, + writer.buffered(), ); } @@ -144,13 +142,13 @@ pub const ReadableIO = union(enum) { defer arena.deinit(); const alloc = arena.allocator(); - var buf = std.ArrayList(u8).init(alloc); + var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); var v: Self = undefined; try v.parseCLI(alloc, "raw:foo"); - try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = raw:foo\n", buf.items); + try v.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = raw:foo\n", buf.written()); } }; @@ -222,7 +220,7 @@ pub const RepeatableReadableIO = struct { /// Used by Formatter pub fn formatEntry( self: Self, - formatter: anytype, + formatter: formatterpkg.EntryFormatter, ) !void { if (self.list.items.len == 0) { try formatter.formatEntry(void, {}); diff --git a/src/config/path.zig b/src/config/path.zig index 651dbdb3a..aeba69b94 100644 --- a/src/config/path.zig +++ b/src/config/path.zig @@ -79,7 +79,7 @@ pub const Path = union(enum) { } /// Used by formatter. - pub fn formatEntry(self: *const Path, formatter: anytype) !void { + pub fn formatEntry(self: *const Path, formatter: formatterpkg.EntryFormatter) !void { var buf: [std.fs.max_path_bytes + 1]u8 = undefined; const value = switch (self.*) { .optional => |path| std.fmt.bufPrint( @@ -154,10 +154,11 @@ pub const Path = union(enum) { &buf, ) catch |err| { try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "error expanding home directory for path {s}: {}", .{ path, err }, + 0, ), }); @@ -194,10 +195,11 @@ pub const Path = union(enum) { } try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "error resolving file path {s}: {}", .{ path, err }, + 0, ), }); @@ -306,7 +308,7 @@ pub const Path = union(enum) { test "formatConfig single item" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -315,13 +317,13 @@ pub const Path = union(enum) { var item: Path = undefined; try item.parseCLI(alloc, "A"); - try item.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A\n", buf.items); + try item.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A\n", buf.written()); } test "formatConfig multiple items" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -331,8 +333,8 @@ pub const Path = union(enum) { var item: Path = undefined; try item.parseCLI(alloc, "A"); try item.parseCLI(alloc, "?B"); - try item.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = ?B\n", buf.items); + try item.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = ?B\n", buf.written()); } }; @@ -382,7 +384,7 @@ pub const RepeatablePath = struct { } /// Used by Formatter - pub fn formatEntry(self: RepeatablePath, formatter: anytype) !void { + pub fn formatEntry(self: RepeatablePath, formatter: formatterpkg.EntryFormatter) !void { if (self.value.items.len == 0) { try formatter.formatEntry(void, {}); return; @@ -453,17 +455,17 @@ pub const RepeatablePath = struct { test "formatConfig empty" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var list: RepeatablePath = .{}; - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = \n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = \n", buf.written()); } test "formatConfig single item" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -472,13 +474,13 @@ pub const RepeatablePath = struct { var list: RepeatablePath = .{}; try list.parseCLI(alloc, "A"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A\n", buf.written()); } test "formatConfig multiple items" { const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); var arena = ArenaAllocator.init(testing.allocator); @@ -488,7 +490,7 @@ pub const RepeatablePath = struct { var list: RepeatablePath = .{}; try list.parseCLI(alloc, "A"); try list.parseCLI(alloc, "?B"); - try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = A\na = ?B\n", buf.items); + try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = A\na = ?B\n", buf.written()); } }; diff --git a/src/config/theme.zig b/src/config/theme.zig index 8fa7c93dc..b1188a5c4 100644 --- a/src/config/theme.zig +++ b/src/config/theme.zig @@ -125,10 +125,11 @@ pub fn open( ) orelse return null; const stat = file.stat() catch |err| { try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "not reading theme from \"{s}\": {}", .{ theme, err }, + 0, ), }); return null; @@ -137,10 +138,11 @@ pub fn open( .file => {}, else => { try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "not reading theme from \"{s}\": it is a {s}", .{ theme, @tagName(stat.kind) }, + 0, ), }); return null; @@ -152,10 +154,11 @@ pub fn open( const basename = std.fs.path.basename(theme); if (!std.mem.eql(u8, theme, basename)) { try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "theme \"{s}\" cannot include path separators unless it is an absolute path", .{theme}, + 0, ), }); return null; @@ -170,10 +173,11 @@ pub fn open( if (cwd.openFile(path, .{})) |file| { const stat = file.stat() catch |err| { try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "not reading theme from \"{s}\": {}", .{ theme, err }, + 0, ), }); return null; @@ -182,10 +186,11 @@ pub fn open( .file => {}, else => { try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "not reading theme from \"{s}\": it is a {s}", .{ theme, @tagName(stat.kind) }, + 0, ), }); return null; @@ -202,10 +207,11 @@ pub fn open( // Anything else is an error we log and give up on. else => { try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "failed to load theme \"{s}\" from the file \"{s}\": {}", .{ theme, path, err }, + 0, ), }); @@ -222,10 +228,11 @@ pub fn open( while (try it.next()) |loc| { const path = try std.fs.path.join(arena_alloc, &.{ loc.dir, theme }); try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "theme \"{s}\" not found, tried path \"{s}\"", .{ theme, path }, + 0, ), }); } @@ -249,17 +256,19 @@ pub fn openAbsolute( return std.fs.openFileAbsolute(theme, .{}) catch |err| { switch (err) { error.FileNotFound => try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "failed to load theme from the path \"{s}\"", .{theme}, + 0, ), }), else => try diags.append(arena_alloc, .{ - .message = try std.fmt.allocPrintZ( + .message = try std.fmt.allocPrintSentinel( arena_alloc, "failed to load theme from the path \"{s}\": {}", .{ theme, err }, + 0, ), }), } diff --git a/src/crash/sentry_envelope.zig b/src/crash/sentry_envelope.zig index 6b675554c..08573b739 100644 --- a/src/crash/sentry_envelope.zig +++ b/src/crash/sentry_envelope.zig @@ -26,7 +26,7 @@ pub const Envelope = struct { headers: std.json.ObjectMap, /// The items in the envelope in the order they're encoded. - items: std.ArrayListUnmanaged(Item), + items: std.ArrayList(Item), /// Parse an envelope from a reader. /// @@ -37,7 +37,7 @@ pub const Envelope = struct { /// parsing in our use case is not a hot path. pub fn parse( alloc_gpa: Allocator, - reader: anytype, + reader: *std.Io.Reader, ) !Envelope { // We use an arena allocator to read from reader. We pair this // with `alloc_if_needed` when parsing json to allow the json @@ -62,23 +62,24 @@ pub const Envelope = struct { fn parseHeader( alloc: Allocator, - reader: anytype, + reader: *std.Io.Reader, ) !std.json.ObjectMap { - var buf: std.ArrayListUnmanaged(u8) = .{}; - reader.streamUntilDelimiter( - buf.writer(alloc), + var buf: std.Io.Writer.Allocating = .init(alloc); + _ = try reader.streamDelimiterLimit( + &buf.writer, '\n', - 1024 * 1024, // 1MB, arbitrary choice - ) catch |err| switch (err) { - // Envelope can be header-only. + .limited(1024 * 1024), // 1MB, arbitrary choice + ); + _ = reader.discardDelimiterInclusive('\n') catch |err| switch (err) { + // It's okay if there isn't a trailing newline error.EndOfStream => {}, - else => |v| return v, + else => return err, }; const value = try std.json.parseFromSliceLeaky( std.json.Value, alloc, - buf.items, + buf.written(), .{ .allocate = .alloc_if_needed }, ); @@ -90,9 +91,9 @@ pub const Envelope = struct { fn parseItems( alloc: Allocator, - reader: anytype, - ) !std.ArrayListUnmanaged(Item) { - var items: std.ArrayListUnmanaged(Item) = .{}; + reader: *std.Io.Reader, + ) !std.ArrayList(Item) { + var items: std.ArrayList(Item) = .{}; errdefer items.deinit(alloc); while (try parseOneItem(alloc, reader)) |item| { try items.append(alloc, item); @@ -103,22 +104,27 @@ pub const Envelope = struct { fn parseOneItem( alloc: Allocator, - reader: anytype, + reader: *std.Io.Reader, ) !?Item { // Get the next item which must start with a header. - var buf: std.ArrayListUnmanaged(u8) = .{}; - reader.streamUntilDelimiter( - buf.writer(alloc), + var buf: std.Io.Writer.Allocating = .init(alloc); + _ = reader.streamDelimiterLimit( + &buf.writer, '\n', - 1024 * 1024, // 1MB, arbitrary choice + .limited(1024 * 1024), // 1MB, arbitrary choice ) catch |err| switch (err) { - error.EndOfStream => return null, - else => |v| return v, + error.StreamTooLong => return null, + else => return err, + }; + _ = reader.discardDelimiterInclusive('\n') catch |err| switch (err) { + // It's okay if there isn't a trailing newline + error.EndOfStream => {}, + else => return err, }; // Parse the header JSON const headers: std.json.ObjectMap = headers: { - const line = std.mem.trim(u8, buf.items, " \t"); + const line = std.mem.trim(u8, buf.written(), " \t"); if (line.len == 0) return null; const value = try std.json.parseFromSliceLeaky( @@ -156,18 +162,16 @@ pub const Envelope = struct { // Get the payload const payload: []const u8 = if (len_) |len| payload: { // The payload length is specified so read the exact length. - var payload = std.ArrayList(u8).init(alloc); + var payload: std.Io.Writer.Allocating = .init(alloc); defer payload.deinit(); - for (0..len) |_| { - const byte = reader.readByte() catch |err| switch (err) { - error.EndOfStream => return error.EnvelopeItemPayloadTooShort, - else => return err, - }; - try payload.append(byte); - } + + reader.streamExact(&payload.writer, len) catch |err| switch (err) { + error.EndOfStream => return error.EnvelopeItemPayloadTooShort, + else => return err, + }; // The next byte must be a newline. - if (reader.readByte()) |byte| { + if (reader.takeByte()) |byte| { if (byte != '\n') return error.EnvelopeItemPayloadNoNewline; } else |err| switch (err) { error.EndOfStream => {}, @@ -177,16 +181,20 @@ pub const Envelope = struct { break :payload try payload.toOwnedSlice(); } else payload: { // The payload is the next line ending in `\n`. It is required. - var payload = std.ArrayList(u8).init(alloc); - defer payload.deinit(); - reader.streamUntilDelimiter( - payload.writer(), + var payload: std.Io.Writer.Allocating = .init(alloc); + _ = reader.streamDelimiterLimit( + &payload.writer, '\n', - 1024 * 1024 * 50, // 50MB, arbitrary choice + .limited(1024 * 1024), // 50MB, arbitrary choice ) catch |err| switch (err) { - error.EndOfStream => return error.EnvelopeItemPayloadTooShort, + error.StreamTooLong => return error.EnvelopeItemPayloadTooShort, else => |v| return v, }; + _ = reader.discardDelimiterInclusive('\n') catch |err| switch (err) { + // It's okay if there isn't a trailing newline + error.EndOfStream => {}, + else => return err, + }; break :payload try payload.toOwnedSlice(); }; @@ -212,15 +220,13 @@ pub const Envelope = struct { /// therefore may allocate. pub fn serialize( self: *Envelope, - writer: anytype, + writer: *std.Io.Writer, ) !void { // Header line first - try std.json.stringify( + try writer.print("{f}\n", .{std.json.fmt( std.json.Value{ .object = self.headers }, json_opts, - writer, - ); - try writer.writeByte('\n'); + )}); // Write each item const alloc = self.allocator(); @@ -230,13 +236,13 @@ pub const Envelope = struct { const encoded = try item.encode(alloc); assert(item.* == .encoded); - try std.json.stringify( - std.json.Value{ .object = encoded.headers }, - json_opts, - writer, - ); - try writer.writeByte('\n'); - try writer.writeAll(encoded.payload); + try writer.print("{f}\n{s}", .{ + std.json.fmt( + std.json.Value{ .object = encoded.headers }, + json_opts, + ), + encoded.payload, + }); } } }; @@ -425,7 +431,7 @@ pub const Attachment = struct { pub const ObjectMapUnmanaged = std.StringArrayHashMapUnmanaged(std.json.Value); /// The options we must use for serialization. -const json_opts: std.json.StringifyOptions = .{ +const json_opts: std.json.Stringify.Options = .{ // This is the default but I want to be explicit because its // VERY important for the correctness of the envelope. This is // the only whitespace type in std.json that doesn't emit newlines. @@ -437,10 +443,10 @@ test "Envelope parse" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); } @@ -448,12 +454,12 @@ test "Envelope parse session" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} \\{"type":"session","length":218} \\{"init":true,"sid":"c148cc2f-5f9f-4231-575c-2e85504d6434","status":"abnormal","errors":0,"started":"2024-08-29T02:38:57.607016Z","duration":0.000343,"attrs":{"release":"0.1.0-HEAD+d37b7d09","environment":"production"}} ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); try testing.expectEqual(@as(usize, 1), v.items.items.len); @@ -464,14 +470,14 @@ test "Envelope parse multiple" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} \\{"type":"session","length":218} \\{"init":true,"sid":"c148cc2f-5f9f-4231-575c-2e85504d6434","status":"abnormal","errors":0,"started":"2024-08-29T02:38:57.607016Z","duration":0.000343,"attrs":{"release":"0.1.0-HEAD+d37b7d09","environment":"production"}} \\{"type":"attachment","length":4,"filename":"test.txt"} \\ABCD ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); try testing.expectEqual(@as(usize, 2), v.items.items.len); @@ -483,14 +489,14 @@ test "Envelope parse multiple no length" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} \\{"type":"session"} \\{} \\{"type":"attachment","length":4,"filename":"test.txt"} \\ABCD ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); try testing.expectEqual(@as(usize, 2), v.items.items.len); @@ -502,13 +508,13 @@ test "Envelope parse end in new line" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} \\{"type":"session","length":218} \\{"init":true,"sid":"c148cc2f-5f9f-4231-575c-2e85504d6434","status":"abnormal","errors":0,"started":"2024-08-29T02:38:57.607016Z","duration":0.000343,"attrs":{"release":"0.1.0-HEAD+d37b7d09","environment":"production"}} \\ ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); try testing.expectEqual(@as(usize, 1), v.items.items.len); @@ -519,12 +525,12 @@ test "Envelope parse attachment" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} \\{"type":"attachment","length":4,"filename":"test.txt"} \\ABCD ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); try testing.expectEqual(@as(usize, 1), v.items.items.len); @@ -537,14 +543,14 @@ test "Envelope parse attachment" { // Serialization test { - var output = std.ArrayList(u8).init(alloc); + var output: std.Io.Writer.Allocating = .init(alloc); defer output.deinit(); - try v.serialize(output.writer()); + try v.serialize(&output.writer); try testing.expectEqualStrings( \\{} \\{"type":"attachment","length":4,"filename":"test.txt"} \\ABCD - , std.mem.trim(u8, output.items, "\n")); + , std.mem.trim(u8, output.written(), "\n")); } } @@ -552,76 +558,40 @@ test "Envelope serialize empty" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); - var output = std.ArrayList(u8).init(alloc); + var output: std.Io.Writer.Allocating = .init(alloc); defer output.deinit(); - try v.serialize(output.writer()); + try v.serialize(&output.writer); try testing.expectEqualStrings( \\{} - , std.mem.trim(u8, output.items, "\n")); + , std.mem.trim(u8, output.written(), "\n")); } test "Envelope serialize session" { const testing = std.testing; const alloc = testing.allocator; - var fbs = std.io.fixedBufferStream( + var reader: std.Io.Reader = .fixed( \\{} \\{"type":"session","length":218} \\{"init":true,"sid":"c148cc2f-5f9f-4231-575c-2e85504d6434","status":"abnormal","errors":0,"started":"2024-08-29T02:38:57.607016Z","duration":0.000343,"attrs":{"release":"0.1.0-HEAD+d37b7d09","environment":"production"}} ); - var v = try Envelope.parse(alloc, fbs.reader()); + var v = try Envelope.parse(alloc, &reader); defer v.deinit(); - var output = std.ArrayList(u8).init(alloc); + var output: std.Io.Writer.Allocating = .init(alloc); defer output.deinit(); - try v.serialize(output.writer()); + try v.serialize(&output.writer); try testing.expectEqualStrings( \\{} \\{"type":"session","length":218} \\{"init":true,"sid":"c148cc2f-5f9f-4231-575c-2e85504d6434","status":"abnormal","errors":0,"started":"2024-08-29T02:38:57.607016Z","duration":0.000343,"attrs":{"release":"0.1.0-HEAD+d37b7d09","environment":"production"}} - , std.mem.trim(u8, output.items, "\n")); + , std.mem.trim(u8, output.written(), "\n")); } - -// // Uncomment this test if you want to extract a minidump file from an -// // existing envelope. This is useful for getting new test contents. -// test "Envelope extract mdmp" { -// const testing = std.testing; -// const alloc = testing.allocator; -// -// var fbs = std.io.fixedBufferStream(@embedFile("in.crash")); -// var v = try Envelope.parse(alloc, fbs.reader()); -// defer v.deinit(); -// -// try testing.expect(v.items.items.len > 0); -// for (v.items.items, 0..) |*item, i| { -// if (item.encoded.type != .attachment) { -// log.warn("ignoring item type={} i={}", .{ item.encoded.type, i }); -// continue; -// } -// -// try item.decode(v.allocator()); -// const attach = item.attachment; -// const attach_type = attach.type orelse { -// log.warn("attachment missing type i={}", .{i}); -// continue; -// }; -// if (!std.mem.eql(u8, attach_type, "event.minidump")) { -// log.warn("ignoring attachment type={s} i={}", .{ attach_type, i }); -// continue; -// } -// -// log.warn("found minidump i={}", .{i}); -// var f = try std.fs.cwd().createFile("out.mdmp", .{}); -// defer f.close(); -// try f.writer().writeAll(attach.payload); -// return; -// } -// } diff --git a/src/datastruct/main.zig b/src/datastruct/main.zig index 5aa68555f..14ee0e504 100644 --- a/src/datastruct/main.zig +++ b/src/datastruct/main.zig @@ -14,7 +14,7 @@ pub const CacheTable = cache_table.CacheTable; pub const CircBuf = circ_buf.CircBuf; pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList; pub const SegmentedPool = segmented_pool.SegmentedPool; -//pub const SplitTree = split_tree.SplitTree; +pub const SplitTree = split_tree.SplitTree; test { @import("std").testing.refAllDecls(@This()); diff --git a/src/datastruct/split_tree.zig b/src/datastruct/split_tree.zig index 28b45ceed..eb371187c 100644 --- a/src/datastruct/split_tree.zig +++ b/src/datastruct/split_tree.zig @@ -1023,45 +1023,33 @@ pub fn SplitTree(comptime V: type) type { } /// Format the tree in a human-readable format. By default this will - /// output a diagram followed by a textual representation. This can - /// be controlled via the formatting string: - /// - /// - `diagram` - Output a diagram of the split tree only. - /// - `text` - Output a textual representation of the split tree only. - /// - Empty - Output both a diagram and a textual representation. - /// + /// output a diagram followed by a textual representation. pub fn format( self: *const Self, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, + writer: *std.Io.Writer, ) !void { - _ = options; - if (self.nodes.len == 0) { try writer.writeAll("empty"); return; } - - if (std.mem.eql(u8, fmt, "diagram")) { - self.formatDiagram(writer) catch - try writer.writeAll("failed to draw split tree diagram"); - } else if (std.mem.eql(u8, fmt, "text")) { - try self.formatText(writer, .root, 0); - } else if (fmt.len == 0) { - self.formatDiagram(writer) catch {}; - try self.formatText(writer, .root, 0); - } else { - return error.InvalidFormat; - } + self.formatDiagram(writer) catch {}; + try self.formatText(writer); } - fn formatText( - self: *const Self, - writer: anytype, + pub fn formatText(self: Self, writer: *std.Io.Writer) std.Io.Writer.Error!void { + if (self.nodes.len == 0) { + try writer.writeAll("empty"); + return; + } + try self.formatTextInner(writer, .root, 0); + } + + fn formatTextInner( + self: Self, + writer: *std.Io.Writer, current: Node.Handle, depth: usize, - ) !void { + ) std.Io.Writer.Error!void { for (0..depth) |_| try writer.writeAll(" "); if (self.zoomed) |zoomed| if (zoomed == current) { @@ -1075,20 +1063,25 @@ pub fn SplitTree(comptime V: type) type { try writer.print("leaf: {d}\n", .{current}), .split => |s| { - try writer.print("split (layout: {s}, ratio: {d:.2})\n", .{ - @tagName(s.layout), + try writer.print("split (layout: {t}, ratio: {d:.2})\n", .{ + s.layout, s.ratio, }); - try self.formatText(writer, s.left, depth + 1); - try self.formatText(writer, s.right, depth + 1); + try self.formatTextInner(writer, s.left, depth + 1); + try self.formatTextInner(writer, s.right, depth + 1); }, } } - fn formatDiagram( - self: *const Self, - writer: anytype, - ) !void { + pub fn formatDiagram( + self: Self, + writer: *std.Io.Writer, + ) std.Io.Writer.Error!void { + if (self.nodes.len == 0) { + try writer.writeAll("empty"); + return; + } + // Use our arena's GPA to allocate some intermediate memory. // Requiring allocation for formatting is nasty but this is really // only used for debugging and testing and shouldn't hit OOM @@ -1099,7 +1092,7 @@ pub fn SplitTree(comptime V: type) type { // Get our spatial representation. const sp = spatial: { - const sp = try self.spatial(alloc); + const sp = self.spatial(alloc) catch return error.WriteFailed; // Scale our spatial representation to have minimum width/height 1. var min_w: f16 = 1; @@ -1111,7 +1104,7 @@ pub fn SplitTree(comptime V: type) type { const ratio_w: f16 = 1 / min_w; const ratio_h: f16 = 1 / min_h; - const slots = try alloc.dupe(Spatial.Slot, sp.slots); + const slots = alloc.dupe(Spatial.Slot, sp.slots) catch return error.WriteFailed; for (slots) |*slot| { slot.x *= ratio_w; slot.y *= ratio_h; @@ -1168,9 +1161,9 @@ pub fn SplitTree(comptime V: type) type { width *= cell_width; height *= cell_height; - const rows = try alloc.alloc([]u8, height); + const rows = alloc.alloc([]u8, height) catch return error.WriteFailed; for (0..rows.len) |y| { - rows[y] = try alloc.alloc(u8, width + 1); + rows[y] = alloc.alloc(u8, width + 1) catch return error.WriteFailed; @memset(rows[y], ' '); rows[y][width] = '\n'; } @@ -1223,7 +1216,7 @@ pub fn SplitTree(comptime V: type) type { const label: []const u8 = if (@hasDecl(View, "splitTreeLabel")) node.leaf.splitTreeLabel() else - try std.fmt.bufPrint(&buf, "{d}", .{handle}); + std.fmt.bufPrint(&buf, "{d}", .{handle}) catch return error.WriteFailed; // Draw the handle in the center const x_mid = width / 2 + x; @@ -1231,7 +1224,7 @@ pub fn SplitTree(comptime V: type) type { const label_width = label.len; const label_start = x_mid - label_width / 2; const row = grid[y_mid][label_start..]; - _ = try std.fmt.bufPrint(row, "{s}", .{label}); + _ = std.fmt.bufPrint(row, "{s}", .{label}) catch return error.WriteFailed; } // Output every row @@ -1339,7 +1332,7 @@ test "SplitTree: empty tree" { var t: TestTree = .empty; defer t.deinit(); - const str = try std.fmt.allocPrint(alloc, "{}", .{t}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{t}); defer alloc.free(str); try testing.expectEqualStrings(str, \\empty @@ -1353,7 +1346,7 @@ test "SplitTree: single node" { var t: TestTree = try .init(alloc, &v); defer t.deinit(); - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{t}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(t, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1383,7 +1376,7 @@ test "SplitTree: split horizontal" { defer t3.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{}", .{t3}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{t3}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---++---+ @@ -1415,7 +1408,7 @@ test "SplitTree: split horizontal" { defer t4.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{}", .{t4}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{t4}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+--------++---++---+ @@ -1449,7 +1442,7 @@ test "SplitTree: split horizontal" { defer t5.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{}", .{t5}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{t5}); defer alloc.free(str); try testing.expectEqualStrings( \\+------------------++--------++---++---+ @@ -1547,7 +1540,7 @@ test "SplitTree: split vertical" { ); defer t3.deinit(); - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{t3}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(t3, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1583,7 +1576,7 @@ test "SplitTree: split horizontal with zero ratio" { const split = splitAB; { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1617,7 +1610,7 @@ test "SplitTree: split vertical with zero ratio" { const split = splitAB; { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1651,7 +1644,7 @@ test "SplitTree: split horizontal with full width" { const split = splitAB; { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1685,7 +1678,7 @@ test "SplitTree: split vertical with full width" { const split = splitAB; { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1727,7 +1720,7 @@ test "SplitTree: remove leaf" { ); defer t4.deinit(); - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{t4}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(t4, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1772,7 +1765,7 @@ test "SplitTree: split twice, remove intermediary" { defer split2.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split2}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split2, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---++---+ @@ -1798,7 +1791,7 @@ test "SplitTree: split twice, remove intermediary" { defer split3.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split3}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split3, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---+ @@ -1883,7 +1876,7 @@ test "SplitTree: spatial goto" { const split = splitBD; { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---++---+ @@ -1943,7 +1936,7 @@ test "SplitTree: spatial goto" { defer equal.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{equal}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(equal, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---++---+ @@ -1979,7 +1972,7 @@ test "SplitTree: resize" { defer split.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+---++---+ @@ -2005,7 +1998,7 @@ test "SplitTree: resize" { 0.25, ); defer resized.deinit(); - const str = try std.fmt.allocPrint(alloc, "{diagram}", .{resized}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(resized, .formatDiagram)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\+-------------++---+ @@ -2026,7 +2019,7 @@ test "SplitTree: clone empty tree" { defer t2.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{}", .{t2}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{t2}); defer alloc.free(str); try testing.expectEqualStrings(str, \\empty @@ -2064,7 +2057,7 @@ test "SplitTree: zoom" { }); { - const str = try std.fmt.allocPrint(alloc, "{text}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatText)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\split (layout: horizontal, ratio: 0.50) @@ -2079,7 +2072,7 @@ test "SplitTree: zoom" { defer clone.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{text}", .{clone}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(clone, .formatText)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\split (layout: horizontal, ratio: 0.50) @@ -2122,7 +2115,7 @@ test "SplitTree: split resets zoom" { defer split.deinit(); { - const str = try std.fmt.allocPrint(alloc, "{text}", .{split}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(split, .formatText)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\split (layout: horizontal, ratio: 0.50) @@ -2178,7 +2171,7 @@ test "SplitTree: remove and zoom" { defer removed.deinit(); try testing.expect(removed.zoomed == null); - const str = try std.fmt.allocPrint(alloc, "{text}", .{removed}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(removed, .formatText)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\leaf: B @@ -2201,7 +2194,7 @@ test "SplitTree: remove and zoom" { ); defer removed.deinit(); - const str = try std.fmt.allocPrint(alloc, "{text}", .{removed}); + const str = try std.fmt.allocPrint(alloc, "{f}", .{std.fmt.alt(removed, .formatText)}); defer alloc.free(str); try testing.expectEqualStrings(str, \\(zoomed) leaf: A diff --git a/src/extra/vim.zig b/src/extra/vim.zig index 4443fd168..ff85e820b 100644 --- a/src/extra/vim.zig +++ b/src/extra/vim.zig @@ -59,14 +59,15 @@ pub const compiler = /// Generates the syntax file at comptime. fn comptimeGenSyntax() []const u8 { comptime { - var counting_writer = std.io.countingWriter(std.io.null_writer); - try writeSyntax(&counting_writer.writer()); + @setEvalBranchQuota(50000); + var counter: std.Io.Writer.Discarding = .init(&.{}); + try writeSyntax(&counter.writer); - var buf: [counting_writer.bytes_written]u8 = undefined; - var stream = std.io.fixedBufferStream(&buf); - try writeSyntax(stream.writer()); + var buf: [counter.count]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); + try writeSyntax(&writer); const final = buf; - return final[0..stream.getWritten().len]; + return final[0..writer.end]; } } diff --git a/src/font/Metrics.zig b/src/font/Metrics.zig index a0bc047c4..668b6f15f 100644 --- a/src/font/Metrics.zig +++ b/src/font/Metrics.zig @@ -471,23 +471,23 @@ pub const Modifier = union(enum) { test "formatConfig percent" { const configpkg = @import("../config.zig"); const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); const p = try parseCLI("24%"); - try p.formatEntry(configpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.items); + try p.formatEntry(configpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.written()); } test "formatConfig absolute" { const configpkg = @import("../config.zig"); const testing = std.testing; - var buf = std.ArrayList(u8).init(testing.allocator); + var buf: std.Io.Writer.Allocating = .init(testing.allocator); defer buf.deinit(); const p = try parseCLI("-30"); - try p.formatEntry(configpkg.entryFormatter("a", buf.writer())); - try std.testing.expectEqualSlices(u8, "a = -30\n", buf.items); + try p.formatEntry(configpkg.entryFormatter("a", &buf.writer)); + try std.testing.expectEqualSlices(u8, "a = -30\n", buf.written()); } }; diff --git a/src/font/SharedGridSet.zig b/src/font/SharedGridSet.zig index 813a8d6d0..4512e23cc 100644 --- a/src/font/SharedGridSet.zig +++ b/src/font/SharedGridSet.zig @@ -596,10 +596,10 @@ pub const Key = struct { // from DerivedConfig below. var config = try DerivedConfig.init(alloc, config_src); - var descriptors = std.ArrayList(discovery.Descriptor).init(alloc); - defer descriptors.deinit(); + var descriptors: std.ArrayList(discovery.Descriptor) = .empty; + defer descriptors.deinit(alloc); for (config.@"font-family".list.items) |family| { - try descriptors.append(.{ + try descriptors.append(alloc, .{ .family = family, .style = config.@"font-style".nameValue(), .size = font_size.points, @@ -617,7 +617,7 @@ pub const Key = struct { // italic. for (config.@"font-family-bold".list.items) |family| { const style = config.@"font-style-bold".nameValue(); - try descriptors.append(.{ + try descriptors.append(alloc, .{ .family = family, .style = style, .size = font_size.points, @@ -627,7 +627,7 @@ pub const Key = struct { } for (config.@"font-family-italic".list.items) |family| { const style = config.@"font-style-italic".nameValue(); - try descriptors.append(.{ + try descriptors.append(alloc, .{ .family = family, .style = style, .size = font_size.points, @@ -637,7 +637,7 @@ pub const Key = struct { } for (config.@"font-family-bold-italic".list.items) |family| { const style = config.@"font-style-bold-italic".nameValue(); - try descriptors.append(.{ + try descriptors.append(alloc, .{ .family = family, .style = style, .size = font_size.points, @@ -681,7 +681,7 @@ pub const Key = struct { return .{ .arena = arena, - .descriptors = try descriptors.toOwnedSlice(), + .descriptors = try descriptors.toOwnedSlice(alloc), .style_offsets = .{ regular_offset, bold_offset, diff --git a/src/font/shaper/run.zig b/src/font/shaper/run.zig index 7bd019fd7..da3c51cee 100644 --- a/src/font/shaper/run.zig +++ b/src/font/shaper/run.zig @@ -356,8 +356,8 @@ pub const RunIterator = struct { // If this is a grapheme, we need to find a font that supports // all of the codepoints in the grapheme. const cps = self.opts.row.grapheme(cell) orelse return primary; - var candidates = try std.ArrayList(font.Collection.Index).initCapacity(alloc, cps.len + 1); - defer candidates.deinit(); + var candidates: std.ArrayList(font.Collection.Index) = try .initCapacity(alloc, cps.len + 1); + defer candidates.deinit(alloc); candidates.appendAssumeCapacity(primary); for (cps) |cp| { diff --git a/src/global.zig b/src/global.zig index e68ec7f74..8034fabe0 100644 --- a/src/global.zig +++ b/src/global.zig @@ -140,7 +140,7 @@ pub const GlobalState = struct { std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); } std.log.info("renderer={}", .{renderer.Renderer}); - std.log.info("libxev default backend={s}", .{@tagName(xev.backend)}); + std.log.info("libxev default backend={t}", .{xev.backend}); // As early as possible, initialize our resource limits. self.rlimits = .init(); @@ -206,7 +206,7 @@ pub const GlobalState = struct { var sa: p.Sigaction = .{ .handler = .{ .handler = p.SIG.IGN }, - .mask = p.empty_sigset, + .mask = p.sigemptyset(), .flags = 0, }; diff --git a/src/helpgen.zig b/src/helpgen.zig index 57296fe86..fe30db10c 100644 --- a/src/helpgen.zig +++ b/src/helpgen.zig @@ -11,19 +11,22 @@ pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); - const stdout = std.io.getStdOut().writer(); - try stdout.writeAll( + var buf: [4096]u8 = undefined; + var stdout = std.fs.File.stdout().writer(&buf); + const writer = &stdout.interface; + try writer.writeAll( \\// THIS FILE IS AUTO GENERATED \\ \\ ); - try genConfig(alloc, stdout); - try genActions(alloc, stdout); - try genKeybindActions(alloc, stdout); + try genConfig(alloc, writer); + try genActions(alloc, writer); + try genKeybindActions(alloc, writer); + try stdout.end(); } -fn genConfig(alloc: std.mem.Allocator, writer: anytype) !void { +fn genConfig(alloc: std.mem.Allocator, writer: *std.Io.Writer) !void { var ast = try std.zig.Ast.parse(alloc, @embedFile("config/Config.zig"), .zig); defer ast.deinit(alloc); @@ -44,7 +47,7 @@ fn genConfig(alloc: std.mem.Allocator, writer: anytype) !void { fn genConfigField( alloc: std.mem.Allocator, - writer: anytype, + writer: *std.Io.Writer, ast: std.zig.Ast, comptime field: []const u8, ) !void { @@ -69,7 +72,7 @@ fn genConfigField( } } -fn genActions(alloc: std.mem.Allocator, writer: anytype) !void { +fn genActions(alloc: std.mem.Allocator, writer: *std.Io.Writer) !void { try writer.writeAll( \\ \\/// Actions help @@ -115,7 +118,7 @@ fn genActions(alloc: std.mem.Allocator, writer: anytype) !void { try writer.writeAll("};\n"); } -fn genKeybindActions(alloc: std.mem.Allocator, writer: anytype) !void { +fn genKeybindActions(alloc: std.mem.Allocator, writer: *std.Io.Writer) !void { var ast = try std.zig.Ast.parse(alloc, @embedFile("input/Binding.zig"), .zig); defer ast.deinit(alloc); @@ -149,24 +152,24 @@ fn extractDocComments( } else unreachable; // Go through and build up the lines. - var lines = std.ArrayList([]const u8).init(alloc); - defer lines.deinit(); + var lines: std.ArrayList([]const u8) = .empty; + defer lines.deinit(alloc); for (start_idx..index + 1) |i| { const token = tokens[i]; if (token != .doc_comment) break; - try lines.append(ast.tokenSlice(@intCast(i))[3..]); + try lines.append(alloc, ast.tokenSlice(@intCast(i))[3..]); } // Convert the lines to a multiline string. - var buffer = std.ArrayList(u8).init(alloc); - const writer = buffer.writer(); + var buffer: std.Io.Writer.Allocating = .init(alloc); + defer buffer.deinit(); const prefix = findCommonPrefix(lines); for (lines.items) |line| { - try writer.writeAll(" \\\\"); - try writer.writeAll(line[@min(prefix, line.len)..]); - try writer.writeAll("\n"); + try buffer.writer.writeAll(" \\\\"); + try buffer.writer.writeAll(line[@min(prefix, line.len)..]); + try buffer.writer.writeAll("\n"); } - try writer.writeAll(";\n"); + try buffer.writer.writeAll(";\n"); return buffer.toOwnedSlice(); } diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 642044067..c44fb0b09 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -7,6 +7,7 @@ const Allocator = std.mem.Allocator; const assert = std.debug.assert; const build_config = @import("../build_config.zig"); const uucode = @import("uucode"); +const EntryFormatter = @import("../config/formatter.zig").EntryFormatter; const key = @import("key.zig"); const KeyEvent = key.KeyEvent; @@ -1184,13 +1185,8 @@ pub const Action = union(enum) { /// action back into the format used by parse. pub fn format( self: Action, - comptime layout: []const u8, - opts: std.fmt.FormatOptions, - writer: anytype, + writer: *std.Io.Writer, ) !void { - _ = layout; - _ = opts; - switch (self) { inline else => |value| { // All actions start with the tag. @@ -1206,16 +1202,16 @@ pub const Action = union(enum) { } fn formatValue( - writer: anytype, + writer: *std.Io.Writer, value: anytype, ) !void { const Value = @TypeOf(value); const value_info = @typeInfo(Value); switch (Value) { void => {}, - []const u8 => try std.zig.stringEscape(value, "", .{}, writer), + []const u8 => try std.zig.stringEscape(value, writer), else => switch (value_info) { - .@"enum" => try writer.print("{s}", .{@tagName(value)}), + .@"enum" => try writer.print("{t}", .{value}), .float => try writer.print("{d}", .{value}), .int => try writer.print("{d}", .{value}), .@"struct" => |info| if (!info.is_tuple) { @@ -1648,13 +1644,8 @@ pub const Trigger = struct { /// Format implementation for fmt package. pub fn format( self: Trigger, - comptime layout: []const u8, - opts: std.fmt.FormatOptions, - writer: anytype, + writer: *std.Io.Writer, ) !void { - _ = layout; - _ = opts; - // Modifiers first if (self.mods.super) try writer.writeAll("super+"); if (self.mods.ctrl) try writer.writeAll("ctrl+"); @@ -1663,7 +1654,7 @@ pub const Trigger = struct { // Key switch (self.key) { - .physical => |k| try writer.print("{s}", .{@tagName(k)}), + .physical => |k| try writer.print("{t}", .{k}), .unicode => |c| try writer.print("{u}", .{c}), } } @@ -1721,13 +1712,8 @@ pub const Set = struct { /// action back into the format used by parse. pub fn format( self: Value, - comptime layout: []const u8, - opts: std.fmt.FormatOptions, - writer: anytype, + writer: *std.Io.Writer, ) !void { - _ = layout; - _ = opts; - switch (self) { .leader => |set| { // the leader key was already printed. @@ -1758,26 +1744,34 @@ pub const Set = struct { /// that is shared between calls to nested levels of the set. /// For example, 'a>b>c=x' and 'a>b>d=y' will re-use the 'a>b' written /// to the buffer before flushing it to the formatter with 'c=x' and 'd=y'. - pub fn formatEntries(self: Value, buffer_stream: anytype, formatter: anytype) !void { + pub fn formatEntries( + self: Value, + buffer: *std.Io.Writer, + formatter: EntryFormatter, + ) !void { switch (self) { .leader => |set| { // We'll rewind to this position after each sub-entry, // sharing the prefix between siblings. - const pos = try buffer_stream.getPos(); + const pos = buffer.end; var iter = set.bindings.iterator(); while (iter.next()) |binding| { - buffer_stream.seekTo(pos) catch unreachable; // can't fail - std.fmt.format(buffer_stream.writer(), ">{s}", .{binding.key_ptr.*}) catch return error.OutOfMemory; - try binding.value_ptr.*.formatEntries(buffer_stream, formatter); + // I'm not exactly if this is safe for any arbitrary + // writer since the Writer interface does not have any + // rewind functions, but for our use case of a + // fixed-size buffer writer this should work just fine. + buffer.end = pos; + buffer.print(">{f}", .{binding.key_ptr.*}) catch return error.OutOfMemory; + try binding.value_ptr.*.formatEntries(buffer, formatter); } }, .leaf => |leaf| { // When we get to the leaf, the buffer_stream contains // the full sequence of keys needed to reach this action. - std.fmt.format(buffer_stream.writer(), "={s}", .{leaf.action}) catch return error.OutOfMemory; - try formatter.formatEntry([]const u8, buffer_stream.getWritten()); + buffer.print("={f}", .{leaf.action}) catch return error.OutOfMemory; + try formatter.formatEntry([]const u8, buffer.buffer[0..buffer.end]); }, } } @@ -3234,11 +3228,8 @@ test "action: format" { const a: Action = .{ .text = "👻" }; - var buf: std.ArrayListUnmanaged(u8) = .empty; - defer buf.deinit(alloc); - - const writer = buf.writer(alloc); - try a.format("", .{}, writer); - - try testing.expectEqualStrings("text:\\xf0\\x9f\\x91\\xbb", buf.items); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + try a.format(&buf.writer); + try testing.expectEqualStrings("text:\\xf0\\x9f\\x91\\xbb", buf.written()); } diff --git a/src/input/command.zig b/src/input/command.zig index bf5061c12..ba55820fc 100644 --- a/src/input/command.zig +++ b/src/input/command.zig @@ -50,7 +50,7 @@ pub const Command = struct { return .{ .action_key = @tagName(self.action), - .action = std.fmt.comptimePrint("{s}", .{self.action}), + .action = std.fmt.comptimePrint("{t}", .{self.action}), .title = self.title, .description = self.description, }; @@ -94,6 +94,7 @@ pub const defaults: []const Command = defaults: { /// Defaults in C-compatible form. pub const defaultsC: []const Command.C = defaults: { + @setEvalBranchQuota(100_000); var result: [defaults.len]Command.C = undefined; for (defaults, 0..) |cmd, i| result[i] = cmd.comptimeCval(); const final = result; diff --git a/src/input/function_keys.zig b/src/input/function_keys.zig index 33a5b89c0..8c89b39bd 100644 --- a/src/input/function_keys.zig +++ b/src/input/function_keys.zig @@ -278,6 +278,7 @@ fn pcStyle(comptime fmt: []const u8) []Entry { // The comptime {} wrapper is superfluous but it prevents us from // accidentally running this function at runtime. comptime { + @setEvalBranchQuota(500_000); var entries: [modifiers.len]Entry = undefined; for (modifiers, 2.., 0..) |mods, code, i| { entries[i] = .{ diff --git a/src/input/helpgen_actions.zig b/src/input/helpgen_actions.zig index 1382bbe95..4210f1f91 100644 --- a/src/input/helpgen_actions.zig +++ b/src/input/helpgen_actions.zig @@ -13,7 +13,7 @@ pub const Format = enum { /// Markdown formatted output markdown, - fn formatFieldName(self: Format, writer: anytype, field_name: []const u8) !void { + fn formatFieldName(self: Format, writer: *std.Io.Writer, field_name: []const u8) !void { switch (self) { .plaintext => { try writer.writeAll(field_name); @@ -27,16 +27,16 @@ pub const Format = enum { } } - fn formatDocLine(self: Format, writer: anytype, line: []const u8) !void { + fn formatDocLine(self: Format, writer: *std.Io.Writer, line: []const u8) !void { switch (self) { .plaintext => { - try writer.appendSlice(" "); - try writer.appendSlice(line); - try writer.appendSlice("\n"); + try writer.writeAll(" "); + try writer.writeAll(line); + try writer.writeAll("\n"); }, .markdown => { - try writer.appendSlice(line); - try writer.appendSlice("\n"); + try writer.writeAll(line); + try writer.writeAll("\n"); }, } } @@ -61,7 +61,7 @@ pub const Format = enum { /// Generate keybind actions documentation with the specified format pub fn generate( - writer: anytype, + writer: *std.Io.Writer, format: Format, show_docs: bool, page_allocator: std.mem.Allocator, @@ -70,8 +70,8 @@ pub fn generate( try writer.writeAll(header); } - var buffer = std.ArrayList(u8).init(page_allocator); - defer buffer.deinit(); + var stream: std.Io.Writer.Allocating = .init(page_allocator); + defer stream.deinit(); const fields = @typeInfo(KeybindAction).@"union".fields; inline for (fields) |field| { @@ -79,10 +79,9 @@ pub fn generate( // Write previously stored doc comment below all related actions if (show_docs and @hasDecl(help_strings.KeybindAction, field.name)) { - try writer.writeAll(buffer.items); + try writer.writeAll(stream.written()); try writer.writeAll("\n"); - - buffer.clearRetainingCapacity(); + stream.clearRetainingCapacity(); } if (show_docs) { @@ -101,13 +100,13 @@ pub fn generate( while (iter.next()) |s| { // If it is the last line and empty, then skip it. if (iter.peek() == null and s.len == 0) continue; - try format.formatDocLine(&buffer, s); + try format.formatDocLine(&stream.writer, s); } } } // Write any remaining buffered documentation - if (buffer.items.len > 0) { - try writer.writeAll(buffer.items); + if (stream.written().len > 0) { + try writer.writeAll(stream.written()); } } diff --git a/src/renderer/link.zig b/src/renderer/link.zig index 410fb8632..9f489ed48 100644 --- a/src/renderer/link.zig +++ b/src/renderer/link.zig @@ -34,19 +34,19 @@ pub const Set = struct { alloc: Allocator, config: []const inputpkg.Link, ) !Set { - var links = std.ArrayList(Link).init(alloc); - defer links.deinit(); + var links: std.ArrayList(Link) = .empty; + defer links.deinit(alloc); for (config) |link| { var regex = try link.oniRegex(); errdefer regex.deinit(); - try links.append(.{ + try links.append(alloc, .{ .regex = regex, .highlight = link.highlight, }); } - return .{ .links = try links.toOwnedSlice() }; + return .{ .links = try links.toOwnedSlice(alloc) }; } pub fn deinit(self: *Set, alloc: Allocator) void { @@ -77,8 +77,8 @@ pub const Set = struct { // as selections which contain the start and end points of // the match. There is no way to map these back to the link // configuration right now because we don't need to. - var matches = std.ArrayList(terminal.Selection).init(alloc); - defer matches.deinit(); + var matches: std.ArrayList(terminal.Selection) = .empty; + defer matches.deinit(alloc); // If our mouse is over an OSC8 link, then we can skip the regex // matches below since OSC8 takes priority. @@ -101,7 +101,7 @@ pub const Set = struct { ); } - return .{ .matches = try matches.toOwnedSlice() }; + return .{ .matches = try matches.toOwnedSlice(alloc) }; } fn matchSetFromOSC8( @@ -112,8 +112,6 @@ pub const Set = struct { mouse_pin: terminal.Pin, mouse_mods: inputpkg.Mods, ) !void { - _ = alloc; - // If the right mods aren't pressed, then we can't match. if (!mouse_mods.equal(inputpkg.ctrlOrSuper(.{}))) return; @@ -135,6 +133,7 @@ pub const Set = struct { if (link.id == .implicit) { const uri = link.uri.offset.ptr(page.memory)[0..link.uri.len]; return try self.matchSetFromOSC8Implicit( + alloc, matches, mouse_pin, uri, @@ -154,7 +153,7 @@ pub const Set = struct { // building our matching selection. if (!row.hyperlink) { if (current) |sel| { - try matches.append(sel); + try matches.append(alloc, sel); current = null; } @@ -191,7 +190,7 @@ pub const Set = struct { // No match, if we have a current selection then complete it. if (current) |sel| { - try matches.append(sel); + try matches.append(alloc, sel); current = null; } } @@ -203,6 +202,7 @@ pub const Set = struct { /// around the mouse pin. fn matchSetFromOSC8Implicit( self: *const Set, + alloc: Allocator, matches: *std.ArrayList(terminal.Selection), mouse_pin: terminal.Pin, uri: []const u8, @@ -264,7 +264,7 @@ pub const Set = struct { sel.endPtr().* = cell_pin; } - try matches.append(sel); + try matches.append(alloc, sel); } /// Fills matches with the matches from regex link matches. @@ -334,7 +334,7 @@ pub const Set = struct { => if (!sel.contains(screen, mouse_pin)) continue, } - try matches.append(sel); + try matches.append(alloc, sel); } } } diff --git a/src/renderer/shadertoy.zig b/src/renderer/shadertoy.zig index 576237587..a8f62cdea 100644 --- a/src/renderer/shadertoy.zig +++ b/src/renderer/shadertoy.zig @@ -129,7 +129,7 @@ pub fn loadFromFile( /// mainImage function and don't define any of the uniforms. This function /// will convert the ShaderToy shader into a valid GLSL shader that can be /// compiled and linked. -pub fn glslFromShader(writer: anytype, src: []const u8) !void { +pub fn glslFromShader(writer: *std.Io.Writer, src: []const u8) !void { const prefix = @embedFile("shaders/shadertoy_prefix.glsl"); try writer.writeAll(prefix); try writer.writeAll("\n\n"); @@ -138,7 +138,7 @@ pub fn glslFromShader(writer: anytype, src: []const u8) !void { /// Convert a GLSL shader into SPIR-V assembly. pub fn spirvFromGlsl( - writer: anytype, + writer: *std.Io.Writer, errlog: ?*SpirvLog, src: [:0]const u8, ) !void { @@ -331,10 +331,10 @@ fn spvCross( /// Convert ShaderToy shader to null-terminated glsl for testing. fn testGlslZ(alloc: Allocator, src: []const u8) ![:0]const u8 { - var list = std.ArrayList(u8).init(alloc); - defer list.deinit(); - try glslFromShader(list.writer(), src); - return try list.toOwnedSliceSentinel(0); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + try glslFromShader(&buf.writer, src); + return try buf.toOwnedSliceSentinel(0); } test "spirv" { @@ -345,9 +345,8 @@ test "spirv" { defer alloc.free(src); var buf: [4096 * 4]u8 = undefined; - var buf_stream = std.io.fixedBufferStream(&buf); - const writer = buf_stream.writer(); - try spirvFromGlsl(writer, null, src); + var writer: std.Io.Writer = .fixed(&buf); + try spirvFromGlsl(&writer, null, src); } test "spirv invalid" { @@ -358,12 +357,11 @@ test "spirv invalid" { defer alloc.free(src); var buf: [4096 * 4]u8 = undefined; - var buf_stream = std.io.fixedBufferStream(&buf); - const writer = buf_stream.writer(); + var writer: std.Io.Writer = .fixed(&buf); var errlog: SpirvLog = .{ .alloc = alloc }; defer errlog.deinit(); - try testing.expectError(error.GlslangFailed, spirvFromGlsl(writer, &errlog, src)); + try testing.expectError(error.GlslangFailed, spirvFromGlsl(&writer, &errlog, src)); try testing.expect(errlog.info.len > 0); } @@ -374,9 +372,14 @@ test "shadertoy to msl" { const src = try testGlslZ(alloc, test_crt); defer alloc.free(src); - var spvlist = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc); - defer spvlist.deinit(); - try spirvFromGlsl(spvlist.writer(), null, src); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + try spirvFromGlsl(&buf.writer, null, src); + + // TODO: Replace this with an aligned version of Writer.Allocating + var spvlist: std.ArrayListAligned(u8, .of(u32)) = .empty; + defer spvlist.deinit(alloc); + try spvlist.appendSlice(alloc, buf.written()); const msl = try mslFromSpv(alloc, spvlist.items); defer alloc.free(msl); @@ -389,9 +392,14 @@ test "shadertoy to glsl" { const src = try testGlslZ(alloc, test_crt); defer alloc.free(src); - var spvlist = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc); - defer spvlist.deinit(); - try spirvFromGlsl(spvlist.writer(), null, src); + var buf: std.Io.Writer.Allocating = .init(alloc); + defer buf.deinit(); + try spirvFromGlsl(&buf.writer, null, src); + + // TODO: Replace this with an aligned version of Writer.Allocating + var spvlist: std.ArrayListAligned(u8, .of(u32)) = .empty; + defer spvlist.deinit(alloc); + try spvlist.appendSlice(alloc, buf.written()); const glsl = try glslFromSpv(alloc, spvlist.items); defer alloc.free(glsl); diff --git a/src/terminal/apc.zig b/src/terminal/apc.zig index a168da4a1..704c3fbe3 100644 --- a/src/terminal/apc.zig +++ b/src/terminal/apc.zig @@ -65,7 +65,9 @@ pub const Handler = struct { .kitty => |*p| kitty: { if (comptime !build_options.kitty_graphics) unreachable; - const command = p.complete() catch |err| { + // Use the same allocator that was used to create the parser. + const alloc = p.arena.child_allocator; + const command = p.complete(alloc) catch |err| { log.warn("kitty graphics protocol error: {}", .{err}); break :kitty null; }; diff --git a/src/terminal/dcs.zig b/src/terminal/dcs.zig index 4694fc457..971ea13a0 100644 --- a/src/terminal/dcs.zig +++ b/src/terminal/dcs.zig @@ -64,7 +64,7 @@ pub const Handler = struct { .state = .{ .tmux = .{ .max_bytes = self.max_bytes, - .buffer = try std.ArrayList(u8).initCapacity( + .buffer = try .initCapacity( alloc, 128, // Arbitrary choice to limit initial reallocs ), diff --git a/src/terminal/kitty/graphics_command.zig b/src/terminal/kitty/graphics_command.zig index dcb4850c9..2ce01f83a 100644 --- a/src/terminal/kitty/graphics_command.zig +++ b/src/terminal/kitty/graphics_command.zig @@ -59,7 +59,7 @@ pub const Parser = struct { errdefer arena.deinit(); var result: Parser = .{ .arena = arena, - .data = std.ArrayList(u8).init(alloc), + .data = .empty, .kv = .{}, .kv_temp_len = 0, .kv_current = 0, @@ -77,8 +77,8 @@ pub const Parser = struct { pub fn deinit(self: *Parser) void { // We don't free the hash map because its in the arena + self.data.deinit(self.arena.child_allocator); self.arena.deinit(); - self.data.deinit(); } /// Parse a complete command string. @@ -86,7 +86,7 @@ pub const Parser = struct { var parser = init(alloc); defer parser.deinit(); for (data) |c| try parser.feed(c); - return try parser.complete(); + return try parser.complete(alloc); } /// Feed a single byte to the parser. @@ -136,7 +136,7 @@ pub const Parser = struct { else => {}, }, - .data => try self.data.append(c), + .data => try self.data.append(self.arena.child_allocator, c), } } @@ -145,7 +145,7 @@ pub const Parser = struct { /// /// The allocator given will be used for the long-lived data /// of the final command. - pub fn complete(self: *Parser) !Command { + pub fn complete(self: *Parser, alloc: Allocator) !Command { switch (self.state) { // We can't ever end in the control key state and be valid. // This means the command looked something like "a=1,b" @@ -194,14 +194,14 @@ pub const Parser = struct { return .{ .control = control, .quiet = quiet, - .data = try self.decodeData(), + .data = try self.decodeData(alloc), }; } /// Decodes the payload data from base64 and returns it as a slice. /// This function will destroy the contents of self.data, it should /// only be used once we are done collecting payload bytes. - fn decodeData(self: *Parser) ![]const u8 { + fn decodeData(self: *Parser, alloc: Allocator) ![]const u8 { if (self.data.items.len == 0) { return ""; } @@ -225,7 +225,7 @@ pub const Parser = struct { // Remove the extra bytes. self.data.items.len = decoded.len; - return try self.data.toOwnedSlice(); + return try self.data.toOwnedSlice(alloc); } fn accumulateValue(self: *Parser, c: u8, overflow_state: State) !void { @@ -969,7 +969,7 @@ test "transmission command" { const input = "f=24,s=10,v=20"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .transmit); @@ -987,7 +987,7 @@ test "transmission ignores 'm' if medium is not direct" { const input = "a=t,t=t,m=1"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .transmit); @@ -1004,7 +1004,7 @@ test "transmission respects 'm' if medium is direct" { const input = "a=t,t=d,m=1"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .transmit); @@ -1021,7 +1021,7 @@ test "query command" { const input = "i=31,s=1,v=1,a=q,t=d,f=24;QUFBQQ"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .query); @@ -1041,7 +1041,7 @@ test "display command" { const input = "a=p,U=1,i=31,c=80,r=120"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .display); @@ -1059,7 +1059,7 @@ test "delete command" { const input = "a=d,d=p,x=3,y=4"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .delete); @@ -1079,7 +1079,7 @@ test "no control data" { const input = ";QUFBQQ"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .transmit); @@ -1094,7 +1094,7 @@ test "ignore unknown keys (long)" { const input = "f=24,s=10,v=20,hello=world"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .transmit); @@ -1112,7 +1112,7 @@ test "ignore very long values" { const input = "f=24,s=10,v=2000000000000000000000000000000000000000"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .transmit); @@ -1130,7 +1130,7 @@ test "ensure very large negative values don't get skipped" { const input = "a=p,i=1,z=-2000000000"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .display); @@ -1147,7 +1147,7 @@ test "ensure proper overflow error for u32" { const input = "a=p,i=10000000000"; for (input) |c| try p.feed(c); - try testing.expectError(error.Overflow, p.complete()); + try testing.expectError(error.Overflow, p.complete(alloc)); } test "ensure proper overflow error for i32" { @@ -1158,7 +1158,7 @@ test "ensure proper overflow error for i32" { const input = "a=p,i=1,z=-9999999999"; for (input) |c| try p.feed(c); - try testing.expectError(error.Overflow, p.complete()); + try testing.expectError(error.Overflow, p.complete(alloc)); } test "all i32 values" { @@ -1171,7 +1171,7 @@ test "all i32 values" { defer p.deinit(); const input = "a=p,i=1,z=-1"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .display); @@ -1186,7 +1186,7 @@ test "all i32 values" { defer p.deinit(); const input = "a=p,i=1,H=-1"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .display); @@ -1201,7 +1201,7 @@ test "all i32 values" { defer p.deinit(); const input = "a=p,i=1,V=-1"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .display); @@ -1259,7 +1259,7 @@ test "delete range command 1" { const input = "a=d,d=r,x=3,y=4"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .delete); @@ -1279,7 +1279,7 @@ test "delete range command 2" { const input = "a=d,d=R,x=5,y=11"; for (input) |c| try p.feed(c); - const command = try p.complete(); + const command = try p.complete(alloc); defer command.deinit(alloc); try testing.expect(command.control == .delete); @@ -1299,7 +1299,7 @@ test "delete range command 3" { const input = "a=d,d=R,x=5,y=4"; for (input) |c| try p.feed(c); - try testing.expectError(error.InvalidFormat, p.complete()); + try testing.expectError(error.InvalidFormat, p.complete(alloc)); } test "delete range command 4" { @@ -1310,7 +1310,7 @@ test "delete range command 4" { const input = "a=d,d=R,x=5"; for (input) |c| try p.feed(c); - try testing.expectError(error.InvalidFormat, p.complete()); + try testing.expectError(error.InvalidFormat, p.complete(alloc)); } test "delete range command 5" { @@ -1321,5 +1321,5 @@ test "delete range command 5" { const input = "a=d,d=R,y=5"; for (input) |c| try p.feed(c); - try testing.expectError(error.InvalidFormat, p.complete()); + try testing.expectError(error.InvalidFormat, p.complete(alloc)); } diff --git a/src/terminal/kitty/graphics_image.zig b/src/terminal/kitty/graphics_image.zig index f32b70be2..268f71601 100644 --- a/src/terminal/kitty/graphics_image.zig +++ b/src/terminal/kitty/graphics_image.zig @@ -259,15 +259,16 @@ pub const LoadingImage = struct { }; } - var buf_reader = std.io.bufferedReader(file.reader()); - const reader = buf_reader.reader(); + var buf: [4096]u8 = undefined; + var buf_reader = file.reader(&buf); + const reader = &buf_reader.interface; // Read the file - var managed = std.ArrayList(u8).init(alloc); - errdefer managed.deinit(); + var managed: std.ArrayList(u8) = .empty; + errdefer managed.deinit(alloc); const size: usize = if (t.size > 0) @min(t.size, max_size) else max_size; - reader.readAllArrayList(&managed, size) catch |err| { - log.warn("failed to read temporary file: {}", .{err}); + reader.appendRemaining(alloc, &managed, .limited(size)) catch { + log.warn("failed to read temporary file: {?}", .{buf_reader.err}); return error.InvalidData; }; @@ -402,14 +403,15 @@ pub const LoadingImage = struct { fn decompressZlib(self: *LoadingImage, alloc: Allocator) !void { // Open our zlib stream - var fbs = std.io.fixedBufferStream(self.data.items); - var stream = std.compress.zlib.decompressor(fbs.reader()); + var buf: [std.compress.flate.max_window_len]u8 = undefined; + var reader: std.Io.Reader = .fixed(self.data.items); + var stream: std.compress.flate.Decompress = .init(&reader, .zlib, &buf); // Write it to an array list - var list = std.ArrayList(u8).init(alloc); - errdefer list.deinit(); - stream.reader().readAllArrayList(&list, max_size) catch |err| { - log.warn("failed to read decompressed data: {}", .{err}); + var list: std.ArrayList(u8) = .empty; + errdefer list.deinit(alloc); + stream.reader.appendRemaining(alloc, &list, .limited(max_size)) catch { + log.warn("failed to read decompressed data: {?}", .{stream.err}); return error.DecompressionFailed; }; diff --git a/src/terminal/kitty/graphics_storage.zig b/src/terminal/kitty/graphics_storage.zig index 0c3022e4a..8aef0ece5 100644 --- a/src/terminal/kitty/graphics_storage.zig +++ b/src/terminal/kitty/graphics_storage.zig @@ -526,8 +526,8 @@ pub const ImageStorage = struct { used: bool, }; - var candidates = std.ArrayList(Candidate).init(alloc); - defer candidates.deinit(); + var candidates: std.ArrayList(Candidate) = .empty; + defer candidates.deinit(alloc); var it = self.images.iterator(); while (it.next()) |kv| { @@ -548,7 +548,7 @@ pub const ImageStorage = struct { break :used false; }; - try candidates.append(.{ + try candidates.append(alloc, .{ .id = img.id, .time = img.transmit_time, .used = used, diff --git a/src/terminal/tmux.zig b/src/terminal/tmux.zig index 1ea9f8c39..67c5a979c 100644 --- a/src/terminal/tmux.zig +++ b/src/terminal/tmux.zig @@ -17,7 +17,7 @@ pub const Client = struct { state: State = .idle, /// The buffer used to store in-progress notifications, output, etc. - buffer: std.ArrayList(u8), + buffer: std.Io.Writer.Allocating, /// The maximum size in bytes of the buffer. This is used to limit /// memory usage. If the buffer exceeds this size, the client will @@ -49,7 +49,7 @@ pub const Client = struct { // Handle a byte of input. pub fn put(self: *Client, byte: u8) !?Notification { - if (self.buffer.items.len >= self.max_bytes) { + if (self.buffer.written().len >= self.max_bytes) { self.broken(); return error.OutOfMemory; } @@ -81,18 +81,19 @@ pub const Client = struct { // If we're in a block then we accumulate until we see a newline // and then we check to see if that line ended the block. .block => if (byte == '\n') { + const written = self.buffer.written(); const idx = if (std.mem.lastIndexOfScalar( u8, - self.buffer.items, + written, '\n', )) |v| v + 1 else 0; - const line = self.buffer.items[idx..]; + const line = written[idx..]; if (std.mem.startsWith(u8, line, "%end") or std.mem.startsWith(u8, line, "%error")) { const err = std.mem.startsWith(u8, line, "%error"); - const output = std.mem.trimRight(u8, self.buffer.items[0..idx], "\r\n"); + const output = std.mem.trimRight(u8, written[0..idx], "\r\n"); // If it is an error then log it. if (err) log.warn("tmux control mode error={s}", .{output}); @@ -107,7 +108,7 @@ pub const Client = struct { }, } - try self.buffer.append(byte); + try self.buffer.writer.writeByte(byte); return null; } @@ -116,7 +117,7 @@ pub const Client = struct { assert(self.state == .notification); const line = line: { - var line = self.buffer.items; + var line = self.buffer.written(); if (line[line.len - 1] == '\r') line = line[0 .. line.len - 1]; break :line line; }; @@ -274,7 +275,7 @@ pub const Client = struct { // Mark the tmux state as broken. fn broken(self: *Client) void { self.state = .broken; - self.buffer.clearAndFree(); + self.buffer.deinit(); } }; @@ -313,7 +314,7 @@ test "tmux begin/end empty" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%begin 1578922740 269 1\n") |byte| try testing.expect(try c.put(byte) == null); for ("%end 1578922740 269 1") |byte| try testing.expect(try c.put(byte) == null); @@ -326,7 +327,7 @@ test "tmux begin/error empty" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%begin 1578922740 269 1\n") |byte| try testing.expect(try c.put(byte) == null); for ("%error 1578922740 269 1") |byte| try testing.expect(try c.put(byte) == null); @@ -339,7 +340,7 @@ test "tmux begin/end data" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%begin 1578922740 269 1\n") |byte| try testing.expect(try c.put(byte) == null); for ("hello\nworld\n") |byte| try testing.expect(try c.put(byte) == null); @@ -353,7 +354,7 @@ test "tmux output" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%output %42 foo bar baz") |byte| try testing.expect(try c.put(byte) == null); const n = (try c.put('\n')).?; @@ -366,7 +367,7 @@ test "tmux session-changed" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%session-changed $42 foo") |byte| try testing.expect(try c.put(byte) == null); const n = (try c.put('\n')).?; @@ -379,7 +380,7 @@ test "tmux sessions-changed" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%sessions-changed") |byte| try testing.expect(try c.put(byte) == null); const n = (try c.put('\n')).?; @@ -390,7 +391,7 @@ test "tmux sessions-changed carriage return" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%sessions-changed\r") |byte| try testing.expect(try c.put(byte) == null); const n = (try c.put('\n')).?; @@ -401,7 +402,7 @@ test "tmux window-add" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%window-add @14") |byte| try testing.expect(try c.put(byte) == null); const n = (try c.put('\n')).?; @@ -413,7 +414,7 @@ test "tmux window-renamed" { const testing = std.testing; const alloc = testing.allocator; - var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; + var c: Client = .{ .buffer = .init(alloc) }; defer c.deinit(); for ("%window-renamed @42 bar") |byte| try testing.expect(try c.put(byte) == null); const n = (try c.put('\n')).?; diff --git a/src/terminfo/Source.zig b/src/terminfo/Source.zig index 7692e6f54..d5c3f427a 100644 --- a/src/terminfo/Source.zig +++ b/src/terminfo/Source.zig @@ -115,9 +115,10 @@ pub fn xtgettcapMap(comptime self: Source) std.StaticStringMap([]const u8) { }, .numeric => |v| numeric: { var buf: [10]u8 = undefined; - const num_len = std.fmt.formatIntBuf(&buf, v, 10, .upper, .{}); + var writer: std.Io.Writer = .fixed(&buf); + writer.printInt(v, 10, .upper, .{}) catch unreachable; const final = buf; - break :numeric final[0..num_len]; + break :numeric final[0..writer.end]; }, }, }; diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 77fd2cc68..8c729fddc 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1518,7 +1518,7 @@ fn execCommand( .shell => |v| shell: { var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 4); - defer args.deinit(); + defer args.deinit(alloc); if (comptime builtin.os.tag == .windows) { // We run our shell wrapped in `cmd.exe` so that we don't have @@ -1539,21 +1539,21 @@ fn execCommand( "cmd.exe", }); - try args.append(cmd); - try args.append("/C"); + try args.append(alloc, cmd); + try args.append(alloc, "/C"); } else { // We run our shell wrapped in `/bin/sh` so that we don't have // to parse the command line ourselves if it has arguments. // Additionally, some environments (NixOS, I found) use /bin/sh // to setup some environment variables that are important to // have set. - try args.append("/bin/sh"); - if (internal_os.isFlatpak()) try args.append("-l"); - try args.append("-c"); + try args.append(alloc, "/bin/sh"); + if (internal_os.isFlatpak()) try args.append(alloc, "-l"); + try args.append(alloc, "-c"); } - try args.append(v); - break :shell try args.toOwnedSlice(); + try args.append(alloc, v); + break :shell try args.toOwnedSlice(alloc); }, }; } diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index 90a697409..8b2648dbd 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -175,7 +175,9 @@ pub fn setupFeatures( inline for (fields) |field| n += field.name.len; break :capacity n; }; - var buffer = try std.BoundedArray(u8, capacity).init(0); + + var buf: [capacity]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); // Sort the fields so that the output is deterministic. This is // done at comptime so it has no runtime cost @@ -197,13 +199,13 @@ pub fn setupFeatures( inline for (fields_sorted) |name| { if (@field(features, name)) { - if (buffer.len > 0) try buffer.append(','); - try buffer.appendSlice(name); + if (writer.end > 0) try writer.writeByte(','); + try writer.writeAll(name); } } - if (buffer.len > 0) { - try env.put("GHOSTTY_SHELL_FEATURES", buffer.slice()); + if (writer.end > 0) { + try env.put("GHOSTTY_SHELL_FEATURES", buf[0..writer.end]); } } @@ -257,8 +259,8 @@ fn setupBash( resource_dir: []const u8, env: *EnvMap, ) !?config.Command { - var args = try std.ArrayList([:0]const u8).initCapacity(alloc, 3); - defer args.deinit(); + var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 3); + defer args.deinit(alloc); // Iterator that yields each argument in the original command line. // This will allocate once proportionate to the command line length. @@ -267,21 +269,22 @@ fn setupBash( // Start accumulating arguments with the executable and initial flags. if (iter.next()) |exe| { - try args.append(try alloc.dupeZ(u8, exe)); + try args.append(alloc, try alloc.dupeZ(u8, exe)); } else return null; - try args.append("--posix"); + try args.append(alloc, "--posix"); // On macOS, we request a login shell to match that platform's norms. if (comptime builtin.target.os.tag.isDarwin()) { - try args.append("--login"); + try args.append(alloc, "--login"); } // Stores the list of intercepted command line flags that will be passed // to our shell integration script: --norc --noprofile // We always include at least "1" so the script can differentiate between // being manually sourced or automatically injected (from here). - var inject = try std.BoundedArray(u8, 32).init(0); - try inject.appendSlice("1"); + var buf: [32]u8 = undefined; + var inject: std.Io.Writer = .fixed(&buf); + try inject.writeAll("1"); // Walk through the rest of the given arguments. If we see an option that // would require complex or unsupported integration behavior, we bail out @@ -296,9 +299,9 @@ fn setupBash( if (std.mem.eql(u8, arg, "--posix")) { return null; } else if (std.mem.eql(u8, arg, "--norc")) { - try inject.appendSlice(" --norc"); + try inject.writeAll(" --norc"); } else if (std.mem.eql(u8, arg, "--noprofile")) { - try inject.appendSlice(" --noprofile"); + try inject.writeAll(" --noprofile"); } else if (std.mem.eql(u8, arg, "--rcfile") or std.mem.eql(u8, arg, "--init-file")) { rcfile = iter.next(); } else if (arg.len > 1 and arg[0] == '-' and arg[1] != '-') { @@ -306,20 +309,20 @@ fn setupBash( if (std.mem.indexOfScalar(u8, arg, 'c') != null) { return null; } - try args.append(try alloc.dupeZ(u8, arg)); + try args.append(alloc, try alloc.dupeZ(u8, arg)); } else if (std.mem.eql(u8, arg, "-") or std.mem.eql(u8, arg, "--")) { // All remaining arguments should be passed directly to the shell // command. We shouldn't perform any further option processing. - try args.append(try alloc.dupeZ(u8, arg)); + try args.append(alloc, try alloc.dupeZ(u8, arg)); while (iter.next()) |remaining_arg| { - try args.append(try alloc.dupeZ(u8, remaining_arg)); + try args.append(alloc, try alloc.dupeZ(u8, remaining_arg)); } break; } else { - try args.append(try alloc.dupeZ(u8, arg)); + try args.append(alloc, try alloc.dupeZ(u8, arg)); } } - try env.put("GHOSTTY_BASH_INJECT", inject.slice()); + try env.put("GHOSTTY_BASH_INJECT", buf[0..inject.end]); if (rcfile) |v| { try env.put("GHOSTTY_BASH_RCFILE", v); } @@ -356,7 +359,7 @@ fn setupBash( // Since we built up a command line, we don't need to wrap it in // ANOTHER shell anymore and can do a direct command. - return .{ .direct = try args.toOwnedSlice() }; + return .{ .direct = try args.toOwnedSlice(alloc) }; } test "bash" {