summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 23:04:59 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit7e6e5e803c6ec2e203b094b2a64291840c5e6aed (patch)
tree22c45825bc56f1199344c93c7c5e4bd6a7e780aa /src
parent2586e804e4df670bf6a5072ec776d9fc00c1c4b9 (diff)
downloadpardes-7e6e5e803c6ec2e203b094b2a64291840c5e6aed.tar.gz
pardes-7e6e5e803c6ec2e203b094b2a64291840c5e6aed.zip
An answer the question does not offer is refused, and the question stands
`answer z` to a question offering a and b succeeded, sent nothing and dropped the question; so did Del's `answer x`. The ctl now takes only an offered choice or -, refuses anything else with the choices it takes (`answer takes k j or -`), and leaves the question asked. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/ninep/ctl.zig11
-rw-r--r--src/pardes.zig24
2 files changed, 32 insertions, 3 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index b4144fd2..c8be3bad 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -516,10 +516,15 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
} 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..], " ");
- if (choice.len != 1) return refuse(p, req, "answer takes one choice, or -", line);
const id = p.paneBySerial(serial).?;
- if (p.panes[id].?.prompt != .del_side and p.panes[id].?.prompt != .repl_choice)
- return refuse(p, req, "no question asked", line);
+ var offered: [64]u8 = undefined;
+ const choices = p.questionChoices(id, &offered) orelse return refuse(p, req, "no question asked", line);
+ // Only a choice the question offers answers it; anything
+ // else leaves it standing, and says what it takes.
+ if (!std.mem.eql(u8, choice, "-") and (choice.len != 1 or std.mem.indexOfScalar(u8, choices, choice[0]) == null)) {
+ var why: [96]u8 = undefined;
+ return refuse(p, req, std.fmt.bufPrint(&why, "answer takes {s} or -", .{choices}) catch "answer takes a choice or -", line);
+ }
if (apply) _ = p.answerQuestion(id, if (choice[0] == '-') null else choice[0]);
} else if (!apply) {
if (checkBuiltin(p, req, line, .pane)) |refusal| return refusal;
diff --git a/src/pardes.zig b/src/pardes.zig
index 6294b6cb..452f6174 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1684,6 +1684,9 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe
_ = exec.executeFrom(p, 0, "print(5)", true);
var want: [32]u8 = undefined;
try std.testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "ask {d} repl a b\n", .{file.serial})));
+ // A choice not offered is refused, and the question stands.
+ try std.testing.expectEqual(ctlfs.E.INVAL, th.wr(p, ctlfs.Node.of(file.serial, .ctl), "answer z\n").errno());
+ try std.testing.expectEqual(Pane.Prompt.repl_choice, file.prompt);
const answered = th.wr(p, ctlfs.Node.of(file.serial, .ctl), "answer a\n");
try std.testing.expectEqual(ctlfs.Status.ok, answered.reply.status);
try std.testing.expectEqualStrings("print(5)\r", answered.pty());
@@ -5499,6 +5502,25 @@ pub const Pardes = struct {
/// the REPL to send to (its letter). Null, or a choice not offered,
/// takes the question back. A key answers it, or `answer` on the pane's
/// ctl; false when the pane asks nothing.
+ /// The choices the question pane `id` asks offers, space-separated
+ /// (`k j`, `a b`), or null when it asks none.
+ pub fn questionChoices(p: *const Pardes, id: usize, buf: []u8) ?[]const u8 {
+ const asked = p.panes[id] orelse return null;
+ switch (asked.prompt) {
+ .del_side => return "k j",
+ .repl_choice => {
+ const ask = p.repl_ask orelse return null;
+ var w = std.Io.Writer.fixed(buf);
+ for (ask.offered, 0..) |serial, i| if (serial != 0) {
+ if (w.buffered().len > 0) w.writeByte(' ') catch {};
+ w.writeByte(@intCast('a' + i)) catch {};
+ };
+ return w.buffered();
+ },
+ else => return null,
+ }
+ }
+
pub fn answerQuestion(p: *Pardes, id: usize, choice: ?u8) bool {
const asked = p.panes[id] orelse return false;
switch (asked.prompt) {
@@ -7001,6 +7023,8 @@ test "Del from a key asks which neighbour takes the rows; nothing else asks" {
p.update(.{ .key = .{ .cp = 'd' } });
var want: [32]u8 = undefined;
try std.testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "ask {d} del k j\n", .{serial})));
+ try std.testing.expectEqual(ctlfs.E.INVAL, th.wr(p, ctl, "answer x\n").errno());
+ try std.testing.expectEqual(Pane.Prompt.del_side, p.panes[asked].?.prompt);
try std.testing.expectEqual(ctlfs.Status.ok, th.wr(p, ctl, "answer -\n").reply.status);
try std.testing.expect(p.panes[asked] != null and p.panes[asked].?.prompt == .none);
p.update(.{ .key = .{ .cp = ' ' } });