summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/exec.zig27
-rw-r--r--src/pardes.zig14
-rw-r--r--test/fs.py12
3 files changed, 49 insertions, 4 deletions
diff --git a/src/exec.zig b/src/exec.zig
index 6e6f528b..b8312a21 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -444,12 +444,28 @@ fn routeToRepl(p: *Pardes, from: usize, text: []const u8) Routed {
}
const repl_enter_wait_ns: u64 = 250 * std.time.ns_per_ms;
+/// Python echoes a long paste over several reads: its Enter waits for the
+/// echo to go quiet this long, not for the first of it.
+const repl_quiet_ns: u64 = 100 * std.time.ns_per_ms;
+
+/// The host's clock now; core time (`now_ns`, `stepped_ns`) is as old as
+/// the last frame, seconds in an idle session.
+fn clockNow(p: *Pardes) u64 {
+ return if (p.host.vtable.now) |clock| clock(p.host.ctx) else p.now_ns;
+}
/// Sends the Enters a paste into pane `id`'s REPL is owed: when the REPL
-/// has answered it (`answered`), or once the wait is up.
+/// has answered it (`answered`), or once the wait is up. Python's answer
+/// only restarts a short wait, so the Enter goes once its echo of the
+/// whole paste is in (a multi-line paste read in part would leave it at
+/// `...`).
pub fn flushReplEnter(p: *Pardes, id: usize, answered: bool) void {
const pane = p.panes[id] orelse return;
if (pane.repl_enter == 0) return;
+ if (answered and (if (pane.repl) |r| r.lang == python_lang else false)) {
+ pane.repl_enter_due_ns = clockNow(p) + repl_quiet_ns;
+ return;
+ }
if (!answered and p.now_ns < pane.repl_enter_due_ns) return;
for (0..pane.repl_enter) |_| p.emitWrite(id, "\r");
pane.repl_enter = 0;
@@ -492,7 +508,11 @@ pub fn sendToRepl(p: *Pardes, from: usize, to: usize, text: []const u8) void {
// A Python block whose last line is indented, or a bare header, is not
// over at one Enter, pasted into 3.13's REPL or typed into the old one:
// the blank line a second Enter gives ends it.
- const enters: u8 = if ((if (dst.repl) |r| r.lang == python_lang else false) and pythonBlockOpen(clean[0..n])) 2 else 1;
+ // So is a paste of more than one line: 3.13's REPL takes it as one
+ // input, which one Enter leaves at `...`.
+ const python = if (dst.repl) |r| r.lang == python_lang else false;
+ const lines = std.mem.count(u8, std.mem.trim(u8, clean[0..n], " \t\n"), "\n") + 1;
+ const enters: u8 = if (python and (pythonBlockOpen(clean[0..n]) or (pasted and lines > 1))) 2 else 1;
if (pasted) {
// An Enter read with the paste is part of it: 3.13's REPL takes it
// as a newline in the pasted text, even a one-line one, and waits.
@@ -503,8 +523,7 @@ pub fn sendToRepl(p: *Pardes, from: usize, to: usize, text: []const u8) void {
// nobody watches), `stepped_ns` and `now_ns` are as old as the last
// frame, and a wait counted from there would be over already, the
// Enter going out with the paste.
- const now = if (p.host.vtable.now) |clock| clock(p.host.ctx) else p.now_ns;
- dst.repl_enter_due_ns = now + repl_enter_wait_ns;
+ dst.repl_enter_due_ns = clockNow(p) + repl_enter_wait_ns;
} else for (0..enters) |_| p.emitWrite(to, "\r");
// Line by line, a blank line inside a Python block ends the block.
if (!panes.Terminal.bracketedPaste(dst) and !dst.repl_warned and (if (dst.repl) |r| r.lang == python_lang else false) and
diff --git a/src/pardes.zig b/src/pardes.zig
index d6859f86..289dcee0 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1652,7 +1652,21 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe
try std.testing.expectEqualStrings("\x1b[200~print(1)\x1b[201~", Sent.said(1));
p.update(.{ .output = .{ .pane = 1, .bytes = "print(1)" } });
Sent.drain(p);
+ // Python's echo only restarts a short wait: the Enter goes once the
+ // echo of the whole paste has gone quiet.
+ try std.testing.expectEqualStrings("", Sent.said(1));
+ p.now_ns = p.panes[1].?.repl_enter_due_ns;
+ p.update(.tick);
+ Sent.drain(p);
try std.testing.expectEqualStrings("\r", Sent.said(1));
+ // A paste of more than one line gets the blank line a block gets.
+ _ = exec.executeFrom(p, 0, "print(11)\nprint(22)", true);
+ Sent.drain(p);
+ try std.testing.expectEqualStrings("\x1b[200~print(11)\nprint(22)\x1b[201~", Sent.said(1));
+ p.now_ns = p.panes[1].?.repl_enter_due_ns;
+ p.update(.tick);
+ Sent.drain(p);
+ try std.testing.expectEqualStrings("\r\r", Sent.said(1));
// Nothing answering, it goes when its wait is up.
_ = exec.executeFrom(p, 0, "print(2)", true);
p.update(.tick); // on the clock: not yet, however old core time is
diff --git a/test/fs.py b/test/fs.py
index b97c6967..09d366b5 100644
--- a/test/fs.py
+++ b/test/fs.py
@@ -524,6 +524,18 @@ def python_repl_runs_one_line(binary):
record = b'MX0 0 0 %d %s\n' % (len(line), line)
client.rpc(118, struct.pack('<IQI', fid, 0, len(record)) + record)
assert wait_for(f'\n{1000 + n}'), (n, body())
+ # More than one line, with and without a newline at the end,
+ # and a block with a statement after it: each runs whole,
+ # and the REPL is back at its prompt, not at `...`.
+ for code, outputs in [(b'print(11)\nprint(22)', ['11', '22']),
+ (b'print(33)\nprint(44)\n', ['33', '44']),
+ (b'for i in range(2):\n print(55 + i)\nprint(77)\n', ['55', '56', '77'])]:
+ record = b'MX0 0 0 %d %s\n' % (len(code), code)
+ client.rpc(118, struct.pack('<IQI', fid, 0, len(record)) + record)
+ for out in outputs:
+ assert wait_for(f'\n{out}\n'), (code, out, body())
+ time.sleep(.5)
+ assert body().rstrip().endswith('>>>'), (code, body()[-200:])
finally:
client.close(fid)