summaryrefslogtreecommitdiff
path: root/src/ninep/ctl.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 17:48:02 -0300
committerGabriel Schneider <[email protected]>2026-10-01 18:08:12 -0300
commit8c74f9a8f199ea0c2d20cc524358d224ea192005 (patch)
treed2a280eb296ad1dce02c9f8e3e8fad20a927edf8 /src/ninep/ctl.zig
parentafec162464d360cbbf475d3fb7457cda83ce918f (diff)
downloadpardes-8c74f9a8f199ea0c2d20cc524358d224ea192005.tar.gz
pardes-8c74f9a8f199ea0c2d20cc524358d224ea192005.zip
Get reads a pane's file again, refusing unsaved edits once as Del does, and Get file loads that file into the pane; in a directory pane it reads the directory again. Load is Restore, as Put is Save; Abort's refusal says it is acme's debugging word
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/ctl.zig')
-rw-r--r--src/ninep/ctl.zig48
1 files changed, 41 insertions, 7 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 0f4e181c..269e9346 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -972,7 +972,7 @@ pub fn writePane(p: *Pardes, req: Req, pane: *Pane) Reply {
if (std.mem.eql(u8, line, "get")) {
if (!apply) continue;
// Quoting the line, as a builtin's refusal (Save's) does.
- if (getRefused(p, pane)) |said_in_ename| {
+ if (getRefused(p, pane, "get")) |said_in_ename| {
// refuse writes ename, where the words are: copy them out.
var said_buf: [320]u8 = undefined;
const said = said_buf[0..said_in_ename.len];
@@ -1127,7 +1127,9 @@ const e_locked = tree.e_in_use ++ ": another open of this ctl holds the lock";
/// 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 {
+/// Asked once over unsaved edits, by `word` (ctl's `get`, the Get builtin):
+/// the words of the refusal, in ename; null to go ahead.
+pub fn getRefused(p: *Pardes, pane: *Pane, word: []const u8) ?[]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.
@@ -1140,15 +1142,16 @@ fn getRefused(p: *Pardes, pane: *Pane) ?[]const u8 {
// write's err record logs it, not a msg too.
// And logged `unsaved <serial> <name>`, as the other refusals are.
tree.events.noteLog(p, .unsaved, pane);
- const listed = std.fmt.allocPrint(p.gpa, "{s}: Modified\n\nget again to discard\n", .{f.path}) catch null;
+ const listed = std.fmt.allocPrint(p.gpa, "{s}: Modified\n\n{s} again to discard\n", .{ f.path, word }) catch null;
if (listed) |content| panes.Output.openUnsaved(p, id, content);
+ var notice: [64]u8 = undefined;
p.fs.unlogged = true;
- p.setMessage(id, "1 unsaved pane \u{2014} get again to discard");
+ p.setMessage(id, std.fmt.bufPrint(&notice, "1 unsaved pane \u{2014} {s} again to discard", .{word}) catch "1 unsaved pane");
p.fs.unlogged = false;
- return std.fmt.bufPrint(&p.fs.ename, "{s}: Modified (get again to discard)", .{f.path}) catch "Modified (get again to discard)";
+ return std.fmt.bufPrint(&p.fs.ename, "{s}: Modified ({s} again to discard)", .{ f.path, word }) catch "Modified (get again to discard)";
}
-fn get(p: *Pardes, pane: *Pane, failed: *anyerror) u16 {
+pub fn get(p: *Pardes, pane: *Pane, failed: *anyerror) u16 {
const f = pane_files.fileOf(pane) orelse return 0;
// A directory pane: the directory read again (Dir.zig).
if (f.listing != null) {
@@ -1775,6 +1778,37 @@ test "get reloads the pane from disk and reports a missing file without losing d
try testing.expect(pane_files.dirtyOf(pane));
}
+test "Get reads the pane's file again, refusing unsaved edits once, and Get file loads that file into the pane" {
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "note.txt", .data = "from disk\n" });
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "other.txt", .data = "the other\n" });
+ var directory_buf: [4096]u8 = undefined;
+ const directory = directory_buf[0..try tmp.dir.realPath(testing.io, &directory_buf)];
+ const p = try withFile(testing.allocator, "in memory\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const pane = p.panes[0].?;
+ var name: [std.fs.max_path_bytes + 8]u8 = undefined;
+ _ = wr(p, Node.of(serial, .name), try std.fmt.bufPrint(&name, "{s}/note.txt\n", .{directory}));
+ try testing.expect(p.executeBuiltinLine(0, "Get"));
+ try testing.expectEqualStrings("from disk\n", pane.file.?.content);
+ try testing.expect(!pane_files.dirtyOf(pane));
+ // Edited: asked once, then read again.
+ _ = wr(p, Node.of(serial, .body), "unsaved\n");
+ const failures = p.fs.failures;
+ try testing.expect(p.executeBuiltinLine(0, "Get"));
+ try testing.expect(p.fs.failures != failures);
+ try testing.expectEqualStrings("from disk\nunsaved\n", pane.file.?.content);
+ try testing.expect(p.executeBuiltinLine(0, "Get"));
+ try testing.expectEqualStrings("from disk\n", pane.file.?.content);
+ // Get file: that file, by a name from the pane's directory.
+ try testing.expect(p.executeBuiltinLine(0, "Get other.txt"));
+ try testing.expectEqualStrings("the other\n", pane.file.?.content);
+ try testing.expect(std.mem.endsWith(u8, pane.file.?.path, "/other.txt"));
+ try testing.expect(!pane_files.dirtyOf(pane));
+}
+
test "a body write to a terminal is typed input, not a paste, even to a program that asked for bracketed paste" {
const p = try Pardes.init(testing.allocator, .{ .cols = 90, .rows = 30 });
defer p.deinit();
@@ -3157,7 +3191,7 @@ test "acme's words run as pardes's (Put is Save, Delete a Del that does not ask)
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
const serial = serialOf(p);
- for ([_][]const u8{ "Snarf\n", "Putall\n", "Zerox\n", "Get\n", "Tab\n", "Indent\n", "Local\n", "Incl\n", "Abort\n" }) |line| {
+ for ([_][]const u8{ "Snarf\n", "Putall\n", "Zerox\n", "Tab\n", "Indent\n", "Local\n", "Incl\n", "Abort\n" }) |line| {
for ([_]u64{ Node.of(serial, .ctl), Node.of(serial, .exec) }) |node| {
const r = wr(p, node, line);
try testing.expectEqual(E.INVAL, r.errno());