diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 18:00:49 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | 33bb985d6d167af6866a5667ad027db5c2ad6b7c (patch) | |
| tree | 8db25f33220b0e5425b65662860e57890393fa26 /src/ninep/ctl.zig | |
| parent | cbb6f821c29ede01648cdd41d2f1d2259a387645 (diff) | |
| download | pardes-33bb985d6d167af6866a5667ad027db5c2ad6b7c.tar.gz pardes-33bb985d6d167af6866a5667ad027db5c2ad6b7c.zip | |
get on a pane with unsaved edits refuses once, as acme's does
A ctl get threw unsaved text away at once. acme's get asks winclean first (exec.c:513): warned once, the same get again discards. A pane dirty only because it was renamed has no text to lose, and is not asked about.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/ctl.zig')
| -rw-r--r-- | src/ninep/ctl.zig | 21 |
1 files changed, 21 insertions, 0 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 16935f41..363524f1 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -454,6 +454,7 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply { if (apply and p.paneBySerial(serial) == null) return Reply.fail(req.tag, E.NOENT); if (std.mem.eql(u8, line, "get")) { if (!apply) continue; + if (getRefused(p, pane)) |said| return tree.failText(req.tag, E.IO, said); const errno = get(p, pane); if (errno != 0) return Reply.fail(req.tag, errno); } else if (std.mem.eql(u8, line, "lock")) { @@ -477,6 +478,21 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply { const e_not_locked = "window not locked by this open"; +/// acme's get asks winclean first (exec.c:513): unsaved text is warned +/// about once, and the same get again, nothing edited since, discards it. +fn getRefused(p: *Pardes, pane: *Pane) ?[]const u8 { + const f = pane_files.fileOf(pane) orelse return null; + if (!panes.Output.fileTraits(f.output).saves or f.revision == f.saved_revision) return null; + // Dirty by a rename alone is no text to lose: only edits are asked about. + const saved = f.saved_hash orelse return null; + if (std.hash.Wyhash.hash(0, f.content) == saved) return null; + if (pane.discard_warned) |w| if (w.revision == f.revision and w.by == .get) return null; + pane.discard_warned = .{ .revision = f.revision, .by = .get }; + const said = std.fmt.bufPrint(&p.fs.ename, "{s}: Modified (get again to discard)", .{f.path}) catch "Modified (get again to discard)"; + p.setMessage(p.paneBySerial(pane.serial).?, said); + return said; +} + fn get(p: *Pardes, pane: *Pane) u16 { const f = pane_files.fileOf(pane) orelse return 0; if (!panes.Output.fileTraits(f.output).saves) return 0; @@ -487,6 +503,7 @@ fn get(p: *Pardes, pane: *Pane) u16 { panes.File.pushUndo(p, pane); panes.File.setContent(p, f, bytes); f.saved_revision = f.revision; + f.saved_hash = std.hash.Wyhash.hash(0, f.content); return 0; } @@ -960,6 +977,10 @@ test "get reloads the pane from disk and reports a missing file without losing d _ = wr(p, Node.of(serial, .name), try std.fmt.bufPrint(&name, "{s}/missing.txt\n", .{directory})); _ = wr(p, Node.of(serial, .body), "unsaved\n"); + // Unsaved text is asked about once, as acme's get asks winclean. + const asked = wr(p, ctl_node, "get\n"); + try testing.expectEqual(E.IO, asked.errno()); + try testing.expect(std.mem.endsWith(u8, asked.reply.ename, ": Modified (get again to discard)")); try testing.expectEqual(E.NOENT, wr(p, ctl_node, "get\n").errno()); try testing.expect(p.paneBySerial(serial) != null); try testing.expectEqualStrings("from disk\nunsaved\n", pane.file.?.content); |
