diff options
Diffstat (limited to 'src/recent.zig')
| -rw-r--r-- | src/recent.zig | 77 |
1 files changed, 65 insertions, 12 deletions
diff --git a/src/recent.zig b/src/recent.zig index b6a1a66a..91c0afeb 100644 --- a/src/recent.zig +++ b/src/recent.zig @@ -15,6 +15,13 @@ pub const Entry = struct { /// Where its dot was when it last closed (1-based; 0 none). line: u32 = 0, col: u32 = 0, + /// 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 { @@ -38,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); @@ -53,6 +65,11 @@ pub const List = struct { /// `path` closed with its dot at `line`:`col`: where a reopen lands. /// Its place in the list is its opening's. pub fn closed(l: *List, gpa: std.mem.Allocator, path: []const u8, line: u32, col: u32) void { + l.closedSel(gpa, path, line, col, 0, 0); + } + + /// `closed`, with the selection's anchor too (0 0 for none). + pub fn closedSel(l: *List, gpa: std.mem.Allocator, path: []const u8, line: u32, col: u32, aline: u32, acol: u32) void { const i = l.find(path) orelse found: { l.opened(gpa, path); // Out of memory for it: nothing to note the dot on. @@ -60,6 +77,9 @@ pub const List = struct { }; l.entries.items[i].line = line; 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; } @@ -67,9 +87,12 @@ pub const List = struct { return l.entries.items[l.find(path) orelse return null]; } - /// One line a file, `<line> <col> <path>`, most recent first. + /// One line a file, `<line> <col> <aline> <acol> <path>`, most recent + /// first. pub fn write(l: *const List, w: *std.Io.Writer) !void { - for (l.entries.items) |e| try w.print("{d} {d} {s}\n", .{ e.line, e.col, 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. @@ -77,13 +100,21 @@ pub const List = struct { var lines = std.mem.splitScalar(u8, bytes, '\n'); while (lines.next()) |line| { if (l.entries.items.len >= cap) break; - var it = std.mem.splitScalar(u8, line, ' '); - const ln = std.fmt.parseInt(u32, it.next() orelse continue, 10) catch continue; - const col = std.fmt.parseInt(u32, it.next() orelse continue, 10) catch continue; - const path = it.rest(); + // Numbers, then the path (which starts with `/`): two of them in + // a list written before the anchor was kept, four since. + var nums: [4]u32 = @splat(0); + var n: usize = 0; + var rest = line; + while (rest.len > 0 and rest[0] != '/' and n < nums.len) : (n += 1) { + const sp = std.mem.indexOfScalar(u8, rest, ' ') orelse break; + nums[n] = std.fmt.parseInt(u32, rest[0..sp], 10) catch break; + rest = rest[sp + 1 ..]; + } + if (n != 2 and n != 4) continue; + const path = rest; if (path.len == 0 or path[0] != '/' or l.find(path) != null) continue; const owned = gpa.dupe(u8, path) catch return; - l.entries.append(gpa, .{ .path = owned, .line = ln, .col = col }) catch { + l.entries.append(gpa, .{ .path = owned, .line = nums[0], .col = nums[1], .aline = nums[2], .acol = nums[3] }) catch { gpa.free(owned); return; }; @@ -105,15 +136,37 @@ test "the list keeps a path once, most recent first, its dot from closing, cappe var buf: [256]u8 = undefined; var w = std.Io.Writer.fixed(&buf); try l.write(&w); - try std.testing.expectEqualStrings("7 3 /a\n0 0 /b\n", w.buffered()); + try std.testing.expectEqualStrings("7 3 0 0 /a\n0 0 0 0 /b\n", w.buffered()); var again: List = .{}; defer again.deinit(gpa); again.read(gpa, w.buffered()); again.read(gpa, "junk\n"); - try std.testing.expectEqual(@as(usize, 2), again.entries.items.len); + // A list written before the anchor was kept still reads. + again.read(gpa, "4 2 /old\n"); + try std.testing.expectEqual(@as(u32, 4), again.get("/old").?.line); + 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); +} |
