diff options
| -rw-r--r-- | src/dump.zig | 2 | ||||
| -rw-r--r-- | src/look.zig | 16 | ||||
| -rw-r--r-- | src/pardes.zig | 2 | ||||
| -rw-r--r-- | src/pdf_view.zig | 4 | ||||
| -rw-r--r-- | test/mode.zig | 2 | ||||
| -rw-r--r-- | test/pdf.zig | 36 |
6 files changed, 56 insertions, 6 deletions
diff --git a/src/dump.zig b/src/dump.zig index 8e767240..4194f628 100644 --- a/src/dump.zig +++ b/src/dump.zig @@ -918,7 +918,7 @@ fn initDump(gpa: std.mem.Allocator, opts: Options, zon_bytes: []const u8, previo &.{}; errdefer if (raw.len > 0) gpa.free(raw); if (comptime pdf_enabled) { - if (pardes.panes.Pdf.openPane(p, i, im.path, src.scroll + 1) catch null) |restored| { + if (pardes.panes.Pdf.openPane(p, i, im.path, src.scroll + 1, .last) catch null) |restored| { if (raw.len > 0) gpa.free(raw); raw = &.{}; restored.cols = @max(1, src.cols); diff --git a/src/look.zig b/src/look.zig index 454074c8..2e8a4b39 100644 --- a/src/look.zig +++ b/src/look.zig @@ -1238,6 +1238,11 @@ test "a miss names a long path by its start and its end, its middle given up to try std.testing.expectEqualStrings("short/path.txt", missText("short/path.txt", &cut)); } +fn missedPage(p: *Pardes, id: usize, path: []const u8, page: usize) void { + var cut: [miss_path_cap + "…".len]u8 = undefined; + missed(p, id, "{s} has no page {d}", .{ missText(path, &cut), page }); +} + 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 }); @@ -1688,6 +1693,11 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void { // `@p<serial>` names no open pane: a miss, said. if (t.id >= MAX_PANES or p.panes[t.id] == null) return missed(p, id, "no pane {s}", .{pl.path[0..@min(pl.path.len, 32)]}); const target = p.panes[t.id].?; + // 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); const before = p.active; clearNavigationSelection(p, target); focusPaneLine(p, t.id, t.at, .center); @@ -1718,8 +1728,10 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void { if (comptime pdf_enabled) if (target.kind == .pdf) { 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 is. - _ = panes.Pdf.openPane(p, free, target.path, target.at.line) catch |err| { + // Said with its path and why, as an image that cannot be read + // is; a page past its last is a miss, as for an open one. + _ = panes.Pdf.openPane(p, free, target.path, target.at.line, .refuse) catch |err| { + if (err == error.NoSuchPage) return missedPage(p, id, target.path, target.at.line); var said: [miss_path_cap + 64]u8 = undefined; var cut: [miss_path_cap + "…".len]u8 = undefined; return p.reportError(id, std.fmt.bufPrint(&said, "look: {s}: not a PDF pardes can read", .{missText(target.path, &cut)}) catch "look: not a PDF pardes can read", err); diff --git a/src/pardes.zig b/src/pardes.zig index c8fffd80..bea79b18 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -4574,7 +4574,7 @@ pub const Pardes = struct { const path = opts.file.?; const opened = initial_doc: { if (comptime pdf_enabled) if (look.isPdfPath(path)) - break :initial_doc panes.Pdf.openPane(p, 0, path, opts.file_line); + break :initial_doc panes.Pdf.openPane(p, 0, path, opts.file_line, .last); if (look.isImagePath(path)) break :initial_doc panes.Image.create(p, 0, path, &.{}); break :initial_doc panes.File.open(p, 0, path, opts.file_line); diff --git a/src/pdf_view.zig b/src/pdf_view.zig index 5646a92f..e435d777 100644 --- a/src/pdf_view.zig +++ b/src/pdf_view.zig @@ -1877,6 +1877,9 @@ pub fn openPane( id: usize, path: []const u8, page_one_based: usize, + /// A page past the last: the last page (a restore, a start-up), or + /// refused with error.NoSuchPage (a look, which names a place). + past_end: enum { last, refuse }, ) !*pardes.Pane { if (comptime !enabled) return error.PdfDisabled; // The slot stays ours across the read: another request may make a @@ -1891,6 +1894,7 @@ pub fn openPane( break :virtual try State.openBytes(core.pdf_gpa, path, bytes, page_one_based); }; errdefer state.deinit(core.pdf_gpa); + if (past_end == .refuse and page_one_based > state.page_count) return error.NoSuchPage; const pane = try core.newDocPane(id); pane.pdf = state; pane.body.cur_pinned = true; diff --git a/test/mode.zig b/test/mode.zig index 767cda93..f49c434c 100644 --- a/test/mode.zig +++ b/test/mode.zig @@ -109,7 +109,7 @@ test "Mode leaves image and PDF document modes unchanged" { const len = try tmp.dir.realPath(std.testing.io, &buffer); const path = try std.fmt.allocPrint(std.testing.allocator, "{s}/mode.pdf", .{buffer[0..len]}); defer std.testing.allocator.free(path); - const document = try panes.Pdf.openPane(p, 2, path, 1); + const document = try panes.Pdf.openPane(p, 2, path, 1, .last); const page = document.pdf.?.page; try mode(p, 2, .normal); try std.testing.expectEqual(page, document.pdf.?.page); diff --git a/test/pdf.zig b/test/pdf.zig index 48869de1..36b3570e 100644 --- a/test/pdf.zig +++ b/test/pdf.zig @@ -343,7 +343,7 @@ test "PdfSections Look follows the exact owning PDF, not an equal path" { try std.testing.expectEqual(first_revision, first_output.file.?.revision); const duplicate_id = p.freeSlot() orelse return error.NoDuplicatePdfSlot; - const duplicate = try panes.Pdf.openPane(p, duplicate_id, path, 0); + const duplicate = try panes.Pdf.openPane(p, duplicate_id, path, 0, .last); pardes.exec.placeNew(p, 0, 0, duplicate_id, .doc); layout.compute(p); panes.Pdf.openSections(p, duplicate_id); @@ -386,6 +386,40 @@ test "PdfSections caches an empty outline output" { try std.testing.expectEqual(serial, p.panes[output_id].?.serial); } +test "a look at a PDF's page past its last is a miss, never the last page" { + if (!pdf_enabled or platform == .web) return; + const gpa = std.testing.allocator; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const fixture = try pdf_impl.makeNoOutlineTestPdf(gpa); + defer gpa.free(fixture); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "plain.pdf", .data = fixture }); + try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "other.pdf", .data = fixture }); + var dir_buf: [4096]u8 = undefined; + const dir = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)]; + var path_buf: [4200]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, "{s}/plain.pdf", .{dir}); + const p = try Pardes.init(gpa, .{ .file = path, .cols = 80, .rows = 28 }); + defer p.deinit(); + const doc = p.panes[0].?; + try std.testing.expect(doc.hasPdf()); + var line: [4300]u8 = undefined; + // Open: it stays on its page. + const page = doc.pdf.?.page; + pardes.look.lookAt(p, 0, try std.fmt.bufPrint(&line, "{s}:999", .{path})); + try std.testing.expect(p.look_missed); + try std.testing.expectEqual(page, doc.pdf.?.page); + // Not open: nothing opens. + p.look_missed = false; + var before: usize = 0; + for (p.panes) |slot| before += @intFromBool(slot != null); + pardes.look.lookAt(p, 0, try std.fmt.bufPrint(&line, "{s}/other.pdf:999", .{dir})); + try std.testing.expect(p.look_missed); + var after: usize = 0; + for (p.panes) |slot| after += @intFromBool(slot != null); + try std.testing.expectEqual(before, after); +} + test "SDL PDF raster policy is materially denser than Kitty" { try std.testing.expect( pardes.sdl_pdf_raster_policy.dpi >= pardes.kitty_pdf_raster_policy.dpi * 2, |
