summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 16:00:17 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit4b7ab8a91defb73006d58abd9f6e7cb3d690d669 (patch)
tree561594386310b0fed1aafc1935c31c339f7d73a5
parent2d24ae98621f24c869ced4dbc06146086ff14887 (diff)
downloadpardes-4b7ab8a91defb73006d58abd9f6e7cb3d690d669.tar.gz
pardes-4b7ab8a91defb73006d58abd9f6e7cb3d690d669.zip
A 9P request that fills a stillborn scratch's column replaces the scratch before it is answered
A pane that joins a column holding only an empty +New takes the column, but that replacement ran at the end of the editor's next update, not of a 9P request: a look answered 19 while index still listed the +New 18, whose del came later. serveFs, a step of its own, now runs it too before answering. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/ninep/ctl.zig22
-rw-r--r--src/pardes.zig4
2 files changed, 26 insertions, 0 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index deb64565..3690ac47 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -988,3 +988,25 @@ test "Joincol with no column to the right and Theme with no such theme say so" {
const pane = p.panes[p.active].?;
try testing.expect(std.mem.indexOf(u8, pane.msg[0..pane.msg_len], "no theme \"drak\"") != null);
}
+
+test "a look that fills a column's stillborn scratch is answered with the scratch already gone" {
+ const gpa = testing.allocator;
+ const p = try withFile(gpa, "x\n");
+ defer p.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "f.txt", .data = "file\n" });
+ var dir: [4096]u8 = undefined;
+ const at = dir[0..try tmp.dir.realPath(testing.io, &dir)];
+ var line: [4200]u8 = undefined;
+ // Newcol makes a column of one empty +New; a file looked from it
+ // joins that column and takes the scratch's place.
+ try testing.expectEqual(tree.Status.ok, wr(p, @intFromEnum(tree.TopFile.ctl), "Newcol\n").reply.status);
+ const scratch = p.panes[p.active].?.serial;
+ try testing.expectEqual(tree.Status.ok, wr(p, Node.of(scratch, .look), try std.fmt.bufPrint(&line, "{s}/f.txt\n", .{at})).reply.status);
+ try testing.expect(p.paneBySerial(scratch) == null);
+ var want: [32]u8 = undefined;
+ const gone = try std.fmt.bufPrint(&want, "{d} ", .{scratch});
+ var rows = std.mem.splitScalar(u8, rd(p, @intFromEnum(tree.TopFile.index), 0, 4096).bytes, '\n');
+ while (rows.next()) |row| try testing.expect(!std.mem.startsWith(u8, row, gone));
+}
diff --git a/src/pardes.zig b/src/pardes.zig
index 43d8e94d..12de47fa 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4999,6 +4999,10 @@ pub const Pardes = struct {
look.cancelLookHover(p);
const reply = ctlfs.handle(p, req);
if (reply.status == .err and (req.op == .write or req.op == .setattr)) ctlfs.events.noteError(p, req, reply);
+ // A pane that joined a column holding only a stillborn scratch takes
+ // it now, before the answer: one read of index after the look shows
+ // the replacement done, and the log has its del with the new.
+ exec.replaceStillborn(p);
// The request was a whole step of its own, so it settles the way a
// step does: the cursor and scroll reconciled, the scripted panes
// told, and the panes it made announced to /log now rather than at