summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/look.zig23
-rw-r--r--src/ninep/ctl.zig10
-rw-r--r--test/pdf.zig45
3 files changed, 77 insertions, 1 deletions
diff --git a/src/look.zig b/src/look.zig
index 3cd1faa5..4d23582b 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -1413,9 +1413,13 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void {
}
const pane = p.panes[target] orelse return;
const f = pane.file orelse {
+ if (comptime pdf_enabled) if (pane.pdf) |*pv| return lookPdfPage(p, id, target, pv, expr);
if (by_serial and pane.isTerminal()) return lookTerminalAddress(p, id, target, expr);
+ // Named as the pane is, the bare `:addr` (path empty) included.
+ var serial_name: [32]u8 = undefined;
+ const subject = if (pane.image) |iv| iv.path else if (path.len > 0) path else std.fmt.bufPrint(&serial_name, config.pane_addr ++ "{d}", .{pane.serial}) catch "the pane";
var cut: [miss_path_cap + "…".len]u8 = undefined;
- return missed(p, id, "{s} is no text to address", .{missText(path, &cut)});
+ return missed(p, id, "{s} is no text to address", .{missText(subject, &cut)});
};
if (!by_serial and path.len > 0 and (f.output != null or !std.mem.endsWith(u8, f.path, std.fs.path.basename(path)))) return;
var a: Addr = .{ .text = f.content, .lim = null, .expr = expr };
@@ -1433,6 +1437,20 @@ fn lookAddress(p: *Pardes, id: usize, path: []const u8, expr: []const u8) void {
p.active = target;
}
+/// A PDF's address is its page: `:N` turns to page N, one past its last a
+/// miss. It has no text to hold a byte offset (`#N`) or a pattern: any
+/// other address is refused with what it takes.
+fn lookPdfPage(p: *Pardes, id: usize, target: usize, pv: anytype, expr: []const u8) void {
+ var cut: [miss_path_cap + "…".len]u8 = undefined;
+ const page = std.fmt.parseInt(usize, std.mem.trim(u8, expr, " \t"), 10) catch 0;
+ if (page == 0) return missed(p, id, "{s}: a PDF has no text to address; its address is a page, :N", .{missText(pv.path, &cut)});
+ if (page > pv.page_count) return missedPage(p, id, pv.path, page);
+ const pane = p.panes[target].?;
+ clearNavigationSelection(p, pane);
+ if (pv.focusLocation(p.pdf_gpa, page, 0)) panes.Pdf.resetPageChrome(pane);
+ p.active = target;
+}
+
/// `@p<terminal>:<address>`: any address over the terminal's body as its
/// logical lines read (the lines a +Search lists as `@p<serial>:<line>`),
/// from its cursor; the match is selected there, as a +Search row's is.
@@ -1599,6 +1617,9 @@ pub fn lookAt(p: *Pardes, id: usize, operand_raw: []const u8) void {
// acme's `file:addr` and `:addr` (look.c:450-543): an address after
// the colon, evaluated in that file from its dot (openfile, look.c:675).
if (addressed(trimmed)) |ad| return lookAddress(p, id, ad.path, ad.expr);
+ // `:N` on a PDF is its page N, never a search for the text.
+ if (comptime pdf_enabled) if (pane.hasPdf() and trimmed.len > 1 and trimmed[0] == ':')
+ return lookAddress(p, id, "", trimmed[1..]);
const pl = parsePathLine(trimmed);
// `file:3:0`: columns count from 1, as addr's do, and a zero is refused
// in addr's words rather than taken as no column.
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 4aa38292..a14b1efb 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1671,6 +1671,16 @@ test "Save path of a file pane logs a save record naming that path" {
try testing.expect(th.logHas(p, try std.fmt.bufPrint(&want, "save {d} /tmp/pardes-save-copy.txt\n", .{serial})));
}
+test "an address looked at in an image pane names the image, never an empty subject" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const id = p.freeSlot().?;
+ const doc = try p.newDocPane(id);
+ doc.image = .{ .path = try p.gpa.dupe(u8, "/tmp/pardes-pic.ppm") };
+ _ = wr(p, Node.of(doc.serial, .look), ":/x/\n");
+ try testing.expect(th.logHas(p, "look: /tmp/pardes-pic.ppm is no text to address"));
+}
+
test "Save and Save path on an image pane are refused with why, never a silent success" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
diff --git a/test/pdf.zig b/test/pdf.zig
index fbd580b3..fe084cc1 100644
--- a/test/pdf.zig
+++ b/test/pdf.zig
@@ -407,6 +407,51 @@ test "Recent keeps a PDF as it keeps a file: open, then closed at its page" {
try std.testing.expect(std.mem.indexOf(u8, out.written(), try std.fmt.bufPrint(&want, "{s}:1:1 open\n", .{path})) != null);
}
+test "`:N` on a PDF turns to page N, from the pane and from the root's look, and `@pN:N` logs no err" {
+ 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.makeOutlineTestPdf(gpa);
+ defer gpa.free(fixture);
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "outline.pdf", .data = fixture });
+ var path_buf: [256]u8 = undefined;
+ const path = try std.fmt.bufPrint(&path_buf, ".zig-cache/tmp/{s}/outline.pdf", .{tmp.sub_path});
+ const p = try Pardes.init(gpa, .{ .file = path, .cols = 80, .rows = 28 });
+ defer p.deinit();
+ const doc = p.panes[0].?;
+ const pages = doc.pdf.?.page_count;
+ try std.testing.expect(pages >= 2);
+ const failures = p.fs.failures;
+ pardes.look.lookAt(p, 0, ":2");
+ try std.testing.expect(!p.look_missed);
+ try std.testing.expectEqual(@as(usize, 1), doc.pdf.?.page);
+ var line: [64]u8 = undefined;
+ pardes.look.lookAt(p, 0, try std.fmt.bufPrint(&line, "@p{d}:1", .{doc.serial}));
+ try std.testing.expect(!p.look_missed);
+ try std.testing.expectEqual(@as(usize, 0), doc.pdf.?.page);
+ try std.testing.expectEqual(failures, p.fs.failures);
+ // Over 9P too: the root's look at `@pN:2` answers ok and logs no err.
+ const ctlfs = pardes.ctlfs;
+ const written = p.serveFs(.{ .tag = 1, .op = .write, .node = @intFromEnum(ctlfs.TopFile.look), .data = try std.fmt.bufPrint(&line, "@p{d}:2\n", .{doc.serial}) });
+ try std.testing.expectEqual(ctlfs.Status.ok, written.status);
+ try std.testing.expectEqual(@as(usize, 1), doc.pdf.?.page);
+ const log = @intFromEnum(ctlfs.TopFile.log);
+ const h = p.serveFs(.{ .tag = 2, .op = .open, .node = log }).handle;
+ const record = p.fsPayload(p.serveFs(.{ .tag = 3, .op = .read, .node = log, .handle = h, .size = 1 << 16 }));
+ try std.testing.expect(std.mem.indexOf(u8, record, "\nerr ") == null);
+ _ = p.serveFs(.{ .tag = 4, .op = .release, .node = log, .handle = h });
+ while (p.nextEffect()) |_| {}
+ // A byte offset or a pattern is no PDF address: refused with words.
+ pardes.look.lookAt(p, 0, ":#3");
+ try std.testing.expect(p.look_missed);
+ try std.testing.expect(std.mem.indexOf(u8, doc.msg[0..doc.msg_len], "its address is a page, :N") != null);
+ p.look_missed = false;
+ pardes.look.lookAt(p, 0, try std.fmt.bufPrint(&line, ":{d}", .{pages + 1}));
+ try std.testing.expect(p.look_missed);
+ try std.testing.expectEqual(@as(usize, 1), doc.pdf.?.page); // where the 9P look left it
+}
+
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;