summaryrefslogtreecommitdiff
path: root/src/nested.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-18 15:13:50 -0300
committerGabriel Schneider <[email protected]>2026-08-18 23:45:54 -0300
commit58aae9a1f7aca8e64814c8775b0c17bf93f4da0f (patch)
tree098a5afec048efc2b99871a1136ae32ab4a29fdc /src/nested.zig
parent382abe3dafb872b3e6c9792a9cc0abcf6267e180 (diff)
downloadpardes-58aae9a1f7aca8e64814c8775b0c17bf93f4da0f.tar.gz
pardes-58aae9a1f7aca8e64814c8775b0c17bf93f4da0f.zip
nested + pardes: snapshot updates and small behavior fixes across panes
Diffstat (limited to 'src/nested.zig')
-rw-r--r--src/nested.zig98
1 files changed, 50 insertions, 48 deletions
diff --git a/src/nested.zig b/src/nested.zig
index 16cea6a1..eb01b2e0 100644
--- a/src/nested.zig
+++ b/src/nested.zig
@@ -25,9 +25,9 @@
//! null and a pardes inside a pardes opens a second session, as before.
//!
//! macOS also has a third executable in the family: the app bundle. Its binary
-//! is the same build as `bin/pardes` installed a second time, at a path that
-//! shares nothing below the install prefix, so identity is compared at that
-//! prefix — see samePardesExecutable.
+//! is named `pardes`, like the tty frontend, wherever the bundle is installed.
+//! Executable identity therefore comes from the family name rather than its
+//! path — see samePardesExecutable.
const std = @import("std");
const builtin = @import("builtin");
const libc = std.c;
@@ -115,44 +115,22 @@ fn stripDeleted(link: []const u8) []const u8 {
return if (std.mem.endsWith(u8, link, suffix)) link[0 .. link.len - suffix.len] else link;
}
-/// The install prefix a program directory belongs to. `bin/pardes` and
-/// `pardes.app/Contents/MacOS/pardes` are one build installed twice and share
-/// no directory at all, so comparing dirnames says they are strangers; both
-/// reduce to the prefix, and so does everything else — a directory that is
-/// neither wrapper is its own prefix, which leaves the same-directory rule
-/// below exactly as strict as it was.
-fn installPrefix(dir: []const u8) []const u8 {
- const macos_dir = "/Contents/MacOS";
- if (std.mem.endsWith(u8, dir, macos_dir)) {
- const app = dir[0 .. dir.len - macos_dir.len];
- if (std.mem.endsWith(u8, app, ".app")) return std.fs.path.dirname(app) orelse app;
- }
- const bin = "/bin";
- if (std.mem.endsWith(u8, dir, bin)) return dir[0 .. dir.len - bin.len];
- return dir;
-}
-
/// The tty, SDL and macOS builds are sibling frontends of the same program.
/// Their installed names differ only by `-gui` (and, for cross builds, share
/// the same `-os-arch` tail), or not at all when one of them is the app bundle
-/// — so any of them must recognise any other as an outer pardes. Requiring the
-/// same install prefix retains the executable-identity check: an unrelated
-/// ancestor merely named `pardes` is not enough.
+/// — so any of them must recognise any other as an outer pardes. Paths are
+/// deliberately ignored: the GUI may be installed system-wide while the tty
+/// frontend is installed in the user's bin directory.
fn samePardesExecutable(a_raw: []const u8, b_raw: []const u8) bool {
const a = stripDeleted(a_raw);
const b = stripDeleted(b_raw);
- if (std.mem.eql(u8, a, b)) return true;
- const a_dir = installPrefix(std.fs.path.dirname(a) orelse return false);
- const b_dir = installPrefix(std.fs.path.dirname(b) orelse return false);
- if (!std.mem.eql(u8, a_dir, b_dir)) return false;
-
return sameFamily(std.fs.path.basename(a), std.fs.path.basename(b));
}
/// What is left of a family name after the frontend part: `` for `pardes` and
/// `pardes-gui`, `-linux-aarch64` for the cross-built spellings of both. Null
-/// when the name is not in the family at all — `not-pardes` and `pardesfoo`
-/// are other programs.
+/// when the name is not in the family at all — `not-pardes`, `pardesfoo`, and
+/// helper binaries such as `pardes-snap` are other programs.
fn familyTail(name: []const u8) ?[]const u8 {
const rest = if (std.mem.startsWith(u8, name, "pardes-gui"))
name["pardes-gui".len..]
@@ -160,23 +138,30 @@ fn familyTail(name: []const u8) ?[]const u8 {
name["pardes".len..]
else
return null;
- // `pardesfoo` shares a prefix and nothing else. A tail is a tail or empty.
- if (rest.len != 0 and rest[0] != '-') return null;
+ if (rest.len == 0) return rest;
+ // Build names have exactly `-os-arch` after the frontend. Validating both
+ // fields keeps sibling installs flexible without mistaking pardes-snap,
+ // pardes-perf, and the other helper executables for editor frontends.
+ if (rest[0] != '-') return null;
+ var fields = std.mem.splitScalar(u8, rest[1..], '-');
+ const os = fields.next() orelse return null;
+ const arch = fields.next() orelse return null;
+ if (fields.next() != null) return null;
+ if (std.meta.stringToEnum(std.Target.Os.Tag, os) == null) return null;
+ if (std.meta.stringToEnum(std.Target.Cpu.Arch, arch) == null) return null;
return rest;
}
-/// Two family names for the same build, given that they already share an
-/// install prefix. The tails have to agree — a linux binary and an x86_64 one
-/// in the same directory are two builds — unless one of them has no tail at
+/// Two executable names in the same family. The tails have to agree — a linux
+/// binary and an x86_64 one are two builds — unless one of them has no tail at
/// all, which is the untagged name the default build and, unavoidably, the app
/// bundle both produce: CFBundleExecutable is a fixed string, so the bundled
/// copy of `pardes-macos-aarch64` is called `pardes` and nothing in the name
-/// records what it was. Loosening it that far is safe because the prefix
-/// already had to match, and a foreign-arch ancestor cannot be running here.
+/// records what it was. A foreign-arch ancestor cannot be running here.
fn sameFamily(a: []const u8, b: []const u8) bool {
- if (std.mem.eql(u8, a, b)) return true;
const a_tail = familyTail(a) orelse return false;
const b_tail = familyTail(b) orelse return false;
+ if (std.mem.eql(u8, a, b)) return true;
return a_tail.len == 0 or b_tail.len == 0 or std.mem.eql(u8, a_tail, b_tail);
}
@@ -262,9 +247,19 @@ pub fn outer() ?libc.pid_t {
if (comptime !supported) return null;
var self_buf: [4096]u8 = undefined;
const self_exe = exeOf(libc.getpid(), &self_buf) orelse return null;
+ // A process harness may deliberately launch a fresh top-level pardes from
+ // inside another one. Its pid is a process-tree boundary, not an opt-out
+ // for the new session itself: pane shells below the child still detect it.
+ // This is what lets the snapshot harness exercise nested launches while
+ // the harness happens to be running in a real pardes pane.
+ const boundary = if (libc.getenv("PARDES_NESTED_BOUNDARY_PID")) |raw|
+ std.fmt.parseInt(libc.pid_t, std.mem.span(raw), 10) catch 0
+ else
+ 0;
var pid = libc.getppid();
var hops: usize = 0;
while (pid > 1 and hops < 64) : (hops += 1) {
+ if (pid == boundary) return null;
var buf: [4096]u8 = undefined;
if (exeOf(pid, &buf)) |exe| if (samePardesExecutable(exe, self_exe)) return pid;
pid = parentOf(pid) orelse return null;
@@ -543,9 +538,11 @@ test "tty and GUI sibling executables recognise each other" {
"/work/zig-out/bin/pardes-linux-aarch64",
"/work/zig-out/bin/pardes-gui-linux-aarch64 (deleted)",
));
- try std.testing.expect(!samePardesExecutable(
- "/usr/bin/pardes",
- "/work/zig-out/bin/pardes-gui",
+ // Installation paths do not define the family. This is the ordinary
+ // system-GUI/user-TTY pairing and the reason this comparison uses names.
+ try std.testing.expect(samePardesExecutable(
+ "/home/who/.local/bin/pardes",
+ "/usr/bin/pardes-gui",
));
try std.testing.expect(!samePardesExecutable(
"/work/zig-out/bin/pardes-linux-aarch64",
@@ -555,9 +552,17 @@ test "tty and GUI sibling executables recognise each other" {
"/work/zig-out/bin/not-pardes",
"/work/zig-out/bin/not-pardes-gui",
));
+ try std.testing.expect(!samePardesExecutable(
+ "/one/bin/not-pardes",
+ "/two/bin/not-pardes",
+ ));
+ try std.testing.expect(!samePardesExecutable(
+ "/work/zig-out/bin/pardes-snap",
+ "/usr/bin/pardes",
+ ));
}
-test "the app bundle is the same build as the binary installed beside it" {
+test "the app bundle is in the same executable family" {
// What `pardes foo.zig` typed into the bundle's own shell has to resolve:
// the ancestor is zig-out/pardes.app/..., this process is zig-out/bin/...,
// and nothing below zig-out is shared.
@@ -577,15 +582,12 @@ test "the app bundle is the same build as the binary installed beside it" {
"/work/zig-out/pardes.app/Contents/MacOS/pardes",
"/work/zig-out/bin/pardes-macos-aarch64",
));
- // A different install is still a different program, however alike the
- // paths look — this is the whole point of comparing anything at all.
- try std.testing.expect(!samePardesExecutable(
+ // Installation location does not matter here either.
+ try std.testing.expect(samePardesExecutable(
"/work/zig-out/pardes.app/Contents/MacOS/pardes",
"/opt/zig-out/bin/pardes",
));
- // The wrapper is only transparent when it IS the wrapper: `Contents/MacOS`
- // under something that is not a bundle keeps its own directory.
- try std.testing.expect(!samePardesExecutable(
+ try std.testing.expect(samePardesExecutable(
"/work/zig-out/pardes/Contents/MacOS/pardes",
"/work/zig-out/bin/pardes",
));