From 5961587b227e5fa097fb033e32c29da08a23fb18 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 2 Aug 2026 17:17:35 -0300 Subject: tty/image: big harness + golden coverage pass --- test/image_harness.zig | 356 +++++++++++++++++++++++++++++++++++++++-- test/snapshots/builtins.golden | 40 ++--- test/snapshots/leader.golden | 4 +- test/snapshots/lspdebug.golden | 12 +- 4 files changed, 374 insertions(+), 38 deletions(-) (limited to 'test') diff --git a/test/image_harness.zig b/test/image_harness.zig index 5ec192c2..390c14e8 100644 --- a/test/image_harness.zig +++ b/test/image_harness.zig @@ -4,6 +4,8 @@ //! real transmit + placement APCs //! zig build image-harness -Dplatform=gui run the real SDL GPU renderer, //! capture a PPM, inspect pixels +//! zig build pdf-harness render a generated two-page PDF +//! zig build pdf-harness -Dplatform=gui //! //! Unlike the text snapshot suite, both arms observe the thing the native //! backend actually emits. The TTY arm completes vaxis's asynchronous @@ -20,16 +22,20 @@ extern "c" fn unsetenv(name: [*:0]const u8) c_int; const gpa = std.heap.page_allocator; const fixture_rgb = [3]u8{ 13, 77, 231 }; +const pdf_red = [3]u8{ 255, 0, 0 }; +const pdf_green = [3]u8{ 0, 255, 0 }; pub fn main(init: std.process.Init) !void { var arena_state: std.heap.ArenaAllocator = .init(gpa); defer arena_state.deinit(); const arena = arena_state.allocator(); const args = try init.minimal.args.toSlice(arena); - if (args.len != 3) return error.BadArgs; + if (args.len != 3 and args.len != 4) return error.BadArgs; const exe = try arena.dupeZ(u8, args[1]); const backend = args[2]; + const pdf_mode = args.len == 4 and std.mem.eql(u8, args[3], "pdf"); + if (args.len == 4 and !pdf_mode) return error.BadArgs; const base = try std.fmt.allocPrintSentinel(arena, "/tmp/pardes-image-harness-{d}", .{libc.getpid()}, 0); if (libc.mkdir(base, 0o755) != 0 and libc.errno(-1) != .EXIST) return error.MkdirFailed; defer cleanupBase(arena, base); @@ -39,14 +45,20 @@ pub fn main(init: std.process.Init) !void { _ = setenv("LC_ALL", "C", 1); if (std.mem.eql(u8, backend, "tty")) { - try runKitty(arena, exe, base); + if (pdf_mode) + try runKittyPdf(arena, exe, base) + else + try runKitty(arena, exe, base); } else if (std.mem.eql(u8, backend, "gui")) { - try runGui(arena, exe, base); + if (pdf_mode) + try runGuiPdf(arena, exe, base) + else + try runGui(arena, exe, base); } else return error.BadArgs; } fn cleanupBase(arena: std.mem.Allocator, base: [:0]const u8) void { - const names = [_][]const u8{ "native.ppm", "latest.ppm", "latest.ppm.tmp", ".bash_history" }; + const names = [_][]const u8{ "native.ppm", "pages.pdf", "latest.ppm", "latest.ppm.tmp", ".bash_history" }; for (names) |name| { const path = std.fmt.allocPrintSentinel(arena, "{s}/{s}", .{ base, name }, 0) catch continue; _ = libc.unlink(path); @@ -54,6 +66,65 @@ fn cleanupBase(arena: std.mem.Allocator, base: [:0]const u8) void { _ = libc.rmdir(base); } +fn appendFmt(arena: std.mem.Allocator, out: *std.ArrayList(u8), comptime fmt: []const u8, args: anytype) !void { + try out.appendSlice(arena, try std.fmt.allocPrint(arena, fmt, args)); +} + +/// A self-contained, valid PDF fixture. Page one is solid red and contains +/// FIRST NEEDLE; page two is solid green and contains SECOND TARGET. Building +/// it here keeps the backend test independent of mutool, fonts, and checked-in +/// binary fixtures while the xref offsets remain exact. +fn writePdf(arena: std.mem.Allocator, path: [:0]const u8) !void { + const first_stream = + \\1 0 0 rg + \\0 0 72 48 re + \\f + \\0 0 0 rg + \\BT + \\/F1 10 Tf + \\4 20 Td + \\(FIRST NEEDLE) Tj + \\ET + ; + const second_stream = + \\0 1 0 rg + \\0 0 72 48 re + \\f + \\0 0 0 rg + \\BT + \\/F1 10 Tf + \\4 20 Td + \\(SECOND TARGET) Tj + \\ET + ; + + var out: std.ArrayList(u8) = .empty; + var offsets: [8]usize = @splat(0); + try out.appendSlice(arena, "%PDF-1.4\n%\xE2\xE3\xCF\xD3\n"); + + offsets[1] = out.items.len; + try out.appendSlice(arena, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"); + offsets[2] = out.items.len; + try out.appendSlice(arena, "2 0 obj\n<< /Type /Pages /Count 2 /Kids [3 0 R 5 0 R] >>\nendobj\n"); + offsets[3] = out.items.len; + try out.appendSlice(arena, "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 72 48] /Resources << /Font << /F1 7 0 R >> >> /Contents 4 0 R >>\nendobj\n"); + offsets[4] = out.items.len; + try appendFmt(arena, &out, "4 0 obj\n<< /Length {d} >>\nstream\n{s}endstream\nendobj\n", .{ first_stream.len, first_stream }); + offsets[5] = out.items.len; + try out.appendSlice(arena, "5 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 72 48] /Resources << /Font << /F1 7 0 R >> >> /Contents 6 0 R >>\nendobj\n"); + offsets[6] = out.items.len; + try appendFmt(arena, &out, "6 0 obj\n<< /Length {d} >>\nstream\n{s}endstream\nendobj\n", .{ second_stream.len, second_stream }); + offsets[7] = out.items.len; + try out.appendSlice(arena, "7 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n"); + + const xref = out.items.len; + try out.appendSlice(arena, "xref\n0 8\n0000000000 65535 f \n"); + for (offsets[1..]) |offset| + try appendFmt(arena, &out, "{d:0>10} 00000 n \n", .{offset}); + try appendFmt(arena, &out, "trailer\n<< /Size 8 /Root 1 0 R >>\nstartxref\n{d}\n%%EOF\n", .{xref}); + try eh.writeFile(path, out.items); +} + fn writePpm(arena: std.mem.Allocator, path: [:0]const u8, width: usize, height: usize) !void { var out: std.ArrayList(u8) = .empty; try out.appendSlice(arena, try std.fmt.allocPrint(arena, "P6\n{d} {d}\n255\n", .{ width, height })); @@ -104,6 +175,148 @@ fn runKitty(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !vo std.debug.print("native image harness ok: kitty transmit + placement\n", .{}); } +fn runKittyPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !void { + _ = unsetenv("PARDES_TEST"); + _ = unsetenv("PARDES_TEST_CAPTURE_DIR"); + const pdf_path = try std.fmt.allocPrintSentinel(arena, "{s}/pages.pdf", .{base}, 0); + try writePdf(arena, pdf_path); + + var h = try eh.Harness.initArgs(gpa, exe, 16, 80, pdf_path); + defer h.deinit(); + try h.pump(150); + try h.expectRawContains("\x1b_Gi=1,a=q\x1b\\", "app did not issue the Kitty graphics capability query"); + + try h.send("\x1b_Gi=1;OK\x1b\\\x1b[?1;2c\x1b[A"); + const transmit = "\x1b_Gf=32,s=144,v=96,i="; + const first_deadline = eh.nowMs() + 5000; + while (eh.nowMs() < first_deadline) { + _ = try h.pumpOnce(50); + const sent = std.mem.indexOf(u8, h.raw.items, transmit) != null; + const placed = std.mem.indexOf(u8, h.raw.items, "\x1b_Ga=p,i=") != null; + if (sent and placed and h.contains("pdf 1/2 width PdfFit")) break; + } + try h.expectRawContains(transmit, "first PDF page was not transmitted as 144x96 RGBA"); + try h.expectRawContains("\x1b_Ga=p,i=", "first PDF page was not placed in the pane body"); + try h.expectContains("pdf 1/2 width PdfFit", "PDF pane did not expose its initial width fit"); + + // Fit-width is the initial PDF policy. It must use Kitty's explicit column + // scaling rather than Vaxis contain. The PDF shares this test session with + // the initial shell pane, making its body slightly narrower than the page: + // the full source is retained and vertically letterboxed by a pixel offset. + const width_apc = kittyPlacementApc(h.raw.items, 0) orelse return error.KittyPdfWidthPlacementMissing; + if (kittyParam(width_apc, 'x') != 0 or kittyParam(width_apc, 'y') != 0 or + kittyParam(width_apc, 'w') != 144) + { + std.debug.print("unexpected Kitty fit-width placement: {s}\n", .{width_apc}); + return error.KittyPdfWidthCropWrong; + } + const source_h = kittyParam(width_apc, 'h') orelse return error.KittyPdfWidthCropMissing; + const pixel_y = kittyParam(width_apc, 'Y') orelse return error.KittyPdfWidthLetterboxMissing; + if (source_h != 96 or pixel_y == 0 or kittyParam(width_apc, 'c') == null) { + std.debug.print("unexpected Kitty fit-width letterbox: {s}\n", .{width_apc}); + return error.KittyPdfWidthLetterboxWrong; + } + + // Placement-only state reuses the terminal image. Middle-click the + // visible PdfFit word in the PDF tag, wait for a fit-height placement + // with explicit rows and a full source image, and prove that no second + // transmit happened. + const transmit_before_fit = std.mem.count(u8, h.raw.items, transmit); + const fit_raw_start = h.raw.items.len; + const tag = try h.screenText(); + defer gpa.free(tag); + const fit_at = std.mem.indexOf(u8, tag, "PdfFit") orelse + return error.PdfFitMissing; + const line_at = std.mem.lastIndexOfScalar(u8, tag[0..fit_at], '\n'); + const mouse_col = fit_at - if (line_at) |at| at + 1 else 0; + const mouse_row = std.mem.count(u8, tag[0..fit_at], "\n"); + const click = try std.fmt.allocPrint( + arena, + "\x1b[<1;{d};{d}M\x1b[<1;{d};{d}m", + .{ mouse_col + 1, mouse_row + 1, mouse_col + 1, mouse_row + 1 }, + ); + try h.send(click); + const fit_deadline = eh.nowMs() + 3000; + while (eh.nowMs() < fit_deadline) { + _ = try h.pumpOnce(50); + if (kittyPlacementApcWithParam(h.raw.items, fit_raw_start, 'r') != null) break; + } + try h.expectContains("pdf 1/2 height PdfFit", "PdfFit did not expose the active height fit"); + const height_apc = kittyPlacementApcWithParam(h.raw.items, fit_raw_start, 'r') orelse + return error.KittyPdfHeightPlacementMissing; + const height_crop_w = kittyParam(height_apc, 'w') orelse return error.KittyPdfHeightPlacementWrong; + if (kittyParam(height_apc, 'x') != 0 or kittyParam(height_apc, 'y') != 0 or + height_crop_w == 0 or height_crop_w >= 144 or kittyParam(height_apc, 'h') != 96 or + kittyParam(height_apc, 'r') == null) + return error.KittyPdfHeightPlacementWrong; + if (std.mem.count(u8, h.raw.items, transmit) != transmit_before_fit) + return error.KittyPdfFitRetransmitted; + + // Same pane, different pixels: page navigation must delete the cached + // Kitty image and transmit the new revision, not keep showing page one. + try h.send("j"); + const second_deadline = eh.nowMs() + 5000; + while (eh.nowMs() < second_deadline) { + _ = try h.pumpOnce(50); + const deleted = std.mem.indexOf(u8, h.raw.items, "\x1b_Ga=d,d=I,i=") != null; + if (deleted and std.mem.count(u8, h.raw.items, transmit) >= 2 and h.contains("pdf 2/2 height PdfFit")) break; + } + try h.expectRawContains("\x1b_Ga=d,d=I,i=", "page navigation did not release the old Kitty PDF image"); + if (std.mem.count(u8, h.raw.items, transmit) < 2) return error.KittyPdfRetransmitMissing; + try h.expectContains("pdf 2/2 height PdfFit", "j did not retain height fit on PDF page two"); + + // Exercise the semantic seam too: return to page one, search an exact + // occurrence across the document, then let n Look the page-two result. + // That Look must change the page and therefore produce another pixel + // generation. + try h.send("k"); + try h.expectWaitFor("pdf 1/2 height PdfFit", 5000, "k did not return to PDF page one at height fit"); + try h.send("/SECOND\r"); + try h.expectWaitFor("pages.pdf:2:1 SECOND", 5000, "PDF search did not preserve the exact page-two query occurrence"); + const before_look = std.mem.count(u8, h.raw.items, transmit); + try h.send("n"); + const look_deadline = eh.nowMs() + 5000; + while (eh.nowMs() < look_deadline) { + _ = try h.pumpOnce(50); + if (std.mem.count(u8, h.raw.items, transmit) > before_look and h.contains("pdf 2/2 height PdfFit")) break; + } + try h.expectContains("pdf 2/2 height PdfFit", "n did not Look the PDF search result at page two"); + if (std.mem.count(u8, h.raw.items, transmit) <= before_look) return error.KittyPdfLookRetransmitMissing; + std.debug.print("PDF harness ok: Kitty fit crop/cache + page revision + search Look\n", .{}); +} + +fn kittyPlacementApc(raw: []const u8, start: usize) ?[]const u8 { + if (start > raw.len) return null; + const relative = std.mem.indexOf(u8, raw[start..], "\x1b_Ga=p,i=") orelse return null; + const begin = start + relative; + const end = std.mem.indexOf(u8, raw[begin..], "\x1b\\") orelse return null; + return raw[begin .. begin + end + 2]; +} + +fn kittyPlacementApcWithParam(raw: []const u8, start: usize, key: u8) ?[]const u8 { + var at = start; + while (kittyPlacementApc(raw, at)) |apc| { + if (kittyParam(apc, key) != null) return apc; + const next = @intFromPtr(apc.ptr) - @intFromPtr(raw.ptr) + apc.len; + if (next <= at or next > raw.len) return null; + at = next; + } + return null; +} + +fn kittyParam(apc: []const u8, key: u8) ?u32 { + var i: usize = 0; + while (i + 3 <= apc.len) : (i += 1) { + if (apc[i] != ',' or apc[i + 1] != key or apc[i + 2] != '=') continue; + const begin = i + 3; + var end = begin; + while (end < apc.len and std.ascii.isDigit(apc[end])) : (end += 1) {} + if (end == begin) return null; + return std.fmt.parseUnsigned(u32, apc[begin..end], 10) catch null; + } + return null; +} + fn runGui(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !void { const image_path = try std.fmt.allocPrintSentinel(arena, "{s}/native.ppm", .{base}, 0); const capture_path = try std.fmt.allocPrintSentinel(arena, "{s}/latest.ppm", .{base}, 0); @@ -139,6 +352,120 @@ fn runGui(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !void std.debug.print("native image harness ok: SDL GPU capture ({d} source pixels)\n", .{exact}); } +fn runGuiPdf(arena: std.mem.Allocator, exe: [:0]const u8, base: [:0]const u8) !void { + const pdf_path = try std.fmt.allocPrintSentinel(arena, "{s}/pages.pdf", .{base}, 0); + const capture_path = try std.fmt.allocPrintSentinel(arena, "{s}/latest.ppm", .{base}, 0); + try writePdf(arena, pdf_path); + _ = libc.unlink(capture_path); + _ = setenv("PARDES_TEST", "1", 1); + _ = setenv("PARDES_TEST_CAPTURE_DIR", base, 1); + _ = setenv("PARDES_TEST_COLS", "20", 1); + _ = setenv("PARDES_TEST_ROWS", "10", 1); + + var h = try eh.Harness.initArgs(gpa, exe, 10, 20, pdf_path); + defer h.deinit(); + const fit_width = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); + + // The narrow PDF pane makes fit-width vertically letterboxed. Fit-height + // keeps roughly the same colored width but fills/crops the full body + // height. These are GPU readback extents, so this exercises SDL's UV crop + // and destination rectangle rather than merely observing the page color. + _ = libc.unlink(capture_path); + try h.send(" tz"); + const fit_height = try waitForCaptureBounds(&h, capture_path, pdf_red, 500, 5000); + const width_delta = if (fit_width.width() > fit_height.width()) + fit_width.width() - fit_height.width() + else + fit_height.width() - fit_width.width(); + if (fit_height.height() < fit_width.height() * 2 or width_delta > 8 or + fit_height.min_y >= fit_width.min_y) + { + std.debug.print("SDL PDF fit extents did not change as expected: width={any}, height={any}\n", .{ fit_width, fit_height }); + return error.SdlPdfFitGeometryWrong; + } + + // Remove the old capture before the key so merely rereading page one's + // file cannot satisfy page two. The app replaces this path atomically. + _ = libc.unlink(capture_path); + try h.send("j"); + const green = try waitForCaptureBounds(&h, capture_path, pdf_green, 500, 5000); + std.debug.print("PDF harness ok: SDL fit geometry + red->green ({d}->{d} pixels)\n", .{ fit_width.count, green.count }); +} + +const ColorBounds = struct { + count: usize, + min_x: usize, + min_y: usize, + max_x: usize, + max_y: usize, + + fn width(bounds: ColorBounds) usize { + return bounds.max_x - bounds.min_x + 1; + } + + fn height(bounds: ColorBounds) usize { + return bounds.max_y - bounds.min_y + 1; + } +}; + +fn waitForCaptureBounds( + h: *eh.Harness, + path: [:0]const u8, + color: [3]u8, + minimum: usize, + timeout_ms: i64, +) !ColorBounds { + const deadline = eh.nowMs() + timeout_ms; + var best: usize = 0; + while (eh.nowMs() < deadline) { + if (readFile(gpa, path)) |capture| { + defer gpa.free(capture); + if (ppmView(capture)) |ppm| { + if (colorBounds(ppm, color)) |bounds| { + best = @max(best, bounds.count); + if (bounds.count >= minimum) return bounds; + } + } + } else |_| {} + _ = try h.pumpOnce(50); + } + std.debug.print("SDL PDF harness found only {d} pixels of color {any}\n", .{ best, color }); + return error.PdfPageColorMissing; +} + +const PpmView = struct { width: usize, height: usize, pixels: []const u8 }; + +fn colorBounds(ppm: PpmView, color: [3]u8) ?ColorBounds { + var bounds = ColorBounds{ + .count = 0, + .min_x = std.math.maxInt(usize), + .min_y = std.math.maxInt(usize), + .max_x = 0, + .max_y = 0, + }; + var pixel: usize = 0; + while (pixel < ppm.width * ppm.height) : (pixel += 1) { + const i = pixel * 3; + if (!std.mem.eql(u8, ppm.pixels[i .. i + 3], &color)) continue; + const x = pixel % ppm.width; + const y = pixel / ppm.width; + bounds.count += 1; + bounds.min_x = @min(bounds.min_x, x); + bounds.min_y = @min(bounds.min_y, y); + bounds.max_x = @max(bounds.max_x, x); + bounds.max_y = @max(bounds.max_y, y); + } + return if (bounds.count == 0) null else bounds; +} + +fn countColor(pixels: []const u8, color: [3]u8) usize { + var count: usize = 0; + var i: usize = 0; + while (i + 2 < pixels.len) : (i += 3) + count += @intFromBool(std.mem.eql(u8, pixels[i .. i + 3], &color)); + return count; +} + fn readFile(allocator: std.mem.Allocator, path: [:0]const u8) ![]u8 { const fd = libc.open(path, .{ .ACCMODE = .RDONLY }, @as(libc.mode_t, 0)); if (fd < 0) return error.OpenFailed; @@ -156,11 +483,20 @@ fn readFile(allocator: std.mem.Allocator, path: [:0]const u8) ![]u8 { } fn ppmPixels(bytes: []const u8) ?[]const u8 { + return (ppmView(bytes) orelse return null).pixels; +} + +fn ppmView(bytes: []const u8) ?PpmView { if (!std.mem.startsWith(u8, bytes, "P6\n")) return null; - var newlines: usize = 0; - for (bytes, 0..) |byte, i| if (byte == '\n') { - newlines += 1; - if (newlines == 3) return bytes[i + 1 ..]; - }; - return null; + const dimensions_end = std.mem.indexOfScalarPos(u8, bytes, 3, '\n') orelse return null; + var dimensions = std.mem.splitScalar(u8, bytes[3..dimensions_end], ' '); + const width = std.fmt.parseUnsigned(usize, dimensions.next() orelse return null, 10) catch return null; + const height = std.fmt.parseUnsigned(usize, dimensions.next() orelse return null, 10) catch return null; + if (dimensions.next() != null) return null; + const max_end = std.mem.indexOfScalarPos(u8, bytes, dimensions_end + 1, '\n') orelse return null; + if (!std.mem.eql(u8, bytes[dimensions_end + 1 .. max_end], "255")) return null; + const expected = std.math.mul(usize, width, height) catch return null; + const byte_len = std.math.mul(usize, expected, 3) catch return null; + if (bytes.len -| (max_end + 1) < byte_len) return null; + return .{ .width = width, .height = height, .pixels = bytes[max_end + 1 ..][0..byte_len] }; } diff --git a/test/snapshots/builtins.golden b/test/snapshots/builtins.golden index 0a8f98da..2b8cf69a 100644 --- a/test/snapshots/builtins.golden +++ b/test/snapshots/builtins.golden @@ -153,7 +153,6 @@ | | | /tmp/pardes-snap/builtins/cwd/+Help Del -| 19 SPC l S WsSymbols | 20 SPC l a CodeAction | 21 SPC l d Diagnostics | 22 SPC l h SelectRefs @@ -172,15 +171,16 @@ | 35 SPC t p Petscii | 36 SPC t r Crt | 37 SPC t t ThemeSel -| 38 SPC w h Left C-w h, C-w left -| 39 SPC w j Down C-w j, C-w down -| 40 SPC w k Up C-w k, C-w up -| 41 SPC w l Right C-w l, C-w right -| 42 SPC w t Toggleterm -| 43 Look enter, right-click -| 44 Exec tab, middle-click -| 45 Theme -| 46 +| 38 SPC t z PdfFit +| 39 SPC w h Left C-w h, C-w left +| 40 SPC w j Down C-w j, C-w down +| 41 SPC w k Up C-w k, C-w up +| 42 SPC w l Right C-w l, C-w right +| 43 SPC w t Toggleterm +| 44 Look enter, right-click +| 45 Exec tab, middle-click +| 46 Theme +| 47 == snap not-a-picker grid=120x60 cursor=7,58 |Kill Newcol Tutor Debug NextColor Dump Find Grep Help | /tmp/pardes-snap/builtins/cwd/notes.txt Save Del /tmp/pardes-snap/builtins/cwd Del @@ -214,7 +214,6 @@ | | | /tmp/pardes-snap/builtins/cwd/+Help Del -| 19 SPC l S WsSymbols | 20 SPC l a CodeAction | 21 SPC l d Diagnostics | 22 SPC l h SelectRefs @@ -233,15 +232,16 @@ | 35 SPC t p Petscii | 36 SPC t r Crt | 37 SPC t t ThemeSel -| 38 SPC w h Left C-w h, C-w left -| 39 SPC w j Down C-w j, C-w down -| 40 SPC w k Up C-w k, C-w up -| 41 SPC w l Right C-w l, C-w right -| 42 SPC w t Toggleterm -| 43 Look enter, right-click -| 44 Exec tab, middle-click -| 45 Theme -| 46 +| 38 SPC t z PdfFit +| 39 SPC w h Left C-w h, C-w left +| 40 SPC w j Down C-w j, C-w down +| 41 SPC w k Up C-w k, C-w up +| 42 SPC w l Right C-w l, C-w right +| 43 SPC w t Toggleterm +| 44 Look enter, right-click +| 45 Exec tab, middle-click +| 46 Theme +| 47 == snap window-group grid=120x60 cursor=7,32 |Kill Newcol Tutor Debug NextColor Dump Find Grep Help | /tmp/pardes-snap/builtins/cwd/notes.txt Save Del /tmp/pardes-snap/builtins/cwd Del diff --git a/test/snapshots/leader.golden b/test/snapshots/leader.golden index 29c2ccf1..8b7a76a3 100644 --- a/test/snapshots/leader.golden +++ b/test/snapshots/leader.golden @@ -400,8 +400,8 @@ | 8 SPC t p Petscii | 9 SPC t r Crt | 10 SPC t t ThemeSel -| 11 -| +| 11 SPC t z PdfFit +| 12 | | | diff --git a/test/snapshots/lspdebug.golden b/test/snapshots/lspdebug.golden index af208cf8..6a3caef3 100644 --- a/test/snapshots/lspdebug.golden +++ b/test/snapshots/lspdebug.golden @@ -203,16 +203,16 @@ | 3 zig lib dir: /usr/lib/zig [OK] | 4 offsets: utf-8 walk caps: 512 files, 2000 rows | 5 -| 6 dependency imports gd can follow (7): +| 6 dependency imports gd can follow (8): | 7 zls /home/goblin/00-projects/0x4200.cafe | 8 tree-sitter /home/goblin/00-projects/0x4200.cafe | 9 zstbi /home/goblin/00-projects/0x4200.cafe | 10 mvzr /home/goblin/00-projects/0x4200.cafe -| 11 ghostty-vt /home/goblin/00-projects/0x4200.cafe -| 12 vaxis /home/goblin/00-projects/0x4200.cafe -| 13 uucode /home/goblin/00-projects/0x4200.cafe -| 14 asked from: /tmp/pardes-snap/lspdebug/cwd/z.zig -| 15 +| 11 mupdf /home/goblin/00-projects/0x4200.cafe +| 12 ghostty-vt /home/goblin/00-projects/0x4200.cafe +| 13 vaxis /home/goblin/00-projects/0x4200.cafe +| 14 uucode /home/goblin/00-projects/0x4200.cafe +| 15 asked from: /tmp/pardes-snap/lspdebug/cwd/z.zig | /tmp/pardes-snap/lspdebug/cwd/+Lsp Del | 1 lsp explain — the definition query at byte 29 of z.zi | 2 -- cgit v1.3