From 2f187232227dde2755a7f2f2ce717f3e1f685f5c Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 11:40:03 -0300 Subject: Recent keeps only files on disk, and its cap never drops an open one A renamed scratch never saved and a /virtual/ pane were recorded as recent files, and at 200 the oldest entry went even when that file was open. Opens, renames and closes now record a path only when it is a file on this host, and the cap drops the oldest closed entry. A name with a newline is not written to the kept list, where it would read back as two. Co-Authored-By: Claude Opus 5.5 --- src/recent.zig | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) (limited to 'src/recent.zig') diff --git a/src/recent.zig b/src/recent.zig index 9f18d737..91c0afeb 100644 --- a/src/recent.zig +++ b/src/recent.zig @@ -18,6 +18,10 @@ pub const Entry = struct { /// The selection's other end then (its anchor), 0 when there was none. aline: u32 = 0, acol: u32 = 0, + /// Open in a pane now (opened, not closed since): never the one the + /// cap drops. ponytail: a pane renamed away leaves its old path marked + /// open until the session ends; kept rather than tracked by pane. + open: bool = false, }; pub const List = struct { @@ -41,9 +45,14 @@ pub const List = struct { var entry: Entry = if (l.find(path)) |i| l.entries.orderedRemove(i) else .{ .path = gpa.dupe(u8, path) catch return, }; + entry.open = true; + // The oldest closed file goes for it; an open one never does. if (l.entries.items.len >= cap) { - const last = l.entries.pop().?; - gpa.free(last.path); + var i = l.entries.items.len; + while (i > 0) : (i -= 1) if (!l.entries.items[i - 1].open) { + gpa.free(l.entries.orderedRemove(i - 1).path); + break; + }; } l.entries.insert(gpa, 0, entry) catch { gpa.free(entry.path); @@ -70,6 +79,7 @@ pub const List = struct { l.entries.items[i].col = col; l.entries.items[i].aline = aline; l.entries.items[i].acol = acol; + l.entries.items[i].open = false; l.dirty = true; } @@ -80,7 +90,9 @@ pub const List = struct { /// One line a file, ` `, most recent /// first. pub fn write(l: *const List, w: *std.Io.Writer) !void { - for (l.entries.items) |e| try w.print("{d} {d} {d} {d} {s}\n", .{ e.line, e.col, e.aline, e.acol, e.path }); + // A name with a newline in it would read back as two lines: not kept. + for (l.entries.items) |e| if (std.mem.indexOfScalar(u8, e.path, '\n') == null) + try w.print("{d} {d} {d} {d} {s}\n", .{ e.line, e.col, e.aline, e.acol, e.path }); } /// The lines `write` wrote; a line that is not one is passed over. @@ -135,7 +147,26 @@ test "the list keeps a path once, most recent first, its dot from closing, cappe try std.testing.expectEqual(@as(usize, 3), again.entries.items.len); for (0..cap + 5) |i| { var name: [16]u8 = undefined; - l.opened(gpa, std.fmt.bufPrint(&name, "/f{d}", .{i}) catch unreachable); + const path = std.fmt.bufPrint(&name, "/f{d}", .{i}) catch unreachable; + l.opened(gpa, path); + l.closed(gpa, path, 1, 1); } try std.testing.expectEqual(@as(usize, cap), l.entries.items.len); } + +test "the cap drops the oldest closed file, never an open one" { + const gpa = std.testing.allocator; + var l: List = .{}; + defer l.deinit(gpa); + var name: [16]u8 = undefined; + // The oldest is open; the next is closed. + l.opened(gpa, "/open"); + l.opened(gpa, "/shut"); + l.closed(gpa, "/shut", 1, 1); + for (2..cap) |i| l.opened(gpa, std.fmt.bufPrint(&name, "/f{d}", .{i}) catch unreachable); + try std.testing.expectEqual(@as(usize, cap), l.entries.items.len); + l.opened(gpa, "/new"); + try std.testing.expectEqual(@as(usize, cap), l.entries.items.len); + try std.testing.expect(l.get("/open") != null); + try std.testing.expect(l.get("/shut") == null); +} -- cgit v1.3