From 4793520998969439bb569c968c3143ae46aefc66 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 22:17:49 -0300 Subject: A pane's question is logged as ask, and answer on its ctl answers it A script could not answer the several-REPLs question, which only a key could. Every question a pane asks on its notice band (which REPL takes an exec, Del's side from the keyboard) is now logged as `ask `, and `answer ` on the pane's ctl answers it as that key would, `answer -` taking it back as Esc does. One function answers for a key and for the ctl. Co-Authored-By: Claude Opus 5.5 --- src/pardes.zig | 91 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 72 insertions(+), 19 deletions(-) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index e20eb17e..8a6fa589 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1514,6 +1514,16 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe Sent.drain(p); try std.testing.expectEqualStrings("print(2)\r", Sent.said(2)); try std.testing.expect(Sent.len[1] == 0 and file.prompt == .none); + // The question is in the log, and a script answers it on the ctl. + { + const th = @import("ninep/testing.zig"); + _ = 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}))); + 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()); + } _ = exec.executeFrom(p, 0, "print(3)", true); p.update(.{ .key = .{ .cp = Key.escape } }); Sent.drain(p); @@ -5295,6 +5305,42 @@ pub const Pardes = struct { } } + /// Answers the question pane `id` asks on its notice band, logged as + /// `ask `: Del's side (`k` above, `j` below) or + /// 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. + pub fn answerQuestion(p: *Pardes, id: usize, choice: ?u8) bool { + const asked = p.panes[id] orelse return false; + switch (asked.prompt) { + .repl_choice => { + asked.prompt = .none; + const ask = p.repl_ask orelse return true; + p.repl_ask = null; + defer p.gpa.free(ask.text); + const letter = choice orelse return true; + if (letter < 'a' or letter > 'z') return true; + const serial = ask.offered[letter - 'a']; + if (serial == 0) return true; + // The pane offered, still that REPL and still running: it + // may have closed or been rebound while the question stood. + if (p.paneBySerial(serial)) |to| if (p.panes[to].?.repl) |r| + if (r.lang == ask.lang and r.letter == letter and exec.replLive(p, to)) { + exec.sendToRepl(p, id, to, ask.text); + return true; + }; + p.setMessage(id, "Repl: the REPL chosen is gone; nothing sent"); + }, + .del_side => { + asked.prompt = .none; + if (choice == 'k') exec.runBuiltin(p, .Del, id, "", "k"); + if (choice == 'j') exec.runBuiltin(p, .Del, id, "", "j"); + }, + else => return false, + } + return true; + } + fn handleKey(p: *Pardes, key: Key) void { // A double-click is two clicks with nothing between them. p.last_click = null; @@ -5303,27 +5349,12 @@ pub const Pardes = struct { // pane or a header edit is in: that key is its answer. for (p.panes, 0..) |slot, id| { const asked = slot orelse continue; - if (asked.prompt == .repl_choice) { - asked.prompt = .none; - const ask = p.repl_ask orelse return; - p.repl_ask = null; - defer p.gpa.free(ask.text); + switch (asked.prompt) { // Its letter sends it there; anything else takes it back. - if (key.ctrl or key.alt or key.cp > 'z' or key.cp < 'a') return; - const serial = ask.offered[key.cp - 'a']; - if (serial == 0) return; - // The pane offered, still that REPL and still running: it - // may have closed or been rebound while the question stood. - if (p.paneBySerial(serial)) |to| if (p.panes[to].?.repl) |r| - if (r.lang == ask.lang and r.letter == key.cp and exec.replLive(p, to)) - return exec.sendToRepl(p, id, to, ask.text); - p.setMessage(id, "Repl: the REPL chosen is gone; nothing sent"); - return; + .repl_choice => _ = p.answerQuestion(id, if (key.ctrl or key.alt or key.cp > 'z' or key.cp < 'a') null else @intCast(key.cp)), + .del_side => _ = p.answerQuestion(id, if (hit(key, config.del_above)) 'k' else if (hit(key, config.del_below)) 'j' else null), + else => continue, } - if (asked.prompt != .del_side) continue; - asked.prompt = .none; - if (hit(key, config.del_above)) exec.runBuiltin(p, .Del, id, "", "k"); - if (hit(key, config.del_below)) exec.runBuiltin(p, .Del, id, "", "j"); return; } const pane = p.panes[p.active] orelse return; @@ -6754,6 +6785,28 @@ test "Del from a key asks which neighbour takes the rows; nothing else asks" { try std.testing.expectEqual(top, p.active); try std.testing.expectEqual(h[2], p.rects[low].h); + // A script answers the question on the pane's ctl, as the log's `ask` + // names it: `answer -` takes it back, a side closes the pane toward it. + { + const th = @import("ninep/testing.zig"); + p.newScratchBelow(top); + const asked = p.active; + p.sync(); + const serial = p.panes[asked].?.serial; + const ctl = ctlfs.Node.of(serial, .ctl); + try std.testing.expectEqual(ctlfs.E.INVAL, th.wr(p, ctl, "answer k\n").errno()); + p.update(.{ .key = .{ .cp = ' ' } }); + 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.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 = ' ' } }); + p.update(.{ .key = .{ .cp = 'd' } }); + try std.testing.expectEqual(ctlfs.Status.ok, th.wr(p, ctl, "answer k\n").reply.status); + try std.testing.expect(p.paneBySerial(serial) == null); + } + // Bare Del from anything but a key closes at once and gives the rows up, // as it always has. p.newScratchBelow(top); -- cgit v1.3