summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.agents/skills/pardes-9p/SKILL.md3
-rw-r--r--docs/fs.md4
-rw-r--r--src/exec.zig12
-rw-r--r--src/pardes.zig5
4 files changed, 22 insertions, 2 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index 471967ad..845326d2 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -144,7 +144,8 @@ tag. A bound terminal's `ctl` line ends with its id (`python-a`); an event
record written back from such a file's body goes to its REPL, as the click
would. `Repl -` unbinds; a bare `Repl` says the binding. Multi-line code
written to `pty/data` should be a bracketed paste, `\e[200~<code>\e[201~`
-then `\r`: sent line by line, a blank line ends a Python block, and Python
+then `\r`, and a second `\r` when the code's last line is indented (a `def`
+or `for` body): one Enter leaves such a block open. Sent line by line, a blank line ends a Python block, and Python
3.14's REPL auto-indents each line it is typed. Every refused 9P write adds an `err <serial|->
<file>: <why>` record to `$m/log`; through a mount the write itself only says
`Invalid argument`.
diff --git a/docs/fs.md b/docs/fs.md
index a883e185..2dee3a49 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -300,7 +300,9 @@ the tag is how to run `make` from that file), `Exec <text>` run by name
the text, looked at or clicked (`# @`pytest -x`` in a script). A 9P `exec`
is no gesture and is never sent: a script writes to the REPL pane's
`pty/data`, multi-line code as a bracketed paste (`\e[200~<code>\e[201~`
-then `\r`), since line by line a blank line ends a Python block and Python
+then `\r`, and a second `\r` when its last line is indented: one Enter
+leaves a block open, pasted or typed; a middle click sends that second one
+itself), since line by line a blank line ends a Python block and Python
3.14's REPL auto-indents each line typed into it. The REPL gets the text
wherever it is -- at a `pdb` or `input()` prompt too. Bindings are not dumped, so a Restore leaves none.
diff --git a/src/exec.zig b/src/exec.zig
index 1e9d4b2d..0c0930e2 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -464,6 +464,13 @@ fn routeToRepl(p: *Pardes, from: usize, text: []const u8) Routed {
return .asked;
}
+fn pythonBlockOpen(text: []const u8) bool {
+ const trimmed = std.mem.trimEnd(u8, text, " \t\n");
+ const start = if (std.mem.lastIndexOfScalar(u8, trimmed, '\n')) |nl| nl + 1 else 0;
+ const last = trimmed[start..];
+ return last.len > 0 and (last[0] == ' ' or last[0] == '\t' or last[last.len - 1] == ':');
+}
+
/// Types `text` into the REPL in `to`, bracketed if its program asked for
/// that, then Enter; says so on `from`'s message row and in the log.
pub fn sendToRepl(p: *Pardes, from: usize, to: usize, text: []const u8) void {
@@ -491,6 +498,11 @@ pub fn sendToRepl(p: *Pardes, from: usize, to: usize, text: []const u8) void {
}
edit.typeToTty(p, to, dst, clean[0..n]);
p.emitWrite(to, "\r");
+ // 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.
+ if ((if (dst.repl) |r| r.lang == python_lang else false) and pythonBlockOpen(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 (if (dst.repl) |r| r.lang == python_lang else false) and
std.mem.indexOfScalar(u8, text, '\n') != null)
diff --git a/src/pardes.zig b/src/pardes.zig
index 239987ea..e20eb17e 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1474,6 +1474,11 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe
_ = 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));
+ // A block left open by its indented last line gets the blank line that
+ // ends it: a second Enter.
+ _ = exec.executeFrom(p, 0, "def f():\n return 1", true);
+ Sent.drain(p);
+ try std.testing.expectEqualStrings("def f():\r return 1\r\r", Sent.said(1));
// Several selections executed at once: each is sent, not the first.
{
const text = try edit.flatSurface(p, &file.body);