summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/fs.md3
-rw-r--r--src/ninep/ctl.zig21
-rw-r--r--src/panes.zig2
3 files changed, 24 insertions, 2 deletions
diff --git a/docs/fs.md b/docs/fs.md
index 12e94b8d..b8425d2f 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -337,7 +337,8 @@ length, a reserved zero, the dirty flag, the width in cells, the font and the
tab width — followed by rio's `current` or `notcurrent` (rio(4), `wctl`):
whether the pane has the keyboard. It takes the pane's builtins (below),
`get`, which reloads the buffer from the name it
-carries, and acme's `lock` and `unlock` (editors/acme/xfid.c:603-611), for an
+carries (unsaved edits are refused once, `<name>: Modified (get again to
+discard)`, as acme's get asks winclean, exec.c:513), and acme's `lock` and `unlock` (editors/acme/xfid.c:603-611), for an
edit of several writes to `addr` and `data` that another client must not
land in the middle of. As in acme the lock binds only the clients that take
it: a `lock` while another open holds it fails at once with `file in use`
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);
diff --git a/src/panes.zig b/src/panes.zig
index 8049da4e..a7e5c668 100644
--- a/src/panes.zig
+++ b/src/panes.zig
@@ -25,7 +25,7 @@ pub const EditText = struct { text: []u8, row0: i32 };
pub const Pane = struct {
/// The words that throw unsaved text away (builtins.zig warnModified).
- pub const Discarding = enum { Exit, Restore };
+ pub const Discarding = enum { Exit, Restore, get };
/// One mouse selection (block-shaped), per button. c/r are text-area relative;
/// r counts from the tag's first row (the body starts at Pane.tag_rows).