summaryrefslogtreecommitdiff
path: root/src/ninep/pane.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 21:11:49 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitb726f10d03f3c1141d99bf20e99a982026da785f (patch)
tree98088780091599a712d812bee3e4b49779d394b4 /src/ninep/pane.zig
parent591d3c7baecc434527d02e5894ac0240d2fa1c8d (diff)
downloadpardes-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]>
Diffstat (limited to 'src/ninep/pane.zig')
-rw-r--r--src/ninep/pane.zig23
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 };