diff options
| -rw-r--r-- | docs/fs.md | 3 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 21 | ||||
| -rw-r--r-- | src/panes.zig | 2 |
3 files changed, 24 insertions, 2 deletions
@@ -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). |
