diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 16:18:03 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | 2a842d241c462c11959102f96a3945aec493cb86 (patch) | |
| tree | 1e284852b1301cd3f19b6c42a61ba6406f59857f | |
| parent | 062523f4684e81715dec844bb1983b7bde7df5fc (diff) | |
| download | pardes-2a842d241c462c11959102f96a3945aec493cb86.tar.gz pardes-2a842d241c462c11959102f96a3945aec493cb86.zip | |
A click written back to event is done as the click, REPL and all; a REPL's ctl line names it
Writing an M record back from a .py body with a REPL bound ran the text as a
shell command (exit 127), where the click itself went to the REPL. acme does
what the click would have done (xfideventwrite, xfid.c:842-844), so a body X
record written back now goes the way a middle click goes: to a bound REPL,
bracketed or line by line, the several-REPLs question included. An F record,
a line written to exec, was never a click and still runs as a command. For a
readable list of bindings, the smallest place: a bound terminal's ctl line
ends with its id after current/notcurrent.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | .agents/skills/pardes-9p/SKILL.md | 4 | ||||
| -rw-r--r-- | docs/fs.md | 8 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 6 | ||||
| -rw-r--r-- | src/ninep/events.zig | 12 | ||||
| -rw-r--r-- | src/pardes.zig | 16 |
5 files changed, 39 insertions, 7 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md index 9f2aa519..7cc3aeb1 100644 --- a/.agents/skills/pardes-9p/SKILL.md +++ b/.agents/skills/pardes-9p/SKILL.md @@ -132,7 +132,9 @@ command pane, below what it showed. A terminal bound as a REPL (`Repl python` on its ctl) takes the middle clicks made on a `.py` body instead, but never a 9P write: a script sends code by writing the REPL pane's `pty/data`, and runs a command from such a file with `Exec <text>` or the -tag. Every refused 9P write adds an `err <serial|-> +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. Every refused 9P write adds an `err <serial|-> <file>: <why>` record to `$m/log`; through a mount the write itself only says `Invalid argument`. @@ -258,7 +258,8 @@ same tree without leaving the process. A terminal can be bound as a language's REPL: `Repl python` in its tag or on its `ctl` (the language names are the syntax table's; `Repl -` unbinds, `Repl` bare says the binding, `Repl python` again changes nothing). Its tag -shows its id, `python-a`, `python-b` for the next, a freed letter reused. +shows its id, `python-a`, `python-b` for the next, a freed letter reused, +and so does the end of its `ctl` line, after `current`/`notcurrent`. Then an exec made by a gesture on the body of a file in that language -- a middle click, the execute key, on a selection or a single word, even `make` in a comment -- or on the REPL's own body is typed into the REPL instead of @@ -299,7 +300,10 @@ pane's own `look` or `exec`, or to the root's while that pane has the keyboard, a click with no place in the text: an `F` record at `0 0` carrying the line. So a client holding `event` that wants a command run gets its own exec back as a record: it runs it through `ctl`, or writes the record back. -Writing a record back performs the action. acme takes back only `<origin> +Writing a record back performs the action, as the click would have (acme's +xfideventwrite): a body `X` goes to a REPL bound for the text as a middle +click does, where an `F` record, a line written to `exec`, runs as the +command it was. acme takes back only `<origin> <action><q0> <q1>`, the text of that range; pardes takes the record whole as it was read too, and for an empty range acts on its text, which is how such a line is done. A click in a file's body carries the offsets of the text it diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 3690ac47..4e1c039b 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -163,7 +163,7 @@ fn writeQuoted(w: *std.Io.Writer, s: []const u8) !void { try w.writeByte('\''); } -pub const pane_text_cap = 7 * 12 + 2 * 256 + 4 + "notcurrent ".len; +pub const pane_text_cap = 7 * 12 + 2 * 256 + 4 + "notcurrent ".len + 48; /// acme's winctlprint: serial, tag length, body length, a reserved zero, the /// dirty flag, then the width in cells, the font and the tab width. Then @@ -186,6 +186,10 @@ pub fn paneText(p: *Pardes, pane: *Pane, buf: []u8) []const u8 { // While a column's or the workspace's tag has the keyboard, no pane does. const current = p.panes[p.active] == pane and !p.header_focus; w.print(" {d:>11} {s} ", .{ config.tab_width, if (current) "current" else "notcurrent" }) catch {}; + // A terminal bound as a REPL ends with its id, `python-a`. + var idbuf: [48]u8 = undefined; + const id = exec_line.replId(&idbuf, pane); + if (id.len > 0) w.print("{s} ", .{id}) catch {}; return w.buffered(); } diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 9ebb4bc1..cff42c55 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -468,7 +468,7 @@ pub fn notePtyOutput(p: *Pardes, id: usize, bytes: []const u8) void { p.fs.news = true; } -const EventRecord = struct { action: Action, q0: u32, q1: u32, text: ?[]const u8 = null }; +const EventRecord = struct { origin: u8, action: Action, q0: u32, q1: u32, text: ?[]const u8 = null }; const EventReader = struct { data: []const u8, @@ -478,6 +478,7 @@ const EventReader = struct { if (er.i >= er.data.len) return null; var i = er.i; if (i + 2 > er.data.len) return null; + const origin = er.data[i]; i += 1; const action = Action.fromChar(er.data[i]) orelse return null; i += 1; @@ -497,7 +498,7 @@ const EventReader = struct { } if (i >= er.data.len or er.data[i] != '\n') return null; er.i = i + 1; - return .{ .action = action, .q0 = q0, .q1 = q1, .text = text }; + return .{ .origin = origin, .action = action, .q0 = q0, .q1 = q1, .text = text }; } }; @@ -539,7 +540,12 @@ pub fn writeEvent(p: *Pardes, req: Req, id: usize) Reply { const said = if (r.q0 == r.q1) r.text orelse whole[lo..hi] else whole[lo..hi]; const text = p.scratch.allocator().dupe(u8, said) catch continue; switch (r.action) { - .body_exec, .tag_exec => _ = exec.execute(p, live, text), + // A body's click written back is done as the click would have + // been (acme's xfideventwrite, xfid.c:842-844): a REPL bound for + // it takes it. One that came from a write to exec (`F`) was + // never a click, and runs as the command it was. + .body_exec => _ = exec.executeFrom(p, live, text, r.origin != 'F'), + .tag_exec => _ = exec.execute(p, live, text), .body_look, .tag_look => look.lookAt(p, live, text), else => unreachable, } diff --git a/src/pardes.zig b/src/pardes.zig index 872ea171..9bf1c5f4 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1471,6 +1471,22 @@ test "a REPL bound for a language takes the exec made on a body in it; tags, Exe try std.testing.expect(!Sent.spawned); try std.testing.expect(file.msg_sent); try std.testing.expectEqualStrings("\u{2192} python-a", file.msg[0..file.msg_len]); + // A body's click written back to event is done as the click: sent. A + // line written to exec came back as F and runs as the command it was. + { + const th = @import("ninep/testing.zig"); + const event = ctlfs.Node.of(file.serial, .event); + const h = th.call(p, .{ .tag = 1, .op = .open, .node = event }).reply.handle; + defer _ = th.call(p, .{ .tag = 2, .op = .release, .node = event, .handle = h }); + // The 9P harness performs the effects: what it wrote is its answer's. + const sent = th.wr(p, event, "MX0 8\n"); + try std.testing.expectEqualStrings("print(1)\r", sent.pty_buf[0..sent.pty_len]); + const ran = th.wr(p, event, "FX0 0 2 2 ls\n"); + try std.testing.expect(ran.pty_len == 0 and ran.spawned); + // The pane's ctl line ends with the id it is bound as. + 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 ")); + } // Several selections executed at once: each is sent, not the first. { const text = try edit.flatSurface(p, &file.body); |
