diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 17:59:09 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | cbb6f821c29ede01648cdd41d2f1d2259a387645 (patch) | |
| tree | 0359efa48c1ec626413c0c939e8c718822a20b5a /src | |
| parent | c5fcbc0cb9c1e76bb01283e9ccfa00ed5bc4bef8 (diff) | |
| download | pardes-cbb6f821c29ede01648cdd41d2f1d2259a387645.tar.gz pardes-cbb6f821c29ede01648cdd41d2f1d2259a387645.zip | |
Text brought back to what was saved is clean again
dirty compared revisions only, so undoing to the save point, or typing the text back, left the pane dirty. The saved text's hash is kept from the first edit after a save or load, and an edit that makes the text equal to it marks the pane saved again, as undo to the save point does in acme.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/File.zig | 9 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 14 |
2 files changed, 23 insertions, 0 deletions
diff --git a/src/File.zig b/src/File.zig index b8368482..929e33bf 100644 --- a/src/File.zig +++ b/src/File.zig @@ -53,6 +53,10 @@ pub const State = struct { /// Revision last known to match disk, after Save or an external reload. /// Equal means the screen matches disk. saved_revision: u32 = 0, + /// The text at `saved_revision`, hashed as the first edit after it + /// replaces it: an edit that brings the text back to it is clean again, + /// as an undo to the save point is in acme. + saved_hash: ?u64 = null, watch_after_save: bool = false, /// Seconds of the last content change, for the filesystem's stat; zero until edited. mtime: u32 = 0, @@ -859,9 +863,14 @@ pub fn setContent(p: *Pardes, f: *State, new: []u8) void { if (f.mini) |*mini| mini.deinit(p.gpa); f.mini = null; const starts: []usize = if (f.line_starts.len > 0) editedLineStarts(p.gpa, f.line_starts, f.content, new) catch &.{} else &.{}; + // ponytail: a whole-text hash per edit; a rolling hash when files grow large. + if (f.revision == f.saved_revision or f.saved_hash == null) f.saved_hash = std.hash.Wyhash.hash(0, f.content); p.gpa.free(f.content); f.content = new; f.revision +%= 1; + if (f.saved_hash) |h| if (std.hash.Wyhash.hash(0, new) == h) { + f.saved_revision = f.revision; + }; f.mtime = pardes.ctlfs.events.now(); if (f.line_starts.len > 0) p.gpa.free(f.line_starts); f.line_starts = starts; diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index bb562672..16935f41 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -744,6 +744,20 @@ test "a second lock is refused until the holder unlocks or closes, and binds nob for (p.fs.opens) |o| try testing.expect(o.node == 0); } +test "dirty goes back to 0 when the text is the saved text again" { + const p = try withFile(testing.allocator, "one\n"); + defer p.deinit(); + const serial = serialOf(p); + const dirty = Node.of(serial, .dirty); + try testing.expectEqualStrings("0\n", rd(p, dirty, 0, 8).bytes); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .body), "two\n").reply.status); + try testing.expectEqualStrings("1\n", rd(p, dirty, 0, 8).bytes); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Undo").reply.status); + try testing.expectEqualStrings("0\n", rd(p, dirty, 0, 8).bytes); + try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .ctl), "Redo").reply.status); + try testing.expectEqualStrings("1\n", rd(p, dirty, 0, 8).bytes); +} + test "a message from a root ctl write is the session's in the log" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); |
