summaryrefslogtreecommitdiff
path: root/src/ninep/ctl.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 05:24:25 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit637ab988f0922173055ea98b1f76916e42f4222a (patch)
tree4b28a9f9409482e58f091cee3e3844c1643c0144 /src/ninep/ctl.zig
parente9eae368dbbe855ad2f409913ff0d44d5ac32bd8 (diff)
downloadpardes-637ab988f0922173055ea98b1f76916e42f4222a.tar.gz
pardes-637ab988f0922173055ea98b1f76916e42f4222a.zip
A prompt a 9P request opens is an ask, answered with answer, never stacked
Save through exec on a terminal (or a scratch) opened its path prompt on screen, where no script could see or answer it, and a second Save stacked another. Opened while a 9P request is served, it now logs ask <serial> save path; answer <path> on the pane's ctl saves there and closes it, answer - closes it; asked again, the open one stands. A pane closed with it open logs answer <serial> -. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/ctl.zig')
-rw-r--r--src/ninep/ctl.zig32
1 files changed, 32 insertions, 0 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 3282c743..d3b008ff 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -741,6 +741,16 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
return tree.failText(req.tag, E.INVAL, std.fmt.bufPrint(&p.fs.ename, "answer takes {s} or -", .{choices}) catch std.fmt.bufPrint(&why, "answer takes a choice or -", .{}) catch "answer takes a choice or -");
}
return tree.failText(req.tag, E.INVAL, "answer takes a choice or -");
+ } else if (std.mem.startsWith(u8, line, "answer ") and p.panes[p.paneBySerial(serial).?].?.prompt == .save) {
+ // Save's path, asked over 9P (`ask <serial> save path`).
+ if (!apply) continue;
+ const id = p.paneBySerial(serial).?;
+ const asked = p.panes[id].?;
+ const path = std.mem.trim(u8, line["answer ".len..], " ");
+ var rec: [300]u8 = undefined;
+ pardes.ctlfs.events.notePath(p, "answer", std.fmt.bufPrint(&rec, "{d} {s}", .{ serial, path[0..@min(path.len, 256)] }) catch "");
+ if (!std.mem.eql(u8, path, "-")) exec_line.saveTo(p, id, path);
+ asked.closePrompt();
} else if (std.mem.startsWith(u8, line, "answer ")) {
// The pane's question (its `ask` in the log), answered.
const choice = std.mem.trim(u8, line["answer ".len..], " ");
@@ -1743,3 +1753,25 @@ test "Newcol at the column cap fails the write and says the cap" {
try testing.expect(std.mem.indexOf(u8, refused.reply.ename, try std.fmt.bufPrint(&want, "no space for a column: {d} max", .{pardes.MAX_COLS})) != null);
try testing.expect(th.logHas(p, "no space for a column"));
}
+
+test "Save on a terminal asked over 9P is an ask in the log, answered with a path, and not asked twice" {
+ const p = try th.withTerm(testing.allocator);
+ defer p.deinit();
+ const serial = serialOf(p);
+ const id = p.paneBySerial(serial).?;
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .tagexec), "Save\n").reply.status);
+ var want: [48]u8 = undefined;
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "ask {d} save path\n", .{serial})));
+ try testing.expect(p.panes[id].?.prompt == .save);
+ // Again: the one open stands, one ask in the log.
+ _ = wr(p, Node.of(serial, .tagexec), "Save\n");
+ const log = @intFromEnum(tree.TopFile.log);
+ const h = call(p, .{ .tag = 1, .op = .open, .node = log }).reply.handle;
+ try testing.expectEqual(@as(usize, 1), std.mem.count(u8, call(p, .{ .tag = 2, .op = .read, .node = log, .handle = h, .size = 1 << 16 }).bytes, "save path\n"));
+ _ = call(p, .{ .tag = 3, .op = .release, .node = log, .handle = h });
+ // Answered: the path is written to, the prompt goes.
+ const answered = wr(p, Node.of(serial, .ctl), "answer /tmp/pardes-ask-save.txt\n");
+ try testing.expectEqual(Status.ok, answered.reply.status);
+ try testing.expect(p.panes[id].?.prompt == .none);
+ try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "answer {d} /tmp/pardes-ask-save.txt\n", .{serial})));
+}