Zig 0.15: zig build GTK exe

pull/9004/head
Mitchell Hashimoto 2025-10-02 14:41:08 -07:00
parent cb295b84a0
commit d59d754e29
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
43 changed files with 321 additions and 256 deletions

View File

@ -305,19 +305,19 @@ const DerivedConfig = struct {
// Build all of our links
const links = links: {
var links = std.ArrayList(Link).init(alloc);
defer links.deinit();
var links: std.ArrayList(Link) = .empty;
defer links.deinit(alloc);
for (config.link.links.items) |link| {
var regex = try link.oniRegex();
errdefer regex.deinit();
try links.append(.{
try links.append(alloc, .{
.regex = regex,
.action = link.action,
.highlight = link.highlight,
});
}
break :links try links.toOwnedSlice();
break :links try links.toOwnedSlice(alloc);
};
errdefer {
for (links) |*link| link.regex.deinit();
@ -2493,7 +2493,7 @@ fn maybeHandleBinding(
self.keyboard.bindings = null;
// Attempt to perform the action
log.debug("key event binding flags={} action={}", .{
log.debug("key event binding flags={} action={f}", .{
leaf.flags,
action,
});
@ -5119,7 +5119,9 @@ fn writeScreenFile(
defer file.close();
// Screen.dumpString writes byte-by-byte, so buffer it
var buf_writer = std.io.bufferedWriter(file.writer());
var buf: [4096]u8 = undefined;
var file_writer = file.writer(&buf);
var buf_writer = &file_writer.interface;
// Write the scrollback contents. This requires a lock.
{
@ -5169,7 +5171,7 @@ fn writeScreenFile(
const br = sel.bottomRight(&self.io.terminal.screen);
try self.io.terminal.screen.dumpString(
buf_writer.writer(),
buf_writer,
.{
.tl = tl,
.br = br,

View File

@ -578,7 +578,7 @@ pub const SetTitle = struct {
value: @This(),
comptime _: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
try writer.print("{s}{{ {s} }}", .{ @typeName(@This()), value.title });
}
@ -602,7 +602,7 @@ pub const Pwd = struct {
value: @This(),
comptime _: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
try writer.print("{s}{{ {s} }}", .{ @typeName(@This()), value.pwd });
}
@ -630,7 +630,7 @@ pub const DesktopNotification = struct {
value: @This(),
comptime _: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
try writer.print("{s}{{ title: {s}, body: {s} }}", .{
@typeName(@This()),

View File

@ -27,7 +27,7 @@ pub fn getRuntimeVersion() std.SemanticVersion {
}
pub fn logVersion() void {
log.info("libadwaita version build={} runtime={}", .{
log.info("libadwaita version build={f} runtime={f}", .{
comptime_version,
getRuntimeVersion(),
});

View File

@ -166,7 +166,7 @@ pub fn main() !void {
/// Generate the icon resources. This works by looking up all the icons
/// specified by `icon_sizes` in `images/icons/`. They are asserted to exist
/// by trying to access the file.
fn genIcons(writer: anytype) !void {
fn genIcons(writer: *std.Io.Writer) !void {
try writer.print(
\\ <gresource prefix="{s}/icons">
\\
@ -208,7 +208,7 @@ fn genIcons(writer: anytype) !void {
}
/// Generate the resources at the root prefix.
fn genRoot(writer: anytype) !void {
fn genRoot(writer: *std.Io.Writer) !void {
try writer.print(
\\ <gresource prefix="{s}">
\\
@ -240,7 +240,7 @@ fn genRoot(writer: anytype) !void {
/// assuming these will be
fn genUi(
alloc: Allocator,
writer: anytype,
writer: *std.Io.Writer,
files: *const std.ArrayListUnmanaged([]const u8),
) !void {
try writer.print(

View File

@ -50,7 +50,7 @@ pub fn init(
) orelse "";
if (!std.mem.eql(u8, original, current)) break :transient current;
alloc.free(current);
std.time.sleep(25 * std.time.ns_per_ms);
std.Thread.sleep(25 * std.time.ns_per_ms);
};
errdefer alloc.free(transient);
log.info("transient scope created cgroup={s}", .{transient});
@ -101,21 +101,21 @@ fn enableControllers(alloc: Allocator, cgroup: []const u8) !void {
defer alloc.free(raw);
// Build our string builder for enabling all controllers
var builder = std.ArrayList(u8).init(alloc);
var builder: std.Io.Writer.Allocating = .init(alloc);
defer builder.deinit();
// Controllers are space-separated
var it = std.mem.splitScalar(u8, raw, ' ');
while (it.next()) |controller| {
try builder.append('+');
try builder.appendSlice(controller);
if (it.rest().len > 0) try builder.append(' ');
try builder.writer.writeByte('+');
try builder.writer.writeAll(controller);
if (it.rest().len > 0) try builder.writer.writeByte(' ');
}
// Enable them all
try internal_os.cgroup.configureControllers(
cgroup,
builder.items,
builder.written(),
);
}

View File

@ -1044,7 +1044,9 @@ pub const Application = extern struct {
defer file.close();
log.info("loading gtk-custom-css path={s}", .{path});
const contents = try file.reader().readAllAlloc(
var buf: [4096]u8 = undefined;
var reader = file.reader(&buf);
const contents = try reader.interface.readAlloc(
alloc,
5 * 1024 * 1024, // 5MB,
);
@ -1115,8 +1117,8 @@ pub const Application = extern struct {
// This should really never, never happen. Its not critical enough
// to actually crash, but this is a bug somewhere. An accelerator
// for a trigger can't possibly be more than 1024 bytes.
error.NoSpaceLeft => {
log.warn("accelerator somehow longer than 1024 bytes: {}", .{trigger});
error.WriteFailed => {
log.warn("accelerator somehow longer than 1024 bytes: {f}", .{trigger});
return;
},
};

View File

@ -485,10 +485,11 @@ const Command = extern struct {
const command = priv.command orelse return null;
priv.action_key = std.fmt.allocPrintZ(
priv.action_key = std.fmt.allocPrintSentinel(
priv.arena.allocator(),
"{}",
"{f}",
.{command.action},
0,
) catch null;
return priv.action_key;

View File

@ -188,9 +188,9 @@ pub const GlobalShortcuts = extern struct {
// If there isn't space to translate the trigger, then our
// buffer might be too small (but 1024 is insane!). In any case
// we don't want to stop registering globals.
error.NoSpaceLeft => {
error.WriteFailed => {
log.warn(
"buffer too small to translate trigger, ignoring={}",
"buffer too small to translate trigger, ignoring={f}",
.{entry.key_ptr.*},
);
continue;
@ -257,7 +257,7 @@ pub const GlobalShortcuts = extern struct {
const trigger = entry.key_ptr.*.ptr;
const action = std.fmt.bufPrintZ(
&action_buf,
"{}",
"{f}",
.{entry.value_ptr.*},
) catch continue;

View File

@ -268,7 +268,7 @@ pub const SplitTree = extern struct {
);
defer new_tree.deinit();
log.debug(
"new split at={} direction={} old_tree={} new_tree={}",
"new split at={} direction={} old_tree={f} new_tree={f}",
.{ handle, direction, old_tree, &new_tree },
);

View File

@ -2117,13 +2117,11 @@ pub const Surface = extern struct {
const alloc = Application.default().allocator();
if (ext.gValueHolds(value, gdk.FileList.getGObjectType())) {
var data = std.ArrayList(u8).init(alloc);
defer data.deinit();
var stream: std.Io.Writer.Allocating = .init(alloc);
defer stream.deinit();
var shell_escape_writer: internal_os.ShellEscapeWriter(std.ArrayList(u8).Writer) = .{
.child_writer = data.writer(),
};
const writer = shell_escape_writer.writer();
var shell_escape_writer: internal_os.ShellEscapeWriter = .init(&stream.writer);
const writer = &shell_escape_writer.writer;
const list: ?*glib.SList = list: {
const unboxed = value.getBoxed() orelse return 0;
@ -2151,7 +2149,7 @@ pub const Surface = extern struct {
}
}
const string = data.toOwnedSliceSentinel(0) catch |err| {
const string = stream.toOwnedSliceSentinel(0) catch |err| {
log.err("unable to convert to a slice: {}", .{err});
return 0;
};
@ -2164,13 +2162,11 @@ pub const Surface = extern struct {
const object = value.getObject() orelse return 0;
const file = gobject.ext.cast(gio.File, object) orelse return 0;
const path = file.getPath() orelse return 0;
var data = std.ArrayList(u8).init(alloc);
defer data.deinit();
var stream: std.Io.Writer.Allocating = .init(alloc);
defer stream.deinit();
var shell_escape_writer: internal_os.ShellEscapeWriter(std.ArrayList(u8).Writer) = .{
.child_writer = data.writer(),
};
const writer = shell_escape_writer.writer();
var shell_escape_writer: internal_os.ShellEscapeWriter = .init(&stream.writer);
const writer = &shell_escape_writer.writer;
writer.writeAll(std.mem.span(path)) catch |err| {
log.err("unable to write path to buffer: {}", .{err});
return 0;
@ -2180,7 +2176,7 @@ pub const Surface = extern struct {
return 0;
};
const string = data.toOwnedSliceSentinel(0) catch |err| {
const string = stream.toOwnedSliceSentinel(0) catch |err| {
log.err("unable to convert to a slice: {}", .{err});
return 0;
};

View File

@ -405,22 +405,21 @@ pub const Tab = extern struct {
};
// Use an allocator to build up our string as we write it.
var buf: std.ArrayList(u8) = .init(Application.default().allocator());
var buf: std.Io.Writer.Allocating = .init(Application.default().allocator());
defer buf.deinit();
const writer = buf.writer();
// If our bell is ringing, then we prefix the bell icon to the title.
if (bell_ringing and config.@"bell-features".title) {
writer.writeAll("🔔 ") catch {};
buf.writer.writeAll("🔔 ") catch {};
}
// If we're zoomed, prefix with the magnifying glass emoji.
if (zoomed) {
writer.writeAll("🔍 ") catch {};
buf.writer.writeAll("🔍 ") catch {};
}
writer.writeAll(plain) catch return glib.ext.dupeZ(u8, plain);
return glib.ext.dupeZ(u8, buf.items);
buf.writer.writeAll(plain) catch return glib.ext.dupeZ(u8, plain);
return glib.ext.dupeZ(u8, buf.written());
}
const C = Common(Self, Private);

View File

@ -26,7 +26,7 @@ pub fn getRuntimeVersion() std.SemanticVersion {
}
pub fn logVersion() void {
log.info("GTK version build={} runtime={}", .{
log.info("GTK version build={f} runtime={f}", .{
comptime_version,
getRuntimeVersion(),
});

View File

@ -12,9 +12,8 @@ const winproto = @import("winproto.zig");
pub fn accelFromTrigger(
buf: []u8,
trigger: input.Binding.Trigger,
) error{NoSpaceLeft}!?[:0]const u8 {
var buf_stream = std.io.fixedBufferStream(buf);
const writer = buf_stream.writer();
) error{WriteFailed}!?[:0]const u8 {
var writer: std.Io.Writer = .fixed(buf);
// Modifiers
if (trigger.mods.shift) try writer.writeAll("<Shift>");
@ -23,11 +22,11 @@ pub fn accelFromTrigger(
if (trigger.mods.super) try writer.writeAll("<Super>");
// Write our key
if (!try writeTriggerKey(writer, trigger)) return null;
if (!try writeTriggerKey(&writer, trigger)) return null;
// We need to make the string null terminated.
try writer.writeByte(0);
const slice = buf_stream.getWritten();
const slice = writer.buffered();
return slice[0 .. slice.len - 1 :0];
}
@ -36,9 +35,8 @@ pub fn accelFromTrigger(
pub fn xdgShortcutFromTrigger(
buf: []u8,
trigger: input.Binding.Trigger,
) error{NoSpaceLeft}!?[:0]const u8 {
var buf_stream = std.io.fixedBufferStream(buf);
const writer = buf_stream.writer();
) error{WriteFailed}!?[:0]const u8 {
var writer: std.Io.Writer = .fixed(buf);
// Modifiers
if (trigger.mods.shift) try writer.writeAll("SHIFT+");
@ -52,15 +50,18 @@ pub fn xdgShortcutFromTrigger(
// to *X11's* keysyms (which I assume is a subset of libxkbcommon's).
// I haven't been able to any evidence to back up that assumption but
// this works for now
if (!try writeTriggerKey(writer, trigger)) return null;
if (!try writeTriggerKey(&writer, trigger)) return null;
// We need to make the string null terminated.
try writer.writeByte(0);
const slice = buf_stream.getWritten();
const slice = writer.buffered();
return slice[0 .. slice.len - 1 :0];
}
fn writeTriggerKey(writer: anytype, trigger: input.Binding.Trigger) error{NoSpaceLeft}!bool {
fn writeTriggerKey(
writer: *std.Io.Writer,
trigger: input.Binding.Trigger,
) error{WriteFailed}!bool {
switch (trigger.key) {
.physical => |k| {
const keyval = keyvalFromKey(k) orelse return false;

View File

@ -21,6 +21,8 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty
.omit_frame_pointer = cfg.strip,
.unwind_tables = if (cfg.strip) .none else .sync,
}),
// Crashes on x86_64 self-hosted on 0.15.1
.use_llvm = true,
});
const install_step = b.addInstallArtifact(exe, .{});

View File

@ -2,12 +2,15 @@ const std = @import("std");
const gen = @import("mdgen.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const alloc = gpa.allocator();
const writer = std.io.getStdOut().writer();
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const writer = &stdout_writer.interface;
try gen.substitute(alloc, @embedFile("ghostty_1_header.md"), writer);
try gen.genActions(writer);
try gen.genConfig(writer, true);
try gen.substitute(alloc, @embedFile("ghostty_1_footer.md"), writer);
try writer.flush();
}

View File

@ -2,12 +2,15 @@ const std = @import("std");
const gen = @import("mdgen.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
const alloc = gpa.allocator();
const output = std.io.getStdOut().writer();
try gen.substitute(alloc, @embedFile("ghostty_5_header.md"), output);
try gen.genConfig(output, false);
try gen.genKeybindActions(output);
try gen.substitute(alloc, @embedFile("ghostty_5_footer.md"), output);
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const writer = &stdout_writer.interface;
try gen.substitute(alloc, @embedFile("ghostty_5_header.md"), writer);
try gen.genConfig(writer, false);
try gen.genKeybindActions(writer);
try gen.substitute(alloc, @embedFile("ghostty_5_footer.md"), writer);
try writer.flush();
}

View File

@ -5,7 +5,7 @@ const Config = @import("../../config/Config.zig");
const Action = @import("../../cli/ghostty.zig").Action;
const KeybindAction = @import("../../input/Binding.zig").Action;
pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: anytype) !void {
pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: *std.Io.Writer) !void {
const output = try alloc.alloc(u8, std.mem.replacementSize(
u8,
input,
@ -18,7 +18,7 @@ pub fn substitute(alloc: std.mem.Allocator, input: []const u8, writer: anytype)
try writer.writeAll(output);
}
pub fn genConfig(writer: anytype, cli: bool) !void {
pub fn genConfig(writer: *std.Io.Writer, cli: bool) !void {
try writer.writeAll(
\\
\\# CONFIGURATION OPTIONS
@ -48,7 +48,7 @@ pub fn genConfig(writer: anytype, cli: bool) !void {
}
}
pub fn genActions(writer: anytype) !void {
pub fn genActions(writer: *std.Io.Writer) !void {
try writer.writeAll(
\\
\\# COMMAND LINE ACTIONS
@ -83,7 +83,7 @@ pub fn genActions(writer: anytype) !void {
}
}
pub fn genKeybindActions(writer: anytype) !void {
pub fn genKeybindActions(writer: *std.Io.Writer) !void {
try writer.writeAll(
\\
\\# KEYBIND ACTIONS

View File

@ -19,18 +19,18 @@ pub const completions = comptimeGenerateBashCompletions();
fn comptimeGenerateBashCompletions() []const u8 {
comptime {
@setEvalBranchQuota(50000);
var counter = std.io.countingWriter(std.io.null_writer);
try writeBashCompletions(&counter.writer());
var counter: std.Io.Writer.Discarding = .init(&.{});
try writeBashCompletions(&counter.writer);
var buf: [counter.bytes_written]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
try writeBashCompletions(stream.writer());
var buf: [counter.count]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try writeBashCompletions(&writer);
const final = buf;
return final[0..stream.getWritten().len];
return final[0..writer.end];
}
}
fn writeBashCompletions(writer: anytype) !void {
fn writeBashCompletions(writer: *std.Io.Writer) !void {
const pad1 = " ";
const pad2 = pad1 ++ pad1;
const pad3 = pad2 ++ pad1;

View File

@ -11,18 +11,18 @@ pub const completions = comptimeGenerateCompletions();
fn comptimeGenerateCompletions() []const u8 {
comptime {
@setEvalBranchQuota(50000);
var counter = std.io.countingWriter(std.io.null_writer);
try writeCompletions(&counter.writer());
var counter: std.Io.Writer.Discarding = .init(&.{});
try writeCompletions(&counter.writer);
var buf: [counter.bytes_written]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
try writeCompletions(stream.writer());
var buf: [counter.count]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try writeCompletions(&writer);
const final = buf;
return final[0..stream.getWritten().len];
return final[0..writer.end];
}
}
fn writeCompletions(writer: anytype) !void {
fn writeCompletions(writer: *std.Io.Writer) !void {
{
try writer.writeAll("set -l commands \"");
var count: usize = 0;

View File

@ -72,7 +72,7 @@ fn comptimeGenSyntax() []const u8 {
}
/// Writes the syntax file to the given writer.
fn writeSyntax(writer: anytype) !void {
fn writeSyntax(writer: *std.Io.Writer) !void {
try writer.writeAll(
\\" Vim syntax file
\\" Language: Ghostty config file

View File

@ -12,18 +12,18 @@ const equals_required = "=-:::";
fn comptimeGenerateZshCompletions() []const u8 {
comptime {
@setEvalBranchQuota(50000);
var counter = std.io.countingWriter(std.io.null_writer);
try writeZshCompletions(&counter.writer());
var counter: std.Io.Writer.Discarding = .init(&.{});
try writeZshCompletions(&counter.writer);
var buf: [counter.bytes_written]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf);
try writeZshCompletions(stream.writer());
var buf: [counter.count]u8 = undefined;
var writer: std.Io.Writer = .fixed(&buf);
try writeZshCompletions(&writer);
const final = buf;
return final[0..stream.getWritten().len];
return final[0..writer.end];
}
}
fn writeZshCompletions(writer: anytype) !void {
fn writeZshCompletions(writer: *std.Io.Writer) !void {
try writer.writeAll(
\\#compdef ghostty
\\

View File

@ -355,7 +355,7 @@ pub fn clear(self: *Atlas) void {
/// swapped because PPM expects RGB. This would be
/// easy enough to fix so next time someone needs
/// to debug a color atlas they should fix it.
pub fn dump(self: Atlas, writer: anytype) !void {
pub fn dump(self: Atlas, writer: *std.Io.Writer) !void {
try writer.print(
\\P{c}
\\{d} {d}

View File

@ -106,7 +106,7 @@ fn FixedPoint(comptime T: type, int_bits: u64, frac_bits: u64) type {
self: Self,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
_ = fmt;
_ = options;
@ -176,7 +176,7 @@ pub const SFNT = struct {
self: OffsetSubtable,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
_ = fmt;
_ = options;
@ -210,7 +210,7 @@ pub const SFNT = struct {
self: TableRecord,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
_ = fmt;
_ = options;

View File

@ -201,7 +201,7 @@ pub const Feature = struct {
self: Feature,
comptime layout: []const u8,
opts: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
_ = layout;
_ = opts;
@ -262,7 +262,7 @@ pub const FeatureList = struct {
self: FeatureList,
comptime layout: []const u8,
opts: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
for (self.features.items, 0..) |feature, i| {
try feature.format(layout, opts, writer);

View File

@ -600,6 +600,7 @@ fn renderModesWindow(self: *Inspector) void {
const t = self.surface.renderer_state.terminal;
inline for (@typeInfo(terminal.Mode).@"enum".fields) |field| {
@setEvalBranchQuota(6000);
const tag: terminal.modes.ModeTag = @bitCast(@as(terminal.modes.ModeTag.Backing, field.value));
cimgui.c.igTableNextRow(cimgui.c.ImGuiTableRowFlags_None, 0);

View File

@ -43,9 +43,9 @@ pub const VTEvent = struct {
) !VTEvent {
var md = Metadata.init(alloc);
errdefer md.deinit();
var buf = std.ArrayList(u8).init(alloc);
var buf: std.Io.Writer.Allocating = .init(alloc);
defer buf.deinit();
try encodeAction(alloc, buf.writer(), &md, action);
try encodeAction(alloc, &buf.writer, &md, action);
const str = try buf.toOwnedSliceSentinel(0);
errdefer alloc.free(str);
@ -115,7 +115,7 @@ pub const VTEvent = struct {
/// Encode a parser action as a string that we show in the logs.
fn encodeAction(
alloc: Allocator,
writer: anytype,
writer: *std.Io.Writer,
md: *Metadata,
action: terminal.Parser.Action,
) !void {
@ -125,16 +125,16 @@ pub const VTEvent = struct {
.csi_dispatch => |v| try encodeCSI(writer, v),
.esc_dispatch => |v| try encodeEsc(writer, v),
.osc_dispatch => |v| try encodeOSC(alloc, writer, md, v),
else => try writer.print("{}", .{action}),
else => try writer.print("{f}", .{action}),
}
}
fn encodePrint(writer: anytype, action: terminal.Parser.Action) !void {
fn encodePrint(writer: *std.Io.Writer, action: terminal.Parser.Action) !void {
const ch = action.print;
try writer.print("'{u}' (U+{X})", .{ ch, ch });
}
fn encodeExecute(writer: anytype, action: terminal.Parser.Action) !void {
fn encodeExecute(writer: *std.Io.Writer, action: terminal.Parser.Action) !void {
const ch = action.execute;
switch (ch) {
0x00 => try writer.writeAll("NUL"),
@ -158,7 +158,7 @@ pub const VTEvent = struct {
try writer.print(" (0x{X})", .{ch});
}
fn encodeCSI(writer: anytype, csi: terminal.Parser.Action.CSI) !void {
fn encodeCSI(writer: *std.Io.Writer, csi: terminal.Parser.Action.CSI) !void {
for (csi.intermediates) |v| try writer.print("{c} ", .{v});
for (csi.params, 0..) |v, i| {
if (i != 0) try writer.writeByte(';');
@ -168,14 +168,14 @@ pub const VTEvent = struct {
try writer.writeByte(csi.final);
}
fn encodeEsc(writer: anytype, esc: terminal.Parser.Action.ESC) !void {
fn encodeEsc(writer: *std.Io.Writer, esc: terminal.Parser.Action.ESC) !void {
for (esc.intermediates) |v| try writer.print("{c} ", .{v});
try writer.writeByte(esc.final);
}
fn encodeOSC(
alloc: Allocator,
writer: anytype,
writer: *std.Io.Writer,
md: *Metadata,
osc: terminal.osc.Command,
) !void {
@ -265,10 +265,10 @@ pub const VTEvent = struct {
const s = if (field.type == void)
try alloc.dupeZ(u8, tag_name)
else
try std.fmt.allocPrintZ(alloc, "{s}={}", .{
try std.fmt.allocPrintSentinel(alloc, "{s}={}", .{
tag_name,
@field(value, field.name),
});
}, 0);
try md.put(key, s);
}
@ -283,7 +283,7 @@ pub const VTEvent = struct {
else => switch (Value) {
u8, u16 => try md.put(
key,
try std.fmt.allocPrintZ(alloc, "{}", .{value}),
try std.fmt.allocPrintSentinel(alloc, "{}", .{value}, 0),
),
[]const u8,

View File

@ -33,7 +33,9 @@ pub fn main() !void {
const action = action_ orelse return error.NoAction;
// Our output always goes to stdout.
const writer = std.io.getStdOut().writer();
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const writer = &stdout_writer.interface;
switch (action) {
.bash => try writer.writeAll(@import("extra/bash.zig").completions),
.fish => try writer.writeAll(@import("extra/fish.zig").completions),

View File

@ -35,7 +35,9 @@ pub fn main() !MainReturn {
// a global is because the C API needs to be able to access this state;
// no other Zig code should EVER access the global state.
state.init() catch |err| {
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;
defer posix.exit(1);
const ErrSet = @TypeOf(err) || error{Unknown};
switch (@as(ErrSet, @errorCast(err))) {
@ -54,6 +56,7 @@ pub fn main() !MainReturn {
else => try stderr.print("invalid CLI invocation err={}\n", .{err}),
}
try stderr.flush();
};
defer state.deinit();
const alloc = state.alloc;
@ -154,8 +157,12 @@ fn logFn(
.stderr => {
// Always try default to send to stderr
const stderr = std.io.getStdErr().writer();
nosuspend stderr.print(level_txt ++ prefix ++ format ++ "\n", args) catch return;
var buffer: [1024]u8 = undefined;
var stderr = std.fs.File.stderr().writer(&buffer);
const writer = &stderr.interface;
nosuspend writer.print(level_txt ++ prefix ++ format ++ "\n", args) catch return;
// TODO: Do we want to use flushless stderr in the future?
writer.flush() catch {};
},
}
}

View File

@ -19,8 +19,9 @@ pub fn current(alloc: Allocator, pid: std.os.linux.pid_t) !?[]const u8 {
defer file.close();
// Read it all into memory -- we don't expect this file to ever be that large.
var buf_reader = std.io.bufferedReader(file.reader());
const contents = try buf_reader.reader().readAllAlloc(
var reader_buf: [4096]u8 = undefined;
var reader = file.reader(&reader_buf);
const contents = try reader.interface.readAlloc(
alloc,
1 * 1024 * 1024, // 1MB
);
@ -52,7 +53,11 @@ pub fn create(
);
const file = try std.fs.cwd().openFile(pid_path, .{ .mode = .write_only });
defer file.close();
try file.writer().print("{}", .{pid});
var file_buf: [64]u8 = undefined;
var writer = file.writer(&file_buf);
try writer.interface.print("{}", .{pid});
try writer.interface.flush();
}
}
@ -182,8 +187,9 @@ pub fn controllers(alloc: Allocator, cgroup: []const u8) ![]const u8 {
// Read it all into memory -- we don't expect this file to ever
// be that large.
var buf_reader = std.io.bufferedReader(file.reader());
const contents = try buf_reader.reader().readAllAlloc(
var reader_buf: [4096]u8 = undefined;
var reader = file.reader(&reader_buf);
const contents = try reader.interface.readAlloc(
alloc,
1 * 1024 * 1024, // 1MB
);
@ -213,7 +219,10 @@ pub fn configureControllers(
defer file.close();
// Write
try file.writer().writeAll(v);
var writer_buf: [4096]u8 = undefined;
var writer = file.writer(&writer_buf);
try writer.interface.writeAll(v);
try writer.interface.flush();
}
pub const Limit = union(enum) {
@ -242,5 +251,8 @@ pub fn configureLimit(cgroup: []const u8, limit: Limit) !void {
defer file.close();
// Write our limit in bytes
try file.writer().print("{}", .{size});
var writer_buf: [4096]u8 = undefined;
var writer = file.writer(&writer_buf);
try writer.interface.print("{}", .{size});
try writer.interface.flush();
}

View File

@ -1,110 +1,121 @@
const std = @import("std");
const testing = std.testing;
const Writer = std.Io.Writer;
/// Writer that escapes characters that shells treat specially to reduce the
/// risk of injection attacks or other such weirdness. Specifically excludes
/// linefeeds so that they can be used to delineate lists of file paths.
///
/// T should be a Zig type that follows the `std.io.Writer` interface.
pub fn ShellEscapeWriter(comptime T: type) type {
return struct {
child_writer: T,
/// T should be a Zig type that follows the `std.Io.Writer` interface.
pub const ShellEscapeWriter = struct {
writer: Writer,
child: *Writer,
fn write(self: *ShellEscapeWriter(T), data: []const u8) error{Error}!usize {
var count: usize = 0;
for (data) |byte| {
const buf = switch (byte) {
'\\',
'"',
'\'',
'$',
'`',
'*',
'?',
' ',
'|',
'(',
')',
=> &[_]u8{ '\\', byte },
else => &[_]u8{byte},
};
self.child_writer.writeAll(buf) catch return error.Error;
count += 1;
}
return count;
pub fn init(child: *Writer) ShellEscapeWriter {
return .{
.writer = .{
// TODO: Actually use a buffer here
.buffer = &.{},
.vtable = &.{ .drain = ShellEscapeWriter.drain },
},
.child = child,
};
}
fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
const self: *ShellEscapeWriter = @fieldParentPtr("writer", w);
// TODO: This is a very naive implementation and does not really make
// full use of the post-Writergate API. However, since we know that
// this is going into an Allocating writer anyways, we can be a bit
// less strict here.
var count: usize = 0;
for (data[0 .. data.len - 1]) |chunk| try self.writeEscaped(chunk, &count);
for (0..splat) |_| try self.writeEscaped(data[data.len], &count);
return count;
}
fn writeEscaped(
self: *ShellEscapeWriter,
s: []const u8,
count: *usize,
) Writer.Error!void {
for (s) |byte| {
const buf = switch (byte) {
'\\',
'"',
'\'',
'$',
'`',
'*',
'?',
' ',
'|',
'(',
')',
=> &[_]u8{ '\\', byte },
else => &[_]u8{byte},
};
try self.child.writeAll(buf);
count.* += 1;
}
const Writer = std.io.Writer(*ShellEscapeWriter(T), error{Error}, write);
pub fn init(child_writer: T) ShellEscapeWriter(T) {
return .{ .child_writer = child_writer };
}
pub fn writer(self: *ShellEscapeWriter(T)) Writer {
return .{ .context = self };
}
};
}
}
};
test "shell escape 1" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("abc");
try testing.expectEqualStrings("abc", fmt.getWritten());
var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer };
try shell.writer.writeAll("abc");
try testing.expectEqualStrings("abc", writer.buffered());
}
test "shell escape 2" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a c");
try testing.expectEqualStrings("a\\ c", fmt.getWritten());
var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer };
try shell.writer.writeAll("a c");
try testing.expectEqualStrings("a\\ c", writer.buffered());
}
test "shell escape 3" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a?c");
try testing.expectEqualStrings("a\\?c", fmt.getWritten());
var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer };
try shell.writer.writeAll("a?c");
try testing.expectEqualStrings("a\\?c", writer.buffered());
}
test "shell escape 4" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a\\c");
try testing.expectEqualStrings("a\\\\c", fmt.getWritten());
var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer };
try shell.writer.writeAll("a\\c");
try testing.expectEqualStrings("a\\\\c", writer.buffered());
}
test "shell escape 5" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a|c");
try testing.expectEqualStrings("a\\|c", fmt.getWritten());
var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer };
try shell.writer.writeAll("a|c");
try testing.expectEqualStrings("a\\|c", writer.buffered());
}
test "shell escape 6" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a\"c");
try testing.expectEqualStrings("a\\\"c", fmt.getWritten());
var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer };
try shell.writer.writeAll("a\"c");
try testing.expectEqualStrings("a\\\"c", writer.buffered());
}
test "shell escape 7" {
var buf: [128]u8 = undefined;
var fmt = std.io.fixedBufferStream(&buf);
var shell: ShellEscapeWriter(@TypeOf(fmt).Writer) = .{ .child_writer = fmt.writer() };
const writer = shell.writer();
try writer.writeAll("a(1)");
try testing.expectEqualStrings("a\\(1\\)", fmt.getWritten());
var writer: std.Io.Writer = .fixed(&buf);
var shell: ShellEscapeWriter = .{ .child_writer = &writer };
try shell.writer.writeAll("a(1)");
try testing.expectEqualStrings("a\\(1\\)", writer.buffered());
}

View File

@ -216,7 +216,7 @@ const PosixPty = struct {
// Reset our signals
var sa: posix.Sigaction = .{
.handler = .{ .handler = posix.SIG.DFL },
.mask = posix.empty_sigset,
.mask = posix.sigemptyset(),
.flags = 0,
};
posix.sigaction(posix.SIG.ABRT, &sa, null);

View File

@ -38,8 +38,8 @@ pub fn loadFromFiles(
paths: configpkg.RepeatablePath,
target: Target,
) ![]const [:0]const u8 {
var list = std.ArrayList([:0]const u8).init(alloc_gpa);
defer list.deinit();
var list: std.ArrayList([:0]const u8) = .empty;
defer list.deinit(alloc_gpa);
errdefer for (list.items) |shader| alloc_gpa.free(shader);
for (paths.value.items) |item| {
@ -56,10 +56,10 @@ pub fn loadFromFiles(
return err;
};
log.info("loaded custom shader path={s}", .{path});
try list.append(shader);
try list.append(alloc_gpa, shader);
}
return try list.toOwnedSlice();
return try list.toOwnedSlice(alloc_gpa);
}
/// Load a single shader from a file and convert it to the target language
@ -73,34 +73,35 @@ pub fn loadFromFile(
defer arena.deinit();
const alloc = arena.allocator();
// Load the shader file
const cwd = std.fs.cwd();
const file = try cwd.openFile(path, .{});
defer file.close();
// Read it all into memory -- we don't expect shaders to be large.
var buf_reader = std.io.bufferedReader(file.reader());
const src = try buf_reader.reader().readAllAlloc(
alloc,
4 * 1024 * 1024, // 4MB
);
const src = src: {
// Load the shader file
const cwd = std.fs.cwd();
const file = try cwd.openFile(path, .{});
defer file.close();
var buf: [4096]u8 = undefined;
var reader = file.reader(&buf);
break :src try reader.interface.readAlloc(
alloc,
4 * 1024 * 1024, // 4MB
);
};
// Convert to full GLSL
const glsl: [:0]const u8 = glsl: {
var list = std.ArrayList(u8).init(alloc);
try glslFromShader(list.writer(), src);
try list.append(0);
break :glsl list.items[0 .. list.items.len - 1 :0];
var stream: std.Io.Writer.Allocating = .init(alloc);
try glslFromShader(&stream.writer, src);
try stream.writer.writeByte(0);
break :glsl stream.written()[0 .. stream.written().len - 1 :0];
};
// Convert to SPIR-V
const spirv: []const u8 = spirv: {
// SpirV pointer must be aligned to 4 bytes since we expect
// a slice of words.
var list = std.ArrayListAligned(u8, @alignOf(u32)).init(alloc);
var stream: std.Io.Writer.Allocating = .init(alloc);
var errlog: SpirvLog = .{ .alloc = alloc };
defer errlog.deinit();
spirvFromGlsl(list.writer(), &errlog, glsl) catch |err| {
spirvFromGlsl(&stream.writer, &errlog, glsl) catch |err| {
if (errlog.info.len > 0 or errlog.debug.len > 0) {
log.warn("spirv error path={s} info={s} debug={s}", .{
path,
@ -111,6 +112,11 @@ pub fn loadFromFile(
return err;
};
// SpirV pointer must be aligned to 4 bytes since we expect
// a slice of words.
var list: std.ArrayListAligned(u8, .of(u32)) = .empty;
try list.appendSlice(alloc, stream.written());
break :spirv list.items;
};

View File

@ -23,7 +23,7 @@ pub fn destroy(self: *Ascii, alloc: Allocator) void {
alloc.destroy(self);
}
pub fn run(self: *Ascii, writer: anytype, rand: std.Random) !void {
pub fn run(self: *Ascii, writer: *std.Io.Writer, rand: std.Random) !void {
_ = self;
var gen: synthetic.Bytes = .{

View File

@ -29,7 +29,7 @@ pub fn destroy(self: *Osc, alloc: Allocator) void {
alloc.destroy(self);
}
pub fn run(self: *Osc, writer: anytype, rand: std.Random) !void {
pub fn run(self: *Osc, writer: *std.Io.Writer, rand: std.Random) !void {
var gen: synthetic.Osc = .{
.rand = rand,
.p_valid = self.opts.@"p-valid",

View File

@ -23,7 +23,7 @@ pub fn destroy(self: *Utf8, alloc: Allocator) void {
alloc.destroy(self);
}
pub fn run(self: *Utf8, writer: anytype, rand: std.Random) !void {
pub fn run(self: *Utf8, writer: *std.Io.Writer, rand: std.Random) !void {
_ = self;
var gen: synthetic.Utf8 = .{

View File

@ -2723,7 +2723,7 @@ pub fn encodeUtf8(
/// 1 | etc.| | 4
/// +-----+ :
/// +--------+
pub fn diagram(self: *const PageList, writer: anytype) !void {
pub fn diagram(self: *const PageList, writer: *std.Io.Writer) !void {
const active_pin = self.getTopLeft(.active);
var active = false;

View File

@ -239,6 +239,11 @@ pub fn deinit(self: *Terminal, alloc: Allocator) void {
self.* = undefined;
}
/// The general allocator we should use for this terminal.
fn gpa(self: *Terminal) Allocator {
return self.screen.alloc;
}
/// Print UTF-8 encoded string to the terminal.
pub fn printString(self: *Terminal, str: []const u8) !void {
const view = try std.unicode.Utf8View.init(str);
@ -2531,7 +2536,7 @@ pub fn resize(
/// Set the pwd for the terminal.
pub fn setPwd(self: *Terminal, pwd: []const u8) !void {
self.pwd.clearRetainingCapacity();
try self.pwd.appendSlice(pwd);
try self.pwd.appendSlice(self.gpa(), pwd);
}
/// Returns the pwd for the terminal, if any. The memory is owned by the

View File

@ -276,7 +276,7 @@ pub const Response = struct {
placement_id: u32 = 0,
message: []const u8 = "OK",
pub fn encode(self: Response, writer: anytype) !void {
pub fn encode(self: Response, writer: *std.Io.Writer) !void {
// We only encode a result if we have either an id or an image number.
if (self.id == 0 and self.image_number == 0) return;

View File

@ -274,7 +274,7 @@ pub const Terminator = enum {
self: Terminator,
comptime _: []const u8,
_: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
try writer.writeAll(self.string());
}

View File

@ -60,7 +60,7 @@ pub const Style = struct {
self: Color,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
_ = fmt;
_ = options;
@ -228,7 +228,7 @@ pub const Style = struct {
self: Style,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
writer: *std.Io.Writer,
) !void {
_ = fmt;
_ = options;

View File

@ -43,7 +43,7 @@ pub const Capability = struct {
/// Encode as a terminfo source file. The encoding is always done in a
/// human-readable format with whitespace. Fields are always written in the
/// order of the slices on this struct; this will not do any reordering.
pub fn encode(self: Source, writer: anytype) !void {
pub fn encode(self: Source, writer: *std.Io.Writer) !void {
// Encode the names in the order specified
for (self.names, 0..) |name, i| {
if (i != 0) try writer.writeAll("|");

View File

@ -591,6 +591,17 @@ const Subprocess = struct {
flatpak_command: ?FlatpakHostCommand = null,
linux_cgroup: Command.LinuxCgroup = Command.linux_cgroup_default,
const ArgsFormatter = struct {
args: []const [:0]const u8,
pub fn format(this: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
for (this.args, 0..) |a, i| {
if (i > 0) try writer.writeAll(", ");
try writer.print("`{s}`", .{a});
}
}
};
/// Initialize the subprocess. This will NOT start it, this only sets
/// up the internal state necessary to start it later.
pub fn init(gpa: Allocator, cfg: Config) !Subprocess {
@ -897,7 +908,7 @@ const Subprocess = struct {
self.pty = null;
};
log.debug("starting command command={s}", .{self.args});
log.debug("starting command command={f}", .{ArgsFormatter{ .args = self.args }});
// If we can't access the cwd, then don't set any cwd and inherit.
// This is important because our cwd can be set by the shell (OSC 7)
@ -1157,7 +1168,7 @@ const Subprocess = struct {
const res = posix.waitpid(pid, std.c.W.NOHANG);
log.debug("waitpid result={}", .{res.pid});
if (res.pid != 0) break;
std.time.sleep(10 * std.time.ns_per_ms);
std.Thread.sleep(10 * std.time.ns_per_ms);
}
},
}
@ -1180,7 +1191,7 @@ const Subprocess = struct {
const pgid = c.getpgid(pid);
if (pgid == my_pgid) {
log.warn("pgid is our own, retrying", .{});
std.time.sleep(10 * std.time.ns_per_ms);
std.Thread.sleep(10 * std.time.ns_per_ms);
continue;
}

View File

@ -310,11 +310,11 @@ pub const StreamHandler = struct {
.kitty => |*kitty_cmd| {
if (self.terminal.kittyGraphics(self.alloc, kitty_cmd)) |resp| {
var buf: [1024]u8 = undefined;
var buf_stream = std.io.fixedBufferStream(&buf);
try resp.encode(buf_stream.writer());
const final = buf_stream.getWritten();
var writer: std.Io.Writer = .fixed(&buf);
try resp.encode(&writer);
const final = writer.buffered();
if (final.len > 2) {
log.debug("kitty graphics response: {s}", .{std.fmt.fmtSliceHexLower(final)});
log.debug("kitty graphics response: {x}", .{final});
self.messageWriter(try termio.Message.writeReq(self.alloc, final));
}
}
@ -1141,7 +1141,7 @@ pub const StreamHandler = struct {
// We need to unescape the path. We first try to unescape onto
// the stack and fall back to heap allocation if we have to.
var pathBuf: [1024]u8 = undefined;
var path_buf: [1024]u8 = undefined;
const path, const heap = path: {
// Get the raw string of the URI. Its unclear to me if the various
// tags of this enum guarantee no percent-encoding so we just
@ -1156,15 +1156,16 @@ pub const StreamHandler = struct {
break :path .{ path, false };
// First try to stack-allocate
var fba = std.heap.FixedBufferAllocator.init(&pathBuf);
if (std.fmt.allocPrint(fba.allocator(), "{raw}", .{uri.path})) |v|
break :path .{ v, false }
else |_| {}
var stack_writer: std.Io.Writer = .fixed(&path_buf);
if (uri.path.formatRaw(&stack_writer)) |_| {
break :path .{ stack_writer.buffered(), false };
} else |_| {}
// Fall back to heap
if (std.fmt.allocPrint(self.alloc, "{raw}", .{uri.path})) |v|
break :path .{ v, true }
else |_| {}
var alloc_writer: std.Io.Writer.Allocating = .init(self.alloc);
if (uri.path.formatRaw(&alloc_writer.writer)) |_| {
break :path .{ alloc_writer.written(), true };
} else |_| {}
// Fall back to using it directly...
log.warn("failed to unescape OSC 7 path, using it directly path={s}", .{path});
@ -1471,15 +1472,15 @@ pub const StreamHandler = struct {
self: *StreamHandler,
request: terminal.kitty.color.OSC,
) !void {
var buf = std.ArrayList(u8).init(self.alloc);
defer buf.deinit();
const writer = buf.writer();
var stream: std.Io.Writer.Allocating = .init(self.alloc);
defer stream.deinit();
const writer = &stream.writer;
for (request.list.items) |item| {
switch (item) {
.query => |key| {
// If the writer buffer is empty, we need to write our prefix
if (buf.items.len == 0) try writer.writeAll("\x1b]21");
if (stream.written().len == 0) try writer.writeAll("\x1b]21");
const color: terminal.color.RGB = switch (key) {
.palette => |palette| self.terminal.color_palette.colors[palette],
@ -1488,17 +1489,17 @@ pub const StreamHandler = struct {
.background => self.background_color orelse self.default_background_color,
.cursor => self.cursor_color orelse self.default_cursor_color,
else => {
log.warn("ignoring unsupported kitty color protocol key: {}", .{key});
log.warn("ignoring unsupported kitty color protocol key: {f}", .{key});
continue;
},
},
} orelse {
try writer.print(";{}=", .{key});
try writer.print(";{f}=", .{key});
continue;
};
try writer.print(
";{}=rgb:{x:0>2}/{x:0>2}/{x:0>2}",
";{f}=rgb:{x:0>2}/{x:0>2}/{x:0>2}",
.{ key, color.r, color.g, color.b },
);
},
@ -1525,7 +1526,7 @@ pub const StreamHandler = struct {
},
else => {
log.warn(
"ignoring unsupported kitty color protocol key: {}",
"ignoring unsupported kitty color protocol key: {f}",
.{v.key},
);
continue;
@ -1560,7 +1561,7 @@ pub const StreamHandler = struct {
},
else => {
log.warn(
"ignoring unsupported kitty color protocol key: {}",
"ignoring unsupported kitty color protocol key: {f}",
.{key},
);
continue;
@ -1576,12 +1577,12 @@ pub const StreamHandler = struct {
}
// If we had any writes to our buffer, we queue them now
if (buf.items.len > 0) {
if (stream.written().len > 0) {
try writer.writeAll(request.terminator.string());
self.messageWriter(.{
.write_alloc = .{
.alloc = self.alloc,
.data = try buf.toOwnedSlice(),
.data = try stream.toOwnedSlice(),
},
});
}