summaryrefslogtreecommitdiff
path: root/test/output.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 18:41:15 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit02ea4db25f9802e76112f3b4e69aa75e5a9464dc (patch)
treedaa17ec75135e055a60eff982d97057dcd109c25 /test/output.zig
parent505dedb2459e7b51cbdb0c066698c6e5596d19dd (diff)
downloadpardes-02ea4db25f9802e76112f3b4e69aa75e5a9464dc.tar.gz
pardes-02ea4db25f9802e76112f3b4e69aa75e5a9464dc.zip
Default location results to stacked addresses and source previews
Diffstat (limited to 'test/output.zig')
-rw-r--r--test/output.zig154
1 files changed, 147 insertions, 7 deletions
diff --git a/test/output.zig b/test/output.zig
index c162c1d1..c738a661 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -92,6 +92,7 @@ test "output callers opt into location highlighting instead of matching prose co
for ([_]panes.Output.Origin{ .{ .cmd = .LocationsConfig }, .{ .cmd = .Config }, .{ .query = .hover } }) |origin| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 140, .rows = 30 });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
_ = try p.setTestFile("ordinary\n");
p.presentation.enabled = false;
if (origin == .cmd and origin.cmd == .LocationsConfig) {
@@ -122,6 +123,7 @@ test "location producers retain rendered location match and diagnostic styles" {
for ([_]panes.Output.Origin{ .search, .{ .cmd = .Grep }, .{ .query = .references }, .{ .query = .diagnostics } }) |origin| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 140, .rows = 30 });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
_ = try p.setTestFile("ordinary\n");
p.presentation.enabled = false;
const diagnostic = origin == .query and origin.query == .diagnostics;
@@ -157,6 +159,7 @@ test "location results sort filenames and numeric positions while remapping thei
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();
+ p.locations_config.layout = .@"inline";
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.?].?;
@@ -170,6 +173,7 @@ test "ranked choices and prose retain their supplied order" {
for ([_]pardes.lsp.Kind{ .completion, .hover, .code_action }) |kind| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
_ = 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| {
@@ -192,6 +196,7 @@ test "reference Look selects the next sorted row from the captured request and k
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();
+ p.locations_config.layout = .@"inline";
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";
@@ -254,6 +259,7 @@ test "location sorting leaves mixed prose alone and preserves newline shape" {
for (cases) |case| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
const source = try p.setTestFile("notes\n");
try panes.Output.fillResults(p, 0, "/tmp", .search, "", try p.gpa.dupe(u8, case.before), 1);
try expectResultRows(case.after, &p.panes[source.search_pane.?].?.file.?);
@@ -301,6 +307,7 @@ test "result locations stop before preview text and retain full addresses" {
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();
+ p.locations_config.layout = .@"inline";
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);
@@ -328,6 +335,7 @@ test "reordered identical results preserve the selected row and warm line index"
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();
+ p.locations_config.layout = .@"inline";
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.?;
@@ -360,6 +368,7 @@ test "result sorting allocation failures preserve the previous answer and naviga
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();
+ p.locations_config.layout = .@"inline";
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.?].?;
@@ -374,6 +383,7 @@ test "Find sorts whole filenames and next location uses numeric columns" {
test "result stepping reaches an unterminated last row" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
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);
@@ -604,6 +614,7 @@ test "EffectCode lists readable virtual sources used by this backend" {
test "every enabled setting builtin mutates the State Config reports" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
try std.testing.expectEqual(config.gui_tagline_font_percent, p.settings.font.tagline_percent);
var font_arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
@@ -730,6 +741,7 @@ test "n/N skips formatted context and selects comment match locations" {
for ([_]bool{ false, true }) |tslocations| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
const text = "const Box = struct {\n" ++
" pub fn run() void {\n" ++
" // /tmp neighboring path\n" ++
@@ -742,7 +754,7 @@ test "n/N skips formatted context and selects comment match locations" {
p.gpa.free(source.file.?.path);
source.file.?.path = try p.gpa.dupe(u8, "/look-walk-context.zig");
p.presentation.enabled = false;
- p.locations_config = .{ .context = 1, .tscontext = true, .tslocations = tslocations };
+ p.locations_config = .{ .layout = .@"inline", .context = 1, .tscontext = true, .tslocations = tslocations };
const path = source.file.?.path;
const input = try std.fmt.allocPrint(p.gpa, "{s}:4:9 {s}\n{s}:6:9 {s}\n", .{
path, modal.lineSlice(text, 3), path, modal.lineSlice(text, 5),
@@ -798,6 +810,7 @@ test "n/N selects one output location per row and opens nothing" {
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.locations_config.layout = .@"inline";
p.update(.{ .resize = .{ .cols = 100, .rows = 40 } });
const rid = try walkFixture(p, p.active, paths.cwd, "one");
const results = p.panes[rid].?;
@@ -837,6 +850,7 @@ test "n/N resumes the result output whose Look moved focus away" {
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();
+ p.locations_config.layout = .@"inline";
try std.testing.expect(p.executeBuiltinLine(0, "Look target"));
const rid = p.panes[0].?.search_pane orelse return error.MissingResults;
@@ -889,6 +903,7 @@ test "N exactly reverses n across output panes and the ring seam" {
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.locations_config.layout = .@"inline";
p.update(.{ .resize = .{ .cols = 100, .rows = 40 } });
const first = try walkFixture(p, p.active, paths.cwd, "one");
p.update(.{ .key = .{ .cp = 'n', .alt = true } });
@@ -995,6 +1010,7 @@ 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();
+ p.locations_config.layout = .@"inline";
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";
@@ -1088,6 +1104,7 @@ test "rejected result refresh frees incoming text and leaves the existing output
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();
+ p.locations_config.layout = .@"inline";
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.?;
@@ -1152,8 +1169,9 @@ test "result alignment chunks keep seventeen matches independent and refresh sta
for ([_]usize{ 7, 8 }) |long_at| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
const source = try p.setTestFile("notes\n");
- p.locations_config = .{ .context = 0, .tscontext = false };
+ p.locations_config = .{ .layout = .@"inline", .context = 0, .tscontext = false };
var input: std.ArrayList(u8) = .empty;
defer input.deinit(p.gpa);
var widths: [17]usize = undefined;
@@ -1234,13 +1252,14 @@ test "result alignment chunks assign boundary context to nearest match with prev
for ([_]panes.Output.Origin{ .search, .{ .query = .references } }) |origin| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
var source_text: std.ArrayList(u8) = .empty;
defer source_text.deinit(p.gpa);
for (0..14) |_| try source_text.appendSlice(p.gpa, "\t source();\n");
const source = try p.setTestFile(source_text.items);
p.gpa.free(source.file.?.path);
source.file.?.path = try p.gpa.dupe(u8, "/chunk-boundary-界.txt");
- p.locations_config = .{ .context = 2, .tscontext = false };
+ p.locations_config = .{ .layout = .@"inline", .context = 2, .tscontext = false };
var input: std.ArrayList(u8) = .empty;
defer input.deinit(p.gpa);
for ([_]usize{ 1, 2, 3, 4, 5, 6, 7, 8, 12 }) |line| {
@@ -1295,10 +1314,11 @@ test "declaration context ellipses mark only final header lines before omitted s
for ([_]u8{ 0, 1 }) |neighbors| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
const source = try p.setTestFile(text);
p.gpa.free(source.file.?.path);
source.file.?.path = try p.gpa.dupe(u8, "/ellipsis-context.zig");
- p.locations_config = .{ .context = neighbors, .tscontext = true, .tslocations = visible };
+ p.locations_config = .{ .layout = .@"inline", .context = neighbors, .tscontext = true, .tslocations = visible };
const input = try std.fmt.allocPrint(p.gpa, "{s}:12:17 {s}\n{s}:16:9 {s}\n", .{
source.file.?.path, modal.lineSlice(text, 11), source.file.?.path, modal.lineSlice(text, 15),
});
@@ -1344,10 +1364,11 @@ test "LSP occurrence tint follows the supplied source span through context" {
for ([_]pardes.lsp.Kind{ .references, .definition }) |kind| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 40 });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
const source = try p.setTestFile(text);
p.gpa.free(source.file.?.path);
source.file.?.path = try p.gpa.dupe(u8, "/occurrence.zig");
- p.locations_config = .{ .context = 1, .tscontext = true };
+ p.locations_config = .{ .layout = .@"inline", .context = 1, .tscontext = true };
p.presentation.enabled = false;
var input: std.Io.Writer.Allocating = .init(p.gpa);
defer input.deinit();
@@ -1400,10 +1421,11 @@ test "result context upper bound stops at earliest enclosing declaration" {
for ([_]bool{ false, true }) |visible| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
const source = try p.setTestFile(text);
p.gpa.free(source.file.?.path);
source.file.?.path = try p.gpa.dupe(u8, "/bounded-context.zig");
- p.locations_config = .{ .context = 100, .tscontext = tscontext, .tslocations = visible };
+ p.locations_config = .{ .layout = .@"inline", .context = 100, .tscontext = tscontext, .tslocations = visible };
const input = try std.fmt.allocPrint(p.gpa, "{s}:8:13 {s}\n", .{ source.file.?.path, modal.lineSlice(text, 7) });
try panes.Output.fillResults(p, 0, "/", origin, "upper bound", input, 0);
const file = &p.panes[source.search_pane.?].?.file.?;
@@ -1431,11 +1453,12 @@ test "result context upper bound stops at earliest enclosing declaration" {
for ([_][]const u8{ "/no-ancestor.zig", "/no-grammar.txt" }) |path| {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ p.locations_config.layout = .@"inline";
const plain = "// first\n// second\n// matched\n// trailing";
const source = try p.setTestFile(plain);
p.gpa.free(source.file.?.path);
source.file.?.path = try p.gpa.dupe(u8, path);
- p.locations_config = .{ .context = 100, .tscontext = true, .tslocations = false };
+ p.locations_config = .{ .layout = .@"inline", .context = 100, .tscontext = true, .tslocations = false };
const input = try std.fmt.allocPrint(p.gpa, "{s}:3:4 // matched\n", .{path});
try panes.Output.fillResults(p, 0, "/", .search, "unbounded", input, 0);
const file = &p.panes[source.search_pane.?].?.file.?;
@@ -1445,3 +1468,120 @@ test "result context upper bound stops at earliest enclosing declaration" {
try std.testing.expectEqual(@as(usize, 2), source.search_row.?);
}
}
+
+test "stacked search and LSP results retain source tint navigation and dump ownership" {
+ if (!pardes.syntax.enabled) return error.SkipZigTest;
+ const first = "\tfoo + 界 + foo;";
+ const second = " return foo;";
+ const source_text = "const Box = struct {\n fn run() void {\n" ++ first ++ "\n const neighbor = 42;\n" ++ second ++ "\n }\n};\n";
+ for ([_]panes.Output.Origin{ .search, .{ .query = .references } }) |origin| {
+ for ([_]bool{ false, true }) |visible| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 50 });
+ defer p.deinit();
+ try std.testing.expectEqual(.stacked, p.locations_config.layout);
+ p.locations_config = .{ .context = 1, .tscontext = true, .tslocations = visible };
+ p.presentation.enabled = false;
+ const source = try p.setTestFile(source_text);
+ p.gpa.free(source.file.?.path);
+ source.file.?.path = try p.gpa.dupe(u8, "/stacked-界.zig");
+ var input: std.Io.Writer.Allocating = .init(p.gpa);
+ defer input.deinit();
+ const first_col = std.mem.lastIndexOf(u8, first, "foo").?;
+ const second_col = std.mem.lastIndexOf(u8, second, "foo").?;
+ try pardes.lsp.sourceSpanRow(&input.writer, source.file.?.path, 2, first_col, 2, first_col + 3, first);
+ try pardes.lsp.sourceSpanRow(&input.writer, source.file.?.path, 4, second_col, 4, second_col + 3, second);
+ try panes.Output.fillResults(p, 0, "/", origin, "stacked", try p.gpa.dupe(u8, input.written()), 1);
+ const id = source.search_pane.?;
+ const pane = p.panes[id].?;
+ const file = &pane.file.?;
+ var matches: [2]usize = undefined;
+ var count: usize = 0;
+ var previews: usize = 0;
+ var context_addresses: usize = 0;
+ for (file.location_rows, 0..) |row, index| {
+ const line = modal.lineSlice(file.content, index);
+ switch (row.kind) {
+ .match => {
+ try std.testing.expectEqual(line.len, row.code_start);
+ try std.testing.expect(row.location_end > 0);
+ try std.testing.expect(!std.mem.containsAtLeast(u8, line, 1, "foo"));
+ matches[count] = index;
+ count += 1;
+ },
+ .preview => {
+ try std.testing.expect(index > 0);
+ try std.testing.expectEqual(.match, file.location_rows[index - 1].kind);
+ try std.testing.expectEqual(file.location_rows[index - 1].at, row.at);
+ try std.testing.expectEqualStrings(file.location_rows[index - 1].path, row.path);
+ try std.testing.expectEqual(@as(usize, 0), row.location_end);
+ try std.testing.expectEqual(@as(usize, 0), row.code_start);
+ try std.testing.expectEqualStrings(if (previews == 0) first else second, line);
+ previews += 1;
+ },
+ .context => {
+ if (row.location_end > 0) {
+ try std.testing.expect(visible and row.declaration);
+ try std.testing.expectEqual(line.len, row.code_start);
+ context_addresses += 1;
+ } else {
+ try std.testing.expectEqual(@as(usize, 0), row.code_start);
+ try std.testing.expectEqualStrings(modal.lineSlice(source_text, row.at.line - 1), line);
+ }
+ },
+ }
+ }
+ try std.testing.expectEqual(@as(usize, 2), count);
+ try std.testing.expectEqual(count, previews);
+ try std.testing.expectEqual(@as(usize, if (visible) 2 else 0), context_addresses);
+ try std.testing.expectEqual(matches[1], source.search_row.?);
+ const original_content = try p.gpa.dupe(u8, file.content);
+ defer p.gpa.free(original_content);
+ try panes.Output.fillResults(p, 0, "/", origin, "stacked", try p.gpa.dupe(u8, input.written()), 0);
+ try std.testing.expectEqual(id, source.search_pane.?);
+ try std.testing.expectEqualStrings(original_content, file.content);
+ pardes.layout.compute(p);
+ var arena = std.heap.ArenaAllocator.init(p.gpa);
+ defer arena.deinit();
+ const surface = try p.render(arena.allocator());
+ const chrome = pardes.ChromeTheme.fromTheme(p.theme());
+ var marked: usize = 0;
+ for (surface.cells) |cell| {
+ if (!std.meta.eql(cell.style.bg, pardes.Color{ .rgb = chrome.search_bg })) continue;
+ try std.testing.expect(cell.text[0] == 'f' or cell.text[0] == 'o');
+ marked += 1;
+ }
+ try std.testing.expectEqual(@as(usize, 6), marked);
+ const number = std.mem.indexOf(u8, file.content, "42").?;
+ try std.testing.expectEqual(@intFromEnum(pardes.syntax.Syn.number), file.highlights[number]);
+ p.active = id;
+ pane.mode = .normal;
+ p.armLookWalk(id);
+ for ([_]struct { key: u21, match: usize }{ .{ .key = 'n', .match = 0 }, .{ .key = 'n', .match = 1 }, .{ .key = 'N', .match = 0 } }) |step| {
+ p.update(.{ .key = .{ .cp = step.key } });
+ try std.testing.expectEqual(@as(i32, @intCast(matches[step.match])), pane.cur_row);
+ const row = file.location_rows[matches[step.match]];
+ const line = modal.lineSlice(file.content, matches[step.match]);
+ try std.testing.expectEqualStrings(line[0..row.location_end], selectedOutputText(pane) orelse "");
+ }
+ try p.dumpState();
+ const restored = try Pardes.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
+ defer restored.deinit();
+ const restored_file = &restored.panes[id].?.file.?;
+ try std.testing.expectEqualStrings(file.content, restored_file.content);
+ try std.testing.expectEqual(file.location_rows.len, restored_file.location_rows.len);
+ for (file.location_rows, restored_file.location_rows) |before, after| {
+ try std.testing.expectEqual(before.kind, after.kind);
+ try std.testing.expectEqual(before.at, after.at);
+ try std.testing.expectEqualStrings(before.path, after.path);
+ try std.testing.expectEqual(before.code_start, after.code_start);
+ try std.testing.expectEqual(before.location_end, after.location_end);
+ try std.testing.expectEqualSlices(u8, before.colors, after.colors);
+ }
+ restored.active = id;
+ restored.armLookWalk(id);
+ restored.update(.{ .key = .{ .cp = 'N' } });
+ try std.testing.expectEqual(@as(i32, @intCast(matches[1])), restored.panes[id].?.cur_row);
+ try std.testing.expectEqualStrings(source_text, source.file.?.content);
+ }
+ }
+}