diff options
Diffstat (limited to 'src/ninep/tree.zig')
| -rw-r--r-- | src/ninep/tree.zig | 76 |
1 files changed, 67 insertions, 9 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 5499a337..b0ffdd68 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -87,18 +87,34 @@ pub const features: cloud9.fs.Features = .{ .remove = true, .create = true }; /// so what parks here is never the syscall's own request. pub fn needsQuiet(p: *Pardes, req: Req) bool { return switch (req.op) { - .write, .setattr => true, - .release => req.remove or holdsLines(p, req), + .write => true, + .setattr => truncates(req), + .release => req.remove, .read => holdsPage(p, req), else => false, }; } +/// A setattr that changes something: a truncate of a pane's file or a +/// tag. `> exec` (a shell's truncating open) changes nothing, so it waits +/// on nothing: through a mount it hung, as 9ns answers nothing else on the +/// mount while it is out, and the editor's step may be out reading a pane's +/// file through that mount, the quiet it waited for never coming. +fn truncates(req: Req) bool { + if (!req.truncate) return false; + return switch (Node.target(req.node) orelse return false) { + .pane => true, + .col => |c| c.file == .tag, + .top => |f| f == .tag, + }; +} + /// `needsQuiet`, plus the open that makes a pane: what costs a frame and a /// wake of the editor. pub fn changesPane(p: *Pardes, req: Req) bool { return switch (req.op) { - .write, .setattr => true, + .write => true, + .setattr => truncates(req), .open => req.node == @intFromEnum(TopFile.new), .release => req.remove or holdsLines(p, req) or holdsPage(p, req), .read => holdsPage(p, req), @@ -973,13 +989,11 @@ fn releaseHandle(p: *Pardes, req: Req) void { p.fs.pages.append(p.gpa, .{ .serial = o.what.pager, .bytes = o.pending }) catch o.pending.deinit(p.gpa); o.pending = .empty; } - // The last line an open wrote with no newline after it runs now. + // The last line an open wrote with no newline after it runs at the + // editor's next step (runClosedLines), as a /pager open's text does. if (o.pending.items.len > 0) { - const rest = p.scratch.allocator().dupe(u8, o.pending.items) catch ""; - o.pending.clearRetainingCapacity(); - const w: Req = .{ .tag = req.tag, .op = .write, .node = req.node, .handle = req.handle, .data = rest }; - const reply = writeNow(p, w, t); - if (reply.status == .err) events.noteError(p, w, reply); + p.fs.closed_lines.append(p.gpa, .{ .node = req.node, .bytes = o.pending }) catch o.pending.deinit(p.gpa); + o.pending = .empty; } 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) { @@ -1169,6 +1183,25 @@ fn linesOpen(target: Target, req: Req) bool { return req.omode & 3 != 0 or resultsFile(target); } +/// The editor's step: the last lines of closed opens run, each as a write +/// of its own; a refusal is the log's err, as no open is left to answer. +pub fn runClosedLines(p: *Pardes) void { + for (p.fs.closed_lines.items) |*line| { + defer line.bytes.deinit(p.gpa); + const t = Node.target(line.node) orelse continue; + const w: Req = .{ .tag = 0, .op = .write, .node = line.node, .data = line.bytes.items }; + const reply = writeNow(p, w, t); + if (reply.status == .err) events.noteError(p, w, reply); + } + p.fs.closed_lines.clearRetainingCapacity(); +} + +/// A close that hands its open's held text, a last line or a /pager's +/// page, to the editor's step: answered at once, never waiting on it. +pub fn handsOff(p: *Pardes, req: Req) bool { + return (req.op == .release and holdsLines(p, req)) or holdsPage(p, req); +} + /// A release that will run a line its open held back. fn holdsLines(p: *Pardes, req: Req) bool { const o = openOf(p, req) orelse return false; @@ -1806,6 +1839,24 @@ test "at the pane cap, pane/new, look and New each say so, and look reads back e try testing.expect(std.mem.indexOf(u8, made.reply.ename, said) != null); } +test "nothing a mount's close or a no-op truncate sends waits on the editor: a held line runs at its next step" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + const exec = @intFromEnum(TopFile.exec); + // `> exec` truncates nothing; `> body` does. + try testing.expect(!needsQuiet(p, .{ .tag = 1, .op = .setattr, .node = exec, .truncate = true })); + try testing.expect(!changesPane(p, .{ .tag = 1, .op = .setattr, .node = exec, .truncate = true })); + try testing.expect(needsQuiet(p, .{ .tag = 1, .op = .setattr, .node = Node.of(serialOf(p), .body), .truncate = true })); + const h = call(p, .{ .tag = 2, .op = .open, .node = exec, .omode = 1 }).reply.handle; + _ = call(p, .{ .tag = 3, .op = .write, .node = exec, .handle = h, .data = "Msg at-step" }); + const close: Req = .{ .tag = 4, .op = .release, .node = exec, .handle = h, .opened = true }; + try testing.expect(!needsQuiet(p, close) and handsOff(p, close)); + _ = call(p, close); + try testing.expect(!th.logHas(p, " at-step\n")); + runClosedLines(p); + try testing.expect(th.logHas(p, " at-step\n")); +} + test "a command line cut across writes runs once whole, and the last runs at release; an Edit block waits for its end" { const p = try th.withFile(testing.allocator, "x\n"); defer p.deinit(); @@ -1821,6 +1872,7 @@ test "a command line cut across writes runs once whole, and the last runs at rel try testing.expect(th.logHas(p, " hello\n") and th.logHas(p, " world\n")); try testing.expect(!th.logHas(p, " he\n") and !th.logHas(p, " tail\n")); _ = call(p, .{ .tag = 3, .op = .release, .node = exec, .handle = h, .opened = true }); + runClosedLines(p); try testing.expect(th.logHas(p, " tail\n")); // An Edit block's text lines come in later writes on the same open. const serial = serialOf(p); @@ -1829,6 +1881,7 @@ test "a command line cut across writes runs once whole, and the last runs at rel for ([_][]const u8{ "Edit ,a\n", "one\ntw", "o\n", ".\n" }) |piece| try testing.expectEqual(Status.ok, call(p, .{ .tag = 5, .op = .write, .node = ctl_node, .handle = c, .data = piece }).reply.status); _ = call(p, .{ .tag = 6, .op = .release, .node = ctl_node, .handle = c, .opened = true }); + runClosedLines(p); try testing.expectEqualStrings("x\none\ntwo\n", p.panes[0].?.file.?.content); } @@ -1878,6 +1931,7 @@ test "a write with no newline waits for its newline or the close, whatever its s try testing.expectEqual(Status.ok, call(p, .{ .tag = 6, .op = .write, .node = ctl_node, .handle = c, .data = "bogus" }).reply.status); try testing.expect(!th.logHas(p, "bogus")); _ = call(p, .{ .tag = 7, .op = .release, .node = ctl_node, .handle = c, .opened = true }); + runClosedLines(p); try testing.expect(th.logHas(p, "unknown control message \"bogus\"")); // A name: a NUL fails its write, not the close after it. const name = Node.of(serial, .name); @@ -1886,6 +1940,7 @@ test "a write with no newline waits for its newline or the close, whatever its s try testing.expectEqual(Status.err, bad.reply.status); try testing.expect(std.mem.indexOf(u8, bad.reply.ename, "a NUL") != null); try testing.expectEqual(Status.ok, call(p, .{ .tag = 12, .op = .release, .node = name, .handle = n, .opened = true }).reply.status); + runClosedLines(p); } test "look and exec read back what their own open's write touched; a fresh open reads the session's last" { @@ -1973,6 +2028,7 @@ test "an Edit block still open when its open closes fails there with an err and const c = call(p, .{ .tag = 1, .op = .open, .node = ctl_node, .omode = 1 }).reply.handle; _ = call(p, .{ .tag = 2, .op = .write, .node = ctl_node, .handle = c, .data = "Edit ,x/b/{\ni/[/\n" }); _ = call(p, .{ .tag = 3, .op = .release, .node = ctl_node, .handle = c, .opened = true }); + runClosedLines(p); try testing.expect(th.logHas(p, "unmatched")); try testing.expectEqualStrings("abc\n", p.panes[0].?.file.?.content); } @@ -1984,6 +2040,7 @@ test "an a, c or i text still open when its open closes fails there too and chan const c = call(p, .{ .tag = 1, .op = .open, .node = ctl_node, .omode = 1 }).reply.handle; _ = call(p, .{ .tag = 2, .op = .write, .node = ctl_node, .handle = c, .data = "Edit $a\nhalf of a block\n" }); _ = call(p, .{ .tag = 3, .op = .release, .node = ctl_node, .handle = c, .opened = true }); + runClosedLines(p); try testing.expect(th.logHas(p, "not ended by a . line")); try testing.expectEqualStrings("abc\n", p.panes[0].?.file.?.content); } @@ -1995,6 +2052,7 @@ test "an a, c or i text cut short through exec is an err in the log, not a msg" const c = call(p, .{ .tag = 1, .op = .open, .node = exec, .omode = 1 }).reply.handle; _ = call(p, .{ .tag = 2, .op = .write, .node = exec, .handle = c, .data = "Edit $a\nhalf\n" }); _ = call(p, .{ .tag = 3, .op = .release, .node = exec, .handle = c, .opened = true }); + runClosedLines(p); var want: [64]u8 = undefined; try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "err {d} exec: Edit: a, c or i text not ended", .{serialOf(p)}))); try testing.expect(!th.logHas(p, "msg 1 Edit: a, c or i")); |
