diff options
Diffstat (limited to 'test/output.zig')
| -rw-r--r-- | test/output.zig | 154 |
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); + } + } +} |
