Zig 0.15: zig build test

This commit is contained in:
Mitchell Hashimoto
2025-10-01 13:10:40 -07:00
parent 3770f97608
commit cb295b84a0
66 changed files with 1264 additions and 1144 deletions

View File

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

View File

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

View File

@@ -38,21 +38,35 @@ pub fn run(alloc_gpa: Allocator) !u8 {
try args.parse(Options, alloc_gpa, &opts, &iter);
}
var buffer: [1024]u8 = undefined;
var stdout_file: std.fs.File = .stdout();
var stdout_writer = stdout_file.writer(&buffer);
const stdout = &stdout_writer.interface;
const result = runInner(alloc, &stdout_file, stdout);
stdout.flush() catch {};
return result;
}
fn runInner(
alloc: Allocator,
stdout_file: *std.fs.File,
stdout: *std.Io.Writer,
) !u8 {
const crash_dir = try crash.defaultDir(alloc);
var reports = std.ArrayList(crash.Report).init(alloc);
var reports: std.ArrayList(crash.Report) = .empty;
errdefer reports.deinit(alloc);
var it = try crash_dir.iterator();
while (try it.next()) |report| try reports.append(.{
while (try it.next()) |report| try reports.append(alloc, .{
.name = try alloc.dupe(u8, report.name),
.mtime = report.mtime,
});
const stdout = std.io.getStdOut();
// If we have no reports, then we're done. If we have a tty then we
// print a message, otherwise we do nothing.
if (reports.items.len == 0) {
if (std.posix.isatty(stdout.handle)) {
if (std.posix.isatty(stdout_file.handle)) {
try stdout.writeAll("No crash reports! 👻\n");
}
return 0;
@@ -60,16 +74,15 @@ pub fn run(alloc_gpa: Allocator) !u8 {
std.mem.sort(crash.Report, reports.items, {}, lt);
const writer = stdout.writer();
for (reports.items) |report| {
var buf: [128]u8 = undefined;
const now = std.time.nanoTimestamp();
const diff = now - report.mtime;
const since = if (diff <= 0) "now" else s: {
const d = Config.Duration{ .duration = @intCast(diff) };
break :s try std.fmt.bufPrint(&buf, "{s} ago", .{d.round(std.time.ns_per_s)});
break :s try std.fmt.bufPrint(&buf, "{f} ago", .{d.round(std.time.ns_per_s)});
};
try writer.print("{s} ({s})\n", .{ report.name, since });
try stdout.print("{s} ({s})\n", .{ report.name, since });
}
return 0;

View File

@@ -16,7 +16,7 @@ pub const Diagnostic = struct {
message: [:0]const u8,
/// Write the full user-friendly diagnostic message to the writer.
pub fn write(self: *const Diagnostic, writer: anytype) !void {
pub fn format(self: *const Diagnostic, writer: *std.Io.Writer) !void {
switch (self.location) {
.none => {},
.cli => |index| try writer.print("cli:{}:", .{index}),
@@ -157,11 +157,14 @@ pub const DiagnosticList = struct {
errdefer _ = self.list.pop();
if (comptime precompute_enabled) {
var buf = std.ArrayList(u8).init(alloc);
defer buf.deinit();
try diag.write(buf.writer());
var stream: std.Io.Writer.Allocating = .init(alloc);
defer stream.deinit();
diag.format(&stream.writer) catch |err| switch (err) {
// WriteFailed in this instance can only mean an OOM
error.WriteFailed => return error.OutOfMemory,
};
const owned: [:0]const u8 = try buf.toOwnedSliceSentinel(0);
const owned: [:0]const u8 = try stream.toOwnedSliceSentinel(0);
errdefer alloc.free(owned);
try self.precompute.messages.append(alloc, owned);

View File

@@ -47,7 +47,9 @@ pub fn run(alloc: Allocator) !u8 {
// not using `exec` anymore and because this command isn't performance
// critical where setting up the defer cleanup is a problem.
const stderr = std.io.getStdErr().writer();
var buffer: [1024]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buffer);
const stderr = &stderr_writer.interface;
var opts: Options = .{};
defer opts.deinit();
@@ -58,6 +60,13 @@ pub fn run(alloc: Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter);
}
const result = runInner(alloc, stderr);
// Flushing *shouldn't* fail but...
stderr.flush() catch {};
return result;
}
fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 {
// We load the configuration once because that will write our
// default configuration files to disk. We don't use the config.
var config = try Config.load(alloc);
@@ -133,23 +142,13 @@ pub fn run(alloc: Allocator) !u8 {
// so this is not a big deal.
comptime assert(builtin.link_libc);
var buf: std.ArrayListUnmanaged(u8) = .empty;
errdefer buf.deinit(alloc);
const writer = buf.writer(alloc);
var shellescape: internal_os.ShellEscapeWriter(std.ArrayListUnmanaged(u8).Writer) = .init(writer);
var shellescapewriter = shellescape.writer();
try writer.writeAll(editor);
try writer.writeByte(' ');
try shellescapewriter.writeAll(path);
const command = try buf.toOwnedSliceSentinel(alloc, 0);
defer alloc.free(command);
const editorZ = try alloc.dupeZ(u8, editor);
defer alloc.free(editorZ);
const pathZ = try alloc.dupeZ(u8, path);
defer alloc.free(pathZ);
const err = std.posix.execvpeZ(
"sh",
&.{ "sh", "-c", command },
editorZ,
&.{ editorZ, pathZ },
std.c.environ,
);

View File

@@ -107,12 +107,18 @@ pub const Action = enum {
// for all commands by just changing this one place.
if (std.mem.eql(u8, field.name, @tagName(self))) {
const stdout = std.io.getStdOut().writer();
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const stdout = &stdout_writer.interface;
const text = @field(help_strings.Action, field.name) ++ "\n";
stdout.writeAll(text) catch |write_err| {
std.log.warn("failed to write help text: {}\n", .{write_err});
break :err 1;
};
stdout.flush() catch |flush_err| {
std.log.warn("failed to flush help text: {}\n", .{flush_err});
break :err 1;
};
break :err 0;
}

View File

@@ -30,7 +30,9 @@ pub fn run(alloc: Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter);
}
const stdout = std.io.getStdOut().writer();
var buffer: [2048]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const stdout = &stdout_writer.interface;
try stdout.writeAll(
\\Usage: ghostty [+action] [options]
\\
@@ -70,6 +72,7 @@ pub fn run(alloc: Allocator) !u8 {
\\where `<action>` is one of actions listed above.
\\
);
try stdout.flush();
return 0;
}

View File

@@ -37,8 +37,15 @@ pub fn run(alloc: Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter);
}
const stdout = std.io.getStdOut().writer();
try helpgen_actions.generate(stdout, .plaintext, opts.docs, std.heap.page_allocator);
var stdout: std.fs.File = .stdout();
var buffer: [4096]u8 = undefined;
var stdout_writer = stdout.writer(&buffer);
try helpgen_actions.generate(
&stdout_writer.interface,
.plaintext,
opts.docs,
std.heap.page_allocator,
);
return 0;
}

View File

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

View File

@@ -77,7 +77,9 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
// Its possible to build Ghostty without font discovery!
if (comptime font.Discover == void) {
const stderr = std.io.getStdErr().writer();
var buffer: [1024]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buffer);
const stderr = &stderr_writer.interface;
try stderr.print(
\\Ghostty was built without a font discovery mechanism. This is a compile-time
\\option. Please review how Ghostty was built from source, contact the
@@ -85,15 +87,18 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
,
.{},
);
try stderr.flush();
return 1;
}
const stdout = std.io.getStdOut().writer();
var buffer: [2048]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const stdout = &stdout_writer.interface;
// We'll be putting our fonts into a list categorized by family
// so it is easier to read the output.
var families = std.ArrayList([]const u8).init(alloc);
var map = std.StringHashMap(std.ArrayListUnmanaged([]const u8)).init(alloc);
var families: std.ArrayList([]const u8) = .empty;
var map: std.StringHashMap(std.ArrayListUnmanaged([]const u8)) = .init(alloc);
// Look up all available fonts
var disco = font.Discover.init();
@@ -123,7 +128,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
const gop = try map.getOrPut(family);
if (!gop.found_existing) {
try families.append(family);
try families.append(alloc, family);
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(alloc, full_name);
@@ -155,5 +160,6 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
try stdout.print("\n", .{});
}
try stdout.flush();
return 0;
}

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

View File

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

View File

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

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
// tests so we just do configfmt.format.
const stdout = std.io.getStdOut().writer();
try configfmt.format("", .{}, stdout);
var stdout: std.fs.File = .stdout();
var buffer: [4096]u8 = undefined;
var stdout_writer = stdout.writer(&buffer);
try configfmt.format(&stdout_writer.interface);
try stdout_writer.end();
return 0;
}

View File

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

View File

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

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

View File

@@ -40,8 +40,19 @@ pub fn run(alloc: std.mem.Allocator) !u8 {
try args.parse(Options, alloc, &opts, &iter);
}
const stdout = std.io.getStdOut().writer();
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const stdout = &stdout_writer.interface;
const result = runInner(alloc, opts, stdout);
try stdout_writer.end();
return result;
}
fn runInner(
alloc: std.mem.Allocator,
opts: Options,
stdout: *std.Io.Writer,
) !u8 {
var cfg = try Config.default(alloc);
defer cfg.deinit();
@@ -58,15 +69,9 @@ pub fn run(alloc: std.mem.Allocator) !u8 {
try cfg.finalize();
if (cfg._diagnostics.items().len > 0) {
var buf = std.ArrayList(u8).init(alloc);
defer buf.deinit();
for (cfg._diagnostics.items()) |diag| {
try diag.write(buf.writer());
try stdout.print("{s}\n", .{buf.items});
buf.clearRetainingCapacity();
try stdout.print("{f}\n", .{diag});
}
return 1;
}

View File

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