diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 01:21:21 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | 8b04e53c194f85256ba6ea4f3d32a06367ee56db (patch) | |
| tree | 7bdead176c8b0e51d4eceb99459cc1ee43c055e4 /src/builtins.zig | |
| parent | 8743acbf8edd5e08e639f68022d135a244052658 (diff) | |
| download | pardes-8b04e53c194f85256ba6ea4f3d32a06367ee56db.tar.gz pardes-8b04e53c194f85256ba6ea4f3d32a06367ee56db.zip | |
A pane renamed but never edited closes on the first Del
The GUI golden's wide-glyph pane, renamed and never edited, was refused by
Del as modified, and every frame after it kept the pane. A rename marks a
pane dirty, but its text is what it loaded: the check now asks whether the
text was ever edited (its first edit keeps a hash of what it had), not only
whether it is dirty, as get's refusal does. Tests that faked an edit by the
dirty mark alone now fake the hash too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/builtins.zig')
| -rw-r--r-- | src/builtins.zig | 27 |
1 files changed, 23 insertions, 4 deletions
diff --git a/src/builtins.zig b/src/builtins.zig index 32e3cd84..51771c8f 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -234,6 +234,7 @@ test "Exit asks once about unsaved text, and quits when asked again" { defer p.deinit(); const pane = try p.setTestFile("saved\n"); pane.file.?.saved_revision = pane.file.?.revision -% 1; // modified + pane.file.?.saved_hash = 0; // edited: what it had is not what it has try std.testing.expect(p.executeBuiltinLine(p.active, "Exit")); try std.testing.expect(!p.quit); try std.testing.expect(std.mem.endsWith(u8, pane.msg[0..pane.msg_len], ": Modified (Exit again to discard)")); @@ -247,6 +248,7 @@ test "Exit over a ctl write names every pane it asks about, and the next Exit di defer p.deinit(); const first = try p.setTestFile("saved\n"); first.file.?.saved_revision = first.file.?.revision -% 1; + first.file.?.saved_hash = 0; // edited: what it had is not what it has p.newScratchBelow(p.active); const second = p.panes[p.active].?; panes.File.setContent(p, &second.file.?, try std.testing.allocator.dupe(u8, "x" ** 120)); @@ -282,6 +284,7 @@ test "Restore asks about unsaved text as Exit does, and restores when asked agai defer p.deinit(); const pane = try p.setTestFile("saved\n"); pane.file.?.saved_revision = pane.file.?.revision -% 1; // modified + pane.file.?.saved_hash = 0; // edited: what it had is not what it has var line_buf: [4200]u8 = undefined; const line = try testDump(&line_buf); try std.testing.expect(p.executeBuiltinLine(p.active, line)); @@ -296,6 +299,7 @@ test "a Restore of a dump that cannot be read says so, before the unsaved-text w defer p.deinit(); const pane = try p.setTestFile("saved\n"); pane.file.?.saved_revision = pane.file.?.revision -% 1; // modified + pane.file.?.saved_hash = 0; // edited: what it had is not what it has p.fs.no_prompt = true; defer p.fs.no_prompt = false; try std.testing.expect(p.executeBuiltinLine(p.active, "Restore /nope/missing.zon")); @@ -310,6 +314,7 @@ test "a Restore's warning is not an Exit's: each word is warned on its own" { defer p.deinit(); const pane = try p.setTestFile("saved\n"); pane.file.?.saved_revision = pane.file.?.revision -% 1; // modified + pane.file.?.saved_hash = 0; // edited: what it had is not what it has var line_buf: [4200]u8 = undefined; try std.testing.expect(p.executeBuiltinLine(p.active, try testDump(&line_buf))); try std.testing.expect(p.restore_req == null); @@ -474,10 +479,10 @@ fn warnModifiedIn(c: Ctx, asking: Pane.Discarding, which: Asked) bool { if (f.revision == f.saved_revision or !(panes.Output.fileTraits(f.output).saves or scratch)) continue; if (f.output != null and f.content.len < 100) continue; // Dirty by a rename alone is no text to lose, as get says (ctl.zig). - // Nor is an empty buffer that never held a file. - if (f.saved_hash) |saved| { - if (std.hash.Wyhash.hash(0, f.content) == saved) continue; - } else if (f.content.len == 0) continue; + // Nor is a buffer never edited at all: its first edit is what keeps + // the text it had (File.zig), so with none there is nothing to lose. + const saved = f.saved_hash orelse continue; + if (std.hash.Wyhash.hash(0, f.content) == saved) continue; if (pane.discard_warned) |w| if (w.revision == f.revision and w.by == asking) continue; pane.discard_warned = .{ .revision = f.revision, .by = asking }; var buf: [limits.host_path_cap + 48]u8 = undefined; @@ -1767,11 +1772,13 @@ test "Exit and Restore name a modified +New scratch as well as files" { defer p.deinit(); const file = try p.setTestFile("saved\n"); file.file.?.saved_revision = file.file.?.revision -% 1; // modified + file.file.?.saved_hash = 0; // edited: what it had is not what it has p.newScratchBelow(p.active); const scratch = &p.panes[p.active].?.file.?; scratch.content = try p.gpa.realloc(scratch.content, 120); @memset(scratch.content, 'n'); // past acme's 100 bytes, where it whines scratch.revision +%= 1; // typed into, never saved + scratch.saved_hash = 0; // edited: what it had is not what it has p.fs.no_prompt = true; defer p.fs.no_prompt = false; try std.testing.expect(p.executeBuiltinLine(p.active, "Exit")); @@ -1786,6 +1793,7 @@ test "Del and Delcol refuse a pane with unsaved text once, as acme's do, and clo defer p.deinit(); const pane = try p.setTestFile("saved\n"); pane.file.?.saved_revision = pane.file.?.revision -% 1; // modified + pane.file.?.saved_hash = 0; // edited: what it had is not what it has const serial = pane.serial; try std.testing.expect(p.executeBuiltinLine(p.active, "Delcol")); try std.testing.expect(p.paneBySerial(serial) != null); @@ -1797,3 +1805,14 @@ test "Del and Delcol refuse a pane with unsaved text once, as acme's do, and clo try std.testing.expect(p.executeBuiltinLine(p.active, "Del")); try std.testing.expect(p.paneBySerial(serial) == null); } + +test "a pane renamed but never edited has no text to lose: Del closes it at once" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer p.deinit(); + p.newScratchBelow(p.active); + const pane = try p.setTestFile("loaded\n"); + pane.file.?.saved_revision = pane.file.?.revision -% 1; // dirty by the rename alone + const serial = pane.serial; + try std.testing.expect(p.executeBuiltinLine(0, "Del")); + try std.testing.expect(p.paneBySerial(serial) == null); +} |
