From b7eb31674e01c992cafc91518e161258b921bd33 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 08:26:42 -0300 Subject: A pane's ctl read says Collapse and a PDF's PdfFit and PdfTint (`collapsed`, `fit:height tint:full`), and a Dump keeps the PDF's two Neither showed in the tag or the ctl, and a Restore brought a PDF back at width fit and the default tint. The ctl read ends with `collapsed` when it is, and a PDF's `fit:` and `tint:` in the words PdfFit and PdfTint take; the dump's image record carries pdf_fit and pdf_tint (empty in older dumps: the defaults). Co-Authored-By: Claude Opus 5.5 --- src/dump.zig | 8 ++++++++ src/image.zig | 2 ++ src/ninep/ctl.zig | 16 +++++++++++++++- test/pdf.zig | 36 +++++++++++++++++++++++++++++++----- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/dump.zig b/src/dump.zig index 1420ea8f..84321457 100644 --- a/src/dump.zig +++ b/src/dump.zig @@ -247,6 +247,10 @@ pub const Image = struct { petscii: bool = false, palette: ImagePalette = .commodore, ascii: bool = true, + /// A PDF's PdfFit and PdfTint, by their tag words (`width`, `filtered`); + /// empty, the defaults, as in dumps from before. + pdf_fit: []const u8 = "", + pdf_tint: []const u8 = "", }; pub const Pane = struct { @@ -961,6 +965,10 @@ fn initDump(gpa: std.mem.Allocator, opts: Options, zon_bytes: []const u8, previo if (pardes.panes.Pdf.openPane(p, i, im.path, src.scroll + 1, .last) catch null) |restored| { if (raw.len > 0) gpa.free(raw); raw = &.{}; + // Its fit and tint as they were (PdfFit, PdfTint). + const pv = &restored.pdf.?; + if (std.meta.stringToEnum(@TypeOf(pv.fit), im.pdf_fit)) |fit| if (fit != pv.fit) pardes.panes.Pdf.toggleFit(restored); + if (std.meta.stringToEnum(@TypeOf(pv.tint), im.pdf_tint)) |tint| while (pv.tint != tint) pardes.panes.Pdf.toggleTint(restored); restored.cols = @max(1, src.cols); restored.rows = @max(1, src.rows); break :restore_image restored; diff --git a/src/image.zig b/src/image.zig index 3a3f1932..9f635d1c 100644 --- a/src/image.zig +++ b/src/image.zig @@ -875,6 +875,8 @@ pub fn dumpPane( .terminal => .terminal, } else .commodore, .ascii = if (pane.image) |state| state.ascii else true, + .pdf_fit = if (comptime pardes.pdf_enabled) (if (pane.pdf) |pv| @tagName(pv.fit) else "") else "", + .pdf_tint = if (comptime pardes.pdf_enabled) (if (pane.pdf) |pv| @tagName(pv.tint) else "") else "", }, }; } diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index c5e47d5d..6c9ee2da 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -382,7 +382,7 @@ fn writeQuoted(w: *std.Io.Writer, s: []const u8) !void { try w.writeByte('\''); } -pub const pane_text_cap = 9 * 12 + 2 * 256 + 4 + "notcurrent ".len + 48; +pub const pane_text_cap = 9 * 12 + 2 * 256 + 4 + "notcurrent ".len + 48 + "collapsed fit:height tint:filtered ".len; /// acme's winctlprint (plan9port wind.c), field for field: serial, tag /// length, body length, isdir (0), the dirty flag, the width in cells, the @@ -412,6 +412,10 @@ pub fn paneText(p: *Pardes, pane: *Pane, buf: []u8) []const u8 { var idbuf: [48]u8 = undefined; const id = exec_line.replId(&idbuf, pane); if (id.len > 0) w.print("{s} ", .{id}) catch {}; + // Then what its tag does not say: Collapse, and a PDF's PdfFit and + // PdfTint, in the words they take. + if (pane.collapsed) w.writeAll("collapsed ") catch {}; + if (comptime pardes.pdf_enabled) if (pane.pdf) |pv| w.print("fit:{s} tint:{s} ", .{ @tagName(pv.fit), @tagName(pv.tint) }) catch {}; return w.buffered(); } @@ -4309,3 +4313,13 @@ test "Lspinfo's answer is the +Lsp pane it fills, as a Rename preview's is its + const shown = p.paneBySerial(p.fs.lsp_result.?).?; try testing.expect(std.mem.endsWith(u8, p.panes[shown].?.file.?.path, "+Lsp")); } + +test "a collapsed pane's ctl says so" { + const p = try withFile(testing.allocator, "x\n"); + defer p.deinit(); + _ = try th.newPane(p); + const id = p.paneBySerial(serialOf(p)).?; + layout.toggleCollapse(p, id); + try testing.expect(p.panes[id].?.collapsed); + try testing.expect(std.mem.endsWith(u8, rd(p, Node.of(serialOf(p), .ctl), 0, 4096).bytes, " collapsed ")); +} diff --git a/test/pdf.zig b/test/pdf.zig index 00d678d4..a0eb8154 100644 --- a/test/pdf.zig +++ b/test/pdf.zig @@ -1081,9 +1081,8 @@ test "MuPDF pane renders, navigates, searches, and round-trips its page" { panes.Pdf.setPage(p, pane, next_page); try std.testing.expectEqualStrings(long_query, pane.pdf.?.search_query); - // PDF dumps intentionally retain the existing image-compatible schema: - // page/path and the exact editable tail are restored, while pane-local - // tint starts from the fresh-PDF default rather than being serialized. + // PDF dumps keep the image-compatible schema: page/path, the exact + // editable tail, and the pane's fit and tint (pdf_fit, pdf_tint). panes.Pdf.toggleTint(pane); try std.testing.expectEqual(PdfTintMode.full, pane.pdf.?.tint); pane.pdf.?.fit = .height; @@ -1095,8 +1094,8 @@ test "MuPDF pane renders, navigates, searches, and round-trips its page" { defer restored.deinit(); try std.testing.expect(hasPdf(restored.panes[0].?)); try std.testing.expectEqual(pane.pdf.?.page, restored.panes[0].?.pdf.?.page); - try std.testing.expectEqual(PdfFitMode.width, restored.panes[0].?.pdf.?.fit); - try std.testing.expectEqual(PdfTintMode.filtered, restored.panes[0].?.pdf.?.tint); + try std.testing.expectEqual(PdfFitMode.height, restored.panes[0].?.pdf.?.fit); + try std.testing.expectEqual(PdfTintMode.full, restored.panes[0].?.pdf.?.tint); const restored_pane = restored.panes[0].?; try std.testing.expect(restored_pane.tag.own != null); try std.testing.expectEqualStrings( @@ -2416,3 +2415,30 @@ test "PDF Back and Forward clear selections and land on recorded pages" { try std.testing.expect(state.drag_anchor == null and state.drag_head == null); } } + +test "a PDF's fit and tint, and Collapse, read in its ctl and come back from a Dump" { + 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].?; + try std.testing.expect(p.executeBuiltinLine(0, "PdfFit height")); + try std.testing.expect(p.executeBuiltinLine(0, "PdfTint full")); + const ctlfs = pardes.ctlfs; + const ctl_node = ctlfs.Node.of(doc.serial, .ctl); + const read = p.fsPayload(p.serveFs(.{ .tag = 1, .op = .read, .node = ctl_node, .size = 4096 })); + try std.testing.expect(std.mem.endsWith(u8, read, "fit:height tint:full ")); + try pardes.dump.dumpState(p); + const restored = try pardes.dump.restore(p, p.dump_out.?, "/tmp/pardes.dump.zon"); + defer restored.deinit(); + const pv = &restored.panes[0].?.pdf.?; + try std.testing.expectEqualStrings("height", @tagName(pv.fit)); + try std.testing.expectEqualStrings("full", @tagName(pv.tint)); +} -- cgit v1.3