summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.agents/skills/pardes-9p/SKILL.md4
-rw-r--r--docs/fs.md8
-rw-r--r--src/ninep/ctl.zig6
-rw-r--r--src/ninep/events.zig12
-rw-r--r--src/pardes.zig16
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`.
diff --git a/docs/fs.md b/docs/fs.md
index 9560382c..8caaff3c 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -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);