From 84b04c8ea7f04ba7569628e49ca900622c8d7858 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 16:53:47 -0300 Subject: A REPL question's answer goes to the pane its letter offered, or says that one is gone The letter was looked up again when answered, so a REPL closed or rebound while the question stood could hand the text to another pane with the same letter, or drop it without a word. The question now keeps each letter's serial; the answer is that pane, still bound and running, or a message. Co-Authored-By: Claude Opus 5.5 --- src/exec.zig | 1 + src/pardes.zig | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/exec.zig b/src/exec.zig index 584d1b96..eb685c05 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -453,6 +453,7 @@ fn routeToRepl(p: *Pardes, from: usize, text: []const u8) Routed { const owned = p.gpa.dupe(u8, text) catch return .none; if (p.repl_ask) |old| p.gpa.free(old.text); p.repl_ask = .{ .from = src.serial, .lang = lang, .text = owned }; + for (bound[0..n]) |i| p.repl_ask.?.offered[p.panes[i].?.repl.?.letter - 'a'] = p.panes[i].?.serial; var w = std.Io.Writer.fixed(&src.question); w.print("send to {s}:", .{grammars[lang].name}) catch {}; for (bound[0..n]) |i| w.print(" {c}", .{p.panes[i].?.repl.?.letter}) catch {}; diff --git a/src/pardes.zig b/src/pardes.zig index 45d96f88..91f0f9f4 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1531,6 +1531,16 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe p.update(.{ .key = .{ .cp = Key.escape } }); Sent.drain(p); try std.testing.expect(Sent.len[1] == 0 and Sent.len[2] == 0); + // The pane a letter offered is the one it answers for: unbound while + // the question stood, it is sent nothing, and that is said. + _ = exec.executeFrom(p, 0, "print(4)", true); + try std.testing.expect(p.executeBuiltinLine(2, "Repl -")); + p.update(.{ .key = .{ .cp = 'b' } }); + Sent.drain(p); + try std.testing.expect(Sent.len[1] == 0 and Sent.len[2] == 0); + try std.testing.expectEqualStrings("Repl: the REPL chosen is gone; nothing sent", file.msg[0..file.msg_len]); + try std.testing.expect(p.executeBuiltinLine(2, "Repl python")); + try std.testing.expectEqualStrings("python-b", exec.replId(&idbuf, p.panes[2].?)); // A REPL whose program has left (Ctrl-D: the shell has the tty) is // sent nothing, and nothing runs: it says so. @@ -4152,7 +4162,9 @@ pub const Pardes = struct { exec_depth: u8 = 0, /// An exec waiting on the REPL question (`Pane.Prompt.repl_choice`): /// the pane it came from, the language, and the text, owned. - repl_ask: ?struct { from: u32, lang: u8, text: []u8 } = null, + /// `offered` holds, by letter, the serial of the pane each answer named + /// when asked: a letter answered later is that pane or nothing. + repl_ask: ?struct { from: u32, lang: u8, text: []u8, offered: [26]u32 = @splat(0) } = null, /// A key press is being handled: someone is at the keyboard to answer, /// so a builtin may ask rather than guess. Nothing else sets it -- not /// a click, a 9P write, a startup line or a pane's shell exiting. @@ -5261,9 +5273,14 @@ pub const Pardes = struct { defer p.gpa.free(ask.text); // 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; - for (p.panes, 0..) |target, to| if (target) |repl| if (repl.repl) |r| + 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; } if (asked.prompt != .del_side) continue; -- cgit v1.3