Zig 0.15: zig build test

pull/9004/head
Mitchell Hashimoto 2025-10-01 13:10:40 -07:00
parent 3770f97608
commit cb295b84a0
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
66 changed files with 1264 additions and 1144 deletions

View File

@ -276,6 +276,8 @@ pub fn build(b: *std.Build) !void {
.omit_frame_pointer = false, .omit_frame_pointer = false,
.unwind_tables = .sync, .unwind_tables = .sync,
}), }),
// Crash on x86_64 without this
.use_llvm = true,
}); });
if (config.emit_test_exe) b.installArtifact(test_exe); if (config.emit_test_exe) b.installArtifact(test_exe);
_ = try deps.add(test_exe); _ = try deps.add(test_exe);
@ -285,7 +287,7 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(&test_run.step); test_step.dependOn(&test_run.step);
// Normal tests always test our libghostty modules // Normal tests always test our libghostty modules
test_step.dependOn(test_lib_vt_step); //test_step.dependOn(test_lib_vt_step);
// Valgrind test running // Valgrind test running
const valgrind_run = b.addSystemCommand(&.{ const valgrind_run = b.addSystemCommand(&.{

View File

@ -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 // child process so there isn't much we can do. We try to output
// something reasonable. Its important to note we MUST NOT return // something reasonable. Its important to note we MUST NOT return
// any other error condition from here on out. // 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) { switch (err) {
error.FileNotFound => stderr.print( error.FileNotFound => stderr.print(
\\Requested executable not found. Please verify the command is on \\Requested executable not found. Please verify the command is on
@ -211,6 +213,7 @@ fn startPosix(self: *Command, arena: Allocator) !void {
.{err}, .{err},
) catch {}, ) catch {},
} }
stderr.flush() catch {};
// We return a very specific error that can be detected to determine // We return a very specific error that can be detected to determine
// we're in the child. // 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. /// 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 { 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(); defer buf.deinit();
const writer = &buf.writer;
for (argv, 0..) |arg, arg_i| { 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) { if (mem.indexOfAny(u8, arg, " \t\n\"") == null) {
try buf.appendSlice(arg); try writer.writeAll(arg);
continue; continue;
} }
try buf.append('"'); try writer.writeByte('"');
var backslash_count: usize = 0; var backslash_count: usize = 0;
for (arg) |byte| { for (arg) |byte| {
switch (byte) { switch (byte) {
'\\' => backslash_count += 1, '\\' => backslash_count += 1,
'"' => { '"' => {
try buf.appendNTimes('\\', backslash_count * 2 + 1); try writer.splatByteAll('\\', backslash_count * 2 + 1);
try buf.append('"'); try writer.writeByte('"');
backslash_count = 0; backslash_count = 0;
}, },
else => { else => {
try buf.appendNTimes('\\', backslash_count); try writer.splatByteAll('\\', backslash_count);
try buf.append(byte); try writer.writeByte(byte);
backslash_count = 0; backslash_count = 0;
}, },
} }
} }
try buf.appendNTimes('\\', backslash_count * 2); try writer.splatByteAll('\\', backslash_count * 2);
try buf.append('"'); try writer.writeByte('"');
} }
return buf.toOwnedSliceSentinel(0); return buf.toOwnedSliceSentinel(0);

View File

@ -45,7 +45,7 @@ pub fn main() !void {
std.debug.print( std.debug.print(
\\`libadwaita` is too old. \\`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 \\compile this blueprint. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty. \\available on your PATH, and then retry building Ghostty.
, .{required_adwaita_version}); , .{required_adwaita_version});
@ -80,7 +80,7 @@ pub fn main() !void {
std.debug.print( std.debug.print(
\\`blueprint-compiler` not found. \\`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 \\`blueprint-compiler` as a build-time dependency starting
\\from version 1.2. Please install it, ensure that it is \\from version 1.2. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty. \\available on your PATH, and then retry building Ghostty.
@ -104,7 +104,7 @@ pub fn main() !void {
std.debug.print( std.debug.print(
\\`blueprint-compiler` is the wrong version. \\`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 \\`blueprint-compiler` as a build-time dependency starting
\\from version 1.2. Please install it, ensure that it is \\from version 1.2. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty. \\available on your PATH, and then retry building Ghostty.
@ -145,7 +145,7 @@ pub fn main() !void {
std.debug.print( std.debug.print(
\\`blueprint-compiler` not found. \\`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 \\`blueprint-compiler` as a build-time dependency starting
\\from version 1.2. Please install it, ensure that it is \\from version 1.2. Please install it, ensure that it is
\\available on your PATH, and then retry building Ghostty. \\available on your PATH, and then retry building Ghostty.

View File

@ -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( try writer.writeAll(
\\<?xml version="1.0" encoding="UTF-8"?> \\<?xml version="1.0" encoding="UTF-8"?>
\\<gresources> \\<gresources>
@ -157,6 +159,8 @@ pub fn main() !void {
\\</gresources> \\</gresources>
\\ \\
); );
try stdout.end();
} }
/// Generate the icon resources. This works by looking up all the icons /// Generate the icon resources. This works by looking up all the icons

View File

@ -117,10 +117,10 @@ pub const Config = extern struct {
errdefer text_buf.unref(); errdefer text_buf.unref();
var buf: [4095:0]u8 = undefined; var buf: [4095:0]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
for (config._diagnostics.items()) |diag| { for (config._diagnostics.items()) |diag| {
fbs.reset(); writer.end = 0;
diag.write(fbs.writer()) catch |err| { diag.format(&writer) catch |err| {
log.warn( log.warn(
"error writing diagnostic to buffer err={}", "error writing diagnostic to buffer err={}",
.{err}, .{err},
@ -128,7 +128,7 @@ pub const Config = extern struct {
continue; continue;
}; };
text_buf.insertAtCursor(&buf, @intCast(fbs.pos)); text_buf.insertAtCursor(&buf, @intCast(writer.end));
text_buf.insertAtCursor("\n", 1); text_buf.insertAtCursor("\n", 1);
} }

View File

@ -29,7 +29,10 @@ payload_builder: *glib.VariantBuilder,
parameters_builder: *glib.VariantBuilder, parameters_builder: *glib.VariantBuilder,
/// Initialize the helper. /// 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 // Get the appropriate bus name and object path for contacting the
// Ghostty instance we're interested in. // 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: { .class => |class| result: {
// Force the usage of the class specified on the CLI to determine the // Force the usage of the class specified on the CLI to determine the
// bus name and object path. // 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, '.', '/');
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) { 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.print("D-Bus bus name is not valid: {s}\n", .{bus_name});
try stderr.flush();
return error.IPCFailed; return error.IPCFailed;
} }
if (glib.Variant.isObjectPath(object_path.ptr) == 0) { 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.print("D-Bus object path is not valid: {s}\n", .{object_path});
try stderr.flush();
return error.IPCFailed; 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_); const dbus_ = gio.busGetSync(.session, null, &err_);
if (err_) |err| { if (err_) |err| {
const stderr = std.io.getStdErr().writer();
try stderr.print( try stderr.print(
"Unable to establish connection to D-Bus session bus: {s}\n", "Unable to establish connection to D-Bus session bus: {s}\n",
.{err.f_message orelse "(unknown)"}, .{err.f_message orelse "(unknown)"},
); );
try stderr.flush();
return error.IPCFailed; return error.IPCFailed;
} }
break :dbus dbus_ orelse { break :dbus dbus_ orelse {
const stderr = std.io.getStdErr().writer();
try stderr.print("gio.busGetSync returned null\n", .{}); try stderr.print("gio.busGetSync returned null\n", .{});
try stderr.flush();
return error.IPCFailed; 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 /// Send the IPC to the remote Ghostty. Once it completes, nothing further
/// should be done with this object other than call `deinit`. /// 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 // finish building the parameters
const parameters = self.parameters_builder.end(); 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(); defer if (result_) |result| result.unref();
if (err_) |err| { if (err_) |err| {
const stderr = std.io.getStdErr().writer();
try stderr.print( try stderr.print(
"D-Bus method call returned an error err={s}\n", "D-Bus method call returned an error err={s}\n",
.{err.f_message orelse "(unknown)"}, .{err.f_message orelse "(unknown)"},
); );
try stderr.flush();
return error.IPCFailed; return error.IPCFailed;
} }
} }

View File

@ -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"]>]' [] // 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( var dbus = try DBus.init(
alloc, alloc,
target, target,

View File

@ -10,7 +10,6 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Benchmark = @import("Benchmark.zig"); const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig"); const options = @import("options.zig");
const uucode = @import("uucode");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig"); const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const simd = @import("../simd/main.zig"); const simd = @import("../simd/main.zig");
const table = @import("../unicode/main.zig").table; const table = @import("../unicode/main.zig").table;
@ -48,9 +47,6 @@ pub const Mode = enum {
/// Test our lookup table implementation. /// Test our lookup table implementation.
table, table,
/// Using uucode, with custom `width` extension based on `wcwidth`.
uucode,
}; };
/// Create a new terminal stream handler for the given arguments. /// Create a new terminal stream handler for the given arguments.
@ -75,7 +71,6 @@ pub fn benchmark(self: *CodepointWidth) Benchmark {
.wcwidth => stepWcwidth, .wcwidth => stepWcwidth,
.table => stepTable, .table => stepTable,
.simd => stepSimd, .simd => stepSimd,
.uucode => stepUucode,
}, },
.setupFn = setup, .setupFn = setup,
.teardownFn = teardown, .teardownFn = teardown,
@ -112,12 +107,15 @@ fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return; 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 d: UTF8Decoder = .{};
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) { while (true) {
const n = r.read(&buf) catch |err| { const n = r.readSliceShort(&buf) catch {
log.warn("error reading data file err={}", .{err}); log.warn("error reading data file err={?}", .{f_reader.err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached if (n == 0) break; // EOF reached
@ -136,12 +134,15 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return; 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 d: UTF8Decoder = .{};
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) { while (true) {
const n = r.read(&buf) catch |err| { const n = r.readSliceShort(&buf) catch {
log.warn("error reading data file err={}", .{err}); log.warn("error reading data file err={?}", .{f_reader.err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached if (n == 0) break; // EOF reached
@ -165,12 +166,15 @@ fn stepSimd(ptr: *anyopaque) Benchmark.Error!void {
const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); const self: *CodepointWidth = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return; 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 d: UTF8Decoder = .{};
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) { while (true) {
const n = r.read(&buf) catch |err| { const n = r.readSliceShort(&buf) catch {
log.warn("error reading data file err={}", .{err}); log.warn("error reading data file err={?}", .{f_reader.err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached 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 { test CodepointWidth {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;

View File

@ -8,7 +8,6 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Benchmark = @import("Benchmark.zig"); const Benchmark = @import("Benchmark.zig");
const options = @import("options.zig"); const options = @import("options.zig");
const uucode = @import("uucode");
const UTF8Decoder = @import("../terminal/UTF8Decoder.zig"); const UTF8Decoder = @import("../terminal/UTF8Decoder.zig");
const unicode = @import("../unicode/main.zig"); const unicode = @import("../unicode/main.zig");
@ -39,9 +38,6 @@ pub const Mode = enum {
/// Ghostty's table-based approach. /// Ghostty's table-based approach.
table, table,
/// uucode implementation
uucode,
}; };
/// Create a new terminal stream handler for the given arguments. /// 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) { .stepFn = switch (self.opts.mode) {
.noop => stepNoop, .noop => stepNoop,
.table => stepTable, .table => stepTable,
.uucode => stepUucode,
}, },
.setupFn = setup, .setupFn = setup,
.teardownFn = teardown, .teardownFn = teardown,
@ -95,12 +90,15 @@ fn stepNoop(ptr: *anyopaque) Benchmark.Error!void {
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr)); const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return; 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 d: UTF8Decoder = .{};
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) { while (true) {
const n = r.read(&buf) catch |err| { const n = r.readSliceShort(&buf) catch {
log.warn("error reading data file err={}", .{err}); log.warn("error reading data file err={?}", .{f_reader.err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached if (n == 0) break; // EOF reached
@ -115,14 +113,17 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const self: *GraphemeBreak = @ptrCast(@alignCast(ptr)); const self: *GraphemeBreak = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return; 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 d: UTF8Decoder = .{};
var state: unicode.GraphemeBreakState = .{}; var state: unicode.GraphemeBreakState = .{};
var cp1: u21 = 0; var cp1: u21 = 0;
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) { while (true) {
const n = r.read(&buf) catch |err| { const n = r.readSliceShort(&buf) catch {
log.warn("error reading data file err={}", .{err}); log.warn("error reading data file err={?}", .{f_reader.err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached 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 { test GraphemeBreak {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;

View File

@ -90,7 +90,8 @@ fn stepUucode(ptr: *anyopaque) Benchmark.Error!void {
const self: *IsSymbol = @ptrCast(@alignCast(ptr)); const self: *IsSymbol = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return; 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 d: UTF8Decoder = .{};
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) { while (true) {
@ -114,7 +115,8 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void {
const self: *IsSymbol = @ptrCast(@alignCast(ptr)); const self: *IsSymbol = @ptrCast(@alignCast(ptr));
const f = self.data_f orelse return; 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 d: UTF8Decoder = .{};
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 align(std.atomic.cache_line) = undefined;
while (true) { while (true) {

View File

@ -75,14 +75,16 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
// the benchmark results and... I know writing this that we // the benchmark results and... I know writing this that we
// aren't currently IO bound. // aren't currently IO bound.
const f = self.data_f orelse return; 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 p: terminalpkg.Parser = .init();
var buf: [4096]u8 align(std.atomic.cache_line) = undefined; var buf: [4096]u8 = undefined;
while (true) { while (true) {
const n = r.read(&buf) catch |err| { const n = r.readSliceShort(&buf) catch {
log.warn("error reading data file err={}", .{err}); log.warn("error reading data file err={?}", .{f_reader.err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached if (n == 0) break; // EOF reached

View File

@ -113,17 +113,19 @@ fn step(ptr: *anyopaque) Benchmark.Error!void {
// the benchmark results and... I know writing this that we // the benchmark results and... I know writing this that we
// aren't currently IO bound. // aren't currently IO bound.
const f = self.data_f orelse return; 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) { while (true) {
const n = r.read(&buf) catch |err| { const n = r.readSliceShort(&buf) catch {
log.warn("error reading data file err={}", .{err}); log.warn("error reading data file err={?}", .{f_reader.err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };
if (n == 0) break; // EOF reached if (n == 0) break; // EOF reached
const chunk = buf[0..n]; self.stream.nextSlice(buf[0..n]) catch |err| {
self.stream.nextSlice(chunk) catch |err| {
log.warn("error processing data file chunk err={}", .{err}); log.warn("error processing data file chunk err={}", .{err});
return error.BenchmarkFailed; return error.BenchmarkFailed;
}; };

View File

@ -10,7 +10,7 @@ pub fn dataFile(path_: ?[]const u8) !?std.fs.File {
const path = path_ orelse return null; const path = path_ orelse return null;
// Stdin // Stdin
if (std.mem.eql(u8, path, "-")) return std.io.getStdIn(); if (std.mem.eql(u8, path, "-")) return .stdin();
// Normal file // Normal file
const file = try std.fs.cwd().openFile(path, .{}); const file = try std.fs.cwd().openFile(path, .{});

View File

@ -43,6 +43,7 @@ pub fn distResources(b: *std.Build) struct {
.root_module = b.createModule(.{ .root_module = b.createModule(.{
.target = b.graph.host, .target = b.graph.host,
}), }),
.use_llvm = true,
}); });
exe.addCSourceFile(.{ exe.addCSourceFile(.{
.file = b.path("src/build/framegen/main.c"), .file = b.path("src/build/framegen/main.c"),

View File

@ -162,10 +162,11 @@ pub fn parse(
error.InvalidField => "unknown field", error.InvalidField => "unknown field",
error.ValueRequired => formatValueRequired(T, arena_alloc, key) catch "value required", error.ValueRequired => formatValueRequired(T, arena_alloc, key) catch "value required",
error.InvalidValue => formatInvalidValue(T, arena_alloc, key, value) catch "invalid value", error.InvalidValue => formatInvalidValue(T, arena_alloc, key, value) catch "invalid value",
else => try std.fmt.allocPrintZ( else => try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"unknown error {}", "unknown error {}",
.{err}, .{err},
0,
), ),
}; };
@ -235,14 +236,16 @@ fn formatValueRequired(
comptime T: type, comptime T: type,
arena_alloc: std.mem.Allocator, arena_alloc: std.mem.Allocator,
key: []const u8, key: []const u8,
) std.mem.Allocator.Error![:0]const u8 { ) std.Io.Writer.Error![:0]const u8 {
var buf = std.ArrayList(u8).init(arena_alloc); var stream: std.Io.Writer.Allocating = .init(arena_alloc);
errdefer buf.deinit(); const writer = &stream.writer;
const writer = buf.writer();
try writer.print("value required", .{}); try writer.print("value required", .{});
try formatValues(T, key, writer); try formatValues(T, key, writer);
try writer.writeByte(0); 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( fn formatInvalidValue(
@ -250,17 +253,23 @@ fn formatInvalidValue(
arena_alloc: std.mem.Allocator, arena_alloc: std.mem.Allocator,
key: []const u8, key: []const u8,
value: ?[]const u8, value: ?[]const u8,
) std.mem.Allocator.Error![:0]const u8 { ) std.Io.Writer.Error![:0]const u8 {
var buf = std.ArrayList(u8).init(arena_alloc); var stream: std.Io.Writer.Allocating = .init(arena_alloc);
errdefer buf.deinit(); const writer = &stream.writer;
const writer = buf.writer();
try writer.print("invalid value \"{?s}\"", .{value}); try writer.print("invalid value \"{?s}\"", .{value});
try formatValues(T, key, writer); try formatValues(T, key, writer);
try writer.writeByte(0); 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); @setEvalBranchQuota(2000);
const typeinfo = @typeInfo(T); const typeinfo = @typeInfo(T);
inline for (typeinfo.@"struct".fields) |f| { inline for (typeinfo.@"struct".fields) |f| {
@ -542,8 +551,8 @@ pub fn parseAutoStruct(
const key = std.mem.trim(u8, entry[0..idx], whitespace); const key = std.mem.trim(u8, entry[0..idx], whitespace);
// used if we need to decode a double-quoted string. // used if we need to decode a double-quoted string.
var buf: std.ArrayListUnmanaged(u8) = .empty; var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(alloc); defer buf.deinit();
const value = value: { const value = value: {
const value = std.mem.trim(u8, entry[idx + 1 ..], whitespace); const value = std.mem.trim(u8, entry[idx + 1 ..], whitespace);
@ -554,10 +563,9 @@ pub fn parseAutoStruct(
value[value.len - 1] == '"') value[value.len - 1] == '"')
{ {
// Decode a double-quoted string as a Zig string literal. // Decode a double-quoted string as a Zig string literal.
const writer = buf.writer(alloc); const parsed = try std.zig.string_literal.parseWrite(&buf.writer, value);
const parsed = try std.zig.string_literal.parseWrite(writer, value);
if (parsed == .failure) return error.InvalidValue; if (parsed == .failure) return error.InvalidValue;
break :value buf.items; break :value buf.written();
} }
break :value value; break :value value;
@ -795,15 +803,13 @@ test "parse: diagnostic location" {
} = .{}; } = .{};
defer if (data._arena) |arena| arena.deinit(); defer if (data._arena) |arena| arena.deinit();
var fbs = std.io.fixedBufferStream( var r: std.Io.Reader = .fixed(
\\a=42 \\a=42
\\what \\what
\\b=two \\b=two
); );
const r = fbs.reader();
const Iter = LineIterator(@TypeOf(r)); var iter: LineIterator = .{ .r = &r, .filepath = "test" };
var iter: Iter = .{ .r = r, .filepath = "test" };
try parse(@TypeOf(data), testing.allocator, &data, &iter); try parse(@TypeOf(data), testing.allocator, &data, &iter);
try testing.expect(data._arena != null); try testing.expect(data._arena != null);
try testing.expectEqualStrings("42", data.a); 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(84, data.value.b);
try testing.expectEqual(24, data.value.c); try testing.expectEqual(24, data.value.c);
// Set with explicit default // Missing require dfield
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
try testing.expectError( try testing.expectError(
error.InvalidValue, error.InvalidValue,
parseIntoField(@TypeOf(data), alloc, &data, "value", "a:hello"), 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. /// 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 /// Each CLI arg is expected to be a single line. This is used to implement
/// configuration files. /// configuration files.
pub fn LineIterator(comptime ReaderType: type) type { pub const LineIterator = struct {
return struct { const Self = @This();
const Self = @This();
/// The maximum size a single line can be. We don't expect any /// 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 /// CLI arg to exceed this size. Can't wait to git blame this in
/// like 4 years and be wrong about this. /// like 4 years and be wrong about this.
pub const MAX_LINE_SIZE = 4096; pub const MAX_LINE_SIZE = 4096;
/// Our stateful reader. /// Our stateful reader.
r: ReaderType, r: *std.Io.Reader,
/// Filepath that is used for diagnostics. This is only used for /// Filepath that is used for diagnostics. This is only used for
/// diagnostic messages so it can be formatted however you want. /// diagnostic messages so it can be formatted however you want.
/// It is prefixed to the messages followed by the line number. /// It is prefixed to the messages followed by the line number.
filepath: []const u8 = "", filepath: []const u8 = "",
/// The current line that we're on. This is 1-indexed because /// The current line that we're on. This is 1-indexed because
/// lines are generally 1-indexed in the real world. The value /// lines are generally 1-indexed in the real world. The value
/// can be zero if we haven't read any lines yet. /// can be zero if we haven't read any lines yet.
line: usize = 0, line: usize = 0,
/// This is the buffer where we store the current entry that /// This is the buffer where we store the current entry that
/// is formatted to be compatible with the parse function. /// is formatted to be compatible with the parse function.
entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)), entry: [MAX_LINE_SIZE]u8 = [_]u8{ '-', '-' } ++ ([_]u8{0} ** (MAX_LINE_SIZE - 2)),
pub fn next(self: *Self) ?[]const u8 { pub fn init(reader: *std.Io.Reader) Self {
// TODO: detect "--" prefixed lines and give a friendlier error return .{ .r = reader };
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;
// Increment our line counter pub fn next(self: *Self) ?[]const u8 {
self.line += 1; // 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 var writer: std.Io.Writer = .fixed(self.entry[2..]);
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];
}
// Ignore blank lines and comments var entry = while (self.r.seek != self.r.end) {
if (entry.len == 0 or entry[0] == '#') continue; // Reset write head
writer.end = 0;
// Trim spaces around '=' _ = self.r.streamDelimiterEnding(&writer, '\n') catch |e| {
if (mem.indexOf(u8, entry, "=")) |idx| { log.warn("cannot read from \"{s}\": {}", .{ self.filepath, e });
const key = std.mem.trim(u8, entry[0..idx], whitespace); return null;
const value = value: { };
var value = std.mem.trim(u8, entry[idx + 1 ..], whitespace); _ = self.r.discardDelimiterInclusive('\n') catch {};
// Detect a quoted string. var entry = writer.buffered();
if (value.len >= 2 and self.line += 1;
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; // 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; // Ignore blank lines and comments
if (entry.len != len) { if (entry.len == 0 or entry[0] == '#') continue;
std.mem.copyForwards(u8, entry, key); break entry;
entry[key.len] = '='; } else return null;
std.mem.copyForwards(u8, entry[key.len + 1 ..], value);
entry = entry[0..len];
}
}
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 const len = key.len + value.len + 1;
// of our buffer so that we can trick the CLI parser to treat it if (entry.len != len) {
// as CLI args. std.mem.copyForwards(u8, entry, key);
return self.entry[0 .. buf.len + 2]; entry[key.len] = '=';
std.mem.copyForwards(u8, entry[key.len + 1 ..], value);
entry = entry[0..len];
}
} }
/// Returns a location for a diagnostic message. // We need to reslice so that we include our '--' at the beginning
pub fn location( // of our buffer so that we can trick the CLI parser to treat it
self: *const Self, // as CLI args.
alloc: Allocator, return self.entry[0 .. entry.len + 2];
) Allocator.Error!?diags.Location { }
// If we have no filepath then we have no location.
if (self.filepath.len == 0) return null;
return .{ .file = .{ /// Returns a location for a diagnostic message.
.path = try alloc.dupe(u8, self.filepath), pub fn location(
.line = self.line, 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). return .{ .file = .{
fn lineIterator(reader: anytype) LineIterator(@TypeOf(reader)) { .path = try alloc.dupe(u8, self.filepath),
return .{ .r = reader }; .line = self.line,
} } };
}
};
/// An iterator valid for arg parsing from a slice. /// An iterator valid for arg parsing from a slice.
pub const SliceIterator = struct { pub const SliceIterator = struct {
@ -1526,7 +1525,7 @@ pub fn sliceIterator(slice: []const []const u8) SliceIterator {
test "LineIterator" { test "LineIterator" {
const testing = std.testing; const testing = std.testing;
var fbs = std.io.fixedBufferStream( var reader: std.Io.Reader = .fixed(
\\A \\A
\\B=42 \\B=42
\\C \\C
@ -1541,7 +1540,7 @@ test "LineIterator" {
\\F= "value " \\F= "value "
); );
var iter = lineIterator(fbs.reader()); var iter: LineIterator = .init(&reader);
try testing.expectEqualStrings("--A", iter.next().?); try testing.expectEqualStrings("--A", iter.next().?);
try testing.expectEqualStrings("--B=42", iter.next().?); try testing.expectEqualStrings("--B=42", iter.next().?);
try testing.expectEqualStrings("--C", iter.next().?); try testing.expectEqualStrings("--C", iter.next().?);
@ -1554,9 +1553,9 @@ test "LineIterator" {
test "LineIterator end in newline" { test "LineIterator end in newline" {
const testing = std.testing; 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.expectEqualStrings("--A", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), 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 '='" { test "LineIterator spaces around '='" {
const testing = std.testing; 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.expectEqualStrings("--A=B", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), 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" { test "LineIterator no value" {
const testing = std.testing; 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.expectEqualStrings("--A=", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), iter.next());
} }
test "LineIterator with CRLF line endings" { test "LineIterator with CRLF line endings" {
const testing = std.testing; 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("--A", iter.next().?);
try testing.expectEqualStrings("--B=C", iter.next().?); try testing.expectEqualStrings("--B=C", iter.next().?);
try testing.expectEqual(@as(?[]const u8, null), iter.next()); try testing.expectEqual(@as(?[]const u8, null), iter.next());

View File

@ -6,7 +6,7 @@ const Allocator = std.mem.Allocator;
const help_strings = @import("help_strings"); const help_strings = @import("help_strings");
const vaxis = @import("vaxis"); const vaxis = @import("vaxis");
const framedata = @import("framedata"); const framedata = @embedFile("framedata");
const vxfw = vaxis.vxfw; const vxfw = vaxis.vxfw;
@ -218,17 +218,20 @@ var frames: []const []const u8 = undefined;
/// Decompress the frames into a slice of individual frames /// Decompress the frames into a slice of individual frames
fn decompressFrames(gpa: Allocator) !void { fn decompressFrames(gpa: Allocator) !void {
var fbs = std.io.fixedBufferStream(framedata.compressed); var src: std.Io.Reader = .fixed(framedata);
var list = std.ArrayList(u8).init(gpa);
try std.compress.flate.decompress(fbs.reader(), list.writer()); // var buf: [std.compress.flate.max_window_len]u8 = undefined;
decompressed_data = try list.toOwnedSlice(); 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'); var frame_iter = std.mem.splitScalar(u8, decompressed_data, '\x01');
while (frame_iter.next()) |frame| { 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);
} }

View File

@ -38,21 +38,35 @@ pub fn run(alloc_gpa: Allocator) !u8 {
try args.parse(Options, alloc_gpa, &opts, &iter); 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); 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(); 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), .name = try alloc.dupe(u8, report.name),
.mtime = report.mtime, .mtime = report.mtime,
}); });
const stdout = std.io.getStdOut();
// If we have no reports, then we're done. If we have a tty then we // If we have no reports, then we're done. If we have a tty then we
// print a message, otherwise we do nothing. // print a message, otherwise we do nothing.
if (reports.items.len == 0) { 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"); try stdout.writeAll("No crash reports! 👻\n");
} }
return 0; return 0;
@ -60,16 +74,15 @@ pub fn run(alloc_gpa: Allocator) !u8 {
std.mem.sort(crash.Report, reports.items, {}, lt); std.mem.sort(crash.Report, reports.items, {}, lt);
const writer = stdout.writer();
for (reports.items) |report| { for (reports.items) |report| {
var buf: [128]u8 = undefined; var buf: [128]u8 = undefined;
const now = std.time.nanoTimestamp(); const now = std.time.nanoTimestamp();
const diff = now - report.mtime; const diff = now - report.mtime;
const since = if (diff <= 0) "now" else s: { const since = if (diff <= 0) "now" else s: {
const d = Config.Duration{ .duration = @intCast(diff) }; 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; return 0;

View File

@ -16,7 +16,7 @@ pub const Diagnostic = struct {
message: [:0]const u8, message: [:0]const u8,
/// Write the full user-friendly diagnostic message to the writer. /// 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) { switch (self.location) {
.none => {}, .none => {},
.cli => |index| try writer.print("cli:{}:", .{index}), .cli => |index| try writer.print("cli:{}:", .{index}),
@ -157,11 +157,14 @@ pub const DiagnosticList = struct {
errdefer _ = self.list.pop(); errdefer _ = self.list.pop();
if (comptime precompute_enabled) { if (comptime precompute_enabled) {
var buf = std.ArrayList(u8).init(alloc); var stream: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(); defer stream.deinit();
try diag.write(buf.writer()); 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); errdefer alloc.free(owned);
try self.precompute.messages.append(alloc, owned); try self.precompute.messages.append(alloc, owned);

View File

@ -47,7 +47,9 @@ pub fn run(alloc: Allocator) !u8 {
// not using `exec` anymore and because this command isn't performance // not using `exec` anymore and because this command isn't performance
// critical where setting up the defer cleanup is a problem. // 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 = .{}; var opts: Options = .{};
defer opts.deinit(); defer opts.deinit();
@ -58,6 +60,13 @@ pub fn run(alloc: Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter); 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 // We load the configuration once because that will write our
// default configuration files to disk. We don't use the config. // default configuration files to disk. We don't use the config.
var config = try Config.load(alloc); var config = try Config.load(alloc);
@ -133,23 +142,13 @@ pub fn run(alloc: Allocator) !u8 {
// so this is not a big deal. // so this is not a big deal.
comptime assert(builtin.link_libc); comptime assert(builtin.link_libc);
var buf: std.ArrayListUnmanaged(u8) = .empty; const editorZ = try alloc.dupeZ(u8, editor);
errdefer buf.deinit(alloc); defer alloc.free(editorZ);
const pathZ = try alloc.dupeZ(u8, path);
const writer = buf.writer(alloc); defer alloc.free(pathZ);
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 err = std.posix.execvpeZ( const err = std.posix.execvpeZ(
"sh", editorZ,
&.{ "sh", "-c", command }, &.{ editorZ, pathZ },
std.c.environ, std.c.environ,
); );

View File

@ -107,12 +107,18 @@ pub const Action = enum {
// for all commands by just changing this one place. // for all commands by just changing this one place.
if (std.mem.eql(u8, field.name, @tagName(self))) { 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"; const text = @field(help_strings.Action, field.name) ++ "\n";
stdout.writeAll(text) catch |write_err| { stdout.writeAll(text) catch |write_err| {
std.log.warn("failed to write help text: {}\n", .{write_err}); std.log.warn("failed to write help text: {}\n", .{write_err});
break :err 1; 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; break :err 0;
} }

View File

@ -30,7 +30,9 @@ pub fn run(alloc: Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter); 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( try stdout.writeAll(
\\Usage: ghostty [+action] [options] \\Usage: ghostty [+action] [options]
\\ \\
@ -70,6 +72,7 @@ pub fn run(alloc: Allocator) !u8 {
\\where `<action>` is one of actions listed above. \\where `<action>` is one of actions listed above.
\\ \\
); );
try stdout.flush();
return 0; return 0;
} }

View File

@ -37,8 +37,15 @@ pub fn run(alloc: Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter); try args.parse(Options, alloc, &opts, &iter);
} }
const stdout = std.io.getStdOut().writer(); var stdout: std.fs.File = .stdout();
try helpgen_actions.generate(stdout, .plaintext, opts.docs, std.heap.page_allocator); 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; return 0;
} }

View File

@ -39,11 +39,9 @@ pub fn run(alloc: Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter); try args.parse(Options, alloc, &opts, &iter);
} }
const stdout = std.io.getStdOut(); var keys: std.ArrayList([]const u8) = .empty;
defer keys.deinit(alloc);
var keys = std.ArrayList([]const u8).init(alloc); for (x11_color.map.keys()) |key| try keys.append(alloc, key);
defer keys.deinit();
for (x11_color.map.keys()) |key| try keys.append(key);
std.mem.sortUnstable([]const u8, keys.items, {}, struct { std.mem.sortUnstable([]const u8, keys.items, {}, struct {
fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool { fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool {
@ -52,12 +50,15 @@ pub fn run(alloc: Allocator) !u8 {
}.lessThan); }.lessThan);
// Despite being under the posix namespace, this also works on Windows as of zig 0.13.0 // 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)) { if (tui.can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) {
var arena = std.heap.ArenaAllocator.init(alloc); var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit(); defer arena.deinit();
return prettyPrint(arena.allocator(), keys.items); return prettyPrint(arena.allocator(), keys.items);
} else { } 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| { for (keys.items) |name| {
const rgb = x11_color.map.get(name).?; const rgb = x11_color.map.get(name).?;
try writer.print("{s} = #{x:0>2}{x:0>2}{x:0>2}\n", .{ 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 { fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 {
// Set up vaxis // Set up vaxis
var tty = try vaxis.Tty.init(); var buf: [1024]u8 = undefined;
var tty = try vaxis.Tty.init(&buf);
defer tty.deinit(); defer tty.deinit();
var vx = try vaxis.init(alloc, .{}); 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 // 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. // event loop to auto-enable it.
vx.caps.unicode = .unicode; vx.caps.unicode = .unicode;
try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set); try tty.writer().writeAll(vaxis.ctlseqs.unicode_set);
defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; defer tty.writer().writeAll(vaxis.ctlseqs.unicode_reset) catch {};
var buf_writer = tty.bufferedWriter();
const writer = buf_writer.writer().any();
const winsize: vaxis.Winsize = switch (builtin.os.tag) { const winsize: vaxis.Winsize = switch (builtin.os.tag) {
// We use some default, it doesn't really matter for what // 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), 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(); const win = vx.window();
@ -203,11 +202,8 @@ fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 {
} }
// output the data // output the data
try vx.prettyPrint(writer); try vx.prettyPrint(tty.writer());
} }
// be sure to flush!
try buf_writer.flush();
return 0; return 0;
} }

View File

@ -77,7 +77,9 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
// Its possible to build Ghostty without font discovery! // Its possible to build Ghostty without font discovery!
if (comptime font.Discover == void) { 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( try stderr.print(
\\Ghostty was built without a font discovery mechanism. This is a compile-time \\Ghostty was built without a font discovery mechanism. This is a compile-time
\\option. Please review how Ghostty was built from source, contact the \\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; 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 // We'll be putting our fonts into a list categorized by family
// so it is easier to read the output. // so it is easier to read the output.
var families = std.ArrayList([]const u8).init(alloc); var families: std.ArrayList([]const u8) = .empty;
var map = std.StringHashMap(std.ArrayListUnmanaged([]const u8)).init(alloc); var map: std.StringHashMap(std.ArrayListUnmanaged([]const u8)) = .init(alloc);
// Look up all available fonts // Look up all available fonts
var disco = font.Discover.init(); var disco = font.Discover.init();
@ -123,7 +128,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
const gop = try map.getOrPut(family); const gop = try map.getOrPut(family);
if (!gop.found_existing) { if (!gop.found_existing) {
try families.append(family); try families.append(alloc, family);
gop.value_ptr.* = .{}; gop.value_ptr.* = .{};
} }
try gop.value_ptr.append(alloc, full_name); 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.print("\n", .{});
} }
try stdout.flush();
return 0; return 0;
} }

View File

@ -64,27 +64,38 @@ pub fn run(alloc: Allocator) !u8 {
var config = if (opts.default) try Config.default(alloc) else try Config.load(alloc); var config = if (opts.default) try Config.default(alloc) else try Config.load(alloc);
defer config.deinit(); 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 stdout.isTty()) {
if (tui.can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) {
var arena = std.heap.ArenaAllocator.init(alloc); var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit(); defer arena.deinit();
return prettyPrint(arena.allocator(), config.keybind); return prettyPrint(arena.allocator(), config.keybind);
} else { } else {
try config.keybind.formatEntryDocs( try config.keybind.formatEntryDocs(
configpkg.entryFormatter("keybind", stdout.writer()), configpkg.entryFormatter("keybind", writer),
opts.docs, opts.docs,
); );
} }
// Don't forget to flush!
try writer.flush();
return 0; 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 { const ChordBinding = struct {
triggers: TriggerList, triggers: std.SinglyLinkedList,
action: Binding.Action, action: Binding.Action,
// Order keybinds based on various properties // Order keybinds based on various properties
@ -109,7 +120,8 @@ const ChordBinding = struct {
const lhs_count: usize = blk: { const lhs_count: usize = blk: {
var count: usize = 0; var count: usize = 0;
var maybe_trigger = lhs.triggers.first; 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.super) count += 1;
if (trigger.data.mods.ctrl) count += 1; if (trigger.data.mods.ctrl) count += 1;
if (trigger.data.mods.shift) count += 1; if (trigger.data.mods.shift) count += 1;
@ -120,7 +132,8 @@ const ChordBinding = struct {
const rhs_count: usize = blk: { const rhs_count: usize = blk: {
var count: usize = 0; var count: usize = 0;
var maybe_trigger = rhs.triggers.first; 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.super) count += 1;
if (trigger.data.mods.ctrl) count += 1; if (trigger.data.mods.ctrl) count += 1;
if (trigger.data.mods.shift) count += 1; if (trigger.data.mods.shift) count += 1;
@ -137,8 +150,8 @@ const ChordBinding = struct {
var l_trigger = lhs.triggers.first; var l_trigger = lhs.triggers.first;
var r_trigger = rhs.triggers.first; var r_trigger = rhs.triggers.first;
while (l_trigger != null and r_trigger != null) { while (l_trigger != null and r_trigger != null) {
const l_int = l_trigger.?.data.mods.int(); const l_int = TriggerNode.get(l_trigger.?).data.mods.int();
const r_int = r_trigger.?.data.mods.int(); const r_int = TriggerNode.get(r_trigger.?).data.mods.int();
if (l_int != r_int) { if (l_int != r_int) {
return l_int > r_int; return l_int > r_int;
@ -154,13 +167,13 @@ const ChordBinding = struct {
while (l_trigger != null and r_trigger != null) { while (l_trigger != null and r_trigger != null) {
const lhs_key: c_int = blk: { const lhs_key: c_int = blk: {
switch (l_trigger.?.data.key) { switch (TriggerNode.get(l_trigger.?).data.key) {
.physical => |key| break :blk @intFromEnum(key), .physical => |key| break :blk @intFromEnum(key),
.unicode => |key| break :blk @intCast(key), .unicode => |key| break :blk @intCast(key),
} }
}; };
const rhs_key: c_int = blk: { const rhs_key: c_int = blk: {
switch (r_trigger.?.data.key) { switch (TriggerNode.get(r_trigger.?).data.key) {
.physical => |key| break :blk @intFromEnum(key), .physical => |key| break :blk @intFromEnum(key),
.unicode => |key| break :blk @intCast(key), .unicode => |key| break :blk @intCast(key),
} }
@ -186,19 +199,18 @@ const ChordBinding = struct {
fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 { fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
// Set up vaxis // Set up vaxis
var tty = try vaxis.Tty.init(); var buf: [1024]u8 = undefined;
var tty = try vaxis.Tty.init(&buf);
defer tty.deinit(); defer tty.deinit();
var vx = try vaxis.init(alloc, .{}); 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 // 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. // event loop to auto-enable it.
vx.caps.unicode = .unicode; vx.caps.unicode = .unicode;
try tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_set); try writer.writeAll(vaxis.ctlseqs.unicode_set);
defer tty.anyWriter().writeAll(vaxis.ctlseqs.unicode_reset) catch {}; defer writer.writeAll(vaxis.ctlseqs.unicode_reset) catch {};
var buf_writer = tty.bufferedWriter();
const writer = buf_writer.writer().any();
const winsize: vaxis.Winsize = switch (builtin.os.tag) { const winsize: vaxis.Winsize = switch (builtin.os.tag) {
// We use some default, it doesn't really matter for what // 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), else => try vaxis.Tty.getWinsize(tty.fd),
}; };
try vx.resize(alloc, tty.anyWriter(), winsize); try vx.resize(alloc, writer, winsize);
const win = vx.window(); 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 result: vaxis.Window.PrintResult = .{ .col = 0, .row = 0, .overflow = false };
var maybe_trigger = bind.triggers.first; 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) { if (trigger.data.mods.super) {
result = win.printSegment(.{ .text = "super", .style = super_style }, .{ .col_offset = result.col }); result = win.printSegment(.{ .text = "super", .style = super_style }, .{ .col_offset = result.col });
result = win.printSegment(.{ .text = " + " }, .{ .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 }); result = win.printSegment(.{ .text = " + " }, .{ .col_offset = result.col });
} }
const key = switch (trigger.data.key) { 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}), .unicode => |c| try std.fmt.allocPrint(alloc, "{u}", .{c}),
}; };
result = win.printSegment(.{ .text = key }, .{ .col_offset = result.col }); result = win.printSegment(.{ .text = key }, .{ .col_offset = result.col });
// Print a separator between chorded keys // 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 }); 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 our action has an argument, we print the argument in a different color
if (std.mem.indexOfScalar(u8, action, ':')) |idx| { if (std.mem.indexOfScalar(u8, action, ':')) |idx| {
_ = win.print(&.{ _ = win.print(&.{
@ -276,29 +290,33 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
} }
try vx.prettyPrint(writer); try vx.prettyPrint(writer);
} }
try buf_writer.flush(); try writer.flush();
return 0; 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 widest_chord: u16 = 0;
var bindings = std.ArrayList(ChordBinding).init(alloc); var bindings: std.ArrayList(ChordBinding) = .empty;
while (iter.next()) |bind| { while (iter.next()) |bind| {
const width = blk: { const width = blk: {
var buf = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
const t = bind.key_ptr.*; const t = bind.key_ptr.*;
if (t.mods.super) try std.fmt.format(buf.writer(), "super + ", .{}); if (t.mods.super) try buf.writer.print("super + ", .{});
if (t.mods.ctrl) try std.fmt.format(buf.writer(), "ctrl + ", .{}); if (t.mods.ctrl) try buf.writer.print("ctrl + ", .{});
if (t.mods.alt) try std.fmt.format(buf.writer(), "alt + ", .{}); if (t.mods.alt) try buf.writer.print("alt + ", .{});
if (t.mods.shift) try std.fmt.format(buf.writer(), "shift + ", .{}); if (t.mods.shift) try buf.writer.print("shift + ", .{});
switch (t.key) { switch (t.key) {
.physical => |k| try std.fmt.format(buf.writer(), "{s}", .{@tagName(k)}), .physical => |k| try buf.writer.print("{t}", .{k}),
.unicode => |c| try std.fmt.format(buf.writer(), "{u}", .{c}), .unicode => |c| try buf.writer.print("{u}", .{c}),
} }
break :blk win.gwidth(buf.items); break :blk win.gwidth(buf.written());
}; };
switch (bind.value_ptr.*) { 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 // Prepend the current keybind onto the list of sub-binds
for (sub_bindings) |*nb| { for (sub_bindings) |*nb| {
const prepend_node = try alloc.create(TriggerList.Node); const prepend_node = try alloc.create(TriggerNode);
prepend_node.* = TriggerList.Node{ .data = bind.key_ptr.* }; prepend_node.* = .{ .data = bind.key_ptr.* };
nb.triggers.prepend(prepend_node); nb.triggers.prepend(&prepend_node.node);
} }
// Add the longest sub-bind width to the current bind width along with a padding // Add the longest sub-bind width to the current bind width along with a padding
// of 5 for the ' > ' spacer // of 5 for the ' > ' spacer
widest_chord = @max(widest_chord, width + max_width + 5); widest_chord = @max(widest_chord, width + max_width + 5);
try bindings.appendSlice(sub_bindings); try bindings.appendSlice(alloc, sub_bindings);
}, },
.leaf => |leaf| { .leaf => |leaf| {
const node = try alloc.create(TriggerList.Node); const node = try alloc.create(TriggerNode);
node.* = TriggerList.Node{ .data = bind.key_ptr.* }; node.* = .{ .data = bind.key_ptr.* };
const triggers = TriggerList{
.first = node,
};
widest_chord = @max(widest_chord, width); 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 };
} }

View File

@ -57,9 +57,12 @@ const ThemeListElement = struct {
.host = .{ .raw = "" }, .host = .{ .raw = "" },
.path = .{ .raw = self.path }, .path = .{ .raw = self.path },
}; };
var buf = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
errdefer buf.deinit(); 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(); return buf.toOwnedSlice();
} }
}; };
@ -114,8 +117,14 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
var arena = std.heap.ArenaAllocator.init(gpa_alloc); var arena = std.heap.ArenaAllocator.init(gpa_alloc);
const alloc = arena.allocator(); const alloc = arena.allocator();
const stderr = std.io.getStdErr().writer(); var stdout_buf: [4096]u8 = undefined;
const stdout = std.io.getStdOut().writer(); 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(); const resources_dir = global_state.resources_dir.app();
if (resources_dir == null) if (resources_dir == null)
@ -124,9 +133,9 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
var count: usize = 0; 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| { while (try it.next()) |loc| {
var dir = std.fs.cwd().openDir(loc.dir, .{ .iterate = true }) catch |err| switch (err) { 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; count += 1;
const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name }); const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name });
try themes.append(.{ try themes.append(alloc, .{
.path = path, .path = path,
.location = loc.location, .location = loc.location,
.theme = try alloc.dupe(u8, entry.name), .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); 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); try preview(gpa_alloc, themes.items, opts.color);
return 0; return 0;
} }
for (themes.items) |theme| { for (themes.items) |theme| {
if (opts.path) 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 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; return 0;
} }
@ -209,23 +220,28 @@ const Preview = struct {
text_input: vaxis.widgets.TextInput, text_input: vaxis.widgets.TextInput,
theme_filter: ColorScheme, 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); const self = try allocator.create(Preview);
self.* = .{ self.* = .{
.allocator = allocator, .allocator = allocator,
.should_quit = false, .should_quit = false,
.tty = try vaxis.Tty.init(), .tty = try .init(buf),
.vx = try vaxis.init(allocator, .{}), .vx = try vaxis.init(allocator, .{}),
.mouse = null, .mouse = null,
.themes = themes, .themes = themes,
.filtered = try std.ArrayList(usize).initCapacity(allocator, themes.len), .filtered = try .initCapacity(allocator, themes.len),
.current = 0, .current = 0,
.window = 0, .window = 0,
.hex = false, .hex = false,
.mode = .normal, .mode = .normal,
.color_scheme = .light, .color_scheme = .light,
.text_input = vaxis.widgets.TextInput.init(allocator, &self.vx.unicode), .text_input = .init(allocator, &self.vx.unicode),
.theme_filter = theme_filter, .theme_filter = theme_filter,
}; };
@ -236,9 +252,9 @@ const Preview = struct {
pub fn deinit(self: *Preview) void { pub fn deinit(self: *Preview) void {
const allocator = self.allocator; const allocator = self.allocator;
self.filtered.deinit(); self.filtered.deinit(allocator);
self.text_input.deinit(); self.text_input.deinit();
self.vx.deinit(allocator, self.tty.anyWriter()); self.vx.deinit(allocator, self.tty.writer());
self.tty.deinit(); self.tty.deinit();
allocator.destroy(self); allocator.destroy(self);
} }
@ -251,12 +267,14 @@ const Preview = struct {
try loop.init(); try loop.init();
try loop.start(); try loop.start();
try self.vx.enterAltScreen(self.tty.anyWriter()); const writer = self.tty.writer();
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.enterAltScreen(writer);
try self.vx.setMouseMode(self.tty.anyWriter(), true); 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) if (self.vx.caps.color_scheme_updates)
try self.vx.subscribeToColorSchemeUpdates(self.tty.anyWriter()); try self.vx.subscribeToColorSchemeUpdates(writer);
while (!self.should_quit) { while (!self.should_quit) {
var arena = std.heap.ArenaAllocator.init(self.allocator); var arena = std.heap.ArenaAllocator.init(self.allocator);
@ -269,9 +287,8 @@ const Preview = struct {
} }
try self.draw(alloc); try self.draw(alloc);
var buffered = self.tty.bufferedWriter(); try self.vx.render(writer);
try self.vx.render(buffered.writer().any()); try writer.flush();
try buffered.flush();
} }
} }
@ -308,11 +325,11 @@ const Preview = struct {
const string = try std.ascii.allocLowerString(self.allocator, buffer); const string = try std.ascii.allocLowerString(self.allocator, buffer);
defer self.allocator.free(string); defer self.allocator.free(string);
var tokens = std.ArrayList([]const u8).init(self.allocator); var tokens: std.ArrayList([]const u8) = .empty;
defer tokens.deinit(); defer tokens.deinit(self.allocator);
var it = std.mem.tokenizeScalar(u8, string, ' '); 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| { for (self.themes, 0..) |*theme, i| {
try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path); try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
@ -322,13 +339,13 @@ const Preview = struct {
.to_lower = true, .to_lower = true,
.plain = true, .plain = true,
}); });
if (theme.rank != null) try self.filtered.append(i); if (theme.rank != null) try self.filtered.append(self.allocator, i);
} }
} else { } else {
for (self.themes, 0..) |*theme, i| { for (self.themes, 0..) |*theme, i| {
try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path); try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
if (shouldIncludeTheme(self.theme_filter, theme_config)) { if (shouldIncludeTheme(self.theme_filter, theme_config)) {
try self.filtered.append(i); try self.filtered.append(self.allocator, i);
theme.rank = null; theme.rank = null;
} }
} }
@ -421,13 +438,13 @@ const Preview = struct {
self.hex = false; self.hex = false;
if (key.matches('c', .{})) if (key.matches('c', .{}))
try self.vx.copyToSystemClipboard( try self.vx.copyToSystemClipboard(
self.tty.anyWriter(), self.tty.writer(),
self.themes[self.filtered.items[self.current]].theme, self.themes[self.filtered.items[self.current]].theme,
alloc, alloc,
) )
else if (key.matches('c', .{ .shift = true })) else if (key.matches('c', .{ .shift = true }))
try self.vx.copyToSystemClipboard( try self.vx.copyToSystemClipboard(
self.tty.anyWriter(), self.tty.writer(),
self.themes[self.filtered.items[self.current]].path, self.themes[self.filtered.items[self.current]].path,
alloc, alloc,
); );
@ -471,7 +488,7 @@ const Preview = struct {
}, },
.color_scheme => |color_scheme| self.color_scheme = color_scheme, .color_scheme => |color_scheme| self.color_scheme = color_scheme,
.mouse => |mouse| self.mouse = mouse, .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(); defer buf.deinit();
for (config._diagnostics.items(), 0..) |diag, captured_i| { for (config._diagnostics.items(), 0..) |diag, captured_i| {
const i: u16 = @intCast(captured_i); const i: u16 = @intCast(captured_i);
try diag.write(buf.writer()); try diag.format(&buf.writer);
_ = child.printSegment( _ = child.printSegment(
.{ .{
.text = buf.items, .text = buf.written(),
.style = self.ui_err(), .style = self.ui_err(),
}, },
.{ .{
@ -1319,7 +1336,7 @@ const Preview = struct {
.{ .text = "const ", .style = color5 }, .{ .text = "const ", .style = color5 },
.{ .text = "stdout ", .style = standard }, .{ .text = "stdout ", .style = standard },
.{ .text = "=", .style = color5 }, .{ .text = "=", .style = color5 },
.{ .text = " std.io.getStdOut().writer();", .style = standard }, .{ .text = " std.Io.getStdOut().writer();", .style = standard },
}, },
.{ .{
.row_offset = 7, .row_offset = 7,
@ -1651,7 +1668,13 @@ fn color(config: Config, palette: usize) vaxis.Color {
const lorem_ipsum = @embedFile("lorem_ipsum.txt"); const lorem_ipsum = @embedFile("lorem_ipsum.txt");
fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !void { 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(); defer app.deinit();
try app.run(); try app.run();
} }

View File

@ -26,7 +26,7 @@ pub const Options = struct {
// If it's not `-e` continue with the standard argument parsning. // If it's not `-e` continue with the standard argument parsning.
if (!std.mem.eql(u8, arg, "-e")) return true; 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 { errdefer {
for (arguments.items) |argument| alloc.free(argument); for (arguments.items) |argument| alloc.free(argument);
arguments.deinit(alloc); arguments.deinit(alloc);
@ -99,12 +99,21 @@ pub const Options = struct {
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
var iter = try args.argsIterator(alloc); var iter = try args.argsIterator(alloc);
defer iter.deinit(); 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 { fn runArgs(
const stderr = std.io.getStdErr().writer(); alloc_gpa: Allocator,
argsIter: anytype,
stderr: *std.Io.Writer,
) !u8 {
var opts: Options = .{}; var opts: Options = .{};
defer opts.deinit(); defer opts.deinit();
@ -126,9 +135,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
inner: inline for (@typeInfo(Options).@"struct".fields) |field| { inner: inline for (@typeInfo(Options).@"struct".fields) |field| {
if (field.name[0] == '_') continue :inner; if (field.name[0] == '_') continue :inner;
if (std.mem.eql(u8, field.name, diagnostic.key)) { if (std.mem.eql(u8, field.name, diagnostic.key)) {
try stderr.writeAll("config error: "); try stderr.print("config error: {f}\n", .{diagnostic});
try diagnostic.write(stderr);
try stderr.writeAll("\n");
exit = true; exit = true;
} }
} }

View File

@ -77,7 +77,10 @@ pub fn run(alloc: Allocator) !u8 {
// For some reason `std.fmt.format` isn't working here but it works in // For some reason `std.fmt.format` isn't working here but it works in
// tests so we just do configfmt.format. // tests so we just do configfmt.format.
const stdout = std.io.getStdOut().writer(); var stdout: std.fs.File = .stdout();
try configfmt.format("", .{}, stdout); var buffer: [4096]u8 = undefined;
var stdout_writer = stdout.writer(&buffer);
try configfmt.format(&stdout_writer.interface);
try stdout_writer.end();
return 0; return 0;
} }

View File

@ -64,13 +64,32 @@ pub const Options = struct {
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
var iter = try args.argsIterator(alloc); var iter = try args.argsIterator(alloc);
defer iter.deinit(); 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 { fn runArgs(
const stdout = std.io.getStdOut().writer(); alloc_gpa: Allocator,
const stderr = std.io.getStdErr().writer(); argsIter: anytype,
stdout: *std.Io.Writer,
stderr: *std.Io.Writer,
) !u8 {
// Its possible to build Ghostty without font discovery! // Its possible to build Ghostty without font discovery!
if (comptime font.Discover == void) { if (comptime font.Discover == void) {
try stderr.print( try stderr.print(
@ -104,9 +123,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
inner: inline for (@typeInfo(Options).@"struct".fields) |field| { inner: inline for (@typeInfo(Options).@"struct".fields) |field| {
if (field.name[0] == '_') continue :inner; if (field.name[0] == '_') continue :inner;
if (std.mem.eql(u8, field.name, diagnostic.key)) { if (std.mem.eql(u8, field.name, diagnostic.key)) {
try stderr.writeAll("config error: "); try stderr.print("config error: {f}\n", .{diagnostic});
try diagnostic.write(stderr);
try stderr.writeAll("\n");
exit = true; exit = true;
} }
} }
@ -138,9 +155,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
if (field.name[0] == '_') continue :inner; 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; 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 stderr.print("config error: {f}\n", .{diagnostic});
try diagnostic.write(stderr);
try stderr.writeAll("\n");
} }
} }
@ -189,8 +204,8 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
fn lookup( fn lookup(
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
stdout: anytype, stdout: *std.Io.Writer,
stderr: anytype, stderr: *std.Io.Writer,
font_grid: *font.SharedGrid, font_grid: *font.SharedGrid,
style: font.Style, style: font.Style,
presentation: ?font.Presentation, presentation: ?font.Presentation,

View File

@ -57,8 +57,6 @@ pub fn clear(self: DiskCache) !void {
pub const AddResult = enum { added, updated }; 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. /// Add or update a hostname entry in the cache.
/// Returns AddResult.added for new entries or AddResult.updated for existing ones. /// 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). /// The cache file is created if it doesn't exist with secure permissions (0600).
@ -66,7 +64,7 @@ pub fn add(
self: DiskCache, self: DiskCache,
alloc: Allocator, alloc: Allocator,
hostname: []const u8, hostname: []const u8,
) AddError!AddResult { ) !AddResult {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid; if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;
// Create cache directory if needed // Create cache directory if needed
@ -130,15 +128,13 @@ pub fn add(
return result; 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. /// Remove a hostname entry from the cache.
/// No error is returned if the hostname doesn't exist or the cache file is missing. /// No error is returned if the hostname doesn't exist or the cache file is missing.
pub fn remove( pub fn remove(
self: DiskCache, self: DiskCache,
alloc: Allocator, alloc: Allocator,
hostname: []const u8, hostname: []const u8,
) RemoveError!void { ) !void {
if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid; if (!isValidCacheKey(hostname)) return error.HostnameIsInvalid;
// Open our file // Open our file
@ -199,7 +195,7 @@ pub fn contains(
return entries.contains(hostname); 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 // Windows does not support chmod
if (comptime builtin.os.tag == .windows) return; 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( fn writeCacheFile(
self: DiskCache, self: DiskCache,
alloc: Allocator, alloc: Allocator,
entries: std.StringHashMap(Entry), entries: std.StringHashMap(Entry),
expire_days: ?u32, expire_days: ?u32,
) WriteCacheFileError!void { ) !void {
var td: TempDir = try .init(); var td: TempDir = try .init();
defer td.deinit(); defer td.deinit();
@ -227,14 +221,18 @@ fn writeCacheFile(
const tmp_path = try td.dir.realpathAlloc(alloc, "ssh-cache"); const tmp_path = try td.dir.realpathAlloc(alloc, "ssh-cache");
defer alloc.free(tmp_path); 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(); var iter = entries.iterator();
while (iter.next()) |kv| { while (iter.next()) |kv| {
// Only write non-expired entries // Only write non-expired entries
if (kv.value_ptr.isExpired(expire_days)) continue; 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 // Atomic replace
try std.fs.renameAbsolute(tmp_path, self.path); try std.fs.renameAbsolute(tmp_path, self.path);
} }
@ -278,8 +276,12 @@ pub fn deinitEntries(
fn readEntries( fn readEntries(
alloc: Allocator, alloc: Allocator,
file: std.fs.File, file: std.fs.File,
) (std.fs.File.ReadError || Allocator.Error || error{FileTooBig})!std.StringHashMap(Entry) { ) !std.StringHashMap(Entry) {
const content = try file.readToEndAlloc(alloc, MAX_CACHE_SIZE); var reader = file.reader(&.{});
const content = try reader.interface.allocRemaining(
alloc,
.limited(MAX_CACHE_SIZE),
);
defer alloc.free(content); defer alloc.free(content);
var entries = std.StringHashMap(Entry).init(alloc); var entries = std.StringHashMap(Entry).init(alloc);
@ -403,10 +405,12 @@ test "disk cache clear" {
// Create our path // Create our path
var td: TempDir = try .init(); var td: TempDir = try .init();
defer td.deinit(); defer td.deinit();
var buf: [4096]u8 = undefined;
{ {
var file = try td.dir.createFile("cache", .{}); var file = try td.dir.createFile("cache", .{});
defer file.close(); 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"); const path = try td.dir.realpathAlloc(alloc, "cache");
defer alloc.free(path); defer alloc.free(path);
@ -429,10 +433,14 @@ test "disk cache operations" {
// Create our path // Create our path
var td: TempDir = try .init(); var td: TempDir = try .init();
defer td.deinit(); defer td.deinit();
var buf: [4096]u8 = undefined;
{ {
var file = try td.dir.createFile("cache", .{}); var file = try td.dir.createFile("cache", .{});
defer file.close(); 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"); const path = try td.dir.realpathAlloc(alloc, "cache");
defer alloc.free(path); defer alloc.free(path);

View File

@ -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( try writer.print(
"{s}|{d}|{s}\n", "{s}|{d}|{s}\n",
.{ self.hostname, self.timestamp, self.terminfo_version }, .{ self.hostname, self.timestamp, self.terminfo_version },

View File

@ -61,9 +61,30 @@ pub fn run(alloc_gpa: Allocator) !u8 {
try args.parse(Options, alloc_gpa, &opts, &iter); try args.parse(Options, alloc_gpa, &opts, &iter);
} }
const stdout = std.io.getStdOut().writer(); var stdout_buffer: [1024]u8 = undefined;
const stderr = std.io.getStdErr().writer(); 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 // Setup our disk cache to the standard location
const cache_path = try DiskCache.defaultPath(alloc, "ghostty"); const cache_path = try DiskCache.defaultPath(alloc, "ghostty");
const cache: DiskCache = .{ .path = cache_path }; const cache: DiskCache = .{ .path = cache_path };
@ -165,7 +186,7 @@ pub fn run(alloc_gpa: Allocator) !u8 {
fn listEntries( fn listEntries(
alloc: Allocator, alloc: Allocator,
entries: *const std.StringHashMap(Entry), entries: *const std.StringHashMap(Entry),
writer: anytype, writer: *std.Io.Writer,
) !void { ) !void {
if (entries.count() == 0) { if (entries.count() == 0) {
try writer.print("No hosts in cache.\n", .{}); try writer.print("No hosts in cache.\n", .{});
@ -173,12 +194,12 @@ fn listEntries(
} }
// Sort entries by hostname for consistent output // Sort entries by hostname for consistent output
var items = std.ArrayList(Entry).init(alloc); var items: std.ArrayList(Entry) = .empty;
defer items.deinit(); defer items.deinit(alloc);
var iter = entries.iterator(); var iter = entries.iterator();
while (iter.next()) |kv| { while (iter.next()) |kv| {
try items.append(kv.value_ptr.*); try items.append(alloc, kv.value_ptr.*);
} }
std.mem.sort(Entry, items.items, {}, struct { std.mem.sort(Entry, items.items, {}, struct {

View File

@ -40,8 +40,19 @@ pub fn run(alloc: std.mem.Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter); 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); var cfg = try Config.default(alloc);
defer cfg.deinit(); defer cfg.deinit();
@ -58,15 +69,9 @@ pub fn run(alloc: std.mem.Allocator) !u8 {
try cfg.finalize(); try cfg.finalize();
if (cfg._diagnostics.items().len > 0) { if (cfg._diagnostics.items().len > 0) {
var buf = std.ArrayList(u8).init(alloc);
defer buf.deinit();
for (cfg._diagnostics.items()) |diag| { for (cfg._diagnostics.items()) |diag| {
try diag.write(buf.writer()); try stdout.print("{f}\n", .{diag});
try stdout.print("{s}\n", .{buf.items});
buf.clearRetainingCapacity();
} }
return 1; return 1;
} }

View File

@ -15,8 +15,12 @@ pub const Options = struct {};
/// The `version` command is used to display information about Ghostty. Recognized as /// The `version` command is used to display information about Ghostty. Recognized as
/// either `+version` or `--version`. /// either `+version` or `--version`.
pub fn run(alloc: Allocator) !u8 { pub fn run(alloc: Allocator) !u8 {
const stdout = std.io.getStdOut().writer(); var buffer: [1024]u8 = undefined;
const tty = std.io.getStdOut().isTty(); 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| { if (tty) if (build_config.version.build) |commit_hash| {
try stdout.print( try stdout.print(
@ -29,7 +33,7 @@ pub fn run(alloc: Allocator) !u8 {
try stdout.print("Version\n", .{}); try stdout.print("Version\n", .{});
try stdout.print(" - version: {s}\n", .{build_config.version_string}); 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("Build Config\n", .{});
try stdout.print(" - Zig version : {s}\n", .{builtin.zig_version_string}); 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(" - app runtime : {}\n", .{build_config.app_runtime});
try stdout.print(" - font engine : {}\n", .{build_config.font_backend}); try stdout.print(" - font engine : {}\n", .{build_config.font_backend});
try stdout.print(" - renderer : {}\n", .{renderer.Renderer}); 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 build_config.app_runtime == .gtk) {
if (comptime builtin.os.tag == .linux) { if (comptime builtin.os.tag == .linux) {
const kernel_info = internal_os.getKernelInfo(alloc); const kernel_info = internal_os.getKernelInfo(alloc);
defer if (kernel_info) |k| alloc.free(k); defer if (kernel_info) |k| alloc.free(k);
try stdout.print(" - kernel version: {s}\n", .{kernel_info orelse "Kernel information unavailable"}); 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(" - GTK version :\n", .{});
try stdout.print(" build : {}\n", .{gtk_version.comptime_version}); try stdout.print(" build : {f}\n", .{gtk_version.comptime_version});
try stdout.print(" runtime : {}\n", .{gtk_version.getRuntimeVersion()}); try stdout.print(" runtime : {f}\n", .{gtk_version.getRuntimeVersion()});
try stdout.print(" - libadwaita : enabled\n", .{}); try stdout.print(" - libadwaita : enabled\n", .{});
try stdout.print(" build : {}\n", .{adw_version.comptime_version}); try stdout.print(" build : {f}\n", .{adw_version.comptime_version});
try stdout.print(" runtime : {}\n", .{adw_version.getRuntimeVersion()}); try stdout.print(" runtime : {f}\n", .{adw_version.getRuntimeVersion()});
if (comptime build_options.x11) { if (comptime build_options.x11) {
try stdout.print(" - libX11 : enabled\n", .{}); try stdout.print(" - libX11 : enabled\n", .{});
} else { } else {
@ -65,5 +69,8 @@ pub fn run(alloc: Allocator) !u8 {
try stdout.print(" - libwayland : disabled\n", .{}); try stdout.print(" - libwayland : disabled\n", .{});
} }
} }
// Don't forget to flush!
try stdout.flush();
return 0; return 0;
} }

View File

@ -3417,10 +3417,10 @@ pub fn loadFile(self: *Config, alloc: Allocator, path: []const u8) !void {
defer file.close(); defer file.close();
std.log.info("reading configuration file path={s}", .{path}); std.log.info("reading configuration file path={s}", .{path});
var buf_reader = std.io.bufferedReader(file.reader()); var buf: [2048]u8 = undefined;
const reader = buf_reader.reader(); var file_reader = file.reader(&buf);
const Iter = cli.args.LineIterator(@TypeOf(reader)); const reader = &file_reader.interface;
var iter: Iter = .{ .r = reader, .filepath = path }; var iter: cli.args.LineIterator = .{ .r = reader, .filepath = path };
try self.loadIter(alloc, &iter); try self.loadIter(alloc, &iter);
try self.expandPaths(std.fs.path.dirname(path).?); 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, .{}); const file = try std.fs.createFileAbsolute(path, .{});
defer file.close(); defer file.close();
try std.fmt.format( var buf: [4096]u8 = undefined;
file.writer(), var file_writer = file.writer(&buf);
const writer = &file_writer.interface;
try writer.print(
@embedFile("./config-template"), @embedFile("./config-template"),
.{ .path = path }, .{ .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 // Next, take all remaining args and use that to build up
// a command to execute. // a command to execute.
var builder = std.ArrayList([:0]const u8).init(arena_alloc); var builder: std.ArrayList([:0]const u8) = .empty;
errdefer builder.deinit(); errdefer builder.deinit(arena_alloc);
for (args) |arg_raw| { for (args) |arg_raw| {
const arg = std.mem.sliceTo(arg_raw, 0); const arg = std.mem.sliceTo(arg_raw, 0);
const copy = try arena_alloc.dupeZ(u8, arg); const copy = try arena_alloc.dupeZ(u8, arg);
try self._replay_steps.append(arena_alloc, .{ .arg = copy }); 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.@"_xdg-terminal-exec" = true;
self.@"initial-command" = .{ .direct = try builder.toOwnedSlice() }; self.@"initial-command" = .{ .direct = try builder.toOwnedSlice(arena_alloc) };
return; return;
} }
} }
@ -3710,13 +3712,13 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
// PRIOR to the "-e" in our replay steps, since everything // PRIOR to the "-e" in our replay steps, since everything
// after "-e" becomes an "initial-command". To do this, we // after "-e" becomes an "initial-command". To do this, we
// dupe the values if we find it. // dupe the values if we find it.
var replay_suffix = std.ArrayList(Replay.Step).init(alloc_gpa); var replay_suffix: std.ArrayList(Replay.Step) = .empty;
defer replay_suffix.deinit(); defer replay_suffix.deinit(alloc_gpa);
for (self._replay_steps.items, 0..) |step, i| if (step == .@"-e") { for (self._replay_steps.items, 0..) |step, i| if (step == .@"-e") {
// We don't need to clone the steps because they should // We don't need to clone the steps because they should
// all be allocated in our arena and we're keeping our // all be allocated in our arena and we're keeping our
// arena. // 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 // Remove our old values. Again, don't need to free any
// memory here because its all part of our arena. // 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 // We must only load a unique file once
if (try loaded.fetchPut(path, {}) != null) { if (try loaded.fetchPut(path, {}) != null) {
const diag: cli.Diagnostic = .{ const diag: cli.Diagnostic = .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"config-file {s}: cycle detected", "config-file {s}: cycle detected",
.{path}, .{path},
0,
), ),
}; };
@ -3759,10 +3762,11 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
var file = std.fs.openFileAbsolute(path, .{}) catch |err| { var file = std.fs.openFileAbsolute(path, .{}) catch |err| {
if (err != error.FileNotFound or !optional) { if (err != error.FileNotFound or !optional) {
const diag: cli.Diagnostic = .{ const diag: cli.Diagnostic = .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"error opening config-file {s}: {}", "error opening config-file {s}: {}",
.{ path, err }, .{ path, err },
0,
), ),
}; };
@ -3778,10 +3782,11 @@ pub fn loadRecursiveFiles(self: *Config, alloc_gpa: Allocator) !void {
.file => {}, .file => {},
else => |kind| { else => |kind| {
const diag: cli.Diagnostic = .{ const diag: cli.Diagnostic = .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"config-file {s}: not reading because file type is {s}", "config-file {s}: not reading because file type is {s}",
.{ path, @tagName(kind) }, .{ 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}); log.info("loading config-file path={s}", .{path});
var buf_reader = std.io.bufferedReader(file.reader()); var buf: [2048]u8 = undefined;
const reader = buf_reader.reader(); var file_reader = file.reader(&buf);
const Iter = cli.args.LineIterator(@TypeOf(reader)); const reader = &file_reader.interface;
var iter: Iter = .{ .r = reader, .filepath = path }; var iter: cli.args.LineIterator = .{ .r = reader, .filepath = path };
try self.loadIter(alloc_gpa, &iter); try self.loadIter(alloc_gpa, &iter);
try self.expandPaths(std.fs.path.dirname(path).?); try self.expandPaths(std.fs.path.dirname(path).?);
} }
@ -3944,10 +3949,10 @@ fn loadTheme(self: *Config, theme: Theme) !void {
errdefer new_config.deinit(); errdefer new_config.deinit();
// Load our theme // Load our theme
var buf_reader = std.io.bufferedReader(file.reader()); var buf: [2048]u8 = undefined;
const reader = buf_reader.reader(); var file_reader = file.reader(&buf);
const Iter = cli.args.LineIterator(@TypeOf(reader)); const reader = &file_reader.interface;
var iter: Iter = .{ .r = reader, .filepath = path }; var iter: cli.args.LineIterator = .{ .r = reader, .filepath = path };
try new_config.loadIter(alloc_gpa, &iter); try new_config.loadIter(alloc_gpa, &iter);
// Setup our replay to be conditional. // 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 (self.@"quit-after-last-window-closed-delay") |duration| {
if (duration.duration < 5 * std.time.ns_per_s) { if (duration.duration < 5 * std.time.ns_per_s) {
log.warn( 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}, .{duration},
); );
} }
@ -4221,22 +4226,23 @@ pub fn parseManuallyHook(
// Build up the command. We don't clean this up because we take // Build up the command. We don't clean this up because we take
// ownership in our allocator. // ownership in our allocator.
var command: std.ArrayList([:0]const u8) = .init(alloc); var command: std.ArrayList([:0]const u8) = .empty;
errdefer command.deinit(); errdefer command.deinit(alloc);
while (iter.next()) |param| { while (iter.next()) |param| {
const copy = try alloc.dupeZ(u8, param); const copy = try alloc.dupeZ(u8, param);
try self._replay_steps.append(alloc, .{ .arg = copy }); try self._replay_steps.append(alloc, .{ .arg = copy });
try command.append(copy); try command.append(alloc, copy);
} }
if (command.items.len == 0) { if (command.items.len == 0) {
try self._diagnostics.append(alloc, .{ try self._diagnostics.append(alloc, .{
.location = try cli.Location.fromIter(iter, alloc), .location = try cli.Location.fromIter(iter, alloc),
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
alloc, alloc,
"missing command after {s}", "missing command after {s}",
.{arg}, .{arg},
0,
), ),
}); });
@ -4371,10 +4377,11 @@ pub fn addDiagnosticFmt(
) Allocator.Error!void { ) Allocator.Error!void {
const alloc = self._arena.?.allocator(); const alloc = self._arena.?.allocator();
try self._diagnostics.append(alloc, .{ try self._diagnostics.append(alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
alloc, alloc,
fmt, fmt,
args, args,
0,
), ),
}); });
} }
@ -4892,7 +4899,7 @@ pub const Color = struct {
} }
/// Used by Formatter /// 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; var buf: [128]u8 = undefined;
try formatter.formatEntry( try formatter.formatEntry(
[]const u8, []const u8,
@ -4959,12 +4966,12 @@ pub const Color = struct {
test "formatConfig" { test "formatConfig" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var color: Color = .{ .r = 10, .g = 11, .b = 12 }; var color: Color = .{ .r = 10, .g = 11, .b = 12 };
try color.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try color.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = #0a0b0c\n", buf.items); try std.testing.expectEqualSlices(u8, "a = #0a0b0c\n", buf.written());
} }
test "parseCLI with whitespace" { test "parseCLI with whitespace" {
@ -4995,7 +5002,7 @@ pub const TerminalColor = union(enum) {
} }
/// Used by Formatter /// Used by Formatter
pub fn formatEntry(self: TerminalColor, formatter: anytype) !void { pub fn formatEntry(self: TerminalColor, formatter: formatterpkg.EntryFormatter) !void {
switch (self) { switch (self) {
.color => try self.color.formatEntry(formatter), .color => try self.color.formatEntry(formatter),
@ -5030,12 +5037,12 @@ pub const TerminalColor = union(enum) {
test "formatConfig" { test "formatConfig" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var sc: TerminalColor = .@"cell-foreground"; var sc: TerminalColor = .@"cell-foreground";
try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try sc.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.items); try testing.expectEqualSlices(u8, "a = cell-foreground\n", buf.written());
} }
}; };
@ -5051,7 +5058,7 @@ pub const BoldColor = union(enum) {
} }
/// Used by Formatter /// Used by Formatter
pub fn formatEntry(self: BoldColor, formatter: anytype) !void { pub fn formatEntry(self: BoldColor, formatter: formatterpkg.EntryFormatter) !void {
switch (self) { switch (self) {
.color => try self.color.formatEntry(formatter), .color => try self.color.formatEntry(formatter),
.bright => try formatter.formatEntry( .bright => try formatter.formatEntry(
@ -5082,12 +5089,12 @@ pub const BoldColor = union(enum) {
test "formatConfig" { test "formatConfig" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var sc: BoldColor = .bright; var sc: BoldColor = .bright;
try sc.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try sc.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try testing.expectEqualSlices(u8, "a = bright\n", buf.items); 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 // Build up the value of our config. Our buffer size should be
// sized to contain all possible maximum values. // sized to contain all possible maximum values.
var buf: [1024]u8 = undefined; var buf: [1024]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
var writer = fbs.writer();
for (self.colors.items, 0..) |color, i| { for (self.colors.items, 0..) |color, i| {
var color_buf: [128]u8 = undefined; var color_buf: [128]u8 = undefined;
const color_str = try color.formatBuf(&color_buf); const color_str = try color.formatBuf(&color_buf);
@ -5185,7 +5191,7 @@ pub const ColorList = struct {
try formatter.formatEntry( try formatter.formatEntry(
[]const u8, []const u8,
fbs.getWritten(), writer.buffered(),
); );
} }
@ -5214,7 +5220,7 @@ pub const ColorList = struct {
test "format" { test "format" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -5223,8 +5229,8 @@ pub const ColorList = struct {
var p: Self = .{}; var p: Self = .{};
try p.parseCLI(alloc, "black,white"); try p.parseCLI(alloc, "black,white");
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = #000000,#ffffff\n", buf.items); try std.testing.expectEqualSlices(u8, "a = #000000,#ffffff\n", buf.written());
} }
}; };
@ -5285,7 +5291,7 @@ pub const Palette = struct {
} }
/// Used by Formatter /// 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; var buf: [128]u8 = undefined;
for (0.., self.value) |k, v| { for (0.., self.value) |k, v| {
try formatter.formatEntry( try formatter.formatEntry(
@ -5340,12 +5346,12 @@ pub const Palette = struct {
test "formatConfig" { test "formatConfig" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var list: Self = .{}; var list: Self = .{};
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = 0=#1d1f21\n", buf.items[0..14]); try std.testing.expectEqualSlices(u8, "a = 0=#1d1f21\n", buf.written()[0..14]);
} }
test "parseCLI with whitespace" { test "parseCLI with whitespace" {
@ -5439,7 +5445,7 @@ pub const RepeatableString = struct {
} }
/// Used by Formatter /// 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 no items, we want to render an empty field.
if (self.list.items.len == 0) { if (self.list.items.len == 0) {
try formatter.formatEntry(void, {}); try formatter.formatEntry(void, {});
@ -5486,17 +5492,17 @@ pub const RepeatableString = struct {
test "formatConfig empty" { test "formatConfig empty" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var list: Self = .{}; var list: Self = .{};
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = \n", buf.items); try std.testing.expectEqualSlices(u8, "a = \n", buf.written());
} }
test "formatConfig single item" { test "formatConfig single item" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -5505,13 +5511,13 @@ pub const RepeatableString = struct {
var list: Self = .{}; var list: Self = .{};
try list.parseCLI(alloc, "A"); try list.parseCLI(alloc, "A");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A\n", buf.items); try std.testing.expectEqualSlices(u8, "a = A\n", buf.written());
} }
test "formatConfig multiple items" { test "formatConfig multiple items" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -5521,8 +5527,8 @@ pub const RepeatableString = struct {
var list: Self = .{}; var list: Self = .{};
try list.parseCLI(alloc, "A"); try list.parseCLI(alloc, "A");
try list.parseCLI(alloc, "B"); try list.parseCLI(alloc, "B");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A\na = B\n", buf.items); try std.testing.expectEqualSlices(u8, "a = A\na = B\n", buf.written());
} }
}; };
@ -5638,7 +5644,7 @@ pub const RepeatableFontVariation = struct {
test "formatConfig single" { test "formatConfig single" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -5647,8 +5653,8 @@ pub const RepeatableFontVariation = struct {
var list: Self = .{}; var list: Self = .{};
try list.parseCLI(alloc, "wght = 200"); try list.parseCLI(alloc, "wght = 200");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = wght=200\n", buf.items); 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. /// 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) { if (self.set.bindings.size == 0) {
try formatter.formatEntry(void, {}); try formatter.formatEntry(void, {});
return; return;
@ -6478,14 +6484,14 @@ pub const Keybinds = struct {
} }
} }
var buffer_stream = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
std.fmt.format(buffer_stream.writer(), "{}", .{k}) catch return error.OutOfMemory; writer.print("{f}", .{k}) catch return error.OutOfMemory;
try v.formatEntries(&buffer_stream, formatter); try v.formatEntries(&writer, formatter);
} }
} }
/// Used by 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); try self.formatEntryDocs(formatter, false);
} }
@ -6502,7 +6508,7 @@ pub const Keybinds = struct {
test "formatConfig single" { test "formatConfig single" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6511,14 +6517,14 @@ pub const Keybinds = struct {
var list: Keybinds = .{}; var list: Keybinds = .{};
try list.parseCLI(alloc, "shift+a=csi:hello"); try list.parseCLI(alloc, "shift+a=csi:hello");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = shift+a=csi:hello\n", buf.items); try std.testing.expectEqualSlices(u8, "a = shift+a=csi:hello\n", buf.written());
} }
// Regression test for https://github.com/ghostty-org/ghostty/issues/2734 // Regression test for https://github.com/ghostty-org/ghostty/issues/2734
test "formatConfig multiple items" { test "formatConfig multiple items" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6528,7 +6534,7 @@ pub const Keybinds = struct {
var list: Keybinds = .{}; var list: Keybinds = .{};
try list.parseCLI(alloc, "ctrl+z>1=goto_tab:1"); try list.parseCLI(alloc, "ctrl+z>1=goto_tab:1");
try list.parseCLI(alloc, "ctrl+z>2=goto_tab:2"); 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 // Note they turn into translated keys because they match
// their ASCII mapping. // their ASCII mapping.
@ -6537,12 +6543,12 @@ pub const Keybinds = struct {
\\keybind = ctrl+z>1=goto_tab:1 \\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" { test "formatConfig multiple items nested" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); 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+b>w=close_window");
try list.parseCLI(alloc, "ctrl+a>ctrl+c>t=new_tab"); try list.parseCLI(alloc, "ctrl+a>ctrl+c>t=new_tab");
try list.parseCLI(alloc, "ctrl+b>ctrl+d>a=previous_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. // NB: This does not currently retain the order of the keybinds.
const want = const want =
@ -6564,7 +6570,7 @@ pub const Keybinds = struct {
\\a = ctrl+b>ctrl+d>a=previous_tab \\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" { test "formatConfig single" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6799,13 +6805,13 @@ pub const RepeatableCodepointMap = struct {
var list: Self = .{}; var list: Self = .{};
try list.parseCLI(alloc, "U+ABCD=Comic Sans"); try list.parseCLI(alloc, "U+ABCD=Comic Sans");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = U+ABCD=Comic Sans\n", buf.items); try std.testing.expectEqualSlices(u8, "a = U+ABCD=Comic Sans\n", buf.written());
} }
test "formatConfig range" { test "formatConfig range" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6814,13 +6820,13 @@ pub const RepeatableCodepointMap = struct {
var list: Self = .{}; var list: Self = .{};
try list.parseCLI(alloc, "U+0001 - U+0005=Verdana"); try list.parseCLI(alloc, "U+0001 - U+0005=Verdana");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = U+0001-U+0005=Verdana\n", buf.items); try std.testing.expectEqualSlices(u8, "a = U+0001-U+0005=Verdana\n", buf.written());
} }
test "formatConfig multiple" { test "formatConfig multiple" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6829,12 +6835,12 @@ pub const RepeatableCodepointMap = struct {
var list: Self = .{}; var list: Self = .{};
try list.parseCLI(alloc, "U+0006-U+0009, U+ABCD=Courier"); 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, try std.testing.expectEqualSlices(u8,
\\a = U+0006-U+0009=Courier \\a = U+0006-U+0009=Courier
\\a = U+ABCD=Courier \\a = U+ABCD=Courier
\\ \\
, buf.items); , buf.written());
} }
}; };
@ -6886,7 +6892,7 @@ pub const FontStyle = union(enum) {
} }
/// Used by Formatter /// Used by Formatter
pub fn formatEntry(self: Self, formatter: anytype) !void { pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void {
switch (self) { switch (self) {
.default, .false => try formatter.formatEntry( .default, .false => try formatter.formatEntry(
[]const u8, []const u8,
@ -6918,7 +6924,7 @@ pub const FontStyle = union(enum) {
test "formatConfig default" { test "formatConfig default" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6927,13 +6933,13 @@ pub const FontStyle = union(enum) {
var p: Self = .{ .default = {} }; var p: Self = .{ .default = {} };
try p.parseCLI(alloc, "default"); try p.parseCLI(alloc, "default");
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = default\n", buf.items); try std.testing.expectEqualSlices(u8, "a = default\n", buf.written());
} }
test "formatConfig false" { test "formatConfig false" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6942,13 +6948,13 @@ pub const FontStyle = union(enum) {
var p: Self = .{ .default = {} }; var p: Self = .{ .default = {} };
try p.parseCLI(alloc, "false"); try p.parseCLI(alloc, "false");
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = false\n", buf.items); try std.testing.expectEqualSlices(u8, "a = false\n", buf.written());
} }
test "formatConfig named" { test "formatConfig named" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -6957,8 +6963,8 @@ pub const FontStyle = union(enum) {
var p: Self = .{ .default = {} }; var p: Self = .{ .default = {} };
try p.parseCLI(alloc, "bold"); try p.parseCLI(alloc, "bold");
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try p.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = bold\n", buf.items); try std.testing.expectEqualSlices(u8, "a = bold\n", buf.written());
} }
}; };
@ -7018,7 +7024,7 @@ pub const RepeatableLink = struct {
} }
/// Used by Formatter /// 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. // This currently can't be set so we don't format anything.
_ = self; _ = self;
_ = formatter; _ = formatter;
@ -7128,7 +7134,10 @@ pub const RepeatableCommand = struct {
} }
/// Used by Formatter /// 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) { if (self.value.items.len == 0) {
try formatter.formatEntry(void, {}); try formatter.formatEntry(void, {});
return; return;
@ -7136,22 +7145,23 @@ pub const RepeatableCommand = struct {
for (self.value.items) |item| { for (self.value.items) |item| {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
var writer = fbs.writer();
writer.writeAll("title:\"") catch return error.OutOfMemory; writer.print(
std.zig.stringEscape(item.title, "", .{}, writer) catch return error.OutOfMemory; "title:\"{f}\"",
writer.writeAll("\"") catch return error.OutOfMemory; .{std.zig.fmtString(item.title)},
) catch return error.OutOfMemory;
if (item.description.len > 0) { if (item.description.len > 0) {
writer.writeAll(",description:\"") catch return error.OutOfMemory; writer.print(
std.zig.stringEscape(item.description, "", .{}, writer) catch return error.OutOfMemory; ",description:\"{f}\"",
writer.writeAll("\"") catch return error.OutOfMemory; .{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" { test "RepeatableCommand formatConfig empty" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var list: RepeatableCommand = .{}; var list: RepeatableCommand = .{};
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = \n", buf.items); try std.testing.expectEqualSlices(u8, "a = \n", buf.written());
} }
test "RepeatableCommand formatConfig single item" { test "RepeatableCommand formatConfig single item" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -7216,13 +7226,13 @@ pub const RepeatableCommand = struct {
var list: RepeatableCommand = .{}; var list: RepeatableCommand = .{};
try list.parseCLI(alloc, "title:Bobr, action:text:Bober"); try list.parseCLI(alloc, "title:Bobr, action:text:Bober");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:Bober\"\n", buf.items); try std.testing.expectEqualSlices(u8, "a = title:\"Bobr\",action:\"text:Bober\"\n", buf.written());
} }
test "RepeatableCommand formatConfig multiple items" { test "RepeatableCommand formatConfig multiple items" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -7232,14 +7242,12 @@ pub const RepeatableCommand = struct {
var list: RepeatableCommand = .{}; var list: RepeatableCommand = .{};
try list.parseCLI(alloc, "title:Bobr, action:text:kurwa"); try list.parseCLI(alloc, "title:Bobr, action:text:kurwa");
try list.parseCLI(alloc, "title:Ja, description: pierdole, action:text:jakie bydle"); try list.parseCLI(alloc, "title:Ja, description: pierdole, action:text:jakie bydle");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); 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 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" { test "RepeatableCommand parseCLI commas" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator);
defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit(); defer arena.deinit();
@ -7455,14 +7463,14 @@ pub const MouseScrollMultiplier = struct {
} }
/// Used by Formatter /// Used by Formatter
pub fn formatEntry(self: Self, formatter: anytype) !void { pub fn formatEntry(self: Self, formatter: formatterpkg.EntryFormatter) !void {
var buf: [32]u8 = undefined; var buf: [4096]u8 = undefined;
const formatted = std.fmt.bufPrint( var writer: std.Io.Writer = .fixed(&buf);
&buf, writer.print(
"precision:{d},discrete:{d}", "precision:{d},discrete:{d}",
.{ self.precision, self.discrete }, .{ self.precision, self.discrete },
) catch return error.OutOfMemory; ) catch return error.OutOfMemory;
try formatter.formatEntry([]const u8, formatted); try formatter.formatEntry([]const u8, writer.buffered());
} }
test "parse" { test "parse" {
@ -7505,12 +7513,12 @@ pub const MouseScrollMultiplier = struct {
test "format entry MouseScrollMultiplier" { test "format entry MouseScrollMultiplier" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var args: Self = .{ .precision = 1.5, .discrete = 2.5 }; var args: Self = .{ .precision = 1.5, .discrete = 2.5 };
try args.formatEntry(formatterpkg.entryFormatter("mouse-scroll-multiplier", buf.writer())); 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 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; return error.MissingUnit;
} }
fn format(self: Size, writer: anytype) !void { fn format(self: Size, writer: *std.Io.Writer) !void {
switch (self) { switch (self) {
.percentage => |v| try writer.print("{d}%", .{v}), .percentage => |v| try writer.print("{d}%", .{v}),
.pixels => |v| try writer.print("{}px", .{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; const primary = self.primary orelse return;
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
const writer = fbs.writer();
primary.format(writer) catch return error.OutOfMemory; primary.format(&writer) catch return error.OutOfMemory;
if (self.secondary) |secondary| { if (self.secondary) |secondary| {
writer.writeByte(',') catch return error.OutOfMemory; 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" { test "parse QuickTerminalSize" {
@ -8318,15 +8325,17 @@ pub const Duration = struct {
return if (value) |v| .{ .duration = v } else error.ValueRequired; 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 buf: [64]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
const writer = fbs.writer(); try self.format(&writer);
try self.format("", .{}, writer); try formatter.formatEntry([]const u8, writer.buffered());
try formatter.formatEntry([]const u8, fbs.getWritten());
} }
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 value = self.duration;
var i: usize = 0; var i: usize = 0;
for (units) |unit| { 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; var buf: [128]u8 = undefined;
if (self.top_left == self.bottom_right) { if (self.top_left == self.bottom_right) {
try formatter.formatEntry( try formatter.formatEntry(
@ -8555,7 +8564,7 @@ test "test format" {
inline for (Duration.units) |unit| { inline for (Duration.units) |unit| {
const d: Duration = .{ .duration = unit.factor }; const d: Duration = .{ .duration = unit.factor };
var actual_buf: [16]u8 = undefined; 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; var expected_buf: [16]u8 = undefined;
const expected = if (!std.mem.eql(u8, unit.name, "us")) const expected = if (!std.mem.eql(u8, unit.name, "us"))
try std.fmt.bufPrint(&expected_buf, "1{s}", .{unit.name}) try std.fmt.bufPrint(&expected_buf, "1{s}", .{unit.name})
@ -8566,12 +8575,12 @@ test "test format" {
} }
test "test entryFormatter" { 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(); defer buf.deinit();
var p: Duration = .{ .duration = std.math.maxInt(u64) }; var p: Duration = .{ .duration = std.math.maxInt(u64) };
try p.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); 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 std.testing.expectEqualStrings("a = 584y 49w 23h 34m 33s 709ms 551µs 615ns\n", buf.written());
} }
const TestIterator = struct { const TestIterator = struct {
@ -8681,15 +8690,20 @@ test "clone can then change conditional state" {
// Setup our test theme // Setup our test theme
var td = try internal_os.TempDir.init(); var td = try internal_os.TempDir.init();
defer td.deinit(); defer td.deinit();
var buf: [4096]u8 = undefined;
{ {
var file = try td.dir.createFile("theme_light", .{}); var file = try td.dir.createFile("theme_light", .{});
defer file.close(); 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", .{}); var file = try td.dir.createFile("theme_dark", .{});
defer file.close(); 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; var light_buf: [std.fs.max_path_bytes]u8 = undefined;
const light = try td.dir.realpath("theme_light", &light_buf); const light = try td.dir.realpath("theme_light", &light_buf);
@ -8815,10 +8829,13 @@ test "theme loading" {
// Setup our test theme // Setup our test theme
var td = try internal_os.TempDir.init(); var td = try internal_os.TempDir.init();
defer td.deinit(); defer td.deinit();
var buf: [4096]u8 = undefined;
{ {
var file = try td.dir.createFile("theme", .{}); var file = try td.dir.createFile("theme", .{});
defer file.close(); 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; var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try td.dir.realpath("theme", &path_buf); const path = try td.dir.realpath("theme", &path_buf);
@ -8851,10 +8868,13 @@ test "theme loading preserves conditional state" {
// Setup our test theme // Setup our test theme
var td = try internal_os.TempDir.init(); var td = try internal_os.TempDir.init();
defer td.deinit(); defer td.deinit();
var buf: [4096]u8 = undefined;
{ {
var file = try td.dir.createFile("theme", .{}); var file = try td.dir.createFile("theme", .{});
defer file.close(); 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; var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try td.dir.realpath("theme", &path_buf); const path = try td.dir.realpath("theme", &path_buf);
@ -8881,10 +8901,13 @@ test "theme priority is lower than config" {
// Setup our test theme // Setup our test theme
var td = try internal_os.TempDir.init(); var td = try internal_os.TempDir.init();
defer td.deinit(); defer td.deinit();
var buf: [4096]u8 = undefined;
{ {
var file = try td.dir.createFile("theme", .{}); var file = try td.dir.createFile("theme", .{});
defer file.close(); 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; var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const path = try td.dir.realpath("theme", &path_buf); const path = try td.dir.realpath("theme", &path_buf);
@ -8915,15 +8938,20 @@ test "theme loading correct light/dark" {
// Setup our test theme // Setup our test theme
var td = try internal_os.TempDir.init(); var td = try internal_os.TempDir.init();
defer td.deinit(); defer td.deinit();
var buf: [4096]u8 = undefined;
{ {
var file = try td.dir.createFile("theme_light", .{}); var file = try td.dir.createFile("theme_light", .{});
defer file.close(); 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", .{}); var file = try td.dir.createFile("theme_dark", .{});
defer file.close(); 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; var light_buf: [std.fs.max_path_bytes]u8 = undefined;
const light = try td.dir.realpath("theme_light", &light_buf); const light = try td.dir.realpath("theme_light", &light_buf);

View File

@ -104,7 +104,7 @@ pub fn equal(self: RepeatableStringMap, other: RepeatableStringMap) bool {
} }
/// Used by formatter /// 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 no items, we want to render an empty field.
if (self.map.count() == 0) { if (self.map.count() == 0) {
try formatter.formatEntry(void, {}); try formatter.formatEntry(void, {});
@ -146,12 +146,12 @@ test "RepeatableStringMap: parseCLI" {
test "RepeatableStringMap: formatConfig empty" { test "RepeatableStringMap: formatConfig empty" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var list: RepeatableStringMap = .{}; var list: RepeatableStringMap = .{};
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = \n", buf.items); try std.testing.expectEqualSlices(u8, "a = \n", buf.written());
} }
test "RepeatableStringMap: formatConfig single item" { test "RepeatableStringMap: formatConfig single item" {
@ -162,20 +162,20 @@ test "RepeatableStringMap: formatConfig single item" {
const alloc = arena.allocator(); const alloc = arena.allocator();
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var map: RepeatableStringMap = .{}; var map: RepeatableStringMap = .{};
try map.parseCLI(alloc, "A=B"); try map.parseCLI(alloc, "A=B");
try map.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try map.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.items); 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(); defer buf.deinit();
var map: RepeatableStringMap = .{}; var map: RepeatableStringMap = .{};
try map.parseCLI(alloc, " A = B "); try map.parseCLI(alloc, " A = B ");
try map.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try map.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.items); try std.testing.expectEqualSlices(u8, "a = A=B\n", buf.written());
} }
} }
@ -187,12 +187,12 @@ test "RepeatableStringMap: formatConfig multiple items" {
const alloc = arena.allocator(); const alloc = arena.allocator();
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var list: RepeatableStringMap = .{}; var list: RepeatableStringMap = .{};
try list.parseCLI(alloc, "A=B"); try list.parseCLI(alloc, "A=B");
try list.parseCLI(alloc, "B = C"); try list.parseCLI(alloc, "B = C");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A=B\na = B=C\n", buf.items); try std.testing.expectEqualSlices(u8, "a = A=B\na = B=C\n", buf.written());
} }
} }

View File

@ -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) { switch (self) {
.shell => |v| try formatter.formatEntry([]const u8, v), .shell => |v| try formatter.formatEntry([]const u8, v),
.direct => |v| { .direct => |v| {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
const writer = fbs.writer();
writer.writeAll("direct:") catch return error.OutOfMemory; writer.writeAll("direct:") catch return error.OutOfMemory;
for (v) |arg| { for (v) |arg| {
writer.writeAll(arg) catch return error.OutOfMemory; writer.writeAll(arg) catch return error.OutOfMemory;
writer.writeByte(' ') catch return error.OutOfMemory; writer.writeByte(' ') catch return error.OutOfMemory;
} }
const written = fbs.getWritten(); const written = writer.buffered();
try formatter.formatEntry( try formatter.formatEntry(
[]const u8, []const u8,
written[0..@intCast(written.len - 1)], written[0..@intCast(written.len - 1)],
@ -292,13 +291,13 @@ pub const Command = union(enum) {
defer arena.deinit(); defer arena.deinit();
const alloc = arena.allocator(); const alloc = arena.allocator();
var buf = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(); defer buf.deinit();
var v: Self = undefined; var v: Self = undefined;
try v.parseCLI(alloc, "echo hello"); try v.parseCLI(alloc, "echo hello");
try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try v.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = echo hello\n", buf.items); try std.testing.expectEqualSlices(u8, "a = echo hello\n", buf.written());
} }
test "Command: formatConfig direct" { test "Command: formatConfig direct" {
@ -307,13 +306,13 @@ pub const Command = union(enum) {
defer arena.deinit(); defer arena.deinit();
const alloc = arena.allocator(); const alloc = arena.allocator();
var buf = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(); defer buf.deinit();
var v: Self = undefined; var v: Self = undefined;
try v.parseCLI(alloc, "direct: echo hello"); try v.parseCLI(alloc, "direct: echo hello");
try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try v.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = direct:echo hello\n", buf.items); try std.testing.expectEqualSlices(u8, "a = direct:echo hello\n", buf.written());
} }
}; };

View File

@ -89,8 +89,8 @@ fn configPath(alloc_arena: Allocator) ![]const u8 {
/// Returns a const list of possible paths the main config file could be /// Returns a const list of possible paths the main config file could be
/// in for the current OS. /// in for the current OS.
fn configPathCandidates(alloc_arena: Allocator) ![]const []const u8 { fn configPathCandidates(alloc_arena: Allocator) ![]const []const u8 {
var paths = try std.ArrayList([]const u8).initCapacity(alloc_arena, 2); var paths: std.ArrayList([]const u8) = try .initCapacity(alloc_arena, 2);
errdefer paths.deinit(); errdefer paths.deinit(alloc_arena);
if (comptime builtin.os.tag == .macos) { if (comptime builtin.os.tag == .macos) {
paths.appendAssumeCapacity(try internal_os.macos.appSupportDir( paths.appendAssumeCapacity(try internal_os.macos.appSupportDir(

View File

@ -8,38 +8,36 @@ const Key = @import("key.zig").Key;
/// Returns a single entry formatter for the given field name and writer. /// Returns a single entry formatter for the given field name and writer.
pub fn entryFormatter( pub fn entryFormatter(
name: []const u8, name: []const u8,
writer: anytype, writer: *std.Io.Writer,
) EntryFormatter(@TypeOf(writer)) { ) EntryFormatter {
return .{ .name = name, .writer = writer }; return .{ .name = name, .writer = writer };
} }
/// The entry formatter type for a given writer. /// The entry formatter type for a given writer.
pub fn EntryFormatter(comptime WriterType: type) type { pub const EntryFormatter = struct {
return struct { name: []const u8,
name: []const u8, writer: *std.Io.Writer,
writer: WriterType,
pub fn formatEntry( pub fn formatEntry(
self: @This(), self: @This(),
comptime T: type, comptime T: type,
value: T, value: T,
) !void { ) !void {
return formatter.formatEntry( return formatter.formatEntry(
T, T,
self.name, self.name,
value, value,
self.writer, self.writer,
); );
} }
}; };
}
/// Format a single type with the given name and value. /// Format a single type with the given name and value.
pub fn formatEntry( pub fn formatEntry(
comptime T: type, comptime T: type,
name: []const u8, name: []const u8,
value: T, value: T,
writer: anytype, writer: *std.Io.Writer,
) !void { ) !void {
switch (@typeInfo(T)) { switch (@typeInfo(T)) {
.bool, .int => { .bool, .int => {
@ -53,7 +51,7 @@ pub fn formatEntry(
}, },
.@"enum" => { .@"enum" => {
try writer.print("{s} = {s}\n", .{ name, @tagName(value) }); try writer.print("{s} = {t}\n", .{ name, value });
return; return;
}, },
@ -143,19 +141,14 @@ pub const FileFormatter = struct {
/// Implements std.fmt so it can be used directly with std.fmt. /// Implements std.fmt so it can be used directly with std.fmt.
pub fn format( pub fn format(
self: FileFormatter, self: FileFormatter,
comptime layout: []const u8, writer: *std.Io.Writer,
opts: std.fmt.FormatOptions, ) std.Io.Writer.Error!void {
writer: anytype,
) !void {
@setEvalBranchQuota(10_000); @setEvalBranchQuota(10_000);
_ = layout;
_ = opts;
// If we're change-tracking then we need the default config to // If we're change-tracking then we need the default config to
// compare against. // compare against.
var default: ?Config = if (self.changed) var default: ?Config = if (self.changed)
try .default(self.alloc) Config.default(self.alloc) catch return error.WriteFailed
else else
null; null;
defer if (default) |*v| v.deinit(); defer if (default) |*v| v.deinit();
@ -179,12 +172,12 @@ pub const FileFormatter = struct {
} }
} }
try formatEntry( formatEntry(
field.type, field.type,
field.name, field.name,
value, value,
writer, writer,
); ) catch return error.WriteFailed;
if (do_docs) try writer.print("\n", .{}); if (do_docs) try writer.print("\n", .{});
} }
@ -198,7 +191,7 @@ test "format default config" {
var cfg = try Config.default(alloc); var cfg = try Config.default(alloc);
defer cfg.deinit(); defer cfg.deinit();
var buf = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(); defer buf.deinit();
// We just make sure this works without errors. We aren't asserting output. // We just make sure this works without errors. We aren't asserting output.
@ -206,9 +199,9 @@ test "format default config" {
.alloc = alloc, .alloc = alloc,
.config = &cfg, .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" { test "format default config changed" {
@ -218,7 +211,7 @@ test "format default config changed" {
defer cfg.deinit(); defer cfg.deinit();
cfg.@"font-size" = 42; cfg.@"font-size" = 42;
var buf = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(); defer buf.deinit();
// We just make sure this works without errors. We aren't asserting output. // We just make sure this works without errors. We aren't asserting output.
@ -227,26 +220,26 @@ test "format default config changed" {
.config = &cfg, .config = &cfg,
.changed = true, .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" { test "formatEntry bool" {
const testing = std.testing; const testing = std.testing;
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
try formatEntry(bool, "a", true, buf.writer()); try formatEntry(bool, "a", true, &buf.writer);
try testing.expectEqualStrings("a = true\n", buf.items); 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(); defer buf.deinit();
try formatEntry(bool, "a", false, buf.writer()); try formatEntry(bool, "a", false, &buf.writer);
try testing.expectEqualStrings("a = false\n", buf.items); try testing.expectEqualStrings("a = false\n", buf.written());
} }
} }
@ -254,10 +247,10 @@ test "formatEntry int" {
const testing = std.testing; const testing = std.testing;
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
try formatEntry(u8, "a", 123, buf.writer()); try formatEntry(u8, "a", 123, &buf.writer);
try testing.expectEqualStrings("a = 123\n", buf.items); try testing.expectEqualStrings("a = 123\n", buf.written());
} }
} }
@ -265,10 +258,10 @@ test "formatEntry float" {
const testing = std.testing; const testing = std.testing;
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
try formatEntry(f64, "a", 0.7, buf.writer()); try formatEntry(f64, "a", 0.7, &buf.writer);
try testing.expectEqualStrings("a = 0.7\n", buf.items); try testing.expectEqualStrings("a = 0.7\n", buf.written());
} }
} }
@ -277,10 +270,10 @@ test "formatEntry enum" {
const Enum = enum { one, two, three }; 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(); defer buf.deinit();
try formatEntry(Enum, "a", .two, buf.writer()); try formatEntry(Enum, "a", .two, &buf.writer);
try testing.expectEqualStrings("a = two\n", buf.items); try testing.expectEqualStrings("a = two\n", buf.written());
} }
} }
@ -288,10 +281,10 @@ test "formatEntry void" {
const testing = std.testing; const testing = std.testing;
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
try formatEntry(void, "a", {}, buf.writer()); try formatEntry(void, "a", {}, &buf.writer);
try testing.expectEqualStrings("a = \n", buf.items); try testing.expectEqualStrings("a = \n", buf.written());
} }
} }
@ -299,17 +292,17 @@ test "formatEntry optional" {
const testing = std.testing; const testing = std.testing;
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
try formatEntry(?bool, "a", null, buf.writer()); try formatEntry(?bool, "a", null, &buf.writer);
try testing.expectEqualStrings("a = \n", buf.items); 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(); defer buf.deinit();
try formatEntry(?bool, "a", false, buf.writer()); try formatEntry(?bool, "a", false, &buf.writer);
try testing.expectEqualStrings("a = false\n", buf.items); try testing.expectEqualStrings("a = false\n", buf.written());
} }
} }
@ -317,10 +310,10 @@ test "formatEntry string" {
const testing = std.testing; const testing = std.testing;
{ {
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
try formatEntry([]const u8, "a", "hello", buf.writer()); try formatEntry([]const u8, "a", "hello", &buf.writer);
try testing.expectEqualStrings("a = hello\n", buf.items); 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(); defer buf.deinit();
try formatEntry(Value, "a", .{}, buf.writer()); try formatEntry(Value, "a", .{}, &buf.writer);
try testing.expectEqualStrings("a = one,no-two\n", buf.items); try testing.expectEqualStrings("a = one,no-two\n", buf.written());
} }
} }

View File

@ -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 buf: [4096]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
const writer = fbs.writer();
switch (self) { switch (self) {
inline else => |v, tag| { inline else => |v, tag| {
writer.writeAll(@tagName(tag)) catch return error.OutOfMemory; writer.writeAll(@tagName(tag)) catch return error.OutOfMemory;
@ -106,10 +105,9 @@ pub const ReadableIO = union(enum) {
}, },
} }
const written = fbs.getWritten();
try formatter.formatEntry( try formatter.formatEntry(
[]const u8, []const u8,
written, writer.buffered(),
); );
} }
@ -144,13 +142,13 @@ pub const ReadableIO = union(enum) {
defer arena.deinit(); defer arena.deinit();
const alloc = arena.allocator(); const alloc = arena.allocator();
var buf = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(); defer buf.deinit();
var v: Self = undefined; var v: Self = undefined;
try v.parseCLI(alloc, "raw:foo"); try v.parseCLI(alloc, "raw:foo");
try v.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try v.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = raw:foo\n", buf.items); try std.testing.expectEqualSlices(u8, "a = raw:foo\n", buf.written());
} }
}; };
@ -222,7 +220,7 @@ pub const RepeatableReadableIO = struct {
/// Used by Formatter /// Used by Formatter
pub fn formatEntry( pub fn formatEntry(
self: Self, self: Self,
formatter: anytype, formatter: formatterpkg.EntryFormatter,
) !void { ) !void {
if (self.list.items.len == 0) { if (self.list.items.len == 0) {
try formatter.formatEntry(void, {}); try formatter.formatEntry(void, {});

View File

@ -79,7 +79,7 @@ pub const Path = union(enum) {
} }
/// Used by formatter. /// 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; var buf: [std.fs.max_path_bytes + 1]u8 = undefined;
const value = switch (self.*) { const value = switch (self.*) {
.optional => |path| std.fmt.bufPrint( .optional => |path| std.fmt.bufPrint(
@ -154,10 +154,11 @@ pub const Path = union(enum) {
&buf, &buf,
) catch |err| { ) catch |err| {
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"error expanding home directory for path {s}: {}", "error expanding home directory for path {s}: {}",
.{ path, err }, .{ path, err },
0,
), ),
}); });
@ -194,10 +195,11 @@ pub const Path = union(enum) {
} }
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"error resolving file path {s}: {}", "error resolving file path {s}: {}",
.{ path, err }, .{ path, err },
0,
), ),
}); });
@ -306,7 +308,7 @@ pub const Path = union(enum) {
test "formatConfig single item" { test "formatConfig single item" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -315,13 +317,13 @@ pub const Path = union(enum) {
var item: Path = undefined; var item: Path = undefined;
try item.parseCLI(alloc, "A"); try item.parseCLI(alloc, "A");
try item.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try item.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A\n", buf.items); try std.testing.expectEqualSlices(u8, "a = A\n", buf.written());
} }
test "formatConfig multiple items" { test "formatConfig multiple items" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -331,8 +333,8 @@ pub const Path = union(enum) {
var item: Path = undefined; var item: Path = undefined;
try item.parseCLI(alloc, "A"); try item.parseCLI(alloc, "A");
try item.parseCLI(alloc, "?B"); try item.parseCLI(alloc, "?B");
try item.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try item.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = ?B\n", buf.items); try std.testing.expectEqualSlices(u8, "a = ?B\n", buf.written());
} }
}; };
@ -382,7 +384,7 @@ pub const RepeatablePath = struct {
} }
/// Used by Formatter /// 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) { if (self.value.items.len == 0) {
try formatter.formatEntry(void, {}); try formatter.formatEntry(void, {});
return; return;
@ -453,17 +455,17 @@ pub const RepeatablePath = struct {
test "formatConfig empty" { test "formatConfig empty" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var list: RepeatablePath = .{}; var list: RepeatablePath = .{};
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = \n", buf.items); try std.testing.expectEqualSlices(u8, "a = \n", buf.written());
} }
test "formatConfig single item" { test "formatConfig single item" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -472,13 +474,13 @@ pub const RepeatablePath = struct {
var list: RepeatablePath = .{}; var list: RepeatablePath = .{};
try list.parseCLI(alloc, "A"); try list.parseCLI(alloc, "A");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A\n", buf.items); try std.testing.expectEqualSlices(u8, "a = A\n", buf.written());
} }
test "formatConfig multiple items" { test "formatConfig multiple items" {
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
var arena = ArenaAllocator.init(testing.allocator); var arena = ArenaAllocator.init(testing.allocator);
@ -488,7 +490,7 @@ pub const RepeatablePath = struct {
var list: RepeatablePath = .{}; var list: RepeatablePath = .{};
try list.parseCLI(alloc, "A"); try list.parseCLI(alloc, "A");
try list.parseCLI(alloc, "?B"); try list.parseCLI(alloc, "?B");
try list.formatEntry(formatterpkg.entryFormatter("a", buf.writer())); try list.formatEntry(formatterpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = A\na = ?B\n", buf.items); try std.testing.expectEqualSlices(u8, "a = A\na = ?B\n", buf.written());
} }
}; };

View File

@ -125,10 +125,11 @@ pub fn open(
) orelse return null; ) orelse return null;
const stat = file.stat() catch |err| { const stat = file.stat() catch |err| {
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"not reading theme from \"{s}\": {}", "not reading theme from \"{s}\": {}",
.{ theme, err }, .{ theme, err },
0,
), ),
}); });
return null; return null;
@ -137,10 +138,11 @@ pub fn open(
.file => {}, .file => {},
else => { else => {
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"not reading theme from \"{s}\": it is a {s}", "not reading theme from \"{s}\": it is a {s}",
.{ theme, @tagName(stat.kind) }, .{ theme, @tagName(stat.kind) },
0,
), ),
}); });
return null; return null;
@ -152,10 +154,11 @@ pub fn open(
const basename = std.fs.path.basename(theme); const basename = std.fs.path.basename(theme);
if (!std.mem.eql(u8, theme, basename)) { if (!std.mem.eql(u8, theme, basename)) {
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"theme \"{s}\" cannot include path separators unless it is an absolute path", "theme \"{s}\" cannot include path separators unless it is an absolute path",
.{theme}, .{theme},
0,
), ),
}); });
return null; return null;
@ -170,10 +173,11 @@ pub fn open(
if (cwd.openFile(path, .{})) |file| { if (cwd.openFile(path, .{})) |file| {
const stat = file.stat() catch |err| { const stat = file.stat() catch |err| {
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"not reading theme from \"{s}\": {}", "not reading theme from \"{s}\": {}",
.{ theme, err }, .{ theme, err },
0,
), ),
}); });
return null; return null;
@ -182,10 +186,11 @@ pub fn open(
.file => {}, .file => {},
else => { else => {
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"not reading theme from \"{s}\": it is a {s}", "not reading theme from \"{s}\": it is a {s}",
.{ theme, @tagName(stat.kind) }, .{ theme, @tagName(stat.kind) },
0,
), ),
}); });
return null; return null;
@ -202,10 +207,11 @@ pub fn open(
// Anything else is an error we log and give up on. // Anything else is an error we log and give up on.
else => { else => {
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"failed to load theme \"{s}\" from the file \"{s}\": {}", "failed to load theme \"{s}\" from the file \"{s}\": {}",
.{ theme, path, err }, .{ theme, path, err },
0,
), ),
}); });
@ -222,10 +228,11 @@ pub fn open(
while (try it.next()) |loc| { while (try it.next()) |loc| {
const path = try std.fs.path.join(arena_alloc, &.{ loc.dir, theme }); const path = try std.fs.path.join(arena_alloc, &.{ loc.dir, theme });
try diags.append(arena_alloc, .{ try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"theme \"{s}\" not found, tried path \"{s}\"", "theme \"{s}\" not found, tried path \"{s}\"",
.{ theme, path }, .{ theme, path },
0,
), ),
}); });
} }
@ -249,17 +256,19 @@ pub fn openAbsolute(
return std.fs.openFileAbsolute(theme, .{}) catch |err| { return std.fs.openFileAbsolute(theme, .{}) catch |err| {
switch (err) { switch (err) {
error.FileNotFound => try diags.append(arena_alloc, .{ error.FileNotFound => try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"failed to load theme from the path \"{s}\"", "failed to load theme from the path \"{s}\"",
.{theme}, .{theme},
0,
), ),
}), }),
else => try diags.append(arena_alloc, .{ else => try diags.append(arena_alloc, .{
.message = try std.fmt.allocPrintZ( .message = try std.fmt.allocPrintSentinel(
arena_alloc, arena_alloc,
"failed to load theme from the path \"{s}\": {}", "failed to load theme from the path \"{s}\": {}",
.{ theme, err }, .{ theme, err },
0,
), ),
}), }),
} }

View File

@ -26,7 +26,7 @@ pub const Envelope = struct {
headers: std.json.ObjectMap, headers: std.json.ObjectMap,
/// The items in the envelope in the order they're encoded. /// 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. /// Parse an envelope from a reader.
/// ///
@ -37,7 +37,7 @@ pub const Envelope = struct {
/// parsing in our use case is not a hot path. /// parsing in our use case is not a hot path.
pub fn parse( pub fn parse(
alloc_gpa: Allocator, alloc_gpa: Allocator,
reader: anytype, reader: *std.Io.Reader,
) !Envelope { ) !Envelope {
// We use an arena allocator to read from reader. We pair this // We use an arena allocator to read from reader. We pair this
// with `alloc_if_needed` when parsing json to allow the json // with `alloc_if_needed` when parsing json to allow the json
@ -62,23 +62,24 @@ pub const Envelope = struct {
fn parseHeader( fn parseHeader(
alloc: Allocator, alloc: Allocator,
reader: anytype, reader: *std.Io.Reader,
) !std.json.ObjectMap { ) !std.json.ObjectMap {
var buf: std.ArrayListUnmanaged(u8) = .{}; var buf: std.Io.Writer.Allocating = .init(alloc);
reader.streamUntilDelimiter( _ = try reader.streamDelimiterLimit(
buf.writer(alloc), &buf.writer,
'\n', '\n',
1024 * 1024, // 1MB, arbitrary choice .limited(1024 * 1024), // 1MB, arbitrary choice
) catch |err| switch (err) { );
// Envelope can be header-only. _ = reader.discardDelimiterInclusive('\n') catch |err| switch (err) {
// It's okay if there isn't a trailing newline
error.EndOfStream => {}, error.EndOfStream => {},
else => |v| return v, else => return err,
}; };
const value = try std.json.parseFromSliceLeaky( const value = try std.json.parseFromSliceLeaky(
std.json.Value, std.json.Value,
alloc, alloc,
buf.items, buf.written(),
.{ .allocate = .alloc_if_needed }, .{ .allocate = .alloc_if_needed },
); );
@ -90,9 +91,9 @@ pub const Envelope = struct {
fn parseItems( fn parseItems(
alloc: Allocator, alloc: Allocator,
reader: anytype, reader: *std.Io.Reader,
) !std.ArrayListUnmanaged(Item) { ) !std.ArrayList(Item) {
var items: std.ArrayListUnmanaged(Item) = .{}; var items: std.ArrayList(Item) = .{};
errdefer items.deinit(alloc); errdefer items.deinit(alloc);
while (try parseOneItem(alloc, reader)) |item| { while (try parseOneItem(alloc, reader)) |item| {
try items.append(alloc, item); try items.append(alloc, item);
@ -103,22 +104,27 @@ pub const Envelope = struct {
fn parseOneItem( fn parseOneItem(
alloc: Allocator, alloc: Allocator,
reader: anytype, reader: *std.Io.Reader,
) !?Item { ) !?Item {
// Get the next item which must start with a header. // Get the next item which must start with a header.
var buf: std.ArrayListUnmanaged(u8) = .{}; var buf: std.Io.Writer.Allocating = .init(alloc);
reader.streamUntilDelimiter( _ = reader.streamDelimiterLimit(
buf.writer(alloc), &buf.writer,
'\n', '\n',
1024 * 1024, // 1MB, arbitrary choice .limited(1024 * 1024), // 1MB, arbitrary choice
) catch |err| switch (err) { ) catch |err| switch (err) {
error.EndOfStream => return null, error.StreamTooLong => return null,
else => |v| return v, 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 // Parse the header JSON
const headers: std.json.ObjectMap = headers: { 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; if (line.len == 0) return null;
const value = try std.json.parseFromSliceLeaky( const value = try std.json.parseFromSliceLeaky(
@ -156,18 +162,16 @@ pub const Envelope = struct {
// Get the payload // Get the payload
const payload: []const u8 = if (len_) |len| payload: { const payload: []const u8 = if (len_) |len| payload: {
// The payload length is specified so read the exact length. // 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(); defer payload.deinit();
for (0..len) |_| {
const byte = reader.readByte() catch |err| switch (err) { reader.streamExact(&payload.writer, len) catch |err| switch (err) {
error.EndOfStream => return error.EnvelopeItemPayloadTooShort, error.EndOfStream => return error.EnvelopeItemPayloadTooShort,
else => return err, else => return err,
}; };
try payload.append(byte);
}
// The next byte must be a newline. // The next byte must be a newline.
if (reader.readByte()) |byte| { if (reader.takeByte()) |byte| {
if (byte != '\n') return error.EnvelopeItemPayloadNoNewline; if (byte != '\n') return error.EnvelopeItemPayloadNoNewline;
} else |err| switch (err) { } else |err| switch (err) {
error.EndOfStream => {}, error.EndOfStream => {},
@ -177,16 +181,20 @@ pub const Envelope = struct {
break :payload try payload.toOwnedSlice(); break :payload try payload.toOwnedSlice();
} else payload: { } else payload: {
// The payload is the next line ending in `\n`. It is required. // The payload is the next line ending in `\n`. It is required.
var payload = std.ArrayList(u8).init(alloc); var payload: std.Io.Writer.Allocating = .init(alloc);
defer payload.deinit(); _ = reader.streamDelimiterLimit(
reader.streamUntilDelimiter( &payload.writer,
payload.writer(),
'\n', '\n',
1024 * 1024 * 50, // 50MB, arbitrary choice .limited(1024 * 1024), // 50MB, arbitrary choice
) catch |err| switch (err) { ) catch |err| switch (err) {
error.EndOfStream => return error.EnvelopeItemPayloadTooShort, error.StreamTooLong => return error.EnvelopeItemPayloadTooShort,
else => |v| return v, 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(); break :payload try payload.toOwnedSlice();
}; };
@ -212,15 +220,13 @@ pub const Envelope = struct {
/// therefore may allocate. /// therefore may allocate.
pub fn serialize( pub fn serialize(
self: *Envelope, self: *Envelope,
writer: anytype, writer: *std.Io.Writer,
) !void { ) !void {
// Header line first // Header line first
try std.json.stringify( try writer.print("{f}\n", .{std.json.fmt(
std.json.Value{ .object = self.headers }, std.json.Value{ .object = self.headers },
json_opts, json_opts,
writer, )});
);
try writer.writeByte('\n');
// Write each item // Write each item
const alloc = self.allocator(); const alloc = self.allocator();
@ -230,13 +236,13 @@ pub const Envelope = struct {
const encoded = try item.encode(alloc); const encoded = try item.encode(alloc);
assert(item.* == .encoded); assert(item.* == .encoded);
try std.json.stringify( try writer.print("{f}\n{s}", .{
std.json.Value{ .object = encoded.headers }, std.json.fmt(
json_opts, std.json.Value{ .object = encoded.headers },
writer, json_opts,
); ),
try writer.writeByte('\n'); encoded.payload,
try writer.writeAll(encoded.payload); });
} }
} }
}; };
@ -425,7 +431,7 @@ pub const Attachment = struct {
pub const ObjectMapUnmanaged = std.StringArrayHashMapUnmanaged(std.json.Value); pub const ObjectMapUnmanaged = std.StringArrayHashMapUnmanaged(std.json.Value);
/// The options we must use for serialization. /// 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 // This is the default but I want to be explicit because its
// VERY important for the correctness of the envelope. This is // VERY important for the correctness of the envelope. This is
// the only whitespace type in std.json that doesn't emit newlines. // the only whitespace type in std.json that doesn't emit newlines.
@ -437,10 +443,10 @@ test "Envelope parse" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; 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(); defer v.deinit();
} }
@ -448,12 +454,12 @@ test "Envelope parse session" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var fbs = std.io.fixedBufferStream( var reader: std.Io.Reader = .fixed(
\\{} \\{}
\\{"type":"session","length":218} \\{"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"}} \\{"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(); defer v.deinit();
try testing.expectEqual(@as(usize, 1), v.items.items.len); try testing.expectEqual(@as(usize, 1), v.items.items.len);
@ -464,14 +470,14 @@ test "Envelope parse multiple" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var fbs = std.io.fixedBufferStream( var reader: std.Io.Reader = .fixed(
\\{} \\{}
\\{"type":"session","length":218} \\{"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"}} \\{"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"} \\{"type":"attachment","length":4,"filename":"test.txt"}
\\ABCD \\ABCD
); );
var v = try Envelope.parse(alloc, fbs.reader()); var v = try Envelope.parse(alloc, &reader);
defer v.deinit(); defer v.deinit();
try testing.expectEqual(@as(usize, 2), v.items.items.len); 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 testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var fbs = std.io.fixedBufferStream( var reader: std.Io.Reader = .fixed(
\\{} \\{}
\\{"type":"session"} \\{"type":"session"}
\\{} \\{}
\\{"type":"attachment","length":4,"filename":"test.txt"} \\{"type":"attachment","length":4,"filename":"test.txt"}
\\ABCD \\ABCD
); );
var v = try Envelope.parse(alloc, fbs.reader()); var v = try Envelope.parse(alloc, &reader);
defer v.deinit(); defer v.deinit();
try testing.expectEqual(@as(usize, 2), v.items.items.len); 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 testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var fbs = std.io.fixedBufferStream( var reader: std.Io.Reader = .fixed(
\\{} \\{}
\\{"type":"session","length":218} \\{"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"}} \\{"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(); defer v.deinit();
try testing.expectEqual(@as(usize, 1), v.items.items.len); try testing.expectEqual(@as(usize, 1), v.items.items.len);
@ -519,12 +525,12 @@ test "Envelope parse attachment" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var fbs = std.io.fixedBufferStream( var reader: std.Io.Reader = .fixed(
\\{} \\{}
\\{"type":"attachment","length":4,"filename":"test.txt"} \\{"type":"attachment","length":4,"filename":"test.txt"}
\\ABCD \\ABCD
); );
var v = try Envelope.parse(alloc, fbs.reader()); var v = try Envelope.parse(alloc, &reader);
defer v.deinit(); defer v.deinit();
try testing.expectEqual(@as(usize, 1), v.items.items.len); try testing.expectEqual(@as(usize, 1), v.items.items.len);
@ -537,14 +543,14 @@ test "Envelope parse attachment" {
// Serialization test // Serialization test
{ {
var output = std.ArrayList(u8).init(alloc); var output: std.Io.Writer.Allocating = .init(alloc);
defer output.deinit(); defer output.deinit();
try v.serialize(output.writer()); try v.serialize(&output.writer);
try testing.expectEqualStrings( try testing.expectEqualStrings(
\\{} \\{}
\\{"type":"attachment","length":4,"filename":"test.txt"} \\{"type":"attachment","length":4,"filename":"test.txt"}
\\ABCD \\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 testing = std.testing;
const alloc = testing.allocator; 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(); defer v.deinit();
var output = std.ArrayList(u8).init(alloc); var output: std.Io.Writer.Allocating = .init(alloc);
defer output.deinit(); defer output.deinit();
try v.serialize(output.writer()); try v.serialize(&output.writer);
try testing.expectEqualStrings( try testing.expectEqualStrings(
\\{} \\{}
, std.mem.trim(u8, output.items, "\n")); , std.mem.trim(u8, output.written(), "\n"));
} }
test "Envelope serialize session" { test "Envelope serialize session" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var fbs = std.io.fixedBufferStream( var reader: std.Io.Reader = .fixed(
\\{} \\{}
\\{"type":"session","length":218} \\{"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"}} \\{"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(); defer v.deinit();
var output = std.ArrayList(u8).init(alloc); var output: std.Io.Writer.Allocating = .init(alloc);
defer output.deinit(); defer output.deinit();
try v.serialize(output.writer()); try v.serialize(&output.writer);
try testing.expectEqualStrings( try testing.expectEqualStrings(
\\{} \\{}
\\{"type":"session","length":218} \\{"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"}} \\{"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;
// }
// }

View File

@ -14,7 +14,7 @@ pub const CacheTable = cache_table.CacheTable;
pub const CircBuf = circ_buf.CircBuf; pub const CircBuf = circ_buf.CircBuf;
pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList; pub const IntrusiveDoublyLinkedList = intrusive_linked_list.DoublyLinkedList;
pub const SegmentedPool = segmented_pool.SegmentedPool; pub const SegmentedPool = segmented_pool.SegmentedPool;
//pub const SplitTree = split_tree.SplitTree; pub const SplitTree = split_tree.SplitTree;
test { test {
@import("std").testing.refAllDecls(@This()); @import("std").testing.refAllDecls(@This());

View File

@ -1023,45 +1023,33 @@ pub fn SplitTree(comptime V: type) type {
} }
/// Format the tree in a human-readable format. By default this will /// Format the tree in a human-readable format. By default this will
/// output a diagram followed by a textual representation. This can /// output a diagram followed by a textual representation.
/// 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.
///
pub fn format( pub fn format(
self: *const Self, self: *const Self,
comptime fmt: []const u8, writer: *std.Io.Writer,
options: std.fmt.FormatOptions,
writer: anytype,
) !void { ) !void {
_ = options;
if (self.nodes.len == 0) { if (self.nodes.len == 0) {
try writer.writeAll("empty"); try writer.writeAll("empty");
return; return;
} }
self.formatDiagram(writer) catch {};
if (std.mem.eql(u8, fmt, "diagram")) { try self.formatText(writer);
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;
}
} }
fn formatText( pub fn formatText(self: Self, writer: *std.Io.Writer) std.Io.Writer.Error!void {
self: *const Self, if (self.nodes.len == 0) {
writer: anytype, try writer.writeAll("empty");
return;
}
try self.formatTextInner(writer, .root, 0);
}
fn formatTextInner(
self: Self,
writer: *std.Io.Writer,
current: Node.Handle, current: Node.Handle,
depth: usize, depth: usize,
) !void { ) std.Io.Writer.Error!void {
for (0..depth) |_| try writer.writeAll(" "); for (0..depth) |_| try writer.writeAll(" ");
if (self.zoomed) |zoomed| if (zoomed == current) { 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}), try writer.print("leaf: {d}\n", .{current}),
.split => |s| { .split => |s| {
try writer.print("split (layout: {s}, ratio: {d:.2})\n", .{ try writer.print("split (layout: {t}, ratio: {d:.2})\n", .{
@tagName(s.layout), s.layout,
s.ratio, s.ratio,
}); });
try self.formatText(writer, s.left, depth + 1); try self.formatTextInner(writer, s.left, depth + 1);
try self.formatText(writer, s.right, depth + 1); try self.formatTextInner(writer, s.right, depth + 1);
}, },
} }
} }
fn formatDiagram( pub fn formatDiagram(
self: *const Self, self: Self,
writer: anytype, writer: *std.Io.Writer,
) !void { ) 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. // Use our arena's GPA to allocate some intermediate memory.
// Requiring allocation for formatting is nasty but this is really // Requiring allocation for formatting is nasty but this is really
// only used for debugging and testing and shouldn't hit OOM // 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. // Get our spatial representation.
const sp = spatial: { 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. // Scale our spatial representation to have minimum width/height 1.
var min_w: f16 = 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_w: f16 = 1 / min_w;
const ratio_h: f16 = 1 / min_h; 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| { for (slots) |*slot| {
slot.x *= ratio_w; slot.x *= ratio_w;
slot.y *= ratio_h; slot.y *= ratio_h;
@ -1168,9 +1161,9 @@ pub fn SplitTree(comptime V: type) type {
width *= cell_width; width *= cell_width;
height *= cell_height; 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| { 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], ' '); @memset(rows[y], ' ');
rows[y][width] = '\n'; rows[y][width] = '\n';
} }
@ -1223,7 +1216,7 @@ pub fn SplitTree(comptime V: type) type {
const label: []const u8 = if (@hasDecl(View, "splitTreeLabel")) const label: []const u8 = if (@hasDecl(View, "splitTreeLabel"))
node.leaf.splitTreeLabel() node.leaf.splitTreeLabel()
else else
try std.fmt.bufPrint(&buf, "{d}", .{handle}); std.fmt.bufPrint(&buf, "{d}", .{handle}) catch return error.WriteFailed;
// Draw the handle in the center // Draw the handle in the center
const x_mid = width / 2 + x; const x_mid = width / 2 + x;
@ -1231,7 +1224,7 @@ pub fn SplitTree(comptime V: type) type {
const label_width = label.len; const label_width = label.len;
const label_start = x_mid - label_width / 2; const label_start = x_mid - label_width / 2;
const row = grid[y_mid][label_start..]; 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 // Output every row
@ -1339,7 +1332,7 @@ test "SplitTree: empty tree" {
var t: TestTree = .empty; var t: TestTree = .empty;
defer t.deinit(); defer t.deinit();
const str = try std.fmt.allocPrint(alloc, "{}", .{t}); const str = try std.fmt.allocPrint(alloc, "{f}", .{t});
defer alloc.free(str); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\empty \\empty
@ -1353,7 +1346,7 @@ test "SplitTree: single node" {
var t: TestTree = try .init(alloc, &v); var t: TestTree = try .init(alloc, &v);
defer t.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1383,7 +1376,7 @@ test "SplitTree: split horizontal" {
defer t3.deinit(); defer t3.deinit();
{ {
const str = try std.fmt.allocPrint(alloc, "{}", .{t3}); const str = try std.fmt.allocPrint(alloc, "{f}", .{t3});
defer alloc.free(str); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---++---+ \\+---++---+
@ -1415,7 +1408,7 @@ test "SplitTree: split horizontal" {
defer t4.deinit(); defer t4.deinit();
{ {
const str = try std.fmt.allocPrint(alloc, "{}", .{t4}); const str = try std.fmt.allocPrint(alloc, "{f}", .{t4});
defer alloc.free(str); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+--------++---++---+ \\+--------++---++---+
@ -1449,7 +1442,7 @@ test "SplitTree: split horizontal" {
defer t5.deinit(); defer t5.deinit();
{ {
const str = try std.fmt.allocPrint(alloc, "{}", .{t5}); const str = try std.fmt.allocPrint(alloc, "{f}", .{t5});
defer alloc.free(str); defer alloc.free(str);
try testing.expectEqualStrings( try testing.expectEqualStrings(
\\+------------------++--------++---++---+ \\+------------------++--------++---++---+
@ -1547,7 +1540,7 @@ test "SplitTree: split vertical" {
); );
defer t3.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1583,7 +1576,7 @@ test "SplitTree: split horizontal with zero ratio" {
const split = splitAB; 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1617,7 +1610,7 @@ test "SplitTree: split vertical with zero ratio" {
const split = splitAB; 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1651,7 +1644,7 @@ test "SplitTree: split horizontal with full width" {
const split = splitAB; 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1685,7 +1678,7 @@ test "SplitTree: split vertical with full width" {
const split = splitAB; 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1727,7 +1720,7 @@ test "SplitTree: remove leaf" {
); );
defer t4.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1772,7 +1765,7 @@ test "SplitTree: split twice, remove intermediary" {
defer split2.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---++---+ \\+---++---+
@ -1798,7 +1791,7 @@ test "SplitTree: split twice, remove intermediary" {
defer split3.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---+ \\+---+
@ -1883,7 +1876,7 @@ test "SplitTree: spatial goto" {
const split = splitBD; 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---++---+ \\+---++---+
@ -1943,7 +1936,7 @@ test "SplitTree: spatial goto" {
defer equal.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---++---+ \\+---++---+
@ -1979,7 +1972,7 @@ test "SplitTree: resize" {
defer split.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+---++---+ \\+---++---+
@ -2005,7 +1998,7 @@ test "SplitTree: resize" {
0.25, 0.25,
); );
defer resized.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\+-------------++---+ \\+-------------++---+
@ -2026,7 +2019,7 @@ test "SplitTree: clone empty tree" {
defer t2.deinit(); defer t2.deinit();
{ {
const str = try std.fmt.allocPrint(alloc, "{}", .{t2}); const str = try std.fmt.allocPrint(alloc, "{f}", .{t2});
defer alloc.free(str); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\empty \\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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\split (layout: horizontal, ratio: 0.50) \\split (layout: horizontal, ratio: 0.50)
@ -2079,7 +2072,7 @@ test "SplitTree: zoom" {
defer clone.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\split (layout: horizontal, ratio: 0.50) \\split (layout: horizontal, ratio: 0.50)
@ -2122,7 +2115,7 @@ test "SplitTree: split resets zoom" {
defer split.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\split (layout: horizontal, ratio: 0.50) \\split (layout: horizontal, ratio: 0.50)
@ -2178,7 +2171,7 @@ test "SplitTree: remove and zoom" {
defer removed.deinit(); defer removed.deinit();
try testing.expect(removed.zoomed == null); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\leaf: B \\leaf: B
@ -2201,7 +2194,7 @@ test "SplitTree: remove and zoom" {
); );
defer removed.deinit(); 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); defer alloc.free(str);
try testing.expectEqualStrings(str, try testing.expectEqualStrings(str,
\\(zoomed) leaf: A \\(zoomed) leaf: A

View File

@ -59,14 +59,15 @@ pub const compiler =
/// Generates the syntax file at comptime. /// Generates the syntax file at comptime.
fn comptimeGenSyntax() []const u8 { fn comptimeGenSyntax() []const u8 {
comptime { comptime {
var counting_writer = std.io.countingWriter(std.io.null_writer); @setEvalBranchQuota(50000);
try writeSyntax(&counting_writer.writer()); var counter: std.Io.Writer.Discarding = .init(&.{});
try writeSyntax(&counter.writer);
var buf: [counting_writer.bytes_written]u8 = undefined; var buf: [counter.count]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
try writeSyntax(stream.writer()); try writeSyntax(&writer);
const final = buf; const final = buf;
return final[0..stream.getWritten().len]; return final[0..writer.end];
} }
} }

View File

@ -471,23 +471,23 @@ pub const Modifier = union(enum) {
test "formatConfig percent" { test "formatConfig percent" {
const configpkg = @import("../config.zig"); const configpkg = @import("../config.zig");
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
const p = try parseCLI("24%"); const p = try parseCLI("24%");
try p.formatEntry(configpkg.entryFormatter("a", buf.writer())); try p.formatEntry(configpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.items); try std.testing.expectEqualSlices(u8, "a = 24%\n", buf.written());
} }
test "formatConfig absolute" { test "formatConfig absolute" {
const configpkg = @import("../config.zig"); const configpkg = @import("../config.zig");
const testing = std.testing; const testing = std.testing;
var buf = std.ArrayList(u8).init(testing.allocator); var buf: std.Io.Writer.Allocating = .init(testing.allocator);
defer buf.deinit(); defer buf.deinit();
const p = try parseCLI("-30"); const p = try parseCLI("-30");
try p.formatEntry(configpkg.entryFormatter("a", buf.writer())); try p.formatEntry(configpkg.entryFormatter("a", &buf.writer));
try std.testing.expectEqualSlices(u8, "a = -30\n", buf.items); try std.testing.expectEqualSlices(u8, "a = -30\n", buf.written());
} }
}; };

View File

@ -596,10 +596,10 @@ pub const Key = struct {
// from DerivedConfig below. // from DerivedConfig below.
var config = try DerivedConfig.init(alloc, config_src); var config = try DerivedConfig.init(alloc, config_src);
var descriptors = std.ArrayList(discovery.Descriptor).init(alloc); var descriptors: std.ArrayList(discovery.Descriptor) = .empty;
defer descriptors.deinit(); defer descriptors.deinit(alloc);
for (config.@"font-family".list.items) |family| { for (config.@"font-family".list.items) |family| {
try descriptors.append(.{ try descriptors.append(alloc, .{
.family = family, .family = family,
.style = config.@"font-style".nameValue(), .style = config.@"font-style".nameValue(),
.size = font_size.points, .size = font_size.points,
@ -617,7 +617,7 @@ pub const Key = struct {
// italic. // italic.
for (config.@"font-family-bold".list.items) |family| { for (config.@"font-family-bold".list.items) |family| {
const style = config.@"font-style-bold".nameValue(); const style = config.@"font-style-bold".nameValue();
try descriptors.append(.{ try descriptors.append(alloc, .{
.family = family, .family = family,
.style = style, .style = style,
.size = font_size.points, .size = font_size.points,
@ -627,7 +627,7 @@ pub const Key = struct {
} }
for (config.@"font-family-italic".list.items) |family| { for (config.@"font-family-italic".list.items) |family| {
const style = config.@"font-style-italic".nameValue(); const style = config.@"font-style-italic".nameValue();
try descriptors.append(.{ try descriptors.append(alloc, .{
.family = family, .family = family,
.style = style, .style = style,
.size = font_size.points, .size = font_size.points,
@ -637,7 +637,7 @@ pub const Key = struct {
} }
for (config.@"font-family-bold-italic".list.items) |family| { for (config.@"font-family-bold-italic".list.items) |family| {
const style = config.@"font-style-bold-italic".nameValue(); const style = config.@"font-style-bold-italic".nameValue();
try descriptors.append(.{ try descriptors.append(alloc, .{
.family = family, .family = family,
.style = style, .style = style,
.size = font_size.points, .size = font_size.points,
@ -681,7 +681,7 @@ pub const Key = struct {
return .{ return .{
.arena = arena, .arena = arena,
.descriptors = try descriptors.toOwnedSlice(), .descriptors = try descriptors.toOwnedSlice(alloc),
.style_offsets = .{ .style_offsets = .{
regular_offset, regular_offset,
bold_offset, bold_offset,

View File

@ -356,8 +356,8 @@ pub const RunIterator = struct {
// If this is a grapheme, we need to find a font that supports // If this is a grapheme, we need to find a font that supports
// all of the codepoints in the grapheme. // all of the codepoints in the grapheme.
const cps = self.opts.row.grapheme(cell) orelse return primary; const cps = self.opts.row.grapheme(cell) orelse return primary;
var candidates = try std.ArrayList(font.Collection.Index).initCapacity(alloc, cps.len + 1); var candidates: std.ArrayList(font.Collection.Index) = try .initCapacity(alloc, cps.len + 1);
defer candidates.deinit(); defer candidates.deinit(alloc);
candidates.appendAssumeCapacity(primary); candidates.appendAssumeCapacity(primary);
for (cps) |cp| { for (cps) |cp| {

View File

@ -140,7 +140,7 @@ pub const GlobalState = struct {
std.log.info("dependency fontconfig={d}", .{fontconfig.version()}); std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
} }
std.log.info("renderer={}", .{renderer.Renderer}); 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. // As early as possible, initialize our resource limits.
self.rlimits = .init(); self.rlimits = .init();
@ -206,7 +206,7 @@ pub const GlobalState = struct {
var sa: p.Sigaction = .{ var sa: p.Sigaction = .{
.handler = .{ .handler = p.SIG.IGN }, .handler = .{ .handler = p.SIG.IGN },
.mask = p.empty_sigset, .mask = p.sigemptyset(),
.flags = 0, .flags = 0,
}; };

View File

@ -11,19 +11,22 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator(); const alloc = gpa.allocator();
const stdout = std.io.getStdOut().writer(); var buf: [4096]u8 = undefined;
try stdout.writeAll( var stdout = std.fs.File.stdout().writer(&buf);
const writer = &stdout.interface;
try writer.writeAll(
\\// THIS FILE IS AUTO GENERATED \\// THIS FILE IS AUTO GENERATED
\\ \\
\\ \\
); );
try genConfig(alloc, stdout); try genConfig(alloc, writer);
try genActions(alloc, stdout); try genActions(alloc, writer);
try genKeybindActions(alloc, stdout); 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); var ast = try std.zig.Ast.parse(alloc, @embedFile("config/Config.zig"), .zig);
defer ast.deinit(alloc); defer ast.deinit(alloc);
@ -44,7 +47,7 @@ fn genConfig(alloc: std.mem.Allocator, writer: anytype) !void {
fn genConfigField( fn genConfigField(
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
writer: anytype, writer: *std.Io.Writer,
ast: std.zig.Ast, ast: std.zig.Ast,
comptime field: []const u8, comptime field: []const u8,
) !void { ) !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( try writer.writeAll(
\\ \\
\\/// Actions help \\/// Actions help
@ -115,7 +118,7 @@ fn genActions(alloc: std.mem.Allocator, writer: anytype) !void {
try writer.writeAll("};\n"); 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); var ast = try std.zig.Ast.parse(alloc, @embedFile("input/Binding.zig"), .zig);
defer ast.deinit(alloc); defer ast.deinit(alloc);
@ -149,24 +152,24 @@ fn extractDocComments(
} else unreachable; } else unreachable;
// Go through and build up the lines. // Go through and build up the lines.
var lines = std.ArrayList([]const u8).init(alloc); var lines: std.ArrayList([]const u8) = .empty;
defer lines.deinit(); defer lines.deinit(alloc);
for (start_idx..index + 1) |i| { for (start_idx..index + 1) |i| {
const token = tokens[i]; const token = tokens[i];
if (token != .doc_comment) break; 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. // Convert the lines to a multiline string.
var buffer = std.ArrayList(u8).init(alloc); var buffer: std.Io.Writer.Allocating = .init(alloc);
const writer = buffer.writer(); defer buffer.deinit();
const prefix = findCommonPrefix(lines); const prefix = findCommonPrefix(lines);
for (lines.items) |line| { for (lines.items) |line| {
try writer.writeAll(" \\\\"); try buffer.writer.writeAll(" \\\\");
try writer.writeAll(line[@min(prefix, line.len)..]); try buffer.writer.writeAll(line[@min(prefix, line.len)..]);
try writer.writeAll("\n"); try buffer.writer.writeAll("\n");
} }
try writer.writeAll(";\n"); try buffer.writer.writeAll(";\n");
return buffer.toOwnedSlice(); return buffer.toOwnedSlice();
} }

View File

@ -7,6 +7,7 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert; const assert = std.debug.assert;
const build_config = @import("../build_config.zig"); const build_config = @import("../build_config.zig");
const uucode = @import("uucode"); const uucode = @import("uucode");
const EntryFormatter = @import("../config/formatter.zig").EntryFormatter;
const key = @import("key.zig"); const key = @import("key.zig");
const KeyEvent = key.KeyEvent; const KeyEvent = key.KeyEvent;
@ -1184,13 +1185,8 @@ pub const Action = union(enum) {
/// action back into the format used by parse. /// action back into the format used by parse.
pub fn format( pub fn format(
self: Action, self: Action,
comptime layout: []const u8, writer: *std.Io.Writer,
opts: std.fmt.FormatOptions,
writer: anytype,
) !void { ) !void {
_ = layout;
_ = opts;
switch (self) { switch (self) {
inline else => |value| { inline else => |value| {
// All actions start with the tag. // All actions start with the tag.
@ -1206,16 +1202,16 @@ pub const Action = union(enum) {
} }
fn formatValue( fn formatValue(
writer: anytype, writer: *std.Io.Writer,
value: anytype, value: anytype,
) !void { ) !void {
const Value = @TypeOf(value); const Value = @TypeOf(value);
const value_info = @typeInfo(Value); const value_info = @typeInfo(Value);
switch (Value) { switch (Value) {
void => {}, void => {},
[]const u8 => try std.zig.stringEscape(value, "", .{}, writer), []const u8 => try std.zig.stringEscape(value, writer),
else => switch (value_info) { else => switch (value_info) {
.@"enum" => try writer.print("{s}", .{@tagName(value)}), .@"enum" => try writer.print("{t}", .{value}),
.float => try writer.print("{d}", .{value}), .float => try writer.print("{d}", .{value}),
.int => try writer.print("{d}", .{value}), .int => try writer.print("{d}", .{value}),
.@"struct" => |info| if (!info.is_tuple) { .@"struct" => |info| if (!info.is_tuple) {
@ -1648,13 +1644,8 @@ pub const Trigger = struct {
/// Format implementation for fmt package. /// Format implementation for fmt package.
pub fn format( pub fn format(
self: Trigger, self: Trigger,
comptime layout: []const u8, writer: *std.Io.Writer,
opts: std.fmt.FormatOptions,
writer: anytype,
) !void { ) !void {
_ = layout;
_ = opts;
// Modifiers first // Modifiers first
if (self.mods.super) try writer.writeAll("super+"); if (self.mods.super) try writer.writeAll("super+");
if (self.mods.ctrl) try writer.writeAll("ctrl+"); if (self.mods.ctrl) try writer.writeAll("ctrl+");
@ -1663,7 +1654,7 @@ pub const Trigger = struct {
// Key // Key
switch (self.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}), .unicode => |c| try writer.print("{u}", .{c}),
} }
} }
@ -1721,13 +1712,8 @@ pub const Set = struct {
/// action back into the format used by parse. /// action back into the format used by parse.
pub fn format( pub fn format(
self: Value, self: Value,
comptime layout: []const u8, writer: *std.Io.Writer,
opts: std.fmt.FormatOptions,
writer: anytype,
) !void { ) !void {
_ = layout;
_ = opts;
switch (self) { switch (self) {
.leader => |set| { .leader => |set| {
// the leader key was already printed. // 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. /// 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 /// 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'. /// 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) { switch (self) {
.leader => |set| { .leader => |set| {
// We'll rewind to this position after each sub-entry, // We'll rewind to this position after each sub-entry,
// sharing the prefix between siblings. // sharing the prefix between siblings.
const pos = try buffer_stream.getPos(); const pos = buffer.end;
var iter = set.bindings.iterator(); var iter = set.bindings.iterator();
while (iter.next()) |binding| { while (iter.next()) |binding| {
buffer_stream.seekTo(pos) catch unreachable; // can't fail // I'm not exactly if this is safe for any arbitrary
std.fmt.format(buffer_stream.writer(), ">{s}", .{binding.key_ptr.*}) catch return error.OutOfMemory; // writer since the Writer interface does not have any
try binding.value_ptr.*.formatEntries(buffer_stream, formatter); // 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| { .leaf => |leaf| {
// When we get to the leaf, the buffer_stream contains // When we get to the leaf, the buffer_stream contains
// the full sequence of keys needed to reach this action. // the full sequence of keys needed to reach this action.
std.fmt.format(buffer_stream.writer(), "={s}", .{leaf.action}) catch return error.OutOfMemory; buffer.print("={f}", .{leaf.action}) catch return error.OutOfMemory;
try formatter.formatEntry([]const u8, buffer_stream.getWritten()); try formatter.formatEntry([]const u8, buffer.buffer[0..buffer.end]);
}, },
} }
} }
@ -3234,11 +3228,8 @@ test "action: format" {
const a: Action = .{ .text = "👻" }; const a: Action = .{ .text = "👻" };
var buf: std.ArrayListUnmanaged(u8) = .empty; var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit(alloc); defer buf.deinit();
try a.format(&buf.writer);
const writer = buf.writer(alloc); try testing.expectEqualStrings("text:\\xf0\\x9f\\x91\\xbb", buf.written());
try a.format("", .{}, writer);
try testing.expectEqualStrings("text:\\xf0\\x9f\\x91\\xbb", buf.items);
} }

View File

@ -50,7 +50,7 @@ pub const Command = struct {
return .{ return .{
.action_key = @tagName(self.action), .action_key = @tagName(self.action),
.action = std.fmt.comptimePrint("{s}", .{self.action}), .action = std.fmt.comptimePrint("{t}", .{self.action}),
.title = self.title, .title = self.title,
.description = self.description, .description = self.description,
}; };
@ -94,6 +94,7 @@ pub const defaults: []const Command = defaults: {
/// Defaults in C-compatible form. /// Defaults in C-compatible form.
pub const defaultsC: []const Command.C = defaults: { pub const defaultsC: []const Command.C = defaults: {
@setEvalBranchQuota(100_000);
var result: [defaults.len]Command.C = undefined; var result: [defaults.len]Command.C = undefined;
for (defaults, 0..) |cmd, i| result[i] = cmd.comptimeCval(); for (defaults, 0..) |cmd, i| result[i] = cmd.comptimeCval();
const final = result; const final = result;

View File

@ -278,6 +278,7 @@ fn pcStyle(comptime fmt: []const u8) []Entry {
// The comptime {} wrapper is superfluous but it prevents us from // The comptime {} wrapper is superfluous but it prevents us from
// accidentally running this function at runtime. // accidentally running this function at runtime.
comptime { comptime {
@setEvalBranchQuota(500_000);
var entries: [modifiers.len]Entry = undefined; var entries: [modifiers.len]Entry = undefined;
for (modifiers, 2.., 0..) |mods, code, i| { for (modifiers, 2.., 0..) |mods, code, i| {
entries[i] = .{ entries[i] = .{

View File

@ -13,7 +13,7 @@ pub const Format = enum {
/// Markdown formatted output /// Markdown formatted output
markdown, 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) { switch (self) {
.plaintext => { .plaintext => {
try writer.writeAll(field_name); 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) { switch (self) {
.plaintext => { .plaintext => {
try writer.appendSlice(" "); try writer.writeAll(" ");
try writer.appendSlice(line); try writer.writeAll(line);
try writer.appendSlice("\n"); try writer.writeAll("\n");
}, },
.markdown => { .markdown => {
try writer.appendSlice(line); try writer.writeAll(line);
try writer.appendSlice("\n"); try writer.writeAll("\n");
}, },
} }
} }
@ -61,7 +61,7 @@ pub const Format = enum {
/// Generate keybind actions documentation with the specified format /// Generate keybind actions documentation with the specified format
pub fn generate( pub fn generate(
writer: anytype, writer: *std.Io.Writer,
format: Format, format: Format,
show_docs: bool, show_docs: bool,
page_allocator: std.mem.Allocator, page_allocator: std.mem.Allocator,
@ -70,8 +70,8 @@ pub fn generate(
try writer.writeAll(header); try writer.writeAll(header);
} }
var buffer = std.ArrayList(u8).init(page_allocator); var stream: std.Io.Writer.Allocating = .init(page_allocator);
defer buffer.deinit(); defer stream.deinit();
const fields = @typeInfo(KeybindAction).@"union".fields; const fields = @typeInfo(KeybindAction).@"union".fields;
inline for (fields) |field| { inline for (fields) |field| {
@ -79,10 +79,9 @@ pub fn generate(
// Write previously stored doc comment below all related actions // Write previously stored doc comment below all related actions
if (show_docs and @hasDecl(help_strings.KeybindAction, field.name)) { if (show_docs and @hasDecl(help_strings.KeybindAction, field.name)) {
try writer.writeAll(buffer.items); try writer.writeAll(stream.written());
try writer.writeAll("\n"); try writer.writeAll("\n");
stream.clearRetainingCapacity();
buffer.clearRetainingCapacity();
} }
if (show_docs) { if (show_docs) {
@ -101,13 +100,13 @@ pub fn generate(
while (iter.next()) |s| { while (iter.next()) |s| {
// If it is the last line and empty, then skip it. // If it is the last line and empty, then skip it.
if (iter.peek() == null and s.len == 0) continue; 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 // Write any remaining buffered documentation
if (buffer.items.len > 0) { if (stream.written().len > 0) {
try writer.writeAll(buffer.items); try writer.writeAll(stream.written());
} }
} }

View File

@ -34,19 +34,19 @@ pub const Set = struct {
alloc: Allocator, alloc: Allocator,
config: []const inputpkg.Link, config: []const inputpkg.Link,
) !Set { ) !Set {
var links = std.ArrayList(Link).init(alloc); var links: std.ArrayList(Link) = .empty;
defer links.deinit(); defer links.deinit(alloc);
for (config) |link| { for (config) |link| {
var regex = try link.oniRegex(); var regex = try link.oniRegex();
errdefer regex.deinit(); errdefer regex.deinit();
try links.append(.{ try links.append(alloc, .{
.regex = regex, .regex = regex,
.highlight = link.highlight, .highlight = link.highlight,
}); });
} }
return .{ .links = try links.toOwnedSlice() }; return .{ .links = try links.toOwnedSlice(alloc) };
} }
pub fn deinit(self: *Set, alloc: Allocator) void { 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 // as selections which contain the start and end points of
// the match. There is no way to map these back to the link // the match. There is no way to map these back to the link
// configuration right now because we don't need to. // configuration right now because we don't need to.
var matches = std.ArrayList(terminal.Selection).init(alloc); var matches: std.ArrayList(terminal.Selection) = .empty;
defer matches.deinit(); defer matches.deinit(alloc);
// If our mouse is over an OSC8 link, then we can skip the regex // If our mouse is over an OSC8 link, then we can skip the regex
// matches below since OSC8 takes priority. // 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( fn matchSetFromOSC8(
@ -112,8 +112,6 @@ pub const Set = struct {
mouse_pin: terminal.Pin, mouse_pin: terminal.Pin,
mouse_mods: inputpkg.Mods, mouse_mods: inputpkg.Mods,
) !void { ) !void {
_ = alloc;
// If the right mods aren't pressed, then we can't match. // If the right mods aren't pressed, then we can't match.
if (!mouse_mods.equal(inputpkg.ctrlOrSuper(.{}))) return; if (!mouse_mods.equal(inputpkg.ctrlOrSuper(.{}))) return;
@ -135,6 +133,7 @@ pub const Set = struct {
if (link.id == .implicit) { if (link.id == .implicit) {
const uri = link.uri.offset.ptr(page.memory)[0..link.uri.len]; const uri = link.uri.offset.ptr(page.memory)[0..link.uri.len];
return try self.matchSetFromOSC8Implicit( return try self.matchSetFromOSC8Implicit(
alloc,
matches, matches,
mouse_pin, mouse_pin,
uri, uri,
@ -154,7 +153,7 @@ pub const Set = struct {
// building our matching selection. // building our matching selection.
if (!row.hyperlink) { if (!row.hyperlink) {
if (current) |sel| { if (current) |sel| {
try matches.append(sel); try matches.append(alloc, sel);
current = null; current = null;
} }
@ -191,7 +190,7 @@ pub const Set = struct {
// No match, if we have a current selection then complete it. // No match, if we have a current selection then complete it.
if (current) |sel| { if (current) |sel| {
try matches.append(sel); try matches.append(alloc, sel);
current = null; current = null;
} }
} }
@ -203,6 +202,7 @@ pub const Set = struct {
/// around the mouse pin. /// around the mouse pin.
fn matchSetFromOSC8Implicit( fn matchSetFromOSC8Implicit(
self: *const Set, self: *const Set,
alloc: Allocator,
matches: *std.ArrayList(terminal.Selection), matches: *std.ArrayList(terminal.Selection),
mouse_pin: terminal.Pin, mouse_pin: terminal.Pin,
uri: []const u8, uri: []const u8,
@ -264,7 +264,7 @@ pub const Set = struct {
sel.endPtr().* = cell_pin; sel.endPtr().* = cell_pin;
} }
try matches.append(sel); try matches.append(alloc, sel);
} }
/// Fills matches with the matches from regex link matches. /// Fills matches with the matches from regex link matches.
@ -334,7 +334,7 @@ pub const Set = struct {
=> if (!sel.contains(screen, mouse_pin)) continue, => if (!sel.contains(screen, mouse_pin)) continue,
} }
try matches.append(sel); try matches.append(alloc, sel);
} }
} }
} }

View File

@ -129,7 +129,7 @@ pub fn loadFromFile(
/// mainImage function and don't define any of the uniforms. This function /// 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 /// will convert the ShaderToy shader into a valid GLSL shader that can be
/// compiled and linked. /// 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"); const prefix = @embedFile("shaders/shadertoy_prefix.glsl");
try writer.writeAll(prefix); try writer.writeAll(prefix);
try writer.writeAll("\n\n"); 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. /// Convert a GLSL shader into SPIR-V assembly.
pub fn spirvFromGlsl( pub fn spirvFromGlsl(
writer: anytype, writer: *std.Io.Writer,
errlog: ?*SpirvLog, errlog: ?*SpirvLog,
src: [:0]const u8, src: [:0]const u8,
) !void { ) !void {
@ -331,10 +331,10 @@ fn spvCross(
/// Convert ShaderToy shader to null-terminated glsl for testing. /// Convert ShaderToy shader to null-terminated glsl for testing.
fn testGlslZ(alloc: Allocator, src: []const u8) ![:0]const u8 { fn testGlslZ(alloc: Allocator, src: []const u8) ![:0]const u8 {
var list = std.ArrayList(u8).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer list.deinit(); defer buf.deinit();
try glslFromShader(list.writer(), src); try glslFromShader(&buf.writer, src);
return try list.toOwnedSliceSentinel(0); return try buf.toOwnedSliceSentinel(0);
} }
test "spirv" { test "spirv" {
@ -345,9 +345,8 @@ test "spirv" {
defer alloc.free(src); defer alloc.free(src);
var buf: [4096 * 4]u8 = undefined; var buf: [4096 * 4]u8 = undefined;
var buf_stream = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
const writer = buf_stream.writer(); try spirvFromGlsl(&writer, null, src);
try spirvFromGlsl(writer, null, src);
} }
test "spirv invalid" { test "spirv invalid" {
@ -358,12 +357,11 @@ test "spirv invalid" {
defer alloc.free(src); defer alloc.free(src);
var buf: [4096 * 4]u8 = undefined; var buf: [4096 * 4]u8 = undefined;
var buf_stream = std.io.fixedBufferStream(&buf); var writer: std.Io.Writer = .fixed(&buf);
const writer = buf_stream.writer();
var errlog: SpirvLog = .{ .alloc = alloc }; var errlog: SpirvLog = .{ .alloc = alloc };
defer errlog.deinit(); 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); try testing.expect(errlog.info.len > 0);
} }
@ -374,9 +372,14 @@ test "shadertoy to msl" {
const src = try testGlslZ(alloc, test_crt); const src = try testGlslZ(alloc, test_crt);
defer alloc.free(src); defer alloc.free(src);
var spvlist = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer spvlist.deinit(); defer buf.deinit();
try spirvFromGlsl(spvlist.writer(), null, src); 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); const msl = try mslFromSpv(alloc, spvlist.items);
defer alloc.free(msl); defer alloc.free(msl);
@ -389,9 +392,14 @@ test "shadertoy to glsl" {
const src = try testGlslZ(alloc, test_crt); const src = try testGlslZ(alloc, test_crt);
defer alloc.free(src); defer alloc.free(src);
var spvlist = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc); var buf: std.Io.Writer.Allocating = .init(alloc);
defer spvlist.deinit(); defer buf.deinit();
try spirvFromGlsl(spvlist.writer(), null, src); 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); const glsl = try glslFromSpv(alloc, spvlist.items);
defer alloc.free(glsl); defer alloc.free(glsl);

View File

@ -65,7 +65,9 @@ pub const Handler = struct {
.kitty => |*p| kitty: { .kitty => |*p| kitty: {
if (comptime !build_options.kitty_graphics) unreachable; 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}); log.warn("kitty graphics protocol error: {}", .{err});
break :kitty null; break :kitty null;
}; };

View File

@ -64,7 +64,7 @@ pub const Handler = struct {
.state = .{ .state = .{
.tmux = .{ .tmux = .{
.max_bytes = self.max_bytes, .max_bytes = self.max_bytes,
.buffer = try std.ArrayList(u8).initCapacity( .buffer = try .initCapacity(
alloc, alloc,
128, // Arbitrary choice to limit initial reallocs 128, // Arbitrary choice to limit initial reallocs
), ),

View File

@ -59,7 +59,7 @@ pub const Parser = struct {
errdefer arena.deinit(); errdefer arena.deinit();
var result: Parser = .{ var result: Parser = .{
.arena = arena, .arena = arena,
.data = std.ArrayList(u8).init(alloc), .data = .empty,
.kv = .{}, .kv = .{},
.kv_temp_len = 0, .kv_temp_len = 0,
.kv_current = 0, .kv_current = 0,
@ -77,8 +77,8 @@ pub const Parser = struct {
pub fn deinit(self: *Parser) void { pub fn deinit(self: *Parser) void {
// We don't free the hash map because its in the arena // We don't free the hash map because its in the arena
self.data.deinit(self.arena.child_allocator);
self.arena.deinit(); self.arena.deinit();
self.data.deinit();
} }
/// Parse a complete command string. /// Parse a complete command string.
@ -86,7 +86,7 @@ pub const Parser = struct {
var parser = init(alloc); var parser = init(alloc);
defer parser.deinit(); defer parser.deinit();
for (data) |c| try parser.feed(c); for (data) |c| try parser.feed(c);
return try parser.complete(); return try parser.complete(alloc);
} }
/// Feed a single byte to the parser. /// Feed a single byte to the parser.
@ -136,7 +136,7 @@ pub const Parser = struct {
else => {}, 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 /// The allocator given will be used for the long-lived data
/// of the final command. /// of the final command.
pub fn complete(self: *Parser) !Command { pub fn complete(self: *Parser, alloc: Allocator) !Command {
switch (self.state) { switch (self.state) {
// We can't ever end in the control key state and be valid. // We can't ever end in the control key state and be valid.
// This means the command looked something like "a=1,b" // This means the command looked something like "a=1,b"
@ -194,14 +194,14 @@ pub const Parser = struct {
return .{ return .{
.control = control, .control = control,
.quiet = quiet, .quiet = quiet,
.data = try self.decodeData(), .data = try self.decodeData(alloc),
}; };
} }
/// Decodes the payload data from base64 and returns it as a slice. /// Decodes the payload data from base64 and returns it as a slice.
/// This function will destroy the contents of self.data, it should /// This function will destroy the contents of self.data, it should
/// only be used once we are done collecting payload bytes. /// 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) { if (self.data.items.len == 0) {
return ""; return "";
} }
@ -225,7 +225,7 @@ pub const Parser = struct {
// Remove the extra bytes. // Remove the extra bytes.
self.data.items.len = decoded.len; 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 { 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"; const input = "f=24,s=10,v=20";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .transmit); 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"; const input = "a=t,t=t,m=1";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .transmit); 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"; const input = "a=t,t=d,m=1";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .transmit); 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"; const input = "i=31,s=1,v=1,a=q,t=d,f=24;QUFBQQ";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .query); 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"; const input = "a=p,U=1,i=31,c=80,r=120";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .display); try testing.expect(command.control == .display);
@ -1059,7 +1059,7 @@ test "delete command" {
const input = "a=d,d=p,x=3,y=4"; const input = "a=d,d=p,x=3,y=4";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .delete); try testing.expect(command.control == .delete);
@ -1079,7 +1079,7 @@ test "no control data" {
const input = ";QUFBQQ"; const input = ";QUFBQQ";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .transmit); 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"; const input = "f=24,s=10,v=20,hello=world";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .transmit); try testing.expect(command.control == .transmit);
@ -1112,7 +1112,7 @@ test "ignore very long values" {
const input = "f=24,s=10,v=2000000000000000000000000000000000000000"; const input = "f=24,s=10,v=2000000000000000000000000000000000000000";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .transmit); 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"; const input = "a=p,i=1,z=-2000000000";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .display); try testing.expect(command.control == .display);
@ -1147,7 +1147,7 @@ test "ensure proper overflow error for u32" {
const input = "a=p,i=10000000000"; const input = "a=p,i=10000000000";
for (input) |c| try p.feed(c); 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" { 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"; const input = "a=p,i=1,z=-9999999999";
for (input) |c| try p.feed(c); 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" { test "all i32 values" {
@ -1171,7 +1171,7 @@ test "all i32 values" {
defer p.deinit(); defer p.deinit();
const input = "a=p,i=1,z=-1"; const input = "a=p,i=1,z=-1";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .display); try testing.expect(command.control == .display);
@ -1186,7 +1186,7 @@ test "all i32 values" {
defer p.deinit(); defer p.deinit();
const input = "a=p,i=1,H=-1"; const input = "a=p,i=1,H=-1";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .display); try testing.expect(command.control == .display);
@ -1201,7 +1201,7 @@ test "all i32 values" {
defer p.deinit(); defer p.deinit();
const input = "a=p,i=1,V=-1"; const input = "a=p,i=1,V=-1";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .display); 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"; const input = "a=d,d=r,x=3,y=4";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .delete); 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"; const input = "a=d,d=R,x=5,y=11";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
const command = try p.complete(); const command = try p.complete(alloc);
defer command.deinit(alloc); defer command.deinit(alloc);
try testing.expect(command.control == .delete); 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"; const input = "a=d,d=R,x=5,y=4";
for (input) |c| try p.feed(c); 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" { test "delete range command 4" {
@ -1310,7 +1310,7 @@ test "delete range command 4" {
const input = "a=d,d=R,x=5"; const input = "a=d,d=R,x=5";
for (input) |c| try p.feed(c); 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" { test "delete range command 5" {
@ -1321,5 +1321,5 @@ test "delete range command 5" {
const input = "a=d,d=R,y=5"; const input = "a=d,d=R,y=5";
for (input) |c| try p.feed(c); for (input) |c| try p.feed(c);
try testing.expectError(error.InvalidFormat, p.complete()); try testing.expectError(error.InvalidFormat, p.complete(alloc));
} }

View File

@ -259,15 +259,16 @@ pub const LoadingImage = struct {
}; };
} }
var buf_reader = std.io.bufferedReader(file.reader()); var buf: [4096]u8 = undefined;
const reader = buf_reader.reader(); var buf_reader = file.reader(&buf);
const reader = &buf_reader.interface;
// Read the file // Read the file
var managed = std.ArrayList(u8).init(alloc); var managed: std.ArrayList(u8) = .empty;
errdefer managed.deinit(); errdefer managed.deinit(alloc);
const size: usize = if (t.size > 0) @min(t.size, max_size) else max_size; const size: usize = if (t.size > 0) @min(t.size, max_size) else max_size;
reader.readAllArrayList(&managed, size) catch |err| { reader.appendRemaining(alloc, &managed, .limited(size)) catch {
log.warn("failed to read temporary file: {}", .{err}); log.warn("failed to read temporary file: {?}", .{buf_reader.err});
return error.InvalidData; return error.InvalidData;
}; };
@ -402,14 +403,15 @@ pub const LoadingImage = struct {
fn decompressZlib(self: *LoadingImage, alloc: Allocator) !void { fn decompressZlib(self: *LoadingImage, alloc: Allocator) !void {
// Open our zlib stream // Open our zlib stream
var fbs = std.io.fixedBufferStream(self.data.items); var buf: [std.compress.flate.max_window_len]u8 = undefined;
var stream = std.compress.zlib.decompressor(fbs.reader()); 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 // Write it to an array list
var list = std.ArrayList(u8).init(alloc); var list: std.ArrayList(u8) = .empty;
errdefer list.deinit(); errdefer list.deinit(alloc);
stream.reader().readAllArrayList(&list, max_size) catch |err| { stream.reader.appendRemaining(alloc, &list, .limited(max_size)) catch {
log.warn("failed to read decompressed data: {}", .{err}); log.warn("failed to read decompressed data: {?}", .{stream.err});
return error.DecompressionFailed; return error.DecompressionFailed;
}; };

View File

@ -526,8 +526,8 @@ pub const ImageStorage = struct {
used: bool, used: bool,
}; };
var candidates = std.ArrayList(Candidate).init(alloc); var candidates: std.ArrayList(Candidate) = .empty;
defer candidates.deinit(); defer candidates.deinit(alloc);
var it = self.images.iterator(); var it = self.images.iterator();
while (it.next()) |kv| { while (it.next()) |kv| {
@ -548,7 +548,7 @@ pub const ImageStorage = struct {
break :used false; break :used false;
}; };
try candidates.append(.{ try candidates.append(alloc, .{
.id = img.id, .id = img.id,
.time = img.transmit_time, .time = img.transmit_time,
.used = used, .used = used,

View File

@ -17,7 +17,7 @@ pub const Client = struct {
state: State = .idle, state: State = .idle,
/// The buffer used to store in-progress notifications, output, etc. /// 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 /// The maximum size in bytes of the buffer. This is used to limit
/// memory usage. If the buffer exceeds this size, the client will /// memory usage. If the buffer exceeds this size, the client will
@ -49,7 +49,7 @@ pub const Client = struct {
// Handle a byte of input. // Handle a byte of input.
pub fn put(self: *Client, byte: u8) !?Notification { 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(); self.broken();
return error.OutOfMemory; 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 // 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. // and then we check to see if that line ended the block.
.block => if (byte == '\n') { .block => if (byte == '\n') {
const written = self.buffer.written();
const idx = if (std.mem.lastIndexOfScalar( const idx = if (std.mem.lastIndexOfScalar(
u8, u8,
self.buffer.items, written,
'\n', '\n',
)) |v| v + 1 else 0; )) |v| v + 1 else 0;
const line = self.buffer.items[idx..]; const line = written[idx..];
if (std.mem.startsWith(u8, line, "%end") or if (std.mem.startsWith(u8, line, "%end") or
std.mem.startsWith(u8, line, "%error")) std.mem.startsWith(u8, line, "%error"))
{ {
const err = 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 it is an error then log it.
if (err) log.warn("tmux control mode error={s}", .{output}); 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; return null;
} }
@ -116,7 +117,7 @@ pub const Client = struct {
assert(self.state == .notification); assert(self.state == .notification);
const line = line: { 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]; if (line[line.len - 1] == '\r') line = line[0 .. line.len - 1];
break :line line; break :line line;
}; };
@ -274,7 +275,7 @@ pub const Client = struct {
// Mark the tmux state as broken. // Mark the tmux state as broken.
fn broken(self: *Client) void { fn broken(self: *Client) void {
self.state = .broken; self.state = .broken;
self.buffer.clearAndFree(); self.buffer.deinit();
} }
}; };
@ -313,7 +314,7 @@ test "tmux begin/end empty" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%begin 1578922740 269 1\n") |byte| try testing.expect(try c.put(byte) == null); 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); 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 testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%begin 1578922740 269 1\n") |byte| try testing.expect(try c.put(byte) == null); 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); 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 testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%begin 1578922740 269 1\n") |byte| try testing.expect(try c.put(byte) == null); 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); 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 testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%output %42 foo bar baz") |byte| try testing.expect(try c.put(byte) == null); for ("%output %42 foo bar baz") |byte| try testing.expect(try c.put(byte) == null);
const n = (try c.put('\n')).?; const n = (try c.put('\n')).?;
@ -366,7 +367,7 @@ test "tmux session-changed" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%session-changed $42 foo") |byte| try testing.expect(try c.put(byte) == null); for ("%session-changed $42 foo") |byte| try testing.expect(try c.put(byte) == null);
const n = (try c.put('\n')).?; const n = (try c.put('\n')).?;
@ -379,7 +380,7 @@ test "tmux sessions-changed" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%sessions-changed") |byte| try testing.expect(try c.put(byte) == null); for ("%sessions-changed") |byte| try testing.expect(try c.put(byte) == null);
const n = (try c.put('\n')).?; const n = (try c.put('\n')).?;
@ -390,7 +391,7 @@ test "tmux sessions-changed carriage return" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%sessions-changed\r") |byte| try testing.expect(try c.put(byte) == null); for ("%sessions-changed\r") |byte| try testing.expect(try c.put(byte) == null);
const n = (try c.put('\n')).?; const n = (try c.put('\n')).?;
@ -401,7 +402,7 @@ test "tmux window-add" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%window-add @14") |byte| try testing.expect(try c.put(byte) == null); for ("%window-add @14") |byte| try testing.expect(try c.put(byte) == null);
const n = (try c.put('\n')).?; const n = (try c.put('\n')).?;
@ -413,7 +414,7 @@ test "tmux window-renamed" {
const testing = std.testing; const testing = std.testing;
const alloc = testing.allocator; const alloc = testing.allocator;
var c: Client = .{ .buffer = std.ArrayList(u8).init(alloc) }; var c: Client = .{ .buffer = .init(alloc) };
defer c.deinit(); defer c.deinit();
for ("%window-renamed @42 bar") |byte| try testing.expect(try c.put(byte) == null); for ("%window-renamed @42 bar") |byte| try testing.expect(try c.put(byte) == null);
const n = (try c.put('\n')).?; const n = (try c.put('\n')).?;

View File

@ -115,9 +115,10 @@ pub fn xtgettcapMap(comptime self: Source) std.StaticStringMap([]const u8) {
}, },
.numeric => |v| numeric: { .numeric => |v| numeric: {
var buf: [10]u8 = undefined; 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; const final = buf;
break :numeric final[0..num_len]; break :numeric final[0..writer.end];
}, },
}, },
}; };

View File

@ -1518,7 +1518,7 @@ fn execCommand(
.shell => |v| shell: { .shell => |v| shell: {
var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 4); var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 4);
defer args.deinit(); defer args.deinit(alloc);
if (comptime builtin.os.tag == .windows) { if (comptime builtin.os.tag == .windows) {
// We run our shell wrapped in `cmd.exe` so that we don't have // We run our shell wrapped in `cmd.exe` so that we don't have
@ -1539,21 +1539,21 @@ fn execCommand(
"cmd.exe", "cmd.exe",
}); });
try args.append(cmd); try args.append(alloc, cmd);
try args.append("/C"); try args.append(alloc, "/C");
} else { } else {
// We run our shell wrapped in `/bin/sh` so that we don't have // We run our shell wrapped in `/bin/sh` so that we don't have
// to parse the command line ourselves if it has arguments. // to parse the command line ourselves if it has arguments.
// Additionally, some environments (NixOS, I found) use /bin/sh // Additionally, some environments (NixOS, I found) use /bin/sh
// to setup some environment variables that are important to // to setup some environment variables that are important to
// have set. // have set.
try args.append("/bin/sh"); try args.append(alloc, "/bin/sh");
if (internal_os.isFlatpak()) try args.append("-l"); if (internal_os.isFlatpak()) try args.append(alloc, "-l");
try args.append("-c"); try args.append(alloc, "-c");
} }
try args.append(v); try args.append(alloc, v);
break :shell try args.toOwnedSlice(); break :shell try args.toOwnedSlice(alloc);
}, },
}; };
} }

View File

@ -175,7 +175,9 @@ pub fn setupFeatures(
inline for (fields) |field| n += field.name.len; inline for (fields) |field| n += field.name.len;
break :capacity n; 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 // Sort the fields so that the output is deterministic. This is
// done at comptime so it has no runtime cost // done at comptime so it has no runtime cost
@ -197,13 +199,13 @@ pub fn setupFeatures(
inline for (fields_sorted) |name| { inline for (fields_sorted) |name| {
if (@field(features, name)) { if (@field(features, name)) {
if (buffer.len > 0) try buffer.append(','); if (writer.end > 0) try writer.writeByte(',');
try buffer.appendSlice(name); try writer.writeAll(name);
} }
} }
if (buffer.len > 0) { if (writer.end > 0) {
try env.put("GHOSTTY_SHELL_FEATURES", buffer.slice()); try env.put("GHOSTTY_SHELL_FEATURES", buf[0..writer.end]);
} }
} }
@ -257,8 +259,8 @@ fn setupBash(
resource_dir: []const u8, resource_dir: []const u8,
env: *EnvMap, env: *EnvMap,
) !?config.Command { ) !?config.Command {
var args = try std.ArrayList([:0]const u8).initCapacity(alloc, 3); var args: std.ArrayList([:0]const u8) = try .initCapacity(alloc, 3);
defer args.deinit(); defer args.deinit(alloc);
// Iterator that yields each argument in the original command line. // Iterator that yields each argument in the original command line.
// This will allocate once proportionate to the command line length. // 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. // Start accumulating arguments with the executable and initial flags.
if (iter.next()) |exe| { if (iter.next()) |exe| {
try args.append(try alloc.dupeZ(u8, exe)); try args.append(alloc, try alloc.dupeZ(u8, exe));
} else return null; } 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. // On macOS, we request a login shell to match that platform's norms.
if (comptime builtin.target.os.tag.isDarwin()) { 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 // Stores the list of intercepted command line flags that will be passed
// to our shell integration script: --norc --noprofile // to our shell integration script: --norc --noprofile
// We always include at least "1" so the script can differentiate between // We always include at least "1" so the script can differentiate between
// being manually sourced or automatically injected (from here). // being manually sourced or automatically injected (from here).
var inject = try std.BoundedArray(u8, 32).init(0); var buf: [32]u8 = undefined;
try inject.appendSlice("1"); 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 // Walk through the rest of the given arguments. If we see an option that
// would require complex or unsupported integration behavior, we bail out // would require complex or unsupported integration behavior, we bail out
@ -296,9 +299,9 @@ fn setupBash(
if (std.mem.eql(u8, arg, "--posix")) { if (std.mem.eql(u8, arg, "--posix")) {
return null; return null;
} else if (std.mem.eql(u8, arg, "--norc")) { } else if (std.mem.eql(u8, arg, "--norc")) {
try inject.appendSlice(" --norc"); try inject.writeAll(" --norc");
} else if (std.mem.eql(u8, arg, "--noprofile")) { } 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")) { } else if (std.mem.eql(u8, arg, "--rcfile") or std.mem.eql(u8, arg, "--init-file")) {
rcfile = iter.next(); rcfile = iter.next();
} else if (arg.len > 1 and arg[0] == '-' and arg[1] != '-') { } 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) { if (std.mem.indexOfScalar(u8, arg, 'c') != null) {
return 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, "--")) { } else if (std.mem.eql(u8, arg, "-") or std.mem.eql(u8, arg, "--")) {
// All remaining arguments should be passed directly to the shell // All remaining arguments should be passed directly to the shell
// command. We shouldn't perform any further option processing. // 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| { 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; break;
} else { } 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| { if (rcfile) |v| {
try env.put("GHOSTTY_BASH_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 // Since we built up a command line, we don't need to wrap it in
// ANOTHER shell anymore and can do a direct command. // ANOTHER shell anymore and can do a direct command.
return .{ .direct = try args.toOwnedSlice() }; return .{ .direct = try args.toOwnedSlice(alloc) };
} }
test "bash" { test "bash" {