From 4b64d4b237b2bc5f8197e2232c84c0d1ed1a8fdb Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 04:15:16 -0300 Subject: A multi-line paste into Python's REPL runs whole, back at its prompt The Enter went on Python's first output after the paste, before it had taken in the whole paste, and one Enter leaves a multi-line input at ... anyway. Python's output now only restarts a 100 ms quiet wait (the 250 ms fallback stays), and a paste of more than one line gets the second Enter a block gets. fs.py runs print(11)\nprint(22), the same with a trailing newline, and a for block followed by a statement in a real python3, each back at >>>. Co-Authored-By: Claude Opus 5.5 --- src/exec.zig | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'src/exec.zig') 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 -- cgit v1.3