diff options
Diffstat (limited to 'src/ninep/cols.zig')
| -rw-r--r-- | src/ninep/cols.zig | 124 |
1 files changed, 115 insertions, 9 deletions
diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig index b6dc7d26..b00ecdba 100644 --- a/src/ninep/cols.zig +++ b/src/ninep/cols.zig @@ -123,8 +123,11 @@ pub fn released(p: *Pardes, serial: ?u32) void { } /// A column's ctl: the words that act on a column, as its tag's clicks do -- -/// Delcol, Joincol, New, Tty -- one a line, each run as from that tag. +/// Delcol, Joincol, New, Tty -- one a line, each run as from that tag. What +/// they say is the session's in the log (`msg -`), not the focused pane's. pub fn writeCtl(p: *Pardes, req: Req, serial: u32) Reply { + p.fs.session_write = true; + defer p.fs.session_write = false; var lines = std.mem.splitScalar(u8, req.data, '\n'); while (lines.next()) |raw| { const line = std.mem.trim(u8, raw, " \t\r"); @@ -133,31 +136,59 @@ pub fn writeCtl(p: *Pardes, req: Req, serial: u32) Reply { const takes = for ([_][]const u8{ "Delcol", "Joincol", "New", "Tty" }) |w| { if (std.mem.eql(u8, w, word)) break true; } else false; - if (!takes) return tree.failText(req.tag, E.INVAL, "a column's ctl takes Delcol, Joincol, New or Tty"); + // Plan 9's words for it, so a mount's errno is EINVAL as the root's + // and a pane's ctl give (9ns reads "control message"). + if (!takes) return tree.failText(req.tag, E.INVAL, std.fmt.bufPrint(&p.fs.ename, "unknown control message \"{s}\": a column's ctl takes Delcol, Joincol, New or Tty", .{line[0..@min(line.len, 64)]}) catch "unknown control message"); + // Only Tty takes a word (its shell); `New extra` is no New. + if (!std.mem.eql(u8, word, "Tty") and word.len != line.len) + return tree.failText(req.tag, E.INVAL, std.fmt.bufPrint(&p.fs.ename, "wrong #args in control message \"{s}\"", .{line[0..@min(line.len, 64)]}) catch "wrong #args in control message"); const col = layout.columnBySerial(p, serial) orelse return Reply.fail(req.tag, E.NOENT); p.exec_column = col; defer p.exec_column = null; + p.exec_header = true; + defer p.exec_header = false; if (pardes.ctlfs.ctl.runBuiltin(p, req, p.active, line)) |refusal| return refusal; } return .{ .tag = req.tag, .written = @intCast(req.data.len) }; } -/// A column's exec: each line run as a middle click on it in that column's -/// tag, acting on that column. -pub fn writeExec(p: *Pardes, req: Req, serial: u32) Reply { +/// A column's exec (`serial`), or the workspace tag's (/tagexec, null): +/// each line run as a middle click on it in that tag, a column's acting on +/// that column. A read answers the panes the last write touched, as /exec's +/// does; what the words say is the session's in the log (`msg -`). +pub fn writeExec(p: *Pardes, req: Req, serial: ?u32) Reply { + p.fs.results_len = 0; + p.fs.session_write = true; + defer p.fs.session_write = false; var lines = std.mem.splitScalar(u8, req.data, '\n'); while (lines.next()) |raw| { const line = std.mem.trim(u8, raw, " \t\r"); if (line.len == 0) continue; - for (line) |c| if (c < ' ' and c != '\t') return Reply.fail(req.tag, E.INVAL); - const col = layout.columnBySerial(p, serial) orelse return Reply.fail(req.tag, E.NOENT); + for (line) |c| if (c < ' ' and c != '\t') return tree.failText(req.tag, E.INVAL, pardes.ctlfs.ctl.e_control); + if (pardes.ctlfs.ctl.tooLong(req, line)) |refusal| return refusal; + const col = if (serial) |s| layout.columnBySerial(p, s) orelse return Reply.fail(req.tag, E.NOENT) else null; + if (p.panes[p.active] == null) return Reply.fail(req.tag, E.NOENT); p.exec_column = col; defer p.exec_column = null; - pardes.ctlfs.ctl.run(p, p.active, true, line); + p.exec_header = true; + defer p.exec_header = false; + if (pardes.ctlfs.ctl.captured(p, req, p.active, true, line, true)) |refusal| return refusal; } return .{ .tag = req.tag, .written = @intCast(req.data.len) }; } +test "New written to a column's exec makes its scratch in the session's directory, as tagexec's does" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const session = "/project/src"; + @memcpy(p.session_dir[0..session.len], session); + p.session_dir_len = session.len; + const serial = layout.columnSerial(p, 0); + const exec_node = tree.Node.ofCol(serial, .exec); + try testing.expectEqual(tree.Status.ok, th.wr(p, exec_node, "New\n").reply.status); + try testing.expectEqualStrings("/project/src/+New", p.panes[p.active].?.file.?.path); +} + /// rmdir of a column closes it when it is empty; one with panes is /// refused, saying so (close them, or Delcol on its ctl, which asks first). pub fn remove(p: *Pardes, req: Req, serial: u32, file: tree.ColFile) Reply { @@ -257,10 +288,12 @@ test "a column's ctl and exec act on it as its tag would, and rmdir closes it on try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(empty, .ctl), "New\n").reply.status); try testing.expectEqual(@as(usize, 1), p.col_n[1]); try testing.expectEqual(E.INVAL, th.wr(p, Node.ofCol(empty, .ctl), "Exit\n").errno()); + var rec: [64]u8 = undefined; + try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err - col/{d}/ctl: unknown control message \"Exit\"", .{empty}))); // rmdir refuses a column with a pane, and says why. const full = th.call(p, .{ .tag = 1, .op = .release, .node = Node.ofCol(empty, .dir), .remove = true }); try testing.expectEqual(E.NOTEMPTY, full.errno()); - try testing.expect(th.logHas(p, "column not empty")); + try testing.expect(th.logHas(p, try std.fmt.bufPrint(&rec, "err - col/{d}: column not empty", .{empty}))); // exec runs a word as a click in that column's tag: Delcol closes it. try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(empty, .exec), "Delcol\n").reply.status); try testing.expectEqual(@as(?usize, null), layout.columnBySerial(p, empty)); @@ -282,3 +315,76 @@ test "a column's ctl and exec act on it as its tag would, and rmdir closes it on // The root's ctl points a column word at the column's own. try testing.expect(std.mem.endsWith(u8, th.wr(p, @intFromEnum(tree.TopFile.ctl), "Delcol\n").reply.ename, "write it to col/<serial>/ctl")); } + +test "tagexec runs a word as a click in the workspace tag; exec files read back what they touched; their messages are the session's" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const tagexec = @intFromEnum(tree.TopFile.tagexec); + try testing.expectEqual(tree.Status.ok, th.look_up(p, tree.root, "tagexec").reply.status); + try testing.expectEqual(tree.Status.ok, th.wr(p, tagexec, "Newcol\n").reply.status); + try testing.expectEqual(@as(usize, 2), p.ncol); + try testing.expectEqual(tree.Status.ok, th.wr(p, tagexec, "Msg from the top\n").reply.status); + try testing.expect(th.logHas(p, "msg - from the top\n")); + // A column's exec makes a pane there, reads it back, and its Msg is + // the session's too. + const right = layout.columnSerial(p, 1); + try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(right, .exec), "New\n").reply.status); + const made = p.panes[p.col_panes[1][0]].?.serial; + var want: [16]u8 = undefined; + try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), th.rd(p, Node.ofCol(right, .exec), 0, 64).bytes); + try testing.expectEqualStrings(try std.fmt.bufPrint(&want, "{d}\n", .{made}), th.rd(p, tagexec, 0, 64).bytes); + try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(right, .exec), "Msg from a column\n").reply.status); + try testing.expect(th.logHas(p, "msg - from a column\n")); +} + +test "a word run from the workspace's or a column's tag runs in the session's directory, a pane's in its own" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const session = p.sessionDir(); + try testing.expect(!std.mem.eql(u8, session, pardes.Pardes.paneDir(p.panes[0].?))); // the file is elsewhere + const dirs = [_]u64{ @intFromEnum(tree.TopFile.tagexec), Node.ofCol(layout.columnSerial(p, 0), .exec) }; + for (dirs) |node| { + try testing.expectEqual(tree.Status.ok, th.wr(p, node, "true\n").reply.status); + const made = p.paneBySerial(p.fs.results[0]).?; + try testing.expectEqualStrings(session, pardes.Pardes.paneDir(p.panes[made].?)); + try p.removePane(made, null); + } + const from_pane = p.panes[0].?.serial; + _ = th.wr(p, Node.of(from_pane, .exec), "true\n"); + const made = p.paneBySerial(p.fs.results[0]).?; + try testing.expectEqualStrings(pardes.Pardes.paneDir(p.panes[0].?), pardes.Pardes.paneDir(p.panes[made].?)); +} + +test "a command run from a column's tag gets its pane in that column, not the last" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + p.newColumn(p.active); + p.newScratchFor(p.active, .fs); // fills the new, right column + p.sync(); + const left = layout.columnSerial(p, 0); + try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(left, .exec), "true\n").reply.status); + const made = p.paneBySerial(p.fs.results[0]).?; + try testing.expectEqual(@as(usize, 0), layout.findPane(p, made).?.col); + // Finished, it is reused only by its own column's commands. + p.panes[made].?.command_done = true; + p.panes[made].?.command_pty = false; + try testing.expectEqual(tree.Status.ok, th.wr(p, Node.ofCol(layout.columnSerial(p, 1), .exec), "true\n").reply.status); + const other = p.paneBySerial(p.fs.results[0]).?; + try testing.expect(other != made); + try testing.expectEqual(@as(usize, 1), layout.findPane(p, other).?.col); +} + +test "a column's ctl refuses an unknown word and an argument as the other ctls do" { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + const ctl_node = Node.ofCol(layout.columnSerial(p, 0), .ctl); + const unknown = th.wr(p, ctl_node, "Bogus\n"); + try testing.expectEqual(E.INVAL, unknown.errno()); + try testing.expect(std.mem.startsWith(u8, unknown.reply.ename, "unknown control message \"Bogus\"")); + const extra = th.wr(p, ctl_node, "New extra\n"); + try testing.expectEqual(E.INVAL, extra.errno()); + try testing.expect(std.mem.startsWith(u8, extra.reply.ename, "wrong #args")); + try testing.expectEqual(@as(usize, 1), p.col_n[0]); + // A pane's bare answer and a winsize of no size say what they take. + try testing.expectEqualStrings("answer takes a choice or -", th.wr(p, Node.of(p.panes[0].?.serial, .ctl), "answer\n").reply.ename); +} |
