summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/builtins.zig27
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);
+}