summaryrefslogtreecommitdiff
path: root/src/ninep/pane.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/pane.zig')
-rw-r--r--src/ninep/pane.zig35
1 files changed, 35 insertions, 0 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index 9cb1dba6..9e832df6 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -56,6 +56,10 @@ pub const State = struct {
/// `printf 'x\ny\n' > data` is one step, as acme's replace is, though
/// bash writes it a line at a time; any other edit between breaks it.
joined: ?u32 = null,
+ /// The open whose writes `joined` joins (0: any, as after a truncation
+ /// or for an open with no record): an interleaved open's write starts
+ /// a step of its own.
+ joined_by: u32 = 0,
noscroll: bool = false,
tag_snap: std.ArrayList(u8) = .empty,
/// An open reads pty/data, so output queues for it; a second is refused.
@@ -446,6 +450,12 @@ pub fn write(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply
};
}
+/// A write joins the step being made only when it is the same open's (or
+/// the step is anyone's, `joined_by` 0): another open's starts its own.
+fn joinOnly(pane: *Pane, handle: u32) void {
+ if (pane.fs.joined_by != 0 and pane.fs.joined_by != handle) pane.fs.joined = null;
+}
+
fn writeBody(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
if (req.data.len == 0) return .{ .tag = req.tag, .written = 0 };
if (pane.file == null) {
@@ -456,9 +466,11 @@ fn writeBody(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply {
return .{ .tag = req.tag, .written = @intCast(take) };
}
const at = bodyOf(pane).len;
+ joinOnly(pane, req.handle);
const take = spliceBody(p, pane, at, at, req.data) orelse
return Reply.fail(req.tag, E.NOMEM);
pane.fs.joined = fileOf(pane).?.revision;
+ pane.fs.joined_by = req.handle;
if (!pane.fs.noscroll) showOffset(pane, at + take);
return .{ .tag = req.tag, .written = @intCast(take) };
}
@@ -511,9 +523,11 @@ fn writeData(p: *Pardes, req: Req, pane: *Pane) Reply {
const q0: usize = pf.addr.q0;
const q1: usize = @max(q0, @as(usize, pf.addr.q1));
const before = dotOf(pane);
+ joinOnly(pane, req.handle);
const take = spliceBody(p, pane, q0, q1, req.data) orelse
return Reply.fail(req.tag, E.NOMEM);
pf.joined = fileOf(pane).?.revision;
+ pf.joined_by = req.handle;
setDot(pane, .{
.q0 = shiftOne(before.q0, clip(q0), clip(q1 - q0), clip(take)),
.q1 = shiftOne(before.q1, clip(q0), clip(q1 - q0), clip(take)),
@@ -734,6 +748,7 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status {
pf.joined = null;
_ = spliceBody(p, pane, 0, bodyOf(pane).len, "") orelse return .err;
pf.joined = fileOf(pane).?.revision;
+ pf.joined_by = 0;
pf.addr = .{};
setDot(pane, .{});
},
@@ -745,6 +760,7 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status {
pf.joined = null;
_ = spliceBody(p, pane, q0, q1, "") orelse return .err;
pf.joined = fileOf(pane).?.revision;
+ pf.joined_by = 0;
setDot(pane, .{ .q0 = shiftOne(before.q0, q0, q1 - q0, 0), .q1 = shiftOne(before.q1, q0, q1 - q0, 0) });
pf.addr = .{ .q0 = q0, .q1 = q0 };
},
@@ -1057,6 +1073,25 @@ test "the writes of one open are one undo step, as bash's line-buffered printf m
}
}
+test "two opens writing one body are two undo steps, however their writes interleave" {
+ const p = try withFile(testing.allocator, "one\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const body = Node.of(serial, .body);
+ const a = call(p, .{ .tag = 1, .op = .open, .node = body, .omode = 1 }).reply.handle;
+ const b = call(p, .{ .tag = 1, .op = .open, .node = body, .omode = 1 }).reply.handle;
+ try testing.expect(a != 0 and b != 0 and a != b);
+ _ = call(p, .{ .tag = 2, .op = .write, .node = body, .handle = a, .data = "a\n" });
+ _ = call(p, .{ .tag = 2, .op = .write, .node = body, .handle = a, .data = "b\n" });
+ _ = call(p, .{ .tag = 2, .op = .write, .node = body, .handle = b, .data = "c\n" });
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Undo").reply.status);
+ try testing.expectEqualStrings("one\na\nb\n", rd(p, body, 0, 64).bytes);
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Undo").reply.status);
+ try testing.expectEqualStrings("one\n", rd(p, body, 0, 64).bytes);
+ _ = call(p, .{ .tag = 3, .op = .release, .node = body, .handle = a, .opened = true });
+ _ = call(p, .{ .tag = 3, .op = .release, .node = body, .handle = b, .opened = true });
+}
+
test "a name cut across writes is one name, applied once at its newline or its close" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();