summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/fs-help.txt4
-rw-r--r--src/ninep/ctl.zig50
-rw-r--r--src/ninep/tree.zig7
3 files changed, 57 insertions, 4 deletions
diff --git a/src/fs-help.txt b/src/fs-help.txt
index f9e9c6bd..44f8f407 100644
--- a/src/fs-help.txt
+++ b/src/fs-help.txt
@@ -5,12 +5,12 @@ index one line per pane: serial, kind (text|term|pdf|image), dirty flag, n
status pid, version and pane count
look write a line: a right click on it at the active pane; read: the serials it touched
exec write a line: a middle click, an editor command word or a shell line; read the same
-log recent events, one a line: new/del/rename/save <serial> <name>, and
- msg <serial|-> <text>, what the editor said; write follow to wait (tail -f won't)
+log recent events: new/del/rename/save <serial> <name>, msg <serial|-> <text>; write follow to wait
screen rendered screen as JSON, frozen from open to close
listeners the session's dial addresses
focus the serial of the pane with the keyboard; write a serial to give it the keyboard
ctl the settings, one a line as a write takes them; write one, or Newcol, Dump, Kill...
+commands every builtin: its word, arg if it takes one, and root or pane: which ctl takes it
pane/new open it to make a pane; the read answers that pane's serial
pane/<n>/ name body tag ctl addr dot limit data xdata sel dirty mark scroll
errors event look exec, and pty/ for terminals; rmdir closes it
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index b43d7b20..14f1ae4f 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -308,6 +308,35 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply {
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
+// ---- /commands ----
+
+/// Every builtin the registry holds, one a line: its word, `arg` when it
+/// takes one, and which ctl takes it, `root` or `pane` -- the registry
+/// itself, so a new builtin is listed the moment it exists.
+fn writeCommands(w: *std.Io.Writer) !void {
+ for (std.enums.values(Builtin)) |b| {
+ try w.print("{s}{s} {s}\n", .{
+ @tagName(b),
+ if (builtins.registry.takesArg(b)) " arg" else "",
+ if (builtins.registry.scope(b) == .session) "root" else "pane",
+ });
+ }
+}
+
+pub fn commandsLen() u64 {
+ var buf: [256]u8 = undefined;
+ var counting: std.Io.Writer.Discarding = .init(&buf);
+ writeCommands(&counting.writer) catch return 0;
+ return counting.fullCount();
+}
+
+pub fn readCommands(p: *Pardes, req: Req) Reply {
+ var text: std.Io.Writer.Allocating = .init(p.scratch.allocator());
+ writeCommands(&text.writer) catch return Reply.fail(req.tag, E.NOMEM);
+ p.fs.stage(p.gpa).appendSlice(p.gpa, text.written()) catch return Reply.fail(req.tag, E.NOMEM);
+ return tree.stagedReply(p, req);
+}
+
// ---- /focus ----
/// The pane with the keyboard, which a write of a serial moves, the way
@@ -452,6 +481,27 @@ test "pane ctl read is index's five fields plus width in cells, font, tab width
try testing.expectEqualStrings("'it''s'", w.buffered());
}
+test "commands lists every builtin with its argument and the ctl that takes it" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const node = @intFromEnum(tree.TopFile.commands);
+ const listed = rd(p, node, 0, 1 << 16);
+ try testing.expectEqual(@as(u64, listed.bytes.len), call(p, .{ .tag = 1, .op = .getattr, .node = node }).reply.attr.size);
+ try testing.expectEqual(E.PERM, wr(p, node, "Del\n").errno());
+ var lines: usize = 0;
+ var it = std.mem.tokenizeScalar(u8, listed.bytes, '\n');
+ while (it.next()) |line| : (lines += 1) {
+ var words = std.mem.tokenizeScalar(u8, line, ' ');
+ const b = std.meta.stringToEnum(Builtin, words.next().?).?;
+ if (builtins.registry.takesArg(b)) try testing.expectEqualStrings("arg", words.next().?);
+ try testing.expectEqualStrings(if (builtins.registry.scope(b) == .session) "root" else "pane", words.next().?);
+ try testing.expect(words.next() == null);
+ }
+ try testing.expectEqual(std.enums.values(Builtin).len, lines);
+ for ([_][]const u8{ "\nDel arg pane\n", "\nNewcol root\n", "\nSave arg pane\n", "\nVerbose arg root\n" }) |row|
+ try testing.expect(std.mem.indexOf(u8, listed.bytes, row) != null);
+}
+
test "focus reads the pane with the keyboard, and a serial written to it moves the keyboard there" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 13dfb0cf..53472a60 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -176,6 +176,7 @@ pub const TopFile = enum(u4) {
new,
focus,
ctl,
+ commands,
pub fn fileName(f: TopFile) []const u8 {
return if (f == .root) "/" else @tagName(f);
@@ -185,7 +186,7 @@ pub const TopFile = enum(u4) {
return switch (f) {
.root, .pane => 0o755,
.look, .exec, .log, .focus, .ctl => 0o666,
- .README, .index, .status, .screen, .listeners, .new => 0o444,
+ .README, .index, .status, .screen, .listeners, .new, .commands => 0o444,
};
}
@@ -447,6 +448,7 @@ fn topSize(p: *Pardes, f: TopFile) u64 {
.log => events.logLen(&p.fs.log),
.listeners => screen.listenersLen(p),
.ctl => ctl.rootLen(p),
+ .commands => ctl.commandsLen(),
.focus => focus: {
const pn = p.panes[p.active] orelse break :focus 0;
var digits: [16]u8 = undefined;
@@ -525,7 +527,7 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply {
}
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, fs.os_root, true, "os");
// After the first ten, which older clients list by position.
- inline for (.{ TopFile.focus, TopFile.ctl }) |t| {
+ inline for (.{ TopFile.focus, TopFile.ctl, TopFile.commands }) |t| {
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, @intFromEnum(t), t.dir(), t.fileName());
}
sources.stage(p, out, "", &skip);
@@ -719,6 +721,7 @@ fn read(p: *Pardes, req: Req, target: Target) Reply {
.listeners => screen.readListeners(p, req),
.focus => ctl.readFocus(p, req),
.ctl => ctl.readRoot(p, req),
+ .commands => ctl.readCommands(p, req),
// The serial the open handed this fid, so that two reads of one
// fid answer the same pane: the read observes, the open acted.
.new => serial: {