diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-30 21:11:49 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | b726f10d03f3c1141d99bf20e99a982026da785f (patch) | |
| tree | 98088780091599a712d812bee3e4b49779d394b4 | |
| parent | 591d3c7baecc434527d02e5894ac0240d2fa1c8d (diff) | |
| download | pardes-b726f10d03f3c1141d99bf20e99a982026da785f.tar.gz pardes-b726f10d03f3c1141d99bf20e99a982026da785f.zip | |
A shell's > into an empty pane's body is one undo step, as into any other
The truncation of an empty body is no edit and pushes no step, but it
still marked the open's writes as joining the step it would have
pushed, so the text written after it had no undo step at all. A
truncation that changed nothing (of body, data or xdata) now leaves the
step to the write that follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/ninep/pane.zig | 23 |
1 files changed, 21 insertions, 2 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index d276b771..14877f0f 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -304,6 +304,21 @@ pub fn spliceBody(p: *Pardes, pane: *Pane, q0: usize, q1: usize, bytes: []const return take; } +test "a shell's > into an empty pane's body is one undo step, as into any other" { + const p = try withFile(testing.allocator, ""); + defer p.deinit(); + const pane = p.panes[0].?; + const f = &pane.file.?; + const body = Node.of(pane.serial, .body); + const steps = f.history.undo_len; + const h = call(p, .{ .tag = 1, .op = .open, .node = body, .omode = 1 }).reply.handle; + _ = call(p, .{ .tag = 2, .op = .setattr, .node = body, .truncate = true }); + try testing.expectEqual(tree.Status.ok, call(p, .{ .tag = 3, .op = .write, .node = body, .handle = h, .data = "fresh\n" }).reply.status); + _ = call(p, .{ .tag = 4, .op = .release, .node = body, .handle = h, .opened = true }); + try testing.expectEqualStrings("fresh\n", f.content); + try testing.expectEqual(steps + 1, f.history.undo_len); +} + 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(); @@ -1017,8 +1032,11 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { switch (file) { .body => if (fileOf(pane) != null) { pf.joined = null; + const was = fileOf(pane).?.revision; _ = spliceBody(p, pane, 0, bodyOf(pane).len, "") orelse return .err; - pf.joined = fileOf(pane).?.revision; + // An empty body truncated is no edit: the write after it makes + // the step, rather than join one that was never pushed. + pf.joined = if (fileOf(pane).?.revision != was) fileOf(pane).?.revision else null; pf.joined_by = 0; pf.addr = .{}; setDot(pane, .{}); @@ -1029,8 +1047,9 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { const q1 = @max(q0, pf.addr.q1); const before = dotOf(pane); pf.joined = null; + const was = fileOf(pane).?.revision; _ = spliceBody(p, pane, q0, q1, "") orelse return .err; - pf.joined = fileOf(pane).?.revision; + pf.joined = if (fileOf(pane).?.revision != was) fileOf(pane).?.revision else null; 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 }; |
