summaryrefslogtreecommitdiff
path: root/test/output.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-06 18:11:36 -0300
committerGabriel Schneider <[email protected]>2026-09-07 13:59:12 -0300
commit60367d8fe23f6af98ec28e3cf6c2094dfe332df0 (patch)
tree310fc734173cf771881f4691c71909135fadde97 /test/output.zig
parentfa82cac885cb4738fe36d1e49b4749b5a3e31a4a (diff)
downloadpardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.tar.gz
pardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.zip
Refactor panes and filesystem; replace FUSE with 9P
Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples. Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
Diffstat (limited to 'test/output.zig')
-rw-r--r--test/output.zig855
1 files changed, 855 insertions, 0 deletions
diff --git a/test/output.zig b/test/output.zig
new file mode 100644
index 00000000..577b8e73
--- /dev/null
+++ b/test/output.zig
@@ -0,0 +1,855 @@
+const std = @import("std");
+const pardes = @import("pardes");
+const panes = pardes.panes;
+const config = pardes.config;
+const builtins = pardes.builtins;
+const modal = pardes.modal;
+
+const Pardes = pardes.Pardes;
+const Key = pardes.Key;
+const platform = pardes.platform;
+const font_picker = pardes.font_picker;
+const fonts = if (font_picker) pardes.fonts else struct {};
+
+test "location results sort filenames and numeric positions while remapping their anchor" {
+ const before = "z.zig:1:1 z\nb.zig:10:2-3 ten\nb.zig:2:10-12 two-b\nb.zig:2:2-3 two-a\na.zig:99:1 a\nb.zig:2:2-3 duplicate\n";
+ const after = "a.zig:99:1 a\nb.zig:2:2-3 two-a\nb.zig:2:2-3 duplicate\nb.zig:2:10-12 two-b\nb.zig:10:2-3 ten\nz.zig:1:1 z\n";
+ for ([_]panes.Output.Origin{ .search, .{ .cmd = .Grep }, .{ .query = .references }, .{ .query = .diagnostics } }) |origin| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("notes\n");
+ try panes.Output.fillResults(p, 0, "/tmp", origin, "", try p.gpa.dupe(u8, before), 2);
+ const results = p.panes[source.search_pane.?].?;
+ try std.testing.expectEqualStrings(after, results.file.?.content);
+ try std.testing.expectEqual(@as(usize, 3), source.search_row.?);
+ }
+}
+
+test "ranked choices and prose retain their supplied order" {
+ const text = "z.zig:10:1 first\na.zig:2:1 second\n";
+ for ([_]pardes.lsp.Kind{ .completion, .hover, .code_action }) |kind| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ _ = try p.setTestFile("notes\n");
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = kind }, "", try p.gpa.dupe(u8, text), null);
+ const result = for (p.panes) |slot| {
+ const pane = slot orelse continue;
+ const file = pane.file orelse continue;
+ if (file.output != null) break file;
+ } else return error.MissingResults;
+ try std.testing.expectEqualStrings(text, result.content);
+ }
+}
+
+test "reference Look selects the next sorted row from the captured request and keeps navigation there" {
+ if (!pardes.lsp.supports.contains(.references)) return error.SkipZigTest;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "m.zig", .data = "const item = 0;\n_ = item;\n_ = item;\n_ = item;\n_ = item;\n" });
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "a.zig", .data = "a\n" });
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "z.zig", .data = "z\n" });
+ var path_buf: [256]u8 = undefined;
+ const path = try std.fmt.bufPrint(&path_buf, ".zig-cache/tmp/{s}/m.zig", .{tmp.sub_path});
+ const p = try Pardes.init(std.testing.allocator, .{ .file = path, .cols = 80, .rows = 24 });
+ defer p.deinit();
+ const source = p.panes[0].?;
+ const response = "m.zig:5:5-8 later\nz.zig:1:1 last-file\nm.zig:1:7-10 earlier\nm.zig:3:5-8 current\na.zig:1:1 first-file\nm.zig:4:5-8 next\n";
+ const sorted = "a.zig:1:1 first-file\nm.zig:1:7-10 earlier\nm.zig:3:5-8 current\nm.zig:4:5-8 next\nm.zig:5:5-8 later\nz.zig:1:1 last-file\n";
+ source.cur_row = 2;
+ source.cur_col = 4;
+ p.lspRequest(0, .references, "");
+ const request = p.lsp_wait.?.id;
+ source.cur_row = 4;
+ p.lspResponse(request, response);
+ const rid = source.search_pane orelse return error.MissingResults;
+ const results = p.panes[rid].?;
+ try std.testing.expectEqualStrings(sorted, results.file.?.content);
+ try std.testing.expectEqual(@as(usize, 0), p.active);
+ try std.testing.expectEqual(@as(i32, 3), source.cur_row);
+ try std.testing.expect(source.vsel.active);
+ try std.testing.expectEqual(@as(i32, 4), @min(source.cur_col, source.vsel.col));
+ try std.testing.expectEqual(@as(i32, 7), @max(source.cur_col, source.vsel.col));
+ try std.testing.expectEqual(@as(i32, 3), results.cur_row);
+ try std.testing.expectEqual(@as(usize, 3), source.search_row.?);
+ try std.testing.expectEqual(results.serial, p.look_walk_owner.?);
+ try std.testing.expectEqual(@as(i32, 3), results.look_at.?.row);
+ const available = p.freeSlot();
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(rid, p.active);
+ try std.testing.expectEqual(@as(i32, 4), results.cur_row);
+ try std.testing.expectEqual(available, p.freeSlot());
+ p.update(.{ .key = .{ .cp = 'N' } });
+ try std.testing.expectEqual(@as(i32, 3), results.cur_row);
+
+ p.active = 0;
+ source.cur_row = 4;
+ source.cur_col = 4;
+ p.lspRequest(0, .references, "");
+ p.lspResponse(p.lsp_wait.?.id, response);
+ try std.testing.expect(std.mem.endsWith(u8, p.panes[p.active].?.file.?.path, "/z.zig"));
+ try std.testing.expectEqual(@as(i32, 5), results.cur_row);
+ try std.testing.expectEqual(@as(usize, 5), source.search_row.?);
+ const last = p.active;
+ p.lspRequest(last, .references, "");
+ p.lspResponse(p.lsp_wait.?.id, response);
+ try std.testing.expect(std.mem.endsWith(u8, p.panes[p.active].?.file.?.path, "/a.zig"));
+ const wrapped = p.panes[p.panes[last].?.search_pane.?].?;
+ try std.testing.expectEqual(@as(i32, 0), wrapped.cur_row);
+ try std.testing.expectEqual(wrapped.serial, p.look_walk_owner.?);
+}
+
+test "location sorting leaves mixed prose alone and preserves newline shape" {
+ const cases = [_]struct { before: []const u8, after: []const u8, anchor: usize }{
+ .{
+ .before = "heading\nz.zig:10:1 z\nnot a location\nx file.zig:2:10 x\nx file.zig:2:2 y\n\n",
+ .after = "heading\nz.zig:10:1 z\nnot a location\nx file.zig:2:10 x\nx file.zig:2:2 y\n\n",
+ .anchor = 1,
+ },
+ .{
+ .before = "z.zig:10:1 z\nx file.zig:2:10 x\nx file.zig:2:2 y",
+ .after = "x file.zig:2:2 y\nx file.zig:2:10 x\nz.zig:10:1 z",
+ .anchor = 1,
+ },
+ };
+ for (cases) |case| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("notes\n");
+ try panes.Output.fillResults(p, 0, "/tmp", .search, "", try p.gpa.dupe(u8, case.before), 1);
+ try std.testing.expectEqualStrings(case.after, p.panes[source.search_pane.?].?.file.?.content);
+ try std.testing.expectEqual(case.anchor, source.search_row.?);
+ }
+}
+
+test "a single language location opens the full spaced filename" {
+ if (!pardes.lsp.supports.contains(.references)) return error.SkipZigTest;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "from.zig", .data = "const item = 0;\n" });
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "with space.zig", .data = "first\nsecond\n" });
+ var path_buf: [256]u8 = undefined;
+ const path = try std.fmt.bufPrint(&path_buf, ".zig-cache/tmp/{s}/from.zig", .{tmp.sub_path});
+ const p = try Pardes.init(std.testing.allocator, .{ .file = path });
+ defer p.deinit();
+ p.lspRequest(0, .references, "");
+ p.lspResponse(p.lsp_wait.?.id, "with space.zig:2:1 second\n");
+ try std.testing.expect(std.mem.endsWith(u8, p.panes[p.active].?.file.?.path, "/with space.zig"));
+ try std.testing.expectEqual(@as(i32, 1), p.panes[p.active].?.cur_row);
+ for (p.panes) |slot| {
+ const pane = slot orelse continue;
+ if (pane.file) |file| try std.testing.expect(file.output == null);
+ }
+}
+
+test "result locations stop before preview text and retain full addresses" {
+ const cases = [_]struct { row: []const u8, path: []const u8, line: usize, col: usize, address: []const u8 }{
+ .{ .row = "a.zig:2 see other.zig:10:1", .path = "a.zig", .line = 2, .col = 0, .address = "a.zig:2" },
+ .{ .row = "a.zig:2:10-12 see other.zig:1:1", .path = "a.zig", .line = 2, .col = 10, .address = "a.zig:2:10-12" },
+ .{ .row = "with space.zig:100:2 café", .path = "with space.zig", .line = 100, .col = 2, .address = "with space.zig:100:2" },
+ .{ .row = "@p3:20:1 pane", .path = "@p3", .line = 20, .col = 1, .address = "@p3:20:1" },
+ .{ .row = "/n/peer/file.zig:5-8 span", .path = "/n/peer/file.zig", .line = 5, .col = 0, .address = "/n/peer/file.zig:5-8" },
+ };
+ for (cases) |case| {
+ const got = panes.Output.location(case.row);
+ try std.testing.expectEqualStrings(case.path, got.path);
+ try std.testing.expectEqual(case.line, got.at.line);
+ try std.testing.expectEqual(case.col, got.at.col);
+ try std.testing.expectEqualStrings(case.address, case.row[0..got.end]);
+ }
+}
+
+test "reordered identical results preserve the selected row and warm line index" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("notes\n");
+ const sorted = "a.zig:2:1 first\na.zig:10:1 second\n";
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "", try p.gpa.dupe(u8, sorted), null);
+ const result = p.panes[source.search_pane.?].?;
+ const file = &result.file.?;
+ const index = try panes.File.lineIndex(p.gpa, file);
+ const content = file.content.ptr;
+ const revision = file.revision;
+ result.cur_row = 1;
+ result.cur_col = 0;
+ result.vsel = .{ .active = true, .explicit = true, .row = 1, .col = 9 };
+ result.select = true;
+ const selected = result.vsel;
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "new request", try p.gpa.dupe(u8, "a.zig:10:1 second\na.zig:2:1 first\n"), 0);
+ try std.testing.expectEqualStrings(sorted, file.content);
+ try std.testing.expectEqual(content, file.content.ptr);
+ try std.testing.expectEqual(index.ptr, file.line_starts.ptr);
+ try std.testing.expectEqual(revision, file.revision);
+ try std.testing.expectEqual(selected, result.vsel);
+ try std.testing.expect(result.select);
+ try std.testing.expectEqual(@as(i32, 1), result.cur_row);
+ try std.testing.expectEqual(@as(usize, 1), source.search_row.?);
+}
+
+test "result sorting allocation failures preserve the previous answer and navigation" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("notes\n");
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "before", try p.gpa.dupe(u8, "a.zig:1:1 keep\n"), 0);
+ const id = source.search_pane.?;
+ const result = p.panes[id].?;
+ const content = result.file.?.content.ptr;
+ const revision = result.file.?.revision;
+ const owner = p.look_walk_owner;
+ var preview: [4096]u8 = @splat('x');
+ for (0..2) |failure| {
+ var failing = std.testing.FailingAllocator.init(p.gpa, .{ .fail_index = failure });
+ const original_scratch = p.scratch;
+ p.scratch = .init(failing.allocator());
+ defer {
+ p.scratch.deinit();
+ p.scratch = original_scratch;
+ }
+ const incoming = try std.fmt.allocPrint(p.gpa, "z.zig:2:1 {s}\na.zig:1:1 {s}\n", .{ &preview, &preview });
+ try std.testing.expectError(error.OutOfMemory, panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "after", incoming, 1));
+ try std.testing.expect(failing.has_induced_failure);
+ try std.testing.expectEqual(content, result.file.?.content.ptr);
+ try std.testing.expectEqual(revision, result.file.?.revision);
+ try std.testing.expectEqualStrings("before", result.file.?.output.?.arg());
+ try std.testing.expectEqual(id, source.search_pane.?);
+ try std.testing.expectEqual(@as(usize, 0), source.search_row.?);
+ try std.testing.expectEqual(@as(usize, 0), p.active);
+ try std.testing.expectEqual(owner, p.look_walk_owner);
+ }
+}
+
+test "Find sorts whole filenames and next location uses numeric columns" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("");
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .cmd = .Find }, "", try p.gpa.dupe(u8, "x:2\nx:10\n"), 0);
+ const result = p.panes[source.search_pane.?].?;
+ try std.testing.expectEqualStrings("x:10\nx:2\n", result.file.?.content);
+ try std.testing.expectEqual(@as(usize, 1), source.search_row.?);
+ const rows = "with space.zig:2:2-3 first\nwith space.zig:2:10-12 next\nwith space.zig:10:1 later";
+ try std.testing.expectEqual(@as(usize, 1), panes.Output.nextResult(rows, "with space.zig", .{ .line = 2, .col = 3 }));
+ try std.testing.expectEqual(@as(usize, 2), panes.Output.nextResult(rows, "with space.zig", .{ .line = 2, .col = 10 }));
+ try std.testing.expectEqual(@as(usize, 0), panes.Output.nextResult(rows, "with space.zig", .{ .line = 10, .col = 1 }));
+}
+
+test "result stepping reaches an unterminated last row" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("one\ntwo\n");
+ const rows = try std.fmt.allocPrint(p.gpa, "{s}:1:1 first\n{s}:2:1 last", .{ source.file.?.path, source.file.?.path });
+ try panes.Output.fillResults(p, 0, "/tmp", .search, "", rows, 0);
+ try std.testing.expect(pardes.test_api.searchStep(p, 0, 1));
+ try std.testing.expectEqual(@as(usize, 1), source.search_row.?);
+ const result = p.panes[source.search_pane.?].?;
+ try std.testing.expectEqual(@as(i32, 1), result.cur_row);
+}
+
+test "Mini uses OS source precedence and refreshes one independent snapshot" {
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "build.zig", .data = "x\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)];
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ try p.panes[0].?.setOwnedCwd(dir);
+ try std.testing.expect(p.executeBuiltinLine(0, "Mini build.zig"));
+ const id = p.active;
+ try std.testing.expect(id != 0);
+ const mini = p.panes[id].?;
+ try std.testing.expect(mini.terminal == null);
+ try std.testing.expectEqualStrings("⠁\n", mini.file.?.content);
+ try std.testing.expect(std.mem.startsWith(u8, mini.file.?.mini.?.source, dir));
+ const next = p.freeSlot();
+ const body = mini.file.?.content.ptr;
+ try std.testing.expect(p.executeBuiltinLine(id, "Mini build.zig"));
+ try std.testing.expectEqual(id, p.active);
+ try std.testing.expectEqual(body, mini.file.?.content.ptr);
+ try std.testing.expectEqual(next, p.freeSlot());
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "build.zig", .data = "xx\n" });
+ try std.testing.expectEqualStrings("⠁\n", mini.file.?.content);
+ try std.testing.expect(p.executeBuiltinLine(id, "Mini build.zig"));
+ try std.testing.expectEqual(id, p.active);
+ try std.testing.expectEqualStrings("⠉\n", mini.file.?.content);
+ while (p.nextEffect()) |effect| switch (effect) {
+ .watch => |watch| try std.testing.expect(watch.pane != id),
+ else => {},
+ };
+ try std.testing.expect(p.executeBuiltinLine(id, "Save"));
+ try std.testing.expect(mini.prompt == .save);
+}
+
+test "Mini dump preserves its exact virtual snapshot and body writes detach colors" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("pub fn start() void {}\n");
+ var command_buf: [128]u8 = undefined;
+ const command = try std.fmt.bufPrint(&command_buf, "Mini /virtual/pane/{d}/body", .{source.serial});
+ try std.testing.expect(p.executeBuiltinLine(0, command));
+ const id = p.active;
+ const before = p.panes[id].?.file.?;
+ try std.testing.expect(before.mini != null);
+ try p.dumpState();
+ panes.File.setContent(p, &source.file.?, try p.gpa.dupe(u8, "changed\n"));
+ const restored = try Pardes.initFromDump(std.testing.allocator, .{ .tty_only = true }, p.dump_out.?);
+ defer restored.deinit();
+ const mini = restored.panes[restored.active].?;
+ try std.testing.expectEqualStrings(before.content, mini.file.?.content);
+ try std.testing.expectEqualStrings(before.mini.?.source, mini.file.?.mini.?.source);
+ try std.testing.expectEqualSlices(u8, before.mini.?.colors, mini.file.?.mini.?.colors);
+ try std.testing.expect(before.content.ptr != mini.file.?.content.ptr);
+ while (restored.nextEffect()) |effect| switch (effect) {
+ .watch => |watch| try std.testing.expect(watch.pane != restored.active),
+ else => {},
+ };
+ var body_buf: [128]u8 = undefined;
+ const body_path = try std.fmt.bufPrint(&body_buf, "/n/self/pane/{d}/body", .{mini.serial});
+ const body = try pardes.filesystem.read(restored, body_path);
+ defer restored.gpa.free(body);
+ try std.testing.expectEqualStrings(before.content, body);
+ try pardes.filesystem.write(restored, body_path, "replacement\n");
+ try std.testing.expectEqualStrings("replacement\n", mini.file.?.content);
+ try std.testing.expect(mini.file.?.mini == null);
+}
+
+test "Mini syntax colors survive toggles themes and rendering without source access" {
+ if (!pardes.syntax.enabled) return error.SkipZigTest;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "demo.zig", .data = "pub fn main() void {\n if (true) return;\n}\n" });
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)];
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 24 });
+ defer p.deinit();
+ try p.panes[0].?.setOwnedCwd(dir);
+ p.settings.colors = false;
+ try panes.Mini.open(p, 0, "demo.zig");
+ const id = p.active;
+ const mini = p.panes[id].?;
+ const keyword = std.mem.indexOfScalar(u8, mini.file.?.mini.?.colors, @intFromEnum(pardes.syntax.Syn.keyword)) orelse return error.MissingKeywordColor;
+ const position = modal.positionAt(mini.file.?.content, keyword);
+ const column = panes.File.displayWidth(modal.lineSlice(mini.file.?.content, position.row)[0..position.col]);
+ try tmp.dir.deleteFile(std.testing.io, "demo.zig");
+ const colors = mini.file.?.mini.?.colors.ptr;
+ var frame = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer frame.deinit();
+ for ([_]bool{ false, true, false, true }) |enabled| {
+ p.settings.colors = enabled;
+ mini.file.?.syntax_dirty = true;
+ const surface = try p.render(frame.allocator());
+ const rect = p.rects[id];
+ const x = rect.x + config.GUTTER + panes.File.gutterWidth(mini) + @as(u16, @intCast(column));
+ const y = (if (p.settings.tag_bottom) rect.y else rect.y + pardes.BOX_H) + @as(u16, @intCast(position.row));
+ if (enabled) try std.testing.expectEqualDeep(pardes.Color{ .rgb = p.theme().kw }, surface.at(x, y).style.fg);
+ try std.testing.expectEqual(colors, mini.file.?.mini.?.colors.ptr);
+ try std.testing.expectEqual(@as(usize, 0), mini.file.?.highlights.len);
+ _ = frame.reset(.retain_capacity);
+ try std.testing.expect(p.executeBuiltinLine(id, "NextColor"));
+ }
+}
+
+test "Mini restore rejects inconsistent metadata before publishing a pane" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const id = p.freeSlot().?;
+ const serial = p.next_serial;
+ for ([_]pardes.dump.File{
+ .{ .content = "x", .mini_source = "/source.zig", .mini_colors_b64 = "AA==" },
+ .{ .content = "x", .origin = "Mini", .mini_source = "/source.zig", .mini_colors_b64 = "" },
+ .{ .content = "x", .origin = "Mini", .mini_source = "/source.zig", .mini_colors_b64 = "BQ==" },
+ .{ .content = "x", .origin = "Mini", .mini_colors_b64 = "AA==" },
+ }) |saved| {
+ try std.testing.expectError(error.InvalidMini, panes.File.restore(p, id, .{ .kind = .file, .tag = "", .body = "", .file = saved }));
+ try std.testing.expect(p.panes[id] == null);
+ try std.testing.expectEqual(serial, p.next_serial);
+ }
+}
+
+test "Mini failures leave its existing snapshot and focus unchanged" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("abc\n");
+ var path_buf: [128]u8 = undefined;
+ const path = try std.fmt.bufPrint(&path_buf, "/virtual/pane/{d}/body", .{source.serial});
+ try panes.Mini.open(p, 0, path);
+ const id = p.active;
+ const mini = p.panes[id].?;
+ const body = mini.file.?.content.ptr;
+ const next = p.freeSlot();
+ try std.testing.expectError(error.FileNotFound, panes.Mini.open(p, 0, "/virtual/does-not-exist"));
+ try std.testing.expectEqual(id, p.active);
+ try std.testing.expectEqual(body, mini.file.?.content.ptr);
+ try std.testing.expectEqual(next, p.freeSlot());
+}
+
+test "Config prints the startup path and refreshes its one output" {
+ const path = "/home/pardes-test/.config/pardes/init";
+ const p = try Pardes.init(std.testing.allocator, .{ .startup_config_path = path });
+ defer p.deinit();
+
+ try std.testing.expect(p.executeBuiltinLine(0, "Config"));
+ const opened = p.active;
+ const out = p.panes[opened].?.file.?;
+ for ([_][]const u8{
+ "Startup config: " ++ path ++ "\n",
+ "Theme: helix\n",
+ "Shell requested (new panes): " ++ config.default_shell ++ " (default)\n",
+ "Shell effective (last spawn): (none)\n",
+ "Shell pending: on\n",
+ }) |line| try std.testing.expect(std.mem.indexOf(u8, out.content, line) != null);
+ try std.testing.expectEqualStrings(config.config_buffer, std.fs.path.basename(out.path));
+ try std.testing.expectEqual(panes.Output.Origin{ .cmd = .Config }, out.output.?.from);
+
+ try std.testing.expect(p.executeBuiltinLine(0, "Config"));
+ try std.testing.expectEqual(opened, p.active);
+ var buffers: usize = 0;
+ for (p.panes) |slot| {
+ const f = (slot orelse continue).file orelse continue;
+ const origin = (f.output orelse continue).from;
+ buffers += @intFromBool(std.meta.eql(origin, panes.Output.Origin{ .cmd = .Config }));
+ }
+ try std.testing.expectEqual(@as(usize, 1), buffers);
+}
+
+test "Config reports the absence of a per-user config path" {
+ const p = try Pardes.init(std.testing.allocator, .{});
+ defer p.deinit();
+ try std.testing.expect(p.executeBuiltinLine(0, "Config"));
+ const report = p.panes[p.active].?.file.?.content;
+ try std.testing.expect(std.mem.indexOf(u8, report, "no per-user config path") != null);
+}
+
+test "EffectCode lists readable virtual sources used by this backend" {
+ if (comptime !builtins.EffectCode.enabled) return;
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+
+ try std.testing.expect(p.executeBuiltinLine(0, "EffectCode PanelSlide"));
+ const out = p.panes[p.active].?.file.?;
+ try std.testing.expectEqualStrings(config.effect_code_buffer, std.fs.path.basename(out.path));
+ try std.testing.expectEqual(panes.Output.Origin{ .cmd = .EffectCode }, out.output.?.from);
+ try std.testing.expectEqualStrings("PanelSlide", out.output.?.arg());
+ try std.testing.expect(out.content.len < 1024);
+ try std.testing.expect(std.mem.indexOf(u8, out.content, "/virtual/src/layout.zig\n") != null);
+ const backend_source = switch (platform) {
+ .tty => "/virtual/src/tty/panel_compositor.zig",
+ .gui => if (@import("pardes_config").gui_shader_sources_prebuilt)
+ "/virtual/shaders/prebuilt/ui.vert.glsl"
+ else
+ "/virtual/shaders/ui.vert.glsl",
+ .macos => "/virtual/src/macos/Sources/ScenePostprocessor.swift",
+ .web, .esp32p4 => unreachable,
+ };
+ try std.testing.expect(std.mem.indexOf(u8, out.content, backend_source) != null);
+ var paths = std.mem.tokenizeScalar(u8, out.content, '\n');
+ var count: usize = 0;
+ while (paths.next()) |path| {
+ if (!std.mem.startsWith(u8, path, "/virtual/")) continue;
+ const expected = pardes.filesystem.sourceBytes(path["/virtual/".len..]) orelse return error.MissingEffectSource;
+ const bytes = try pardes.filesystem.read(p, path);
+ defer p.gpa.free(bytes);
+ try std.testing.expectEqualStrings(expected, bytes);
+ count += 1;
+ }
+ try std.testing.expect(count >= 3);
+ const report = p.active;
+ p.lookAt(report, backend_source);
+ try std.testing.expect(p.active != report);
+ const source = p.panes[p.active].?.file.?;
+ try std.testing.expectEqualStrings(backend_source, source.path);
+ try std.testing.expectEqualStrings(pardes.filesystem.sourceBytes(backend_source["/virtual/".len..]).?, source.content);
+}
+
+test "every enabled setting builtin mutates the State Config reports" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ try std.testing.expectEqual(config.gui_tagline_font_percent, p.settings.font.tagline_percent);
+
+ var font_arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
+ defer font_arena.deinit();
+ var chosen_font: ?[]const u8 = null;
+ var buf: [512]u8 = undefined;
+ for (config.Runtime.settings) |setting| {
+ if (!setting.enabled(builtins.capabilities)) continue;
+ const command: []const u8 = switch (setting.action) {
+ .theme => try std.fmt.bufPrint(&buf, "{s} acme", .{setting.word}),
+ .shell => try std.fmt.bufPrint(&buf, "{s} fish", .{setting.word}),
+ .tagline_size => try std.fmt.bufPrint(&buf, "{s} 67", .{setting.word}),
+ .font => continue,
+ else => setting.word,
+ };
+ try std.testing.expect(p.executeBuiltinLine(p.active, command));
+ }
+ if (comptime font_picker) {
+ const before_invalid = p.settings.font.tagline_percent;
+ try std.testing.expect(p.executeBuiltinLine(p.active, "TaglineSize 0"));
+ try std.testing.expectEqual(before_invalid, p.settings.font.tagline_percent);
+ const installed = fonts.list(font_arena.allocator(), null);
+ if (installed.len > 0) {
+ chosen_font = installed[0].name;
+ const command = try std.fmt.bufPrint(&buf, "Font {s}", .{installed[0].name});
+ try std.testing.expect(p.executeBuiltinLine(p.active, command));
+ }
+ }
+
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Config"));
+ const report = p.panes[p.active].?.file.?.content;
+ for ([_][]const u8{
+ "Colors: off\n",
+ "Wrap: off\n",
+ "Tagbottom: on\n",
+ "Debug: on\n",
+ "Theme: acme\n",
+ "Shell requested (new panes): fish\n",
+ }) |line| try std.testing.expect(std.mem.indexOf(u8, report, line) != null);
+ const transition = if (builtins.capabilities.panel_transitions)
+ try std.fmt.bufPrint(&buf, "Panel transition: {s}\n", .{
+ config.Runtime.findAction(.{ .transition = p.settings.panel_transition }).?.word,
+ })
+ else
+ "Panel transition: unsupported\n";
+ try std.testing.expect(std.mem.indexOf(u8, report, transition) != null);
+ const scene_status = if (builtins.capabilities.scene_shaders) "on" else "unsupported";
+ for ([_][]const u8{ "Crt", "Ripple", "Glitch" }) |name| {
+ const line = try std.fmt.bufPrint(&buf, "{s}: {s}\n", .{ name, scene_status });
+ try std.testing.expect(std.mem.indexOf(u8, report, line) != null);
+ }
+ const tagline = if (!builtins.capabilities.tagline_font_size)
+ "TaglineSize: unsupported\n"
+ else
+ try std.fmt.bufPrint(&buf, "TaglineSize: {d}%{s}\n", .{
+ p.settings.font.tagline_percent,
+ if (builtins.capabilities.font_picker) "" else " (build-time only)",
+ });
+ try std.testing.expect(std.mem.indexOf(u8, report, tagline) != null);
+ if (chosen_font) |name| {
+ try std.testing.expect(std.mem.indexOf(u8, report, name) != null);
+ try std.testing.expect(std.mem.indexOf(u8, report, "Font pending: on\n") != null);
+ }
+}
+
+fn walkFixture(p: *Pardes, id: usize, cwd: []const u8, pattern: []const u8) !usize {
+ const rows = try p.gpa.dupe(u8,
+ \\build.zig:1:1 first
+ \\(mise.toml) and build.zig.zon:3:2-9 two on one row
+ \\nothing look-able on this row at all
+ \\uucode_config.zig:7:1 last
+ \\
+ );
+ try panes.Output.fillResults(p, id, cwd, .search, pattern, rows, null);
+ return p.panes[id].?.search_pane orelse error.MissingResults;
+}
+
+const ProjectPaths = struct { cwd: []const u8, boot: []const u8 };
+
+fn projectPaths(cwd_buf: *[4096]u8, path_buf: *[4096]u8) !ProjectPaths {
+ const raw = std.c.getcwd(cwd_buf, cwd_buf.len) orelse return error.GetCwdFailed;
+ const cwd = std.mem.span(@as([*:0]u8, @ptrCast(raw)));
+ return .{ .cwd = cwd, .boot = try std.fmt.bufPrint(path_buf, "{s}/mise.toml", .{cwd}) };
+}
+
+fn selectedOutputText(pane: *const pardes.Pane) ?[]const u8 {
+ const file = pane.file orelse return null;
+ if (!pane.vsel.active or pane.vsel.row != pane.cur_row or pane.cur_row < 0) return null;
+ const line = modal.lineSlice(file.content, @intCast(pane.cur_row));
+ const lo: usize = @intCast(@max(0, @min(pane.vsel.col, pane.cur_col)));
+ const hi: usize = @intCast(@max(0, @max(pane.vsel.col, pane.cur_col)));
+ if (lo >= line.len) return "";
+ return line[lo..@min(line.len, hi + 1)];
+}
+
+test "n/N selects one output location per row and opens nothing" {
+ if (platform == .web) return;
+ var cwd_buf: [4096]u8 = undefined;
+ var path_buf: [4096]u8 = undefined;
+ const paths = try projectPaths(&cwd_buf, &path_buf);
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 40, .file = paths.boot });
+ defer p.deinit();
+ p.update(.{ .resize = .{ .cols = 100, .rows = 40 } });
+ const rid = try walkFixture(p, p.active, paths.cwd, "one");
+ const results = p.panes[rid].?;
+ const panes_before = p.freeSlot();
+
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(rid, p.active);
+ try std.testing.expectEqual(panes_before, p.freeSlot());
+ try std.testing.expect(results.vsel.active and results.vsel.explicit);
+ try std.testing.expectEqualStrings("build.zig:1:1", selectedOutputText(results) orelse "");
+ try std.testing.expectEqual(panes.Output.Grain.line, panes.Output.grain(results.file.?.output));
+
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(@as(i32, 1), results.cur_row);
+ try std.testing.expectEqualStrings("mise.toml", selectedOutputText(results) orelse "");
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(@as(i32, 3), results.cur_row); // row 2 is not look-able
+ try std.testing.expectEqualStrings("uucode_config.zig:7:1", selectedOutputText(results) orelse "");
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(@as(i32, 0), results.cur_row); // ring seam
+
+ p.update(.{ .key = .{ .cp = Key.enter } });
+ try std.testing.expect(p.freeSlot() != panes_before);
+ try std.testing.expect(std.mem.endsWith(u8, p.panes[p.active].?.file.?.path, "/build.zig"));
+ try std.testing.expectEqual(panes.Output.Grain.word, panes.Output.grain(p.panes[p.active].?.file.?.output));
+}
+
+test "n/N resumes the result output whose Look moved focus away" {
+ if (platform == .web) return;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(std.testing.io, .{
+ .sub_path = "look-owner.txt",
+ .data = "alpha target\nbeta target\ngamma target\n",
+ });
+ var path_buf: [256]u8 = undefined;
+ const path = try std.fmt.bufPrint(&path_buf, ".zig-cache/tmp/{s}/look-owner.txt", .{tmp.sub_path});
+ const p = try Pardes.init(std.testing.allocator, .{ .file = path, .cols = 80, .rows = 24 });
+ defer p.deinit();
+
+ try std.testing.expect(p.executeBuiltinLine(0, "Look target"));
+ const rid = p.panes[0].?.search_pane orelse return error.MissingResults;
+ const results = p.panes[rid].?;
+ const first = results.cur_row;
+ try std.testing.expectEqual(@as(usize, 0), p.active);
+ try std.testing.expect(results.look_at != null);
+ try std.testing.expectEqual(results.serial, p.look_walk_owner orelse return error.MissingLookOwner);
+
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(rid, p.active);
+ try std.testing.expectEqual(first + 1, results.cur_row);
+ p.update(.{ .key = .{ .cp = 'N' } });
+ try std.testing.expectEqual(first, results.cur_row);
+
+ // A later no-match answer has no position to resume and therefore cannot
+ // steal the provenance established by the Look above.
+ const owner = results.serial;
+ const dir = std.fs.path.dirname(path) orelse ".";
+ try panes.Output.fillResults(
+ p,
+ 0,
+ dir,
+ .search,
+ "no-such-result",
+ try p.gpa.dupe(u8, ""),
+ null,
+ );
+ try std.testing.expectEqual(owner, p.look_walk_owner.?);
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(rid, p.active);
+ try std.testing.expectEqual(first + 1, results.cur_row);
+
+ // Emptying the owner itself keeps its identity as the ring's starting
+ // point. Deleting it then leaves a stale serial, never an id that can bind
+ // to the unrelated pane subsequently allocated in the same slot.
+ try panes.Output.fillResults(p, 0, dir, .search, "target", try p.gpa.dupe(u8, ""), null);
+ try std.testing.expectEqual(owner, p.look_walk_owner.?);
+ try std.testing.expect(p.executeBuiltinLine(rid, "Del"));
+ try std.testing.expect(p.paneBySerial(owner) == null);
+ const replacement = try p.newShell(rid, "");
+ try std.testing.expect(replacement.serial != owner);
+ try std.testing.expect(p.paneBySerial(owner) == null);
+}
+
+test "N exactly reverses n across output panes and the ring seam" {
+ if (platform == .web) return;
+ var cwd_buf: [4096]u8 = undefined;
+ var path_buf: [4096]u8 = undefined;
+ const paths = try projectPaths(&cwd_buf, &path_buf);
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 40, .file = paths.boot });
+ defer p.deinit();
+ p.update(.{ .resize = .{ .cols = 100, .rows = 40 } });
+ const first = try walkFixture(p, p.active, paths.cwd, "one");
+ p.update(.{ .key = .{ .cp = 'n', .alt = true } });
+ const second = try walkFixture(p, p.active, paths.cwd, "two");
+ try std.testing.expect(first != second);
+
+ const Mark = struct { pane: usize, row: i32, col: i32 };
+ const here = struct {
+ fn at(pp: *Pardes) Mark {
+ const pane = pp.panes[pp.active].?;
+ return .{ .pane = pp.active, .row = pane.cur_row, .col = pane.cur_col };
+ }
+ }.at;
+ p.update(.{ .key = .{ .cp = 'n' } });
+ const base = here(p);
+ var trail: [12]Mark = undefined;
+ for (&trail) |*mark| {
+ p.update(.{ .key = .{ .cp = 'n' } });
+ mark.* = here(p);
+ }
+ var i = trail.len;
+ while (i > 0) {
+ i -= 1;
+ p.update(.{ .key = .{ .cp = 'N' } });
+ const want = if (i == 0) base else trail[i - 1];
+ try std.testing.expectEqual(want, here(p));
+ }
+ var saw_first = false;
+ var saw_second = false;
+ for (trail) |mark| {
+ saw_first = saw_first or mark.pane == first;
+ saw_second = saw_second or mark.pane == second;
+ }
+ try std.testing.expect(saw_first and saw_second);
+}
+
+test "n/N selects command outputs by whole row" {
+ if (platform == .web) return;
+ var cwd_buf: [4096]u8 = undefined;
+ var path_buf: [4096]u8 = undefined;
+ const paths = try projectPaths(&cwd_buf, &path_buf);
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 40, .file = paths.boot });
+ defer p.deinit();
+ p.update(.{ .resize = .{ .cols = 100, .rows = 40 } });
+
+ try std.testing.expect(p.executeBuiltinLine(p.active, "ThemeSel"));
+ const tid = p.panes[p.active].?.search_pane orelse return error.MissingThemeList;
+ const themes = p.panes[tid].?;
+ try std.testing.expect(panes.Output.fileTraits(themes.file.?.output).commands);
+ p.update(.{ .key = .{ .cp = 'n' } });
+ const row0 = std.mem.trimEnd(u8, modal.lineSlice(themes.file.?.content, 0), " \t\r");
+ try std.testing.expectEqualStrings(row0, selectedOutputText(themes) orelse "");
+ const theme_before = p.settings.theme;
+ p.update(.{ .key = .{ .cp = 'n' } });
+ p.update(.{ .key = .{ .cp = Key.tab } });
+ try std.testing.expect(p.settings.theme != theme_before);
+
+ try std.testing.expect(p.executeBuiltinLine(0, "Look build.zig"));
+ p.update(.tick);
+ try std.testing.expect(p.active != 0);
+ p.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(@as(usize, 0), p.active);
+}
+
+test "identical result refresh keeps warm content caches without allocation" {
+ var allocator: std.testing.FailingAllocator = .init(std.testing.allocator, .{});
+ const p = try Pardes.init(allocator.allocator(), .{ .tty_only = true, .cols = 80, .rows = 24 });
+ defer p.deinit();
+ const source = try p.setTestFile("notes\n");
+ p.settings.colors = true;
+ const text = "a.zig:1:1 pub fn first() void {}\na.zig:2:1 if (true) return;\n";
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "before", try p.gpa.dupe(u8, text), 0);
+ const id = source.search_pane orelse return error.MissingResults;
+ const result = p.panes[id].?;
+ const file = &result.file.?;
+ result.cur_row = 1;
+ result.cur_col = 12;
+ result.vsel = .{ .active = true, .explicit = true, .row = 0, .col = 2 };
+ result.select = true;
+ file.scroll = 1;
+ result.hscroll = 3;
+ panes.File.refreshHighlights(p);
+ const index = try panes.File.lineIndex(p.gpa, file);
+ const styles = try std.testing.allocator.dupe(u8, file.highlights);
+ defer std.testing.allocator.free(styles);
+ if (@import("pardes_config").syntax_zig_grammar) try std.testing.expect(styles.len > 0);
+ const revision = file.revision;
+ const saved_revision = file.saved_revision;
+ const body_ptr = file.content.ptr;
+ const style_ptr = file.highlights.ptr;
+ const highlight_start = file.highlight_start;
+ const selected = result.vsel;
+ const event_node = pardes.filesystem.Node.of(result.serial, .event);
+ const opened = pardes.filesystem.handle(p, .{ .tag = 0, .op = .open, .node = event_node });
+ try std.testing.expectEqual(.ok, opened.status);
+ defer _ = pardes.filesystem.handle(p, .{ .tag = 0, .op = .release, .node = event_node, .handle = opened.handle });
+
+ for (0..32) |_| {
+ const incoming = try p.gpa.dupe(u8, text);
+ const allocations = allocator.allocations;
+ const freed = allocator.freed_bytes;
+ const deallocations = allocator.deallocations;
+ allocator.fail_index = allocator.alloc_index;
+ defer allocator.fail_index = std.math.maxInt(usize);
+ p.active = id;
+ source.search_pane = null;
+ source.search_row = null;
+ p.look_walk_owner = null;
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "after", incoming, 1);
+ try std.testing.expectEqual(revision, file.revision);
+ try std.testing.expectEqual(saved_revision, file.saved_revision);
+ try std.testing.expectEqual(body_ptr, file.content.ptr);
+ try std.testing.expectEqual(index.ptr, file.line_starts.ptr);
+ try std.testing.expectEqual(style_ptr, file.highlights.ptr);
+ try std.testing.expectEqual(highlight_start, file.highlight_start);
+ try std.testing.expectEqualSlices(u8, styles, file.highlights);
+ try std.testing.expect(!file.syntax_dirty);
+ panes.File.refreshHighlights(p);
+ try std.testing.expectEqual(allocations, allocator.allocations);
+ try std.testing.expectEqual(freed + text.len, allocator.freed_bytes);
+ try std.testing.expectEqual(deallocations + 1, allocator.deallocations);
+ try std.testing.expect(!allocator.has_induced_failure);
+ try std.testing.expectEqual(selected, result.vsel);
+ try std.testing.expect(result.select);
+ try std.testing.expectEqual(@as(i32, 1), result.cur_row);
+ try std.testing.expectEqual(@as(i32, 12), result.cur_col);
+ try std.testing.expectEqual(@as(usize, 1), file.scroll);
+ try std.testing.expectEqual(@as(i32, 3), result.hscroll);
+ try std.testing.expectEqualStrings("after", file.output.?.arg());
+ try std.testing.expectEqual(@as(usize, 0), p.active);
+ try std.testing.expectEqual(id, source.search_pane.?);
+ try std.testing.expectEqual(@as(usize, 1), source.search_row.?);
+ try std.testing.expectEqual(result.serial, p.look_walk_owner.?);
+ try std.testing.expect(p.fs.panes[id].events.empty());
+ }
+
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "changed", try p.gpa.dupe(u8, "short\n"), null);
+ try std.testing.expectEqual(revision + 1, file.revision);
+ try std.testing.expectEqualStrings("short\n", file.content);
+ try std.testing.expectEqualStrings("changed", file.output.?.arg());
+ try std.testing.expectEqual(@as(usize, 0), file.line_starts.len);
+ try std.testing.expectEqual(@as(usize, 0), file.highlights.len);
+ try std.testing.expect(file.syntax_dirty);
+ try std.testing.expect(!result.vsel.active and !result.select);
+ try std.testing.expectEqual(@as(i32, 0), result.cur_row);
+ try std.testing.expectEqual(@as(i32, 0), result.cur_col);
+ try std.testing.expectEqual(@as(usize, 0), file.scroll);
+ try std.testing.expectEqual(@as(i32, 0), result.hscroll);
+ try std.testing.expect(source.search_row == null);
+ const events = &p.fs.panes[id].events;
+ try std.testing.expect(std.mem.startsWith(u8, events.peek().?, "KD"));
+ events.pop();
+ try std.testing.expect(std.mem.startsWith(u8, events.peek().?, "KI"));
+ events.pop();
+ try std.testing.expect(events.empty());
+}
+
+test "rejected result refresh frees incoming text and leaves the existing output intact" {
+ var allocator: std.testing.FailingAllocator = .init(std.testing.allocator, .{});
+ const p = try Pardes.init(allocator.allocator(), .{ .tty_only = true, .cols = 80, .rows = 24 });
+ defer p.deinit();
+ const source = try p.setTestFile("notes\n");
+ try panes.Output.fillResults(p, 0, "/tmp", .{ .query = .references }, "before", try p.gpa.dupe(u8, "keep\n"), 1);
+ const id = source.search_pane.?;
+ const result = p.panes[id].?;
+ const file = &result.file.?;
+ const body_ptr = file.content.ptr;
+ const revision = file.revision;
+ const free_slot = p.freeSlot();
+ var oversized: [panes.Output.max_arg + 1]u8 = @splat('x');
+ for ([_]bool{ false, true }) |oom| {
+ const incoming = try p.gpa.dupe(u8, "discard\n");
+ const freed = allocator.freed_bytes;
+ const deallocations = allocator.deallocations;
+ if (oom) allocator.fail_index = allocator.alloc_index;
+ defer allocator.fail_index = std.math.maxInt(usize);
+ try std.testing.expectError(if (oom) error.OutOfMemory else error.ArgumentTooLong, panes.Output.fillResults(
+ p,
+ 0,
+ "/tmp",
+ if (oom) .search else .{ .query = .references },
+ if (oom) "new output" else &oversized,
+ incoming,
+ 0,
+ ));
+ try std.testing.expectEqual(freed + "discard\n".len, allocator.freed_bytes);
+ try std.testing.expectEqual(deallocations + 1, allocator.deallocations);
+ try std.testing.expectEqual(body_ptr, file.content.ptr);
+ try std.testing.expectEqual(revision, file.revision);
+ try std.testing.expectEqualStrings("keep\n", file.content);
+ try std.testing.expectEqualStrings("before", file.output.?.arg());
+ try std.testing.expectEqual(free_slot, p.freeSlot());
+ try std.testing.expectEqual(id, source.search_pane.?);
+ try std.testing.expectEqual(@as(usize, 1), source.search_row.?);
+ try std.testing.expectEqual(@as(usize, 0), p.active);
+ }
+ try std.testing.expect(allocator.has_induced_failure);
+}