summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 08:22:47 -0300
committerGabriel Schneider <[email protected]>2026-10-01 08:53:11 -0300
commitf3070c04d3eaa0e0d3965478781c2d0b456446bf (patch)
tree56cc5dd8138f7fd83755133798253ec2220ecd32
parentc5c9768bed2368c6a341d0400b13b3428c138b6e (diff)
downloadpardes-f3070c04d3eaa0e0d3965478781c2d0b456446bf.tar.gz
pardes-f3070c04d3eaa0e0d3965478781c2d0b456446bf.zip
A PDF place `:P:H` whose search hit H is not on page P, or with no search, is a miss with an err, as a page past the last is
The second number of a PDF place is a hit of the pane's search (its search rows are `file.pdf:P:H`), and one that named no hit turned to page P silently. pdf State.hitExists asks the pane's search for page P: a hit it does not have, or any hit with no search, is now `<file> has no search hit H on page P`, on the pane itself (`:P:H`) and from anywhere (`file.pdf:P:H`); a search row's hit is always there. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--src/look.zig26
-rw-r--r--src/pdf_view.zig10
-rw-r--r--test/pdf.zig10
3 files changed, 41 insertions, 5 deletions
diff --git a/src/look.zig b/src/look.zig
index 11c77760..0f5103ba 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1260,6 +1260,20 @@ fn missedPage(p: *Pardes, id: usize, path: []const u8, page: usize) void {
missed(p, id, "{s} has no page {d}", .{ missText(path, &cut), page });
}
+/// `file.pdf:P:H` names hit H of the pane's search on page P: none there,
+/// or no search, is a miss as a page past the last is.
+fn missedHit(p: *Pardes, id: usize, path: []const u8, page: usize, hit: usize) void {
+ var cut: [miss_path_cap + "…".len]u8 = undefined;
+ missed(p, id, "{s} has no search hit {d} on page {d}", .{ missText(path, &cut), hit, page });
+}
+
+/// The open PDF pane showing `path`, if one does.
+fn pdfPaneOf(p: *Pardes, path: []const u8) ?*panes.Pdf.State {
+ if (comptime !pdf_enabled) return null;
+ for (p.panes) |slot| if (slot) |pane| if (pane.pdf) |*pv| if (std.mem.eql(u8, pv.path, path)) return pv;
+ return null;
+}
+
fn missedLine(p: *Pardes, id: usize, path: []const u8, line: anytype) void {
var cut: [miss_path_cap + "…".len]u8 = undefined;
missed(p, id, "{s} has no line {d}", .{ missText(path, &cut), line });
@@ -1505,6 +1519,7 @@ fn lookPdfPage(p: *Pardes, id: usize, target: usize, pv: anytype, expr: []const
return missed(p, id, no_text, .{missText(pv.path, &cut)}) else 0;
if (page == 0) return missedPage(p, id, pv.path, 0);
if (page > pv.page_count) return missedPage(p, id, pv.path, page);
+ if (!pv.hitExists(p.pdf_gpa, page, hit)) return missedHit(p, id, pv.path, page, hit);
const pane = p.panes[target].?;
clearNavigationSelection(p, pane);
if (pv.focusLocation(p.pdf_gpa, page, hit)) panes.Pdf.resetPageChrome(pane);
@@ -1761,8 +1776,10 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void {
// A PDF's page past its last is a miss, as a line past a file's
// end is: never the last page in its place (opening one, the
// same: Pdf.openPane's .refuse).
- if (comptime pdf_enabled) if (target.pdf) |pv| if (t.at.line > pv.page_count)
- return missedPage(p, id, pv.path, t.at.line);
+ if (comptime pdf_enabled) if (target.pdf) |*pv| {
+ if (t.at.line > pv.page_count) return missedPage(p, id, pv.path, t.at.line);
+ if (!pv.hitExists(p.pdf_gpa, @max(1, t.at.line), t.at.col)) return missedHit(p, id, pv.path, @max(1, t.at.line), t.at.col);
+ };
const before = p.active;
clearNavigationSelection(p, target);
focusPaneLine(p, t.id, t.at, .center);
@@ -1791,6 +1808,11 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void {
},
.file => |target| {
if (comptime pdf_enabled) if (target.kind == .pdf) {
+ // A hit of a search: one of the open pane's, checked by
+ // focusPaneByPath's pane; a PDF not open has none.
+ if (target.at.col > 0) if (pdfPaneOf(p, target.path)) |pv| {
+ if (!pv.hitExists(p.pdf_gpa, @max(1, target.at.line), target.at.col)) return missedHit(p, id, target.path, @max(1, target.at.line), target.at.col);
+ } else return missedHit(p, id, target.path, @max(1, target.at.line), target.at.col);
if (focusPaneByPath(p, target.path, target.at)) return;
const free = p.freeSlot() orelse return p.reportError(id, "look", error.NoPaneSlots);
// Said with its path and why, as an image that cannot be read
diff --git a/src/pdf_view.zig b/src/pdf_view.zig
index b307d1f3..d5769ad3 100644
--- a/src/pdf_view.zig
+++ b/src/pdf_view.zig
@@ -547,6 +547,16 @@ pub const State = if (enabled) struct {
state.search_results = results;
}
+ /// Whether page `page` (one-based) holds search hit `hit` (one-based) of
+ /// the pane's search: none asked, no hit is there.
+ pub fn hitExists(state: *@This(), gpa: std.mem.Allocator, page: usize, hit: usize) bool {
+ if (hit == 0) return true;
+ if (state.search_query.len == 0 or page == 0 or page > state.page_count) return false;
+ var results = state.document.search(gpa, page - 1, state.search_query) catch return false;
+ defer results.deinit(gpa);
+ return hit <= results.hit_count;
+ }
+
pub fn ensureOutline(state: *@This(), gpa: std.mem.Allocator) ?*const pdf.Outline {
if (!state.outline_tried) {
state.outline_tried = true;
diff --git a/test/pdf.zig b/test/pdf.zig
index a1be4075..00d678d4 100644
--- a/test/pdf.zig
+++ b/test/pdf.zig
@@ -492,12 +492,16 @@ test "`:N` on a PDF turns to page N, from the pane and from the root's look, and
try std.testing.expect(std.mem.indexOf(u8, record, "\nerr ") == null);
_ = p.serveFs(.{ .tag = 4, .op = .release, .node = log, .handle = h });
while (p.nextEffect()) |_| {}
- // `:N:H` on the pane is its page N, as `file.pdf:N:H` is.
+ // `:N:H` on the pane is hit H of its search on page N, as `file.pdf:N:H`
+ // (a search row) is: with no search there is no hit 3, a miss that
+ // turns no page.
p.look_missed = false;
pardes.look.lookAt(p, 0, ":1");
pardes.look.lookAt(p, 0, ":2:3");
- try std.testing.expect(!p.look_missed);
- try std.testing.expectEqual(@as(usize, 1), doc.pdf.?.page);
+ try std.testing.expect(p.look_missed);
+ try std.testing.expect(std.mem.indexOf(u8, doc.msg[0..doc.msg_len], "has no search hit 3 on page 2") != null);
+ try std.testing.expectEqual(@as(usize, 0), doc.pdf.?.page);
+ pardes.look.lookAt(p, 0, ":2");
// Page 0 is no page: the has-no-page miss.
p.look_missed = false;
pardes.look.lookAt(p, 0, ":0");