diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 16:34:52 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | b586d916c2aac1e5e577162898ebb86632f32db0 (patch) | |
| tree | ac109d93e28c478ec7b596bef1d5ae7664f2ec75 /src | |
| parent | f30ee05b0609b8f3aa488c94f3ff21733ed37053 (diff) | |
| download | pardes-b586d916c2aac1e5e577162898ebb86632f32db0.tar.gz pardes-b586d916c2aac1e5e577162898ebb86632f32db0.zip | |
Text sent to a REPL is plain lines, sent whole or not at all
A CRLF became two line ends, the blank line ending a Python block; an ESC in
the text, ESC[201~ above all, could end a bracketed paste early; and on the
board, whose writes are 128 effects of 64 bytes with nothing pending, a send
past 8 KB was cut and lost its closing ESC[201~ and Enter, leaving the REPL
in a paste. Lines now end in LF alone, ESC and every control byte but tab and
newline are dropped, and a send larger than can be written at once
(Pardes.writeRoom) is refused with a message instead of cut.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/exec.zig | 20 | ||||
| -rw-r--r-- | src/pardes.zig | 12 |
2 files changed, 31 insertions, 1 deletions
diff --git a/src/exec.zig b/src/exec.zig index cd2b4089..7f2e0e1f 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -471,7 +471,25 @@ pub fn sendToRepl(p: *Pardes, from: usize, to: usize, text: []const u8) void { const src = p.panes[from] orelse return; var idbuf: [48]u8 = undefined; const id = replId(&idbuf, dst); - edit.typeToTty(p, to, dst, text); + // Lines end in \n alone (a \r\n would give Python a blank line that + // ends its block), and no ESC or other control byte but a tab goes: an + // ESC[201~ in the text would end a bracketed paste early. + const clean = p.scratch.allocator().alloc(u8, text.len) catch return; + var n: usize = 0; + for (text, 0..) |c, i| { + if (c == '\r') { + if (i + 1 < text.len and text[i + 1] == '\n') continue; + clean[n] = '\n'; + } else if (c < ' ' and c != '\t' and c != '\n' or c == 0x7f) continue else clean[n] = c; + n += 1; + } + // Sent whole or not at all: a send cut short would leave the REPL in a + // paste, or with half a block. + if (n + 16 > p.writeRoom(to)) { + var said: [128]u8 = undefined; + return p.setMessage(from, std.fmt.bufPrint(&said, "Repl: {d} bytes is more than can be sent at once; send less", .{n}) catch "Repl: too long to send"); + } + edit.typeToTty(p, to, dst, clean[0..n]); 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 std.mem.startsWith(u8, id, "python") and diff --git a/src/pardes.zig b/src/pardes.zig index 64c05fff..85eae10f 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1487,6 +1487,10 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe var line: [ctlfs.ctl.pane_text_cap]u8 = undefined; try std.testing.expect(std.mem.endsWith(u8, ctlfs.ctl.paneText(p, p.panes[1].?, &line), " python-a ")); } + // What is sent is plain text: CRLF is LF, no ESC or other control. + _ = exec.executeFrom(p, 0, "a = 1\r\nb = \x1b[201~2", true); + Sent.drain(p); + try std.testing.expectEqualStrings("a = 1\rb = [201~2\r", Sent.said(1)); // Several selections executed at once: each is sent, not the first. { const text = try edit.flatSurface(p, &file.body); @@ -4626,6 +4630,14 @@ pub const Pardes = struct { if (off < bytes.len) p.parkPendingWrite(id, bytes[off..]); } + /// How many bytes `emitWrite` can take for pane `id` now without + /// dropping any: the effects left, then what the pending buffer holds. + pub fn writeRoom(p: *const Pardes, id: usize) usize { + const pending = limits.pending_write_cap -| p.pending_write_bytes; + if (p.pending_write[id] != null) return pending; + return (p.effects.len - p.effects_len) * 64 + pending; + } + fn parkPendingWrite(p: *Pardes, id: usize, bytes: []const u8) void { if (comptime limits.pending_write_cap == 0) return; if (p.pending_write_bytes + bytes.len > limits.pending_write_cap) return; |
