summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 20:09:32 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitf8fe738416fe3a40a7136f2cb5528e8fd57f40df (patch)
tree4f5f3ff990d79523a191c0754a154f3f5ffdee43 /src
parent506ca99f13aec67d593e4a30bbe416dea176d0ba (diff)
downloadpardes-f8fe738416fe3a40a7136f2cb5528e8fd57f40df.tar.gz
pardes-f8fe738416fe3a40a7136f2cb5528e8fd57f40df.zip
Recent and /recent keep PDFs and images as they keep files, and an open file's row is where its dot is now
Only text files were recorded, so a PDF read yesterday was not there to find again, and an open file's row showed the place it last closed at, stale by however far it had moved since. A PDF is kept at its page and reopened there from its row; an image by its path; an open one's row reads its pane's place at the time of the listing. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/Output.zig10
-rw-r--r--src/look.zig5
-rw-r--r--src/ninep/ctl.zig21
-rw-r--r--src/ninep/events.zig5
-rw-r--r--src/pardes.zig16
5 files changed, 51 insertions, 6 deletions
diff --git a/src/Output.zig b/src/Output.zig
index 06f2f1dc..4aa0c021 100644
--- a/src/Output.zig
+++ b/src/Output.zig
@@ -612,10 +612,16 @@ pub fn openJumps(p: *Pardes, id: usize) !void {
/// The recent list as rows: `<path>:<line>:<col> open|closed`, most recent
/// first; a look at a row opens the file there, as a +Jumps row does.
pub fn recentText(p: *Pardes, w: *std.Io.Writer, look_rows: bool) !void {
- for (p.recent.entries.items) |e| {
+ for (p.recent.entries.items) |stored| {
+ // An open one's row is where it is now, not where it last closed.
+ var e = stored;
var is_open = false;
- for (p.panes) |slot| if (slot) |pane| if (pane.file) |f| if (f.output == null and std.mem.eql(u8, f.path, e.path)) {
+ for (p.panes) |slot| if (slot) |pane| if (Pardes.recentPlace(pane)) |d| if (std.mem.eql(u8, d.path, e.path)) {
is_open = true;
+ e.line = d.line;
+ e.col = d.col;
+ e.aline = d.aline;
+ e.acol = d.acol;
};
const state: []const u8 = if (is_open) "open" else "(closed)";
if (!look_rows) {
diff --git a/src/look.zig b/src/look.zig
index 2e8a4b39..36b91545 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1481,6 +1481,11 @@ pub fn reopenAt(p: *Pardes, from: usize, path: []const u8, at: Spot) void {
lookAt(p, from, path);
const id = p.active;
const pane = p.panes[id] orelse return;
+ // A PDF's place is its page, the last if it has fewer now.
+ if (comptime pdf_enabled) if (pane.pdf) |*pv| if (std.mem.eql(u8, pv.path, path)) {
+ if (at.line > 0 and pv.focusLocation(p.pdf_gpa, @min(at.line, pv.page_count), 0)) panes.Pdf.resetPageChrome(pane);
+ return;
+ };
const f = if (pane.file) |*file| file else return;
if (!std.mem.eql(u8, f.path, path)) return;
// The empty "line" after a final newline is no line to land on.
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index eff8003b..75a7eb9a 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -3060,6 +3060,27 @@ test "a Jumplist entry is (closed) only while no pane has its file open" {
for (p.jumps[0..p.njumps]) |j| try testing.expect(j.serial != 0);
}
+test "Recent's row for a file still open is where its dot is now, not where it last closed" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "a.txt", .data = "one\ntwo\nthree\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)];
+ var line: [4200]u8 = undefined;
+ try testing.expectEqual(Status.ok, wr(p, root_look, try std.fmt.bufPrint(&line, "{s}/a.txt:3\n", .{dir})).reply.status);
+ p.sync();
+ const pane = p.panes[p.active].?;
+ pane.body.vsel.active = false; // the look's selection of line 3 let go
+ pane.body.cur_row = 1;
+ pane.body.cur_col = 2;
+ try testing.expectEqual(Status.ok, wr(p, root_exec, "Recent\n").reply.status);
+ const rows = p.panes[p.paneBySerial(try std.fmt.parseInt(u32, std.mem.trimEnd(u8, rd(p, root_exec, 0, 64).bytes, "\n"), 10)).?].?;
+ var want: [4300]u8 = undefined;
+ try testing.expect(std.mem.indexOf(u8, rows.file.?.content, try std.fmt.bufPrint(&want, "{s}/a.txt:2:3 open\n", .{dir})) != null);
+}
+
test "a closed file is found again: Recent and /recent list it closed, a look at its row reopens it at its dot, and a jump back does too" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index a09f7278..bacf3ad6 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -140,8 +140,9 @@ pub fn announce(p: *Pardes) void {
if (!pane.fs.unannounced) continue;
pane.fs.unannounced = false;
noteLog(p, .new, pane);
- // A file opened (a look, a Restore, a boot): first in Recent.
- if (pane.file) |f| if (f.output == null and pardes.Pardes.recentKeeps(f.path)) p.recent.opened(p.recentGpa(), f.path);
+ // A file, PDF or image opened (a look, a Restore, a boot): first in
+ // Recent.
+ if (pardes.Pardes.recentPlace(pane)) |d| if (pardes.Pardes.recentKeeps(d.path)) p.recent.opened(p.recentGpa(), d.path);
}
}
diff --git a/src/pardes.zig b/src/pardes.zig
index bea79b18..fbc19b8a 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4709,13 +4709,25 @@ pub const Pardes = struct {
return exec.isFile(local);
}
- /// A file pane closing: its place noted in Recent, when Recent keeps it.
+ /// A file, PDF or image pane closing: its place noted in Recent, when
+ /// Recent keeps it.
fn noteClosed(p: *Pardes, pane: *const Pane) void {
- const d = recentDot(pane) orelse return;
+ const d = recentPlace(pane) orelse return;
if (p.recent.get(d.path) == null and !recentKeeps(d.path)) return;
p.recent.closedSel(p.recentGpa(), d.path, d.line, d.col, d.aline, d.acol);
}
+ pub const RecentPlace = struct { path: []const u8, line: u32, col: u32, aline: u32 = 0, acol: u32 = 0 };
+
+ /// Where a pane is, as Recent keeps it: a file's dot, a PDF's page
+ /// (`:page:1`), an image's path alone (line 0). Null for any other.
+ pub fn recentPlace(pane: *const Pane) ?RecentPlace {
+ if (recentDot(pane)) |d| return .{ .path = d.path, .line = d.line, .col = d.col, .aline = d.aline, .acol = d.acol };
+ if (pane.pdfPath()) |path| return .{ .path = path, .line = @intCast(pane.pdfPage().? + 1), .col = 1 };
+ if (pane.image) |iv| return .{ .path = iv.path, .line = 0, .col = 0 };
+ return null;
+ }
+
fn recentDot(pane: *const Pane) ?struct { path: []const u8, line: u32, col: u32, aline: u32 = 0, acol: u32 = 0 } {
const f = pane.file orelse return null;
if (f.output != null) return null;