Various changes.

This commit is contained in:
Greyson Parrelli committed 2026-10-28 09:22:29 -04:00
1 parent bb90255002
commit 3475ca95c0
29 files changed
+110 -85

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@ with the content inset to its right.
## Quick start
```sh
nix develop # Zig 0.16 + GTK4 + libadwaita + node, no host toolchain needed
nix develop # Zig 0.17 + GTK4 + libadwaita + node, no host toolchain needed
mise run web-deps # once: npm deps for the review pane's UI
zig build run
```
+2 -2
View File
@@ -110,7 +110,7 @@ pub fn build(b: *std.Build) void {
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| run_cmd.addArgs(args);
run_cmd.addPassthruArgs();
b.step("run", "Run the app").dependOn(&run_cmd.step);
// Tests are rooted at Layouts.zig rather than main.zig: the parts of this
@@ -319,7 +319,7 @@ fn addWebInputs(b: *std.Build, run: *std.Build.Step.Run) void {
const io = b.graph.io;
for ([_][]const u8{ "web/src", "web/public" }) |root| {
var dir = b.build_root.handle.openDir(io, root, .{ .iterate = true }) catch continue;
var dir = b.root.openDir(io, root, .{ .iterate = true }) catch continue;
defer dir.close(io);
var walker = dir.walk(b.allocator) catch continue;
+5 -5
View File
@@ -4,7 +4,7 @@
// Half package-name hash, half per-package constant, so renaming the
// package invalidates it. Regenerated when vtabs became playpen.
.fingerprint = 0x5a26ca72bb4d2c53,
.minimum_zig_version = "0.16.0",
.minimum_zig_version = "0.17.0",
.paths = .{
"build.zig",
"build.zig.zon",
@@ -12,12 +12,12 @@
},
.dependencies = .{
.ghostty = .{
.url = "https://github.com/ghostty-org/ghostty/archive/94d775fefc21f74d9cc85a46b34c4e1d85318fd0.tar.gz",
.hash = "ghostty-1.3.2-dev-5UdBCyCSPwV-Pp82UMGLutqeo0gckMrI0GfbUbUY0-Jt",
.url = "https://github.com/ghostty-org/ghostty/archive/83fb95624b0439ee660c2679b43b38c8b63769cd.tar.gz",
.hash = "ghostty-1.4.0-5UdBC-qohwWqLS0BImOz05t4hVyTwbaKslyTPvKYvAtE",
},
.gobject = .{
.url = "https://github.com/ghostty-org/zig-gobject/releases/download/0.10.0-2026-07-28-36-1/ghostty-gobject-0.10.0-2026-07-28-36-1.tar.zst",
.hash = "gobject-0.3.2-Skun7F6HogCMynX2JqeSHS7xr-8pK4ob-qRFIcEasVi3",
.url = "https://github.com/ghostty-org/zig-gobject/releases/download/0.11.0-zig-0.17.0-2026-10-02-38-1/ghostty-gobject-0.11.0-zig-0.17.0-2026-10-02-38-1.tar.zst",
.hash = "gobject-0.3.2-Skun7LuIogBbEh-6WrLeEzMaRCnOMB3eOf__RaPYSMCZ",
},
},
}
Generated
+3 -3
View File
@@ -66,11 +66,11 @@
]
},
"locked": {
"lastModified": 1786364596,
"narHash": "sha256-rMHN2/qJ10ORy+PfSm8hfG2mCdnsYHsWrKeq8DLraec=",
"lastModified": 1790988737,
"narHash": "sha256-b55etI4Uxu+WHhYZXym3HPIz63ipO9lqwJLAphjqtTc=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "840c2310085417892ec51f691002ca0a497d0287",
"rev": "dcffc7a25ed2cbbc7c1c3c448c312abf46ae6cb8",
"type": "github"
},
"original": {
+7 -2
View File
@@ -5,6 +5,11 @@
# Ghostty tracks nixpkgs-unstable to get GTK 4.20 / GNOME 49. We follow
# suit so that our zig-gobject bindings (shared with Ghostty) match the
# GObject introspection data of the GTK we link against.
#
# Follow the channel, not necessarily Ghostty's exact lock: the binary
# runs on the host's dynamic loader and libc, so a nixpkgs whose glibc is
# newer than the host's builds fine and then fails to start with
# "GLIBC_2.43 not found". What has to match is GTK's version, not the rev.
nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz";
flake-compat = {
@@ -17,7 +22,7 @@
flake = false;
};
# Zig 0.16.0 is not in nixpkgs yet; libghostty-vt requires it.
# Zig 0.17.0 is not in nixpkgs yet; libghostty-vt requires it.
zig = {
url = "github:mitchellh/zig-overlay";
inputs = {
@@ -45,7 +50,7 @@
name = "playpen";
nativeBuildInputs = [
zig.packages.${system}."0.16.0"
zig.packages.${system}."0.17.0"
pkgs.pkg-config
pkgs.gdb
+1 -1
View File
@@ -11,7 +11,7 @@ run = "nix develop --command npm --prefix web run dev"
[tasks.build]
description = "Build a release binary into zig-out/bin"
run = "nix develop --command zig build -Doptimize=ReleaseFast"
run = "nix develop --command zig build -Doptimize=fast"
[tasks.build-debug]
description = "Build a debug binary (leak checking, safety checks)"
+1 -1
View File
@@ -264,7 +264,7 @@ fn resolve(self: *Browser, raw: []const u8) ?[:0]const u8 {
return copyZ(&self.url_buf, input);
}
if (looksLikeHost(input)) {
return std.fmt.bufPrintZ(&self.url_buf, "https://{s}", .{input}) catch null;
return std.mem.printSentinel(&self.url_buf, "https://{s}", .{input}, 0) catch null;
}
return searchUrl(&self.url_buf, input);
}
+3 -3
View File
@@ -279,11 +279,11 @@ fn onCountedMatches(_: *webkit.FindController, matches: c_uint, self: *FindBar)
// it gets a "or more" rather than a number that isn't the total.
var buf: [32]u8 = undefined;
const label = if (matches >= max_matches)
std.fmt.bufPrintZ(&buf, "{d}+ matches", .{max_matches}) catch return
std.mem.printSentinel(&buf, "{d}+ matches", .{max_matches}, 0) catch return
else if (matches == 1)
std.fmt.bufPrintZ(&buf, "1 match", .{}) catch return
std.mem.printSentinel(&buf, "1 match", .{}, 0) catch return
else
std.fmt.bufPrintZ(&buf, "{d} matches", .{matches}) catch return;
std.mem.printSentinel(&buf, "{d} matches", .{matches}, 0) catch return;
self.status.setText(label);
}
+2 -2
View File
@@ -150,13 +150,13 @@ pub fn find(self: *Layouts, name: []const u8) ?*Layout {
/// when that isn't set — whichever GLib reports as the user's config dir.
pub fn configPath(buf: []u8) ?[:0]const u8 {
const dir = std.mem.span(glib.getUserConfigDir());
return std.fmt.bufPrintZ(buf, "{s}/playpen/layouts.json", .{dir}) catch null;
return std.mem.printSentinel(buf, "{s}/playpen/layouts.json", .{dir}, 0) catch null;
}
/// The directory `configPath` lives in.
fn configDir(buf: []u8) ?[:0]const u8 {
const dir = std.mem.span(glib.getUserConfigDir());
return std.fmt.bufPrintZ(buf, "{s}/playpen", .{dir}) catch null;
return std.mem.printSentinel(buf, "{s}/playpen", .{dir}, 0) catch null;
}
// -------------------------------------------------------------------------
+4 -4
View File
@@ -61,7 +61,7 @@ pub fn present(
};
var title_buf: [128]u8 = undefined;
const title = std.fmt.bufPrintZ(&title_buf, "Open “{s}”", .{layout.name}) catch "Open layout";
const title = std.mem.printSentinel(&title_buf, "Open “{s}”", .{layout.name}, 0) catch "Open layout";
self.window.setTitle(title);
self.window.setTransientFor(parent);
self.window.setModal(1);
@@ -81,14 +81,14 @@ pub fn present(
// is the fallback so a parameter is never unlabeled.
var label_buf: [128]u8 = undefined;
const text = if (param.description.len > 0) param.description else param.name;
label.setText(std.fmt.bufPrintZ(&label_buf, "{s}", .{text}) catch "parameter");
label.setText(std.mem.printSentinel(&label_buf, "{s}", .{text}, 0) catch "parameter");
label.setXalign(0);
label.as(gtk.Widget).addCssClass("playpen-dialog-label");
fields.attach(label.as(gtk.Widget), 0, @intCast(i), 1, 1);
var hint_buf: [128]u8 = undefined;
const placeholder: ?[*:0]const u8 =
std.fmt.bufPrintZ(&hint_buf, "{{{{{s}}}}}", .{param.name}) catch null;
std.mem.printSentinel(&hint_buf, "{{{{{s}}}}}", .{param.name}, 0) catch null;
const entry, const field = switch (param.type) {
.string => blk: {
@@ -150,7 +150,7 @@ pub fn present(
fn setEntryText(entry: *gtk.Entry, text: []const u8) void {
var buf: [1024]u8 = undefined;
const z = std.fmt.bufPrintZ(&buf, "{s}", .{text}) catch return;
const z = std.mem.printSentinel(&buf, "{s}", .{text}, 0) catch return;
entry.as(gtk.Editable).setText(z);
}
+4 -3
View File
@@ -423,11 +423,11 @@ fn buildRow(self: *PaletteEditor, key: palette.Key) *gtk.Widget {
// the settings file, since anyone hand-editing that file is looking at this
// list to find out what to call things.
var tip: [256]u8 = undefined;
const tooltip: [:0]const u8 = std.fmt.bufPrintZ(&tip, "{s}{s}{s}", .{
const tooltip: [:0]const u8 = std.mem.printSentinel(&tip, "{s}{s}{s}", .{
key.hint() orelse "",
if (key.hint() != null) "\n" else "",
@tagName(key),
}) catch @tagName(key);
}, 0) catch @tagName(key);
row.as(gtk.Widget).setTooltipText(tooltip);
const hex = gtk.Label.new(null);
@@ -497,11 +497,12 @@ fn reload(self: *PaletteEditor) void {
const changes = Settings.get().colors.of(scheme);
var buf: [192]u8 = undefined;
const heading: [:0]const u8 = std.fmt.bufPrintZ(
const heading: [:0]const u8 = std.mem.printSentinel(
&buf,
"Editing the {s} palette, the one on screen — changes apply as you make them. " ++
"Switch the theme above to edit the other.",
.{scheme.label()},
0,
) catch "Changes apply as you make them.";
self.scheme_hint.setText(heading);
+7 -7
View File
@@ -193,7 +193,7 @@ pub fn setText(self: *PathEntry, value: []const u8) void {
defer self.quiet = false;
var buf: [max_path]u8 = undefined;
const z = std.fmt.bufPrintZ(&buf, "{s}", .{value}) catch return;
const z = std.mem.printSentinel(&buf, "{s}", .{value}, 0) catch return;
self.entry.as(gtk.Editable).setText(z);
self.hide();
}
@@ -243,7 +243,7 @@ fn fill(self: *PathEntry) void {
for (offer.names) |name| {
var buf: [max_path]u8 = undefined;
const label = gtk.Label.new(std.fmt.bufPrintZ(&buf, "{s}/", .{name}) catch continue);
const label = gtk.Label.new(std.mem.printSentinel(&buf, "{s}/", .{name}, 0) catch continue);
label.setXalign(0);
label.as(gtk.Widget).addCssClass("playpen-path-row");
self.list.append(label.as(gtk.Widget));
@@ -253,7 +253,7 @@ fn fill(self: *PathEntry) void {
if (hidden == 0) return;
var buf: [64]u8 = undefined;
const counted = std.fmt.bufPrintZ(&buf, "… {d} more", .{hidden}) catch return;
const counted = std.mem.printSentinel(&buf, "… {d} more", .{hidden}, 0) catch return;
const label = gtk.Label.new(counted);
label.setXalign(0);
label.as(gtk.Widget).addCssClass("playpen-path-more");
@@ -309,10 +309,10 @@ fn accept(self: *PathEntry, index: usize) void {
if (offer.dir_len > value.len) return self.hide();
var buf: [max_path]u8 = undefined;
const z = std.fmt.bufPrintZ(&buf, "{s}{s}/", .{
const z = std.mem.printSentinel(&buf, "{s}{s}/", .{
value[0..offer.dir_len],
offer.names[index],
}) catch return;
}, 0) catch return;
// Not quiet: writing this re-reads the directory it names, so completing
// `~/src/pl` to `~/src/playpen/` immediately offers what is inside it.
@@ -338,10 +338,10 @@ fn complete(self: *PathEntry) void {
if (shared <= typed) return;
var buf: [max_path]u8 = undefined;
const z = std.fmt.bufPrintZ(&buf, "{s}{s}", .{
const z = std.mem.printSentinel(&buf, "{s}{s}", .{
value[0..offer.dir_len],
offer.names[0][0..shared],
}) catch return;
}, 0) catch return;
self.entry.as(gtk.Editable).setText(z);
self.entry.as(gtk.Editable).setPosition(-1);
+6 -6
View File
@@ -126,7 +126,7 @@ pub fn create(
const envp_z = try buildEnv(alloc, env_extra);
defer freeEnv(alloc, envp_z);
const slave_path_z = try alloc.dupeZ(u8, slave_path);
const slave_path_z = try alloc.dupeSentinel(u8, slave_path, 0);
defer alloc.free(slave_path_z);
const pid = c.fork();
@@ -226,12 +226,12 @@ fn buildEnv(
if (std.mem.startsWith(u8, span, "TERM=")) continue;
if (std.mem.startsWith(u8, span, "COLORTERM=")) continue;
if (shadowedBy(span, extra)) continue;
try list.append(alloc, (try alloc.dupeZ(u8, span)).ptr);
try list.append(alloc, (try alloc.dupeSentinel(u8, span, 0)).ptr);
}
try list.append(alloc, (try alloc.dupeZ(u8, "TERM=xterm-256color")).ptr);
try list.append(alloc, (try alloc.dupeZ(u8, "COLORTERM=truecolor")).ptr);
try list.append(alloc, (try alloc.dupeSentinel(u8, "TERM=xterm-256color", 0)).ptr);
try list.append(alloc, (try alloc.dupeSentinel(u8, "COLORTERM=truecolor", 0)).ptr);
for (extra) |entry| {
try list.append(alloc, (try alloc.dupeZ(u8, entry)).ptr);
try list.append(alloc, (try alloc.dupeSentinel(u8, entry, 0)).ptr);
}
const result = try alloc.allocSentinel(?[*:0]const u8, list.items.len, null);
@@ -264,7 +264,7 @@ fn freeEnv(alloc: std.mem.Allocator, envp: [:null]?[*:0]const u8) void {
/// portable API for it, and this app is Linux-only regardless.
pub fn cwd(self: Pty, buf: []u8) ?[]const u8 {
var link_buf: [64]u8 = undefined;
const link = std.fmt.bufPrintZ(&link_buf, "/proc/{d}/cwd", .{self.pid}) catch return null;
const link = std.mem.printSentinel(&link_buf, "/proc/{d}/cwd", .{self.pid}, 0) catch return null;
const n = c.readlink(link.ptr, buf.ptr, buf.len);
if (n <= 0) return null;
+1 -1
View File
@@ -98,7 +98,7 @@ pub fn create(
const self = try alloc.create(Review);
errdefer alloc.destroy(self);
const url = try alloc.dupeZ(u8, opts.url);
const url = try alloc.dupeSentinel(u8, opts.url, 0);
errdefer alloc.free(url);
self.* = .{
+3 -3
View File
@@ -233,10 +233,10 @@ fn buildPaneSection(self: *SaveLayoutDialog, index: usize, leaf: *Layouts.Node)
box.as(gtk.Widget).addCssClass("playpen-dialog-pane");
var title_buf: [64]u8 = undefined;
const title = std.fmt.bufPrintZ(&title_buf, "Pane {d} — {s}", .{
const title = std.mem.printSentinel(&title_buf, "Pane {d} — {s}", .{
index + 1,
@tagName(spec.kind),
}) catch "Pane";
}, 0) catch "Pane";
const label = gtk.Label.new(title);
label.setXalign(0);
label.as(gtk.Widget).addCssClass("playpen-dialog-label");
@@ -524,6 +524,6 @@ fn hint(text: [:0]const u8) *gtk.Widget {
fn setEntryText(entry: *gtk.Entry, text: []const u8) void {
var buf: [4096]u8 = undefined;
const z = std.fmt.bufPrintZ(&buf, "{s}", .{text}) catch return;
const z = std.mem.printSentinel(&buf, "{s}", .{text}, 0) catch return;
entry.as(gtk.Editable).setText(z);
}
+14 -6
View File
@@ -185,7 +185,7 @@ pub fn create(
defer alloc.free(argv0);
const cwd_z: ?[:0]const u8 = if (opts.cwd.len > 0)
try alloc.dupeZ(u8, opts.cwd)
try alloc.dupeSentinel(u8, opts.cwd, 0)
else
null;
defer if (cwd_z) |z| alloc.free(z);
@@ -246,14 +246,14 @@ pub fn destroy(self: *Session) void {
/// for systems with no usable passwd entry, such as minimal containers.
fn defaultShell(alloc: std.mem.Allocator) ![:0]const u8 {
var buf: [std.fs.max_path_bytes]u8 = undefined;
if (Pty.loginShell(&buf)) |shell| return alloc.dupeZ(u8, shell);
if (Pty.loginShell(&buf)) |shell| return alloc.dupeSentinel(u8, shell, 0);
if (std.c.getenv("SHELL")) |sh| {
const span = std.mem.span(sh);
if (span.len > 0) return alloc.dupeZ(u8, span);
if (span.len > 0) return alloc.dupeSentinel(u8, span, 0);
}
return alloc.dupeZ(u8, "/bin/sh");
return alloc.dupeSentinel(u8, "/bin/sh", 0);
}
/// Main loop callback: the PTY has data (or hung up).
@@ -305,7 +305,7 @@ fn flushStartupCommand(self: *Session) void {
}
/// Effect callback: the terminal wants to send bytes back to the child.
fn effectWritePty(handler: *vt.TerminalStream.Handler, data: [:0]const u8) void {
fn effectWritePty(handler: *vt.TerminalStream.Handler, data: []const u8) void {
const self = fromHandler(handler);
self.pty.writeAll(data);
}
@@ -394,8 +394,16 @@ fn fromHandler(handler: *vt.TerminalStream.Handler) *Session {
/// palette *defaults* to, so anything the program inside set for itself with
/// OSC 4 survives the switch. The caller still has to queue a redraw — nothing
/// here touches the widget.
///
/// A palette other than libghostty's built-in one is copied into memory the
/// terminal owns. If that copy can't be made, fall back to the built-in
/// palette, which never allocates, rather than keep the previous scheme's.
pub fn refreshPalette(self: *Session) void {
self.term.colors.palette.changeDefault(theme.ansiPalette());
const palette = &self.term.colors.palette;
palette.changeDefault(self.alloc, theme.ansiPalette()) catch |err| {
std.log.err("could not change the palette: {s}", .{@errorName(err)});
palette.resetDefault(self.alloc);
};
}
/// Send user input to the child.
+2 -2
View File
@@ -740,12 +740,12 @@ fn serialize(self: *Settings) SaveError![]u8 {
/// `~/.config/playpen/settings.json` when that isn't set.
fn configPath(buf: []u8) ?[:0]const u8 {
const dir = std.mem.span(glib.getUserConfigDir());
return std.fmt.bufPrintZ(buf, "{s}/playpen/settings.json", .{dir}) catch null;
return std.mem.printSentinel(buf, "{s}/playpen/settings.json", .{dir}, 0) catch null;
}
fn configDir(buf: []u8) ?[:0]const u8 {
const dir = std.mem.span(glib.getUserConfigDir());
return std.fmt.bufPrintZ(buf, "{s}/playpen", .{dir}) catch null;
return std.mem.printSentinel(buf, "{s}/playpen", .{dir}, 0) catch null;
}
// -------------------------------------------------------------------------
+6 -6
View File
@@ -648,7 +648,7 @@ fn newLayoutPicker(self: *SettingsDialog, selected: []const u8) *gtk.DropDown {
for (self.layouts.items.items) |layout| {
var buf: [256]u8 = undefined;
const name = std.fmt.bufPrintZ(&buf, "{s}", .{layout.name}) catch continue;
const name = std.mem.printSentinel(&buf, "{s}", .{layout.name}, 0) catch continue;
model.append(name.ptr);
if (std.mem.eql(u8, layout.name, selected)) index = count;
count += 1;
@@ -656,7 +656,7 @@ fn newLayoutPicker(self: *SettingsDialog, selected: []const u8) *gtk.DropDown {
if (selected.len > 0 and index == 0) {
var buf: [256]u8 = undefined;
if (std.fmt.bufPrintZ(&buf, "{s}", .{selected})) |name| {
if (std.mem.printSentinel(&buf, "{s}", .{selected}, 0)) |name| {
model.append(name.ptr);
index = count;
} else |_| {}
@@ -712,7 +712,7 @@ fn fillParams(
var hint_buf: [128]u8 = undefined;
const placeholder: ?[*:0]const u8 =
std.fmt.bufPrintZ(&hint_buf, "{{{{{s}}}}}", .{param.name}) catch null;
std.mem.printSentinel(&hint_buf, "{{{{{s}}}}}", .{param.name}, 0) catch null;
const value = valueOf(values, param.name) orelse param.default;
// The same two shapes the layout prompt offers, for the same reason: a
@@ -741,9 +741,9 @@ fn fillParams(
errdefer self.alloc.free(field.name);
var label_buf: [128]u8 = undefined;
const text = std.fmt.bufPrintZ(&label_buf, "{s}", .{
const text = std.mem.printSentinel(&label_buf, "{s}", .{
if (param.description.len > 0) param.description else param.name,
}) catch "parameter";
}, 0) catch "parameter";
const label = gtk.Label.new(text);
label.setXalign(0);
label.as(gtk.Widget).addCssClass("playpen-dialog-sublabel");
@@ -974,7 +974,7 @@ fn hint(text: [:0]const u8) *gtk.Widget {
fn setEntryText(entry: *gtk.Entry, text: []const u8) void {
var buf: [4096]u8 = undefined;
const z = std.fmt.bufPrintZ(&buf, "{s}", .{text}) catch return;
const z = std.mem.printSentinel(&buf, "{s}", .{text}, 0) catch return;
entry.as(gtk.Editable).setText(z);
}
+2 -2
View File
@@ -141,12 +141,12 @@ pub fn add(self: *Snapshot, tab: Tab) !void {
/// when that isn't set — whichever GLib reports as the user's state dir.
fn statePath(buf: []u8) ?[:0]const u8 {
const dir = std.mem.span(glib.getUserStateDir());
return std.fmt.bufPrintZ(buf, "{s}/playpen/session.json", .{dir}) catch null;
return std.mem.printSentinel(buf, "{s}/playpen/session.json", .{dir}, 0) catch null;
}
fn stateDir(buf: []u8) ?[:0]const u8 {
const dir = std.mem.span(glib.getUserStateDir());
return std.fmt.bufPrintZ(buf, "{s}/playpen", .{dir}) catch null;
return std.mem.printSentinel(buf, "{s}/playpen", .{dir}, 0) catch null;
}
// -------------------------------------------------------------------------
+2 -2
View File
@@ -116,9 +116,9 @@ pub fn present(
};
var title_buf: [160]u8 = undefined;
const title = std.fmt.bufPrintZ(&title_buf, "Tab settings — {s}", .{
const title = std.mem.printSentinel(&title_buf, "Tab settings — {s}", .{
clip(opts.tab_name, 96),
}) catch "Tab settings";
}, 0) catch "Tab settings";
self.window.setTitle(title);
self.window.setTransientFor(parent);
+6 -5
View File
@@ -577,7 +577,7 @@ fn newTabEmpty(self: *Window) !*Tab {
.name = undefined,
.name_len = 0,
};
const printed = std.fmt.bufPrintZ(&tab.name, "tab{d}", .{id}) catch unreachable;
const printed = std.mem.printSentinel(&tab.name, "tab{d}", .{id}, 0) catch unreachable;
tab.name_len = printed.len;
// ---- sidebar row ---------------------------------------------------
@@ -960,7 +960,7 @@ fn refreshLayoutMenu(self: *Window) void {
const line = gtk.Box.new(.horizontal, 4);
var label_buf: [128]u8 = undefined;
const text = std.fmt.bufPrintZ(&label_buf, "{s}", .{layout.name}) catch continue;
const text = std.mem.printSentinel(&label_buf, "{s}", .{layout.name}, 0) catch continue;
const open = gtk.Button.newWithLabel(text);
open.as(gtk.Widget).addCssClass("flat");
@@ -1491,10 +1491,10 @@ fn offerRestore(self: *Window) void {
// is, just without the number.
const count = self.snapshot.tabs.items.len;
var buf: [64]u8 = undefined;
const detail: [:0]const u8 = std.fmt.bufPrintZ(&buf, "{d} tab{s} from last time", .{
const detail: [:0]const u8 = std.mem.printSentinel(&buf, "{d} tab{s} from last time", .{
count,
if (count == 1) "" else "s",
}) catch "from last time";
}, 0) catch "from last time";
self.restore_detail.setText(detail.ptr);
self.restore_offered = true;
@@ -2352,7 +2352,7 @@ fn askBeforeQuitting(self: *Window) void {
// window. The buffer is sized so the format cannot fail, but the fallback
// says the same thing without the number rather than nothing at all.
var buf: [160]u8 = undefined;
const body: [:0]const u8 = std.fmt.bufPrintZ(
const body: [:0]const u8 = std.mem.printSentinel(
&buf,
"{d} tab{s} will close, and every shell in {s} will exit.",
.{
@@ -2360,6 +2360,7 @@ fn askBeforeQuitting(self: *Window) void {
if (self.tabs.items.len == 1) "" else "s",
if (self.tabs.items.len == 1) "it" else "them",
},
0,
) catch "Every tab will close, and every shell in them will exit.";
dialog.setBody(body.ptr);
+1 -1
View File
@@ -44,7 +44,7 @@ pub const Emoji = struct {
pub fn matches(emoji: Emoji, query: []const u8) bool {
var terms = std.mem.tokenizeAny(u8, query, " \t");
while (terms.next()) |term| {
if (std.ascii.indexOfIgnoreCase(emoji.keywords, term) == null) return false;
if (std.ascii.findIgnoreCase(emoji.keywords, term) == null) return false;
}
return true;
}
+2 -2
View File
@@ -37,11 +37,11 @@ pub const std_options: std.Options = .{
var debug_gpa: std.heap.DebugAllocator(.{}) = .init;
fn gpa() std.mem.Allocator {
return if (builtin.mode == .Debug) debug_gpa.allocator() else std.heap.c_allocator;
return if (builtin.mode == .debug) debug_gpa.allocator() else std.heap.c_allocator;
}
pub fn main() u8 {
defer if (builtin.mode == .Debug) {
defer if (builtin.mode == .debug) {
_ = debug_gpa.deinit();
};
+4 -2
View File
@@ -100,20 +100,22 @@ pub const Mute = union(enum) {
.until => |deadline| blk: {
const left_min = @divTrunc(deadline - now + 59_999, std.time.ms_per_min);
if (left_min <= 1) break :blk "Muted for another minute";
if (left_min < 60) break :blk std.fmt.bufPrintZ(
if (left_min < 60) break :blk std.mem.printSentinel(
buf,
"Muted for another {d} minutes",
.{left_min},
0,
) catch "Muted";
// Rounded down and in hours past the first: someone who muted a
// tab for eight hours wants to know it is still most of the
// afternoon, not that it is 437 minutes.
const left_hours = @divTrunc(left_min, 60);
break :blk std.fmt.bufPrintZ(
break :blk std.mem.printSentinel(
buf,
"Muted for another {d} hours",
.{left_hours},
0,
) catch "Muted";
},
};
+7 -7
View File
@@ -159,10 +159,10 @@ pub fn resolve(buf: []u8, dir: []const u8) ?[:0]const u8 {
if (dir[0] == '~' and (dir.len == 1 or dir[1] == '/')) {
const home = std.mem.span(glib.getHomeDir());
if (home.len == 0) return null;
return std.fmt.bufPrintZ(buf, "{s}{s}", .{ home, dir[1..] }) catch null;
return std.mem.printSentinel(buf, "{s}{s}", .{ home, dir[1..] }, 0) catch null;
}
return std.fmt.bufPrintZ(buf, "{s}", .{dir}) catch null;
return std.mem.printSentinel(buf, "{s}", .{dir}, 0) catch null;
}
/// The typed value, if it is a directory that exists. Used for where to open
@@ -179,7 +179,7 @@ pub fn existingDirectory(buf: []u8, value: []const u8) ?[:0]const u8 {
fn isDirectory(dir: [:0]const u8, name: []const u8) bool {
var buf: [max_path]u8 = undefined;
const separator: []const u8 = if (std.mem.endsWith(u8, dir, "/")) "" else "/";
const path = std.fmt.bufPrintZ(&buf, "{s}{s}{s}", .{ dir, separator, name }) catch
const path = std.mem.printSentinel(&buf, "{s}{s}{s}", .{ dir, separator, name }, 0) catch
return false;
return glib.fileTest(path.ptr, .{ .is_dir = true }) != 0;
}
@@ -214,17 +214,17 @@ const Tree = struct {
};
defer glib.free(raw);
const root = try testing.allocator.dupeZ(u8, std.mem.span(raw));
const root = try testing.allocator.dupeSentinel(u8, std.mem.span(raw), 0);
for (names) |name| {
var buf: [max_path]u8 = undefined;
const dir = try std.fmt.bufPrintZ(&buf, "{s}/{s}", .{ root, name });
const dir = try std.mem.printSentinel(&buf, "{s}/{s}", .{ root, name }, 0);
if (glib.mkdirWithParents(dir.ptr, 0o700) != 0) return error.MkdirFailed;
}
for (files) |name| {
var buf: [max_path]u8 = undefined;
const file = try std.fmt.bufPrintZ(&buf, "{s}/{s}", .{ root, name });
const file = try std.mem.printSentinel(&buf, "{s}/{s}", .{ root, name }, 0);
if (glib.fileSetContents(file.ptr, "x", 1, &err) == 0) {
if (err) |e| e.free();
return error.WriteFailed;
@@ -253,7 +253,7 @@ const Tree = struct {
fn remove(self: Tree, name: []const u8) void {
var buf: [max_path]u8 = undefined;
const target = std.fmt.bufPrintZ(&buf, "{s}/{s}", .{ self.root, name }) catch return;
const target = std.mem.printSentinel(&buf, "{s}/{s}", .{ self.root, name }, 0) catch return;
_ = glib.remove(target.ptr);
}
};
+1 -1
View File
@@ -243,7 +243,7 @@ pub fn suggestedBase(path: []const u8, branch: []const u8, all: []const []const
/// release-branch flow. A path check because it has to work before anything has
/// been read out of the repository.
fn isAndroidPath(path: []const u8) bool {
return std.ascii.indexOfIgnoreCase(path, "android") != null;
return std.ascii.findIgnoreCase(path, "android") != null;
}
// -------------------------------------------------------------------------
+2 -2
View File
@@ -63,10 +63,10 @@ pub fn adopt(pid: std.c.pid_t) void {
// which it does whenever the shell left a daemon behind — Gradle's does this
// as a matter of course — and the pid is later reused.
var name_buf: [96]u8 = undefined;
const name = std.fmt.bufPrintZ(&name_buf, "app-playpen-shell-{d}-{x:0>8}.scope", .{
const name = std.mem.printSentinel(&name_buf, "app-playpen-shell-{d}-{x:0>8}.scope", .{
pid,
glib.randomInt(),
}) catch return;
}, 0) catch return;
// `CollectMode` because a scope in which something *was* OOM-killed ends up
// "failed" when it finally empties, and would otherwise sit in
+2 -2
View File
@@ -65,7 +65,7 @@ pub fn run(alloc: std.mem.Allocator, command: []const u8, vars: []const Var) Err
var argv0 = "/bin/sh".*;
var argv1 = "-c".*;
const command_z = try alloc.dupeZ(u8, command);
const command_z = try alloc.dupeSentinel(u8, command, 0);
defer alloc.free(command_z);
var argv = [_:null]?[*:0]u8{ &argv0, &argv1, command_z.ptr };
@@ -185,7 +185,7 @@ fn environment(alloc: std.mem.Allocator, vars: []const Var) Error![*:null]?[*:0]
const name = envName(alloc, v.name) catch continue;
defer alloc.free(name);
const value = alloc.dupeZ(u8, v.value) catch continue;
const value = alloc.dupeSentinel(u8, v.value, 0) catch continue;
defer alloc.free(value);
// Takes ownership of `envp` and hands back the updated list.
+9 -1
View File
@@ -38,6 +38,14 @@ const vt = @import("ghostty-vt");
/// the letter keys are `key_a` and not `a`.
pub const Key = vt.input.Key;
/// Every key by its enum name, for `keyFromName`. Built once here rather than
/// by `std.meta.stringToEnum` at the call: libghostty's key enum is large enough
/// that building the lookup overruns the default comptime branch quota.
const key_names = names: {
@setEvalBranchQuota(100_000_000);
break :names std.StaticStringMap(Key).initEnum();
};
/// The modifiers a shortcut can ask for.
///
/// Deliberately not libghostty's `KeyMods`, which also carries the lock keys
@@ -371,7 +379,7 @@ fn keyFromName(name: []const u8) ?Key {
break :tag alias(lower);
};
const key = std.meta.stringToEnum(Key, tag) orelse return null;
const key = key_names.get(tag) orelse return null;
// The enum's own name for "we could not tell", which is not something to
// bind to and not something `key.zig` ever hands back.