From acd9e032a147f4aa7ad49110dc2f4fb945e7a9bb Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 27 Sep 2026 20:14:04 -0300 Subject: Keep a pane's filesystem state on the Pane instead of a slot-indexed table addr, limit, the event queue and its readers, pty/data's queue, a waiting run and the /log announcement flag lived in p.fs.panes[slot], reset when the pane went; a reused slot is how the bug class came in. acme keeps the same state on its Window (editors/acme/dat.h:226-266), so now Pane.fs holds it: announced, retired (noteRetire, which still answers a waiting run and drops the listener count first) and freed with the pane itself. Co-Authored-By: Claude Opus 5.5 --- src/pardes.zig | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index a38eb837..7ae9cf32 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -6826,8 +6826,7 @@ pub const Pardes = struct { const watched = (if (pane.file) |f| f.output == null else false) or pane.hasPdf(); for (p.panes, 0..) |slot, id| if (slot == pane) { if (watched) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = false } }); - ctlfs.events.noteRetire(p, id, pane); - p.fs.forget(p.gpa, id); + ctlfs.events.noteRetire(p, pane); // ...and so do bytes still queued for the pty it no longer has. p.dropPendingWrite(id); }; @@ -6854,6 +6853,7 @@ pub const Pardes = struct { for (pane.ed_redo[0..pane.ed_redo_len]) |sn| if (sn.ovl) |o| p.gpa.free(o.text); panes.Terminal.deinitEmulator(pane, p.gpa); pane.clearCwd(); + pane.fs.deinit(p.gpa); p.gpa.destroy(pane); } @@ -6872,7 +6872,9 @@ pub const Pardes = struct { pane.serial = p.next_serial; p.rects[id] = .{}; p.panes[id] = pane; - ctlfs.events.noteInstall(p, id); + // /log hears of it once the update that made it ends, when it has + // its file, name and place (`ctlfs.events.announce`). + pane.fs.unannounced = true; } pub fn newShell(p: *Pardes, id: usize, cwd: []const u8) !*Pane { @@ -7029,10 +7031,10 @@ pub const Pardes = struct { } pub fn acknowledgeShell(p: *Pardes, id: usize, executable: []const u8, prompt_marks: bool) void { - if (id < MAX_PANES) { - ctlfs.pty.shellGone(p, id); // a respawn replaced whatever ran - p.fs.panes[id].unmarked = !prompt_marks; - } + if (id < MAX_PANES) if (p.panes[id]) |pane| { + ctlfs.pty.shellGone(p, pane); // a respawn replaced whatever ran + pane.fs.unmarked = !prompt_marks; + }; if (id < MAX_PANES) panes.Terminal.shellSpawned(p, id, prompt_marks); if (p.settings.shell.effective.set(executable)) p.settings.shell.pending = false; @@ -7913,9 +7915,9 @@ pub const Pardes = struct { if (p.fs.listeners == 0) return; for (p.panes, 0..) |slot, id| { const pane = slot orelse continue; - if (!p.fs.scripted(id)) continue; + if (pane.fs.readers == 0) continue; const tag = p.tagText(p.scratch.allocator(), pane) catch continue; - const snap = &p.fs.panes[id].tag_snap; + const snap = &pane.fs.tag_snap; if (std.mem.eql(u8, snap.items, tag)) continue; if (snap.capacity != 0 or snap.items.len != 0) ctlfs.events.noteReplace(p, id, true, snap.items, tag); @@ -12462,7 +12464,8 @@ pub const Pardes = struct { operand: PointerOperand, chorded: bool, ) bool { - if (!p.fs.scripted(id)) return false; + const scripted = p.panes[id] orelse return false; + if (scripted.fs.readers == 0) return false; const is_look = cmd == config.look_cmd; const action: ctlfs.events.Action = if (is_look) (if (on_tag) .tag_look else .body_look) -- cgit v1.3