From 7e6e5e803c6ec2e203b094b2a64291840c5e6aed Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 23:04:59 -0300 Subject: 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 --- src/ninep/ctl.zig | 11 ++++++++--- src/pardes.zig | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) (limited to 'src') 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 = ' ' } }); -- cgit v1.3