summaryrefslogtreecommitdiff
path: root/src/ninep/tree.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/tree.zig')
-rw-r--r--src/ninep/tree.zig111
1 files changed, 93 insertions, 18 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 7279b64a..9459be4f 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -18,6 +18,7 @@ pub const events = @import("events.zig");
pub const pane = @import("pane.zig");
pub const addr = @import("addr.zig");
pub const ctl = @import("ctl.zig");
+pub const cols = @import("cols.zig");
pub const pty = @import("pty.zig");
pub const sources = @import("sources.zig");
pub const screen = @import("screen.zig");
@@ -171,7 +172,7 @@ pub fn openOf(p: *Pardes, req: Req) ?*Open {
// ---- nodes ----
-pub const TopFile = enum(u4) {
+pub const TopFile = enum(u5) {
root = 1,
README,
index,
@@ -186,6 +187,9 @@ pub const TopFile = enum(u4) {
focus,
ctl,
commands,
+ layout,
+ tag,
+ col,
pub fn fileName(f: TopFile) []const u8 {
return if (f == .root) "/" else @tagName(f);
@@ -193,14 +197,14 @@ pub const TopFile = enum(u4) {
pub fn mode(f: TopFile) u16 {
return switch (f) {
- .root, .pane => 0o755,
- .look, .exec, .log, .focus, .ctl => 0o666,
- .README, .index, .status, .screen, .listeners, .new, .commands => 0o444,
+ .root, .pane, .col => 0o755,
+ .look, .exec, .log, .focus, .ctl, .tag => 0o666,
+ .README, .index, .status, .screen, .listeners, .new, .commands, .layout => 0o444,
};
}
pub fn dir(f: TopFile) bool {
- return f == .root or f == .pane;
+ return f == .root or f == .pane or f == .col;
}
};
@@ -264,6 +268,11 @@ pub const PaneFile = enum(u5) {
}
};
+/// /col/<n> and its tag: a column is named by its index, which a serial
+/// never is (serials are 32 bits; this bit is past them).
+pub const ColFile = enum(u5) { dir = 0, tag = 1 };
+const col_bit: u59 = 1 << 58;
+
pub const Node = packed struct(u64) {
file: u5 = 0,
serial: u59 = 0,
@@ -273,11 +282,19 @@ pub const Node = packed struct(u64) {
return @bitCast(Node{ .file = @intFromEnum(file), .serial = serial });
}
+ pub fn ofCol(index: usize, file: ColFile) u64 {
+ return @bitCast(Node{ .file = @intFromEnum(file), .serial = col_bit | @as(u59, @intCast(index)) });
+ }
+
pub fn target(node: u64) ?Target {
const n: Node = @bitCast(node);
if (n.serial == 0) {
return .{ .top = std.enums.fromInt(TopFile, n.file) orelse return null };
}
+ if (n.serial & col_bit != 0) return .{ .col = .{
+ .index = std.math.cast(u32, n.serial & ~col_bit) orelse return null,
+ .file = std.enums.fromInt(ColFile, n.file) orelse return null,
+ } };
return .{ .pane = .{
.serial = std.math.cast(u32, n.serial) orelse return null,
.file = std.enums.fromInt(PaneFile, n.file) orelse return null,
@@ -288,6 +305,7 @@ pub const Node = packed struct(u64) {
pub const Target = union(enum) {
top: TopFile,
pane: struct { serial: u32, file: PaneFile },
+ col: struct { index: u32, file: ColFile },
};
/// Opening /pane/new makes a pane and reading the open fid answers its
@@ -388,7 +406,7 @@ pub fn handle(p: *Pardes, req: Req) Reply {
const target = Node.target(req.node) orelse return Reply.fail(req.tag, E.NOENT);
if (req.op == .write or req.op == .setattr) p.fs.origin = switch (target) {
.pane => |t| @as(u8, if (t.file == .body or t.file == .tag) 'E' else 'F'),
- .top => 'F',
+ .top, .col => 'F',
};
return switch (req.op) {
.lookup => lookup(p, req, target),
@@ -410,7 +428,7 @@ pub fn handle(p: *Pardes, req: Req) Reply {
fn runAttr(p: *Pardes, req: Req, target: Target) ?Reply.Attr {
const t = switch (target) {
.pane => |t| t,
- .top => return null,
+ .top, .col => return null,
};
if (t.file != .pty_run) return null;
const run = if (req.handle != 0) openOf(p, req) else for (&p.fs.opens) |*o| {
@@ -429,6 +447,19 @@ fn attrOf(p: *Pardes, target: Target) ?Reply.Attr {
.mode = f.mode(),
.size = topSize(p, f),
.mtime = p.fs.started,
+ .append = f == .tag,
+ },
+ .col => |c| {
+ if (c.index >= p.ncol) return null;
+ return .{
+ .name = if (c.file == .dir) (std.fmt.bufPrint(&p.fs.node_name, "{d}", .{c.index}) catch unreachable) else "tag",
+ .node = Node.ofCol(c.index, c.file),
+ .dir = c.file == .dir,
+ .mode = if (c.file == .dir) 0o755 else 0o666,
+ .size = if (c.file == .tag) cols.headerText(p, c.index).?.len + 1 else 0,
+ .mtime = p.fs.started,
+ .append = c.file == .tag,
+ };
},
.pane => |t| {
const id = p.paneBySerial(t.serial) orelse return null;
@@ -461,7 +492,9 @@ fn topSize(p: *Pardes, f: TopFile) u64 {
return switch (f) {
// ponytail: /screen has no length until an open renders its frame,
// and /pane/new none until an open has a pane to name.
- .root, .pane, .screen, .new => 0,
+ .root, .pane, .col, .screen, .new => 0,
+ .layout => cols.layoutLen(p),
+ .tag => cols.headerText(p, null).?.len + 1,
.index => pane.indexLen(p),
.README => fs.help.len,
.status => ctl.statusLen(p),
@@ -492,6 +525,7 @@ fn lookup(p: *Pardes, req: Req, target: Target) Reply {
.pty => Node.of(t.serial, .dir),
else => return Reply.fail(req.tag, E.NOTDIR),
},
+ .col => |c| if (c.file == .dir) @intFromEnum(TopFile.col) else return Reply.fail(req.tag, E.NOTDIR),
};
return handle(p, .{ .tag = req.tag, .op = .getattr, .node = parent });
}
@@ -514,8 +548,19 @@ fn lookup(p: *Pardes, req: Req, target: Target) Reply {
if (serial == 0) return Reply.fail(req.tag, E.NOENT);
break :pane Node.of(serial, .dir);
},
+ // A column is its index, in decimal digits.
+ .col => col: {
+ if (name.len > 4) return Reply.fail(req.tag, E.NOENT);
+ for (name) |c| if (c < '0' or c > '9') return Reply.fail(req.tag, E.NOENT);
+ break :col Node.ofCol(std.fmt.parseInt(u32, name, 10) catch return Reply.fail(req.tag, E.NOENT), .dir);
+ },
else => return Reply.fail(req.tag, E.NOTDIR),
},
+ .col => |c| col: {
+ if (c.file != .dir) return Reply.fail(req.tag, E.NOTDIR);
+ if (!std.mem.eql(u8, name, "tag")) return Reply.fail(req.tag, E.NOENT);
+ break :col Node.ofCol(c.index, .tag);
+ },
.pane => |t| pane: {
_ = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT);
const f = switch (t.file) {
@@ -555,7 +600,7 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply {
.root => {
// The guide, then what describes the session, then the
// files that act, then the directories.
- inline for (.{ TopFile.README, TopFile.index, TopFile.status, TopFile.focus, TopFile.ctl, TopFile.commands, TopFile.look, TopFile.exec, TopFile.log, TopFile.screen, TopFile.listeners, TopFile.pane }) |t| {
+ inline for (.{ TopFile.README, TopFile.index, TopFile.status, TopFile.focus, TopFile.ctl, TopFile.commands, TopFile.look, TopFile.exec, TopFile.log, TopFile.screen, TopFile.listeners, TopFile.layout, TopFile.tag, TopFile.pane, TopFile.col }) |t| {
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, @intFromEnum(t), t.dir(), t.fileName());
}
if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, fs.os_root, true, "os");
@@ -575,8 +620,21 @@ fn readdir(p: *Pardes, req: Req, target: Target) Reply {
stageDirent(out, p.gpa, Node.of(serial, .dir), true, name);
}
},
+ .col => for (0..p.ncol) |c| {
+ if (skip > 0) {
+ skip -= 1;
+ continue;
+ }
+ var buf: [16]u8 = undefined;
+ stageDirent(out, p.gpa, Node.ofCol(c, .dir), true, std.fmt.bufPrint(&buf, "{d}", .{c}) catch unreachable);
+ },
else => return Reply.fail(req.tag, E.NOTDIR),
},
+ .col => |c| {
+ if (c.index >= p.ncol) return Reply.fail(req.tag, E.NOENT);
+ if (c.file != .dir) return Reply.fail(req.tag, E.NOTDIR);
+ if (skip > 0) skip -= 1 else stageDirent(out, p.gpa, Node.ofCol(c.index, .tag), false, "tag");
+ },
.pane => |t| {
const id = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT);
const terminal = p.panes[id].?.isTerminal();
@@ -610,6 +668,7 @@ fn open(p: *Pardes, req: Req, target: Target) Reply {
.log => .{ .log = .{} },
else => return .{ .tag = req.tag },
},
+ .col => |c| return if (c.index < p.ncol) .{ .tag = req.tag } else Reply.fail(req.tag, E.NOENT),
.pane => |t| what: {
const pn = p.panes[p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT)].?;
if (t.file.inPty() and !pn.isTerminal()) return Reply.fail(req.tag, E.NOENT);
@@ -665,7 +724,7 @@ fn remove(p: *Pardes, req: Req) Reply {
const target = Node.target(req.node) orelse return Reply.fail(req.tag, E.NOENT);
const t = switch (target) {
.pane => |t| t,
- .top => return Reply.fail(req.tag, E.PERM),
+ .top, .col => return Reply.fail(req.tag, E.PERM),
};
if (t.file != .dir) return Reply.fail(req.tag, E.PERM);
const id = p.paneBySerial(t.serial) orelse return Reply.fail(req.tag, E.NOENT);
@@ -690,6 +749,8 @@ fn releaseHandle(p: *Pardes, req: Req) void {
p.panes[id].?.fs.tag_rewrite = false;
p.panes[id].?.fs.tag_held_newline = false;
};
+ if (t == .col and t.col.file == .tag) cols.released(p, t.col.index);
+ if (t == .top and t.top == .tag) cols.released(p, null);
const o = openOf(p, req) orelse return;
const of: ?*Pane = if (t == .pane) (if (p.paneBySerial(t.pane.serial)) |id| p.panes[id] else null) else null;
if (of) |pn| switch (o.what) {
@@ -725,7 +786,8 @@ fn setattr(p: *Pardes, req: Req, target: Target) Reply {
return failText(req.tag, E.INVAL, pane.e_addr_failed);
if (pane.truncate(p, p.panes[id].?, t.file) != .ok) return Reply.fail(req.tag, E.NOMEM);
},
- .top => {},
+ .col => |c| if (c.file == .tag and cols.truncate(p, c.index) != .ok) return Reply.fail(req.tag, E.NOENT),
+ .top => |f| if (f == .tag and cols.truncate(p, null) != .ok) return Reply.fail(req.tag, E.NOMEM),
};
return .{ .tag = req.tag, .attr = attrOf(p, target) orelse return Reply.fail(req.tag, E.NOENT) };
}
@@ -770,8 +832,9 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
var last: u32 = 0;
while (nextSerialAfter(p, last)) |serial| {
last = serial;
- const pn = p.panes[p.paneBySerial(serial).?].?;
- out.print(p.gpa, "{d} {s} {d} {s}\n", .{ serial, pane.kindOf(pn), @intFromBool(pane.dirtyOf(pn)), pane.nameOf(pn) }) catch {};
+ const id = p.paneBySerial(serial).?;
+ const pn = p.panes[id].?;
+ out.print(p.gpa, "{d} {s} {d} {s} {d}\n", .{ serial, pane.kindOf(pn), @intFromBool(pane.dirtyOf(pn)), pane.nameOf(pn), pane.columnOf(p, id) }) catch {};
}
break :index stagedReply(p, req);
},
@@ -783,6 +846,13 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
.focus => ctl.readFocus(p, req),
.ctl => ctl.readRoot(p, req),
.commands => ctl.readCommands(p, req),
+ .layout => layout: {
+ var text: std.Io.Writer.Allocating = .init(p.scratch.allocator());
+ cols.writeLayout(p, &text.writer) catch break :layout Reply.fail(req.tag, E.NOMEM);
+ p.fs.stage(p.gpa).appendSlice(p.gpa, text.written()) catch break :layout Reply.fail(req.tag, E.NOMEM);
+ break :layout stagedReply(p, req);
+ },
+ .tag => cols.read(p, req, null),
// 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: {
@@ -791,7 +861,7 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
break :serial Reply.fail(req.tag, E.NOMEM);
break :serial stagedReply(p, req);
},
- .root, .pane => Reply.fail(req.tag, E.PERM),
+ .root, .pane, .col => Reply.fail(req.tag, E.PERM),
},
.pane => |t| {
// A run's answer outlives the pane it ran in.
@@ -807,6 +877,7 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
if (t.file.inPty() and !pn.isTerminal()) return Reply.fail(req.tag, E.NOENT);
return pane.read(p, req, id, pn, t.file);
},
+ .col => |c| return if (c.file == .tag) cols.read(p, req, c.index) else Reply.fail(req.tag, E.PERM),
}
}
@@ -817,6 +888,7 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
.log => events.writeLog(p, req),
.focus => ctl.writeFocus(p, req),
.ctl => ctl.writeRoot(p, req),
+ .tag => cols.write(p, req, null),
else => Reply.fail(req.tag, E.PERM),
},
.pane => |t| {
@@ -825,6 +897,7 @@ fn write(p: *Pardes, req: Req, target: Target) Reply {
if (t.file.inPty() and !pn.isTerminal()) return Reply.fail(req.tag, E.NOENT);
return pane.write(p, req, id, pn, t.file);
},
+ .col => |c| return if (c.file == .tag) cols.write(p, req, c.index) else Reply.fail(req.tag, E.PERM),
}
}
@@ -836,6 +909,7 @@ test {
_ = addr;
_ = ctl;
_ = pty;
+ _ = cols;
_ = sources;
_ = screen;
}
@@ -968,12 +1042,13 @@ test "readdir lists the root and a pane directory without creating anything" {
@memcpy(top_bytes[0..top_reply.bytes.len], top_reply.bytes);
const top = th.dirents(top_bytes[0..top_reply.bytes.len], &buf);
try testing.expect(top.len >= 10);
- for ([_][]const u8{ "README", "index", "status", "focus", "ctl", "commands", "look", "exec", "log", "screen", "listeners", "pane", "os" }, 0..) |name, i| {
+ for ([_][]const u8{ "README", "index", "status", "focus", "ctl", "commands", "look", "exec", "log", "screen", "listeners", "layout", "tag", "pane", "col", "os" }, 0..) |name, i| {
try testing.expectEqualStrings(name, top[i].name);
- try testing.expectEqual(i >= 11, top[i].dir);
+ try testing.expectEqual(i >= 13, top[i].dir);
}
- try testing.expectEqual(@intFromEnum(TopFile.pane), top[11].node);
- try testing.expectEqual(fs.os_root, top[12].node);
+ try testing.expectEqual(@intFromEnum(TopFile.pane), top[13].node);
+ try testing.expectEqual(@intFromEnum(TopFile.col), top[14].node);
+ try testing.expectEqual(fs.os_root, top[15].node);
var idbuf: [16]u8 = undefined;
try testing.expect(th.nameAt(top, try std.fmt.bufPrint(&idbuf, "{d}", .{serial})) == null);
try testing.expect(th.nameAt(top, "self") == null);