diff options
| -rw-r--r-- | docs/fs.md | 4 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 11 | ||||
| -rw-r--r-- | src/pardes.zig | 24 |
3 files changed, 35 insertions, 4 deletions
@@ -372,7 +372,9 @@ for the question the pane asks on its notice band, which the log names as `ask <serial> <what> <choices>` -- `ask 4 del k j` for Del's side from the keyboard (`k` the pane above takes the rows, `j` the one below), `ask 4 repl a b` for which bound REPL takes an exec (a REPL's letter) -- `answer -` -taking it back as Esc does (with no question standing, `answer` is refused), +taking it back as Esc does (a choice the question does not offer is refused, +naming those it does, and the question stands; with no question standing, +`answer` is refused), and acme's `lock` and `unlock` (editors/acme/xfid.c:603-611), for an edit of several writes to `addr` and `data` that another client must not land in the middle of. As in acme the lock binds only the clients that take 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 = ' ' } }); |
