diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 19:00:49 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | 8948c9ec1b8ade3d1e4ed7103fd4f6d17a8ea23e (patch) | |
| tree | 0e7ca727fd79fd0c1605a166203917db3d968566 /src/ninep/pane.zig | |
| parent | 5a5c9c9698c2621d3509c687880d22e904f25139 (diff) | |
| download | pardes-8948c9ec1b8ade3d1e4ed7103fd4f6d17a8ea23e.tar.gz pardes-8948c9ec1b8ade3d1e4ed7103fd4f6d17a8ea23e.zip | |
A replace through data or body, truncate then write, is one undo step
A shell's printf x > data truncates the addressed range and then writes, and each pushed its own undo point, so one replace took two Undos. A truncation of data, xdata or body now lets the write that follows it at once join its step, as acme's replace is one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/pane.zig')
| -rw-r--r-- | src/ninep/pane.zig | 22 |
1 files changed, 21 insertions, 1 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index a5acccbc..50fc220e 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -47,6 +47,10 @@ pub const State = struct { event_reader: bool = false, events: events.Queue = .{}, nomark: bool = false, + /// The revision a truncation of `data`, `xdata` or `body` left: the + /// write that follows it at once joins its undo step, so a shell's + /// `printf x > data` is one step, as acme's replace is. + joined: ?u32 = null, noscroll: bool = false, tag_snap: std.ArrayList(u8) = .empty, /// An open reads pty/data, so output queues for it; a second is refused. @@ -190,7 +194,9 @@ pub fn spliceBody(p: *Pardes, pane: *Pane, q0: usize, q1: usize, bytes: []const @memcpy(new[0..lo], f.content[0..lo]); @memcpy(new[lo..][0..take], bytes[0..take]); @memcpy(new[lo + take ..], f.content[hi..]); - if (!pane.fs.nomark) panes.File.pushUndo(p, pane); + const join = pane.fs.joined == f.revision; + pane.fs.joined = null; + if (!pane.fs.nomark and !join) panes.File.pushUndo(p, pane); panes.File.setContent(p, f, new); return take; } @@ -614,6 +620,7 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { switch (file) { .body => if (fileOf(pane) != null) { _ = spliceBody(p, pane, 0, bodyOf(pane).len, "") orelse return .err; + pf.joined = fileOf(pane).?.revision; pf.addr = .{}; setDot(pane, .{}); }, @@ -623,6 +630,7 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { const q1 = @max(q0, pf.addr.q1); const before = dotOf(pane); _ = spliceBody(p, pane, q0, q1, "") orelse return .err; + pf.joined = fileOf(pane).?.revision; setDot(pane, .{ .q0 = shiftOne(before.q0, q0, q1 - q0, 0), .q1 = shiftOne(before.q1, q0, q1 - q0, 0) }); pf.addr = .{ .q0 = q0, .q1 = q0 }; }, @@ -909,6 +917,18 @@ test "the flag files read and write the state acme kept behind ctl verbs" { try testing.expectEqual(E.INVAL, wr(term, Node.of(serialOf(term), .dirty), "1").errno()); } +test "a replace through data, truncate then write, is one undo step" { + const p = try withFile(testing.allocator, "one two\n"); + defer p.deinit(); + const serial = serialOf(p); + _ = wr(p, Node.of(serial, .addr), "/two/"); + _ = call(p, .{ .tag = 1, .op = .setattr, .node = Node.of(serial, .data), .truncate = true }); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .data), "2").reply.status); + try testing.expectEqualStrings("one 2\n", rd(p, Node.of(serial, .body), 0, 64).bytes); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Undo").reply.status); + try testing.expectEqualStrings("one two\n", rd(p, Node.of(serial, .body), 0, 64).bytes); +} + test "writes under mark 0 are one undo step, even with no history before them" { const p = try withFile(testing.allocator, "one\n"); defer p.deinit(); |
