diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 21:25:52 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 237071bd4865f0220c35c8c37895c8dba48aed84 (patch) | |
| tree | f9bc231af7e51f2d8b06225a63c095f5b9b6f0cf | |
| parent | 137dc2ea4be0fc5baf4cab1d8a757ce426e5cbb2 (diff) | |
| download | pardes-237071bd4865f0220c35c8c37895c8dba48aed84.tar.gz pardes-237071bd4865f0220c35c8c37895c8dba48aed84.zip | |
Attach in a detached session is refused with words: bare is wrong #args, a name no session has is "no such session"
A detached session's core has no frontend loop to take an Attach, so
the word was asked and never answered: a ctl write of it succeeded
and nothing happened. The core now knows it is a detached session's,
and Attach there says why: bare names no session but itself, a name
with no session behind it is said so, and one that is a session is
told to be attached from a frontend.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/builtins.zig | 22 | ||||
| -rw-r--r-- | src/detached/server.zig | 1 | ||||
| -rw-r--r-- | src/pardes.zig | 17 |
3 files changed, 39 insertions, 1 deletions
diff --git a/src/builtins.zig b/src/builtins.zig index cb588ba3..bc9bb075 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -742,9 +742,29 @@ pub const Attach = struct { if (comptime enabled) ask(c) else unreachable; } fn ask(c: Ctx) void { - const name = c.arg orelse ""; + const name = std.mem.trim(u8, c.arg orelse "", " \t\r\n"); if (name.len > pardes.attach_name_max) return c.p.reportError(c.id, comptime word(@This()), error.NameTooLong); + // A detached session has no terminal to move: bare, there is no + // "the one session there" but itself; a name is said for what it is. + if (c.p.opts.detached_session) { + if (name.len == 0) return c.p.reportFailure(c.id, "wrong #args in control message \"Attach\": a detached session names the session"); + // A session is its socket, `pardes-detached-<name>.sock` beside + // the 9P ones (detached/server.zig socketPath); not imported, as + // the core links no frontend's code. + const ninep_io = @import("9p_io.zig"); + var dir_buf: [ninep_io.sun_path_len:0]u8 = undefined; + var path_buf: [ninep_io.sun_path_len + 64:0]u8 = undefined; + const exists = if (ninep_io.socketDir(&dir_buf)) |dir| + (if (std.fmt.bufPrintSentinel(&path_buf, "{s}/pardes-detached-{s}.sock", .{ dir, name }, 0)) |path| ninep_io.statNoFollow(path) != null else |_| false) + else + false; + var said: [pardes.attach_name_max + 96]u8 = undefined; + return c.p.reportFailure(c.id, if (exists) + std.fmt.bufPrint(&said, "Attach: {s}: a detached session has no terminal to attach; Attach from a frontend", .{name}) catch "Attach: a detached session has no terminal to attach" + else + std.fmt.bufPrint(&said, "Attach: {s}: no such session", .{name}) catch "Attach: no such session"); + } c.p.emit(.{ .attach = .{ .pane = @intCast(c.id), .name = .from(name) } }); } }; diff --git a/src/detached/server.zig b/src/detached/server.zig index 001acc47..2b87bd60 100644 --- a/src/detached/server.zig +++ b/src/detached/server.zig @@ -1243,6 +1243,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options, name: []const u8) !void // and an attaching frontend its own. options.cols = detached_cols; options.rows = detached_rows; + options.detached_session = true; options.image_allocator = allocs.image; options.pdf_allocator = allocs.pdf; options.tree_sitter_allocator = allocs.tree_sitter; diff --git a/src/pardes.zig b/src/pardes.zig index f479f44d..4472df8f 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -573,6 +573,20 @@ test "Attach asks for a session and tears nothing down" { try std.testing.expect(p.panes[0] != null); } +test "Attach in a detached session: bare is wrong #args, a name no session has says so, and nothing is asked" { + if (comptime !can_attach) return; + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .detached_session = true }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const pane = p.panes[0].?; + try std.testing.expect(p.executeBuiltinLine(0, "Attach")); + try std.testing.expect(drainForAttach(p) == null); + try std.testing.expectStringStartsWith(pane.msg[0..pane.msg_len], "wrong #args"); + try std.testing.expect(p.executeBuiltinLine(0, "Attach zz-no-such-session-qq")); + try std.testing.expect(drainForAttach(p) == null); + try std.testing.expectEqualStrings("Attach: zz-no-such-session-qq: no such session", pane.msg[0..pane.msg_len]); +} + test "Detach asks the frontend to leave, and says so when there is nothing to leave" { if (comptime !can_attach) return; // compiled out where nothing polls `takeAttach` — see `can_attach` const gpa = std.testing.allocator; @@ -4199,6 +4213,9 @@ pub const Options = struct { /// load a dump of another instance instead of spawning shells (acme -l) load_path: ?[]const u8 = null, nested: bool = false, + /// The core of a `--detach` session (detached/server.zig): it has no + /// terminal of its own, so Attach there is refused with why. + detached_session: bool = false, startup_config: ?[]const u8 = null, ninep_name: []const u8 = "", ninep_tcp: ?[]const u8 = null, |
