diff options
Diffstat (limited to 'src/ninep/pane.zig')
| -rw-r--r-- | src/ninep/pane.zig | 35 |
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(); |
