diff options
Diffstat (limited to 'src/ninep/pane.zig')
| -rw-r--r-- | src/ninep/pane.zig | 52 |
1 files changed, 50 insertions, 2 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 78bec09f..ce6d0422 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -44,6 +44,10 @@ pub const State = struct { /// The limit a truncation cleared, kept until the write that follows it /// on that open (a shell's `>`): a write that fails puts it back. limit_truncated: ?Range = null, + /// `dot` was truncated and nothing written since: the write that + /// follows sets it or, refused, leaves it as it was; with none, the + /// open's release empties it (tree.zig releaseHandle). + dot_truncated: bool = false, /// Opens of `event`, which hold the pane scripted. readers: u16 = 0, /// One of `readers` reads `event`; a second reading open is refused. @@ -286,6 +290,9 @@ pub fn spliceBody(p: *Pardes, pane: *Pane, q0: usize, q1: usize, bytes: []const const take = if (bytes.len == 0) 0 else wholeUtf8(bytes); const lo = @min(q0, f.content.len); const hi = @max(lo, @min(q1, f.content.len)); + // An empty range deleted, or a range replaced by what it holds: no + // edit, so no undo step and no new revision. + if (std.mem.eql(u8, f.content[lo..hi], bytes[0..take])) return take; const new = p.gpa.alloc(u8, f.content.len - (hi - lo) + take) catch return null; @memcpy(new[0..lo], f.content[0..lo]); @memcpy(new[lo..][0..take], bytes[0..take]); @@ -297,6 +304,41 @@ pub fn spliceBody(p: *Pardes, pane: *Pane, q0: usize, q1: usize, bytes: []const return take; } +test "a write that changes nothing, an empty range deleted or a range replaced by itself, makes no undo step" { + const p = try withFile(testing.allocator, "abc\ndef\n"); + defer p.deinit(); + const pane = p.panes[0].?; + const serial = pane.serial; + const f = &pane.file.?; + const steps = f.history.undo_len; + const revision = f.revision; + const data = Node.of(serial, .data); + // Each as a shell does it, on an open of its own: `: > data` after + // an empty address, then `echo abc > data` over the line it holds. + _ = wr(p, Node.of(serial, .addr), "#2,#2"); + const h = call(p, .{ .tag = 1, .op = .open, .node = data, .omode = 1 }).reply.handle; + try testing.expectEqual(tree.Status.ok, call(p, .{ .tag = 2, .op = .setattr, .node = data, .truncate = true }).reply.status); + _ = call(p, .{ .tag = 3, .op = .release, .node = data, .handle = h, .opened = true }); + for ([_][]const u8{ "abc\n", "abd\n" }) |text| { + _ = wr(p, Node.of(serial, .addr), "1"); + const w = call(p, .{ .tag = 4, .op = .open, .node = data, .omode = 1 }).reply.handle; + try testing.expectEqual(tree.Status.ok, call(p, .{ .tag = 5, .op = .write, .node = data, .handle = w, .data = text }).reply.status); + _ = call(p, .{ .tag = 6, .op = .release, .node = data, .handle = w, .opened = true }); + if (text[2] == 'c') { + try testing.expectEqualStrings("abc\ndef\n", f.content); + try testing.expectEqual(steps, f.history.undo_len); + try testing.expectEqual(revision, f.revision); + } + } + // Edit's `s/d/d/` is the same nothing. + const undone = f.history.undo_len; + try testing.expectEqual(tree.Status.ok, wr(p, Node.of(serial, .ctl), "Edit ,s/d/d/\n").reply.status); + try testing.expectEqual(undone, f.history.undo_len); + // A real change is still one. + try testing.expectEqualStrings("abd\ndef\n", f.content); + try testing.expectEqual(steps + 1, f.history.undo_len); +} + test "rows through the line index are the ones counting newlines gives, at every offset" { const p = try withFile(testing.allocator, "héllo\nwo\u{301}rld\n\n last line é\ntail"); defer p.deinit(); @@ -800,9 +842,11 @@ fn writeRange(req: Req, pane: *Pane, file: PaneFile) Reply { pf.limit = kept; pf.limit_truncated = null; }; + if (file == .dot) pf.dot_truncated = false; return tree.failText(req.tag, E.INVAL, a.err); }; if (file == .limit) pf.limit_truncated = null; + if (file == .dot) pf.dot_truncated = false; switch (file) { .addr => { pf.addr = range; @@ -1000,7 +1044,7 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { pf.limit_truncated = pf.limit; pf.limit = null; }, - .dot => if (fileOf(pane) != null) setDot(pane, .{}), + .dot => pf.dot_truncated = fileOf(pane) != null, else => {}, } return .ok; @@ -1211,8 +1255,12 @@ test "sel reads the selection and writing sel replaces it" { try testing.expectEqual(@as(u32, 0), p.panes[0].?.fs.addr.q0); try testing.expectEqual(@as(u32, 3), p.panes[0].?.fs.addr.q1); - // A truncation empties dot; an address expression addresses it. + // A truncation empties dot once its open goes, nothing written after + // it; an address expression addresses it. + const h = call(p, .{ .tag = 8, .op = .open, .node = dot, .omode = 1 }).reply.handle; _ = call(p, .{ .tag = 9, .op = .setattr, .node = dot, .truncate = true }); + try testing.expectEqualStrings("one", rd(p, sel, 0, 100).bytes); + _ = call(p, .{ .tag = 10, .op = .release, .node = dot, .handle = h, .opened = true }); try testing.expectEqualStrings("", rd(p, sel, 0, 100).bytes); try testing.expectEqual(Status.ok, wr(p, dot, "#0,#3").reply.status); |
