Various changes.
This commit is contained in:
1 parent
bb90255002
commit
3475ca95c0
29 files changed
+110
-85
No files matched your search
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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.* = .{
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user