summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 04:55:00 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit253a5c1e036d96eada910c47eb500ed0c8e37b5c (patch)
tree88366de4f3e17029a61b94ddb37e80795a9849fb /src
parentcdf4cbbf1fdd121aabdbbea33f11d5ee44d7764e (diff)
downloadpardes-253a5c1e036d96eada910c47eb500ed0c8e37b5c.tar.gz
pardes-253a5c1e036d96eada910c47eb500ed0c8e37b5c.zip
A pane has a tagexec: a word run as a click in its tag
The workspace had /tagexec and a column its exec, but a pane's own tag could be clicked only by an event record. pane/<n>/tagexec runs each line as a click in that pane's tag (never redirected to its event reader, as a header's is not) and reads back what it touched. The README, fs.md and the skill list it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/fs-help.txt2
-rw-r--r--src/ninep/ctl.zig4
-rw-r--r--src/ninep/pane.zig19
-rw-r--r--src/ninep/pty.zig4
-rw-r--r--src/ninep/tree.zig13
5 files changed, 29 insertions, 13 deletions
diff --git a/src/fs-help.txt b/src/fs-help.txt
index 60ce4d4e..9e6a9240 100644
--- a/src/fs-help.txt
+++ b/src/fs-help.txt
@@ -15,7 +15,7 @@ layout a line per column: serial index x width current|notcurrent empty|ful
tag the workspace tag, tagexec a word as a click in it; col/<serial>/ a column's tag, ctl (Delcol Joincol New Tty), exec; rmdir: empty only
pane/new open it to make a pane (named +New) in the active column, filling it if empty; the read answers its serial
pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll errors (write-only:
- appended to its directory's +Errors pane) event look exec, pty/ on terminals; rmdir closes
+ appended to its directory's +Errors pane) event look exec tagexec, pty/ on terminals; rmdir closes
os/, src/ the host filesystem; the editor's own sources
$m below: $NINE_MOUNT/pardes/<pid|NAME>; $NINE_MOUNT under 9ns --unix; PARDES_MOUNT in a Tty9p shell.
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index eb8ed292..f8623903 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -168,7 +168,7 @@ pub fn completeEnd(p: *Pardes, data: []const u8) usize {
/// clicked text itself, so it may not be empty or carry control characters.
/// A null `serial` clicks at whichever pane is active as each line runs,
/// which is what the root's own look and exec mean; a serial stays put.
-pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool) Reply {
+pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool, in_tag: bool) Reply {
for ([2]bool{ false, true }) |apply| {
if (apply) p.fs.results_len = 0;
var it = Messages.init(p, req.data);
@@ -181,7 +181,7 @@ pub fn command(p: *Pardes, req: Req, serial: ?u32, exec: bool) Reply {
const id = if (serial) |s| p.paneBySerial(s) orelse break else p.active;
if (p.panes[id] == null) return Reply.fail(req.tag, E.NOENT);
p.fs.no_pane_slot = false;
- run(p, id, exec, text, false);
+ run(p, id, exec, text, in_tag);
// A pane it would have opened had no slot: the write fails, and
// a look reads back nothing.
if (p.fs.no_pane_slot) {
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index 8cac55d6..4a50b9a1 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -259,7 +259,7 @@ pub fn fileSize(p: *Pardes, id: usize, f: PaneFile) u64 {
.dot => range_len,
.limit => if (pf.limit == null) 0 else range_len,
.dirty, .mark, .scroll => 2,
- .look, .exec => ctl.resultsLen(p),
+ .look, .exec, .tagexec => ctl.resultsLen(p),
// What the next read would answer, which a client can see without
// parking on it.
.event => if (pf.events.peek()) |record| record.len else 0,
@@ -328,7 +328,7 @@ pub fn read(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply
.dirty => readFlag(p, req, dirtyOf(pane)),
.mark => readFlag(p, req, !pf.nomark),
.scroll => readFlag(p, req, !pf.noscroll),
- .look, .exec => ctl.readResults(p, req),
+ .look, .exec, .tagexec => ctl.readResults(p, req),
.event => events.readQueue(p, req, &pf.events),
.pty_status => pty.readStatus(p, req, id, pane),
.pty_data => pty.readData(p, req, pf),
@@ -420,7 +420,9 @@ pub fn write(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply
.data, .xdata => writeData(p, req, pane),
.sel => writeSel(p, req, pane),
.dirty, .mark, .scroll => writeFlag(p, req, pane, file),
- .look, .exec => ctl.command(p, req, pane.serial, file == .exec),
+ .look, .exec => ctl.command(p, req, pane.serial, file == .exec, false),
+ // A click in its tag: never an event reader's to hear (cols.writeExec).
+ .tagexec => ctl.command(p, req, pane.serial, true, true),
.event => events.writeEvent(p, req, id),
.errors => if (appendErrors(p, id, req.data)) |took|
.{ .tag = req.tag, .written = @intCast(took) }
@@ -1275,3 +1277,14 @@ test "a limit write that fails keeps the limit a truncation cleared before it" {
_ = call(p, .{ .tag = 3, .op = .release, .node = limit });
try testing.expectEqualStrings("", rd(p, limit, 0, 100).bytes);
}
+
+test "a pane's tagexec runs a word as a click in its tag, and reads back what it touched" {
+ const gpa = testing.allocator;
+ const p = try withFile(gpa, "x\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .tagexec), "Msg from the tag\n").reply.status);
+ try testing.expect(th.logHas(p, "from the tag"));
+ var want: [16]u8 = undefined;
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{serial}), rd(p, Node.of(serial, .tagexec), 0, 64).bytes);
+}
diff --git a/src/ninep/pty.zig b/src/ninep/pty.zig
index 76cef83b..6b2abd0a 100644
--- a/src/ninep/pty.zig
+++ b/src/ninep/pty.zig
@@ -453,7 +453,7 @@ test "a pane that is not a terminal has no pty/ at all" {
var buf: [32]th.Dirent = undefined;
const files = th.dirents(rdir(p, dir, 0).bytes, &buf);
- try testing.expectEqual(@as(usize, 17), files.len);
+ try testing.expectEqual(@as(usize, 18), files.len);
try testing.expect(th.nameAt(files, "pty") == null);
try testing.expectEqual(E.NOENT, look_up(p, dir, "pty_ctl").errno());
@@ -474,7 +474,7 @@ test "a terminal pane's pty/ holds exactly ctl, status and data" {
var buf: [32]th.Dirent = undefined;
const files = th.dirents(rdir(p, dir, 0).bytes, &buf);
- try testing.expectEqual(@as(usize, 18), files.len);
+ try testing.expectEqual(@as(usize, 19), files.len);
try testing.expect(th.nameAt(files, "pty").?.dir);
const inside = th.dirents(rdir(p, Node.of(serial, .pty), 0).bytes, &buf);
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index f45a1a90..398824d5 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -259,6 +259,9 @@ pub const PaneFile = enum(u5) {
event,
look,
exec,
+ /// A word run as a click in the pane's tag, as /tagexec is in the
+ /// workspace's and col/<n>/exec in a column's.
+ tagexec,
pty,
pty_ctl,
pty_status,
@@ -965,7 +968,7 @@ fn linesFile(target: Target) bool {
return switch (target) {
.top => |f| f == .look or f == .exec or f == .ctl or f == .tagexec,
.col => |c| c.file == .ctl or c.file == .exec,
- .pane => |t| t.file == .look or t.file == .exec or t.file == .ctl,
+ .pane => |t| t.file == .look or t.file == .exec or t.file == .ctl or t.file == .tagexec,
};
}
@@ -974,7 +977,7 @@ fn resultsFile(target: Target) bool {
return switch (target) {
.top => |f| f == .look or f == .exec or f == .tagexec,
.col => |c| c.file == .exec,
- .pane => |t| t.file == .look or t.file == .exec,
+ .pane => |t| t.file == .look or t.file == .exec or t.file == .tagexec,
};
}
@@ -1025,7 +1028,7 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
fn writeNow(p: *Pardes, req: Req, target: Target) Reply {
switch (target) {
.top => |f| return switch (f) {
- .look, .exec => ctl.command(p, req, null, f == .exec),
+ .look, .exec => ctl.command(p, req, null, f == .exec, false),
.log => events.writeLog(p, req),
.focus => ctl.writeFocus(p, req),
.ctl => ctl.writeRoot(p, req),
@@ -1234,10 +1237,10 @@ test "readdir lists the root and a pane directory without creating anything" {
const dir = rdir(p, Node.of(serial, .dir), 0);
const files = th.dirents(dir.bytes, &buf);
- try testing.expectEqual(@as(usize, 17), files.len);
+ try testing.expectEqual(@as(usize, 18), files.len);
for ([_][]const u8{
"name", "body", "tag", "ctl", "addr", "dot", "limit", "data", "xdata",
- "sel", "dirty", "mark", "scroll", "errors", "event", "look", "exec",
+ "sel", "dirty", "mark", "scroll", "errors", "event", "look", "exec", "tagexec",
}, 0..) |name, i|
try testing.expectEqualStrings(name, files[i].name);
try testing.expect(th.nameAt(files, ".") == null);