summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 18:52:46 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit3f4f26e0e40d06427e72f72e760d01adcdc0326c (patch)
treed4bd56c6bc97f27e7ca11f3ee646513a9e4b116d /test
parent02ea4db25f9802e76112f3b4e69aa75e5a9464dc (diff)
downloadpardes-3f4f26e0e40d06427e72f72e760d01adcdc0326c.tar.gz
pardes-3f4f26e0e40d06427e72f72e760d01adcdc0326c.zip
Reuse bounded source analysis and speed up result context traversal
Diffstat (limited to 'test')
-rw-r--r--test/locations_bench.zig61
-rw-r--r--test/output.zig139
2 files changed, 200 insertions, 0 deletions
diff --git a/test/locations_bench.zig b/test/locations_bench.zig
new file mode 100644
index 00000000..54cebb59
--- /dev/null
+++ b/test/locations_bench.zig
@@ -0,0 +1,61 @@
+const std = @import("std");
+const pds = @import("pardes");
+const loc = pds.locations_config;
+fn now() u64 {
+ var ts: std.c.timespec = undefined;
+ _ = std.c.clock_gettime(.MONOTONIC, &ts);
+ return @as(u64, @intCast(ts.sec)) * 1_000_000_000 + @as(u64, @intCast(ts.nsec));
+}
+// Run: zig build locations-bench -Doptimize=ReleaseFast
+// Timed work is the real Output.fillResults pipeline. Fixtures are open unsaved
+// sources; source generation, input duplication, rendering and subprocesses are
+// excluded. Each layout/config starts with an empty source-analysis cache.
+// Two repeats retain that cache and existing output. No latency threshold applies.
+test "locations benchmark cold and warm formatting" {
+ if (!pds.syntax.supportsPath("large.zig")) return error.ZigGrammarRequired;
+ std.debug.print("layout,context,matches,source_bytes,phase,repeat,elapsed_ns\n", .{});
+ const gpa = std.heap.smp_allocator;
+ for ([_]usize{ 100, 500, 1500 }) |functions| {
+ var source: std.Io.Writer.Allocating = .init(gpa);
+ defer source.deinit();
+ var input: std.Io.Writer.Allocating = .init(gpa);
+ defer input.deinit();
+ try source.writer.writeAll("pub const Module = struct {\n");
+ var row: usize = 2;
+ for (0..functions) |i| {
+ try source.writer.print(" pub fn f{d}(x: u32) u32 {{\n", .{i});
+ row += 1;
+ for (0..20) |j| {
+ try source.writer.print(" const v{d} = x + {d}; // explanatory source comment\n", .{ j, j });
+ row += 1;
+ }
+ const line = " return x; // BENCH_MATCH";
+ try source.writer.print("{s}\n }}\n", .{line});
+ try input.writer.print("large.zig:{d}:1 {s}\n", .{ row, line });
+ row += 2;
+ }
+ try source.writer.writeAll("};\n");
+ const p = try pds.Pardes.init(gpa, .{ .tty_only = true, .cols = 120, .rows = 40 });
+ defer p.deinit();
+ const pane = try p.setTestFile(source.written());
+ gpa.free(pane.file.?.path);
+ pane.file.?.path = try gpa.dupe(u8, "/bench/large.zig");
+ for ([_]loc.Config.Layout{ .@"inline", .stacked }) |layout| {
+ for ([_]loc.Config{ .{ .context = 0, .tscontext = false }, .{ .context = 2, .tscontext = false }, .{ .context = 0, .tscontext = true }, .{ .context = 2, .tscontext = true } }, 0..) |config, mode| {
+ p.locations_config = config;
+ p.locations_cache.deinit(p.tree_sitter_gpa);
+ p.locations_cache = .{};
+ p.locations_config.layout = layout;
+ for (0..3) |rep| {
+ _ = p.scratch.reset(.retain_capacity);
+ const data = try gpa.dupe(u8, input.written());
+ const t = now();
+ try pds.panes.Output.fillResults(p, 0, "/bench", .{ .cmd = .Grep }, "BENCH_MATCH", data, null);
+ const elapsed = now() - t;
+ const modes = [_][]const u8{ "off", "ordinary", "tree_sitter", "both" };
+ std.debug.print("{s},{s},{d},{d},{s},{d},{d}\n", .{ @tagName(layout), modes[mode], functions, source.written().len, if (rep == 0) "cold" else "warm", rep, elapsed });
+ }
+ }
+ }
+ }
+}
diff --git a/test/output.zig b/test/output.zig
index c738a661..21e9f04c 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -1585,3 +1585,142 @@ test "stacked search and LSP results retain source tint navigation and dump owne
}
}
}
+
+test "location analysis cache refreshes unsaved ancestry and query context" {
+ if (!pardes.syntax.enabled) return error.SkipZigTest;
+ const before = "const Outer = struct {\n" ++
+ " fn first() void {\n" ++
+ " marker();\n" ++
+ " }\n" ++
+ " fn second() void {\n" ++
+ " marker();\n" ++
+ " }\n" ++
+ "};\n";
+ const after = "pub fn revised() void {\n // new neighbor\n marker();\n}\n";
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile(before);
+ p.gpa.free(source.file.?.path);
+ source.file.?.path = try p.gpa.dupe(u8, "/analysis-cache.zig");
+ p.locations_config = .{ .context = 0, .tscontext = true, .tslocations = false };
+ const Runner = struct {
+ fn fill(editor: *Pardes, line: usize) !void {
+ const path = editor.panes[0].?.file.?.path;
+ const input = try std.fmt.allocPrint(editor.gpa, "{s}:{d}:9-14 marker();\n", .{ path, line });
+ try panes.Output.fillResults(editor, 0, "/", .search, "marker", input, 0);
+ }
+ };
+ try Runner.fill(p, 3);
+ const id = source.search_pane.?;
+ const file = &p.panes[id].?.file.?;
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "const Outer") != null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn first") != null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn second") == null);
+ const original = file.content.ptr;
+ const revision = file.revision;
+ const analyses = p.locations_cache.analyses;
+ const hits = p.locations_cache.hits;
+ try Runner.fill(p, 3);
+ try std.testing.expectEqual(id, source.search_pane.?);
+ try std.testing.expectEqual(original, file.content.ptr);
+ try std.testing.expectEqual(revision, file.revision);
+ try std.testing.expectEqual(analyses, p.locations_cache.analyses);
+ try std.testing.expect(p.locations_cache.hits > hits);
+
+ // Reusing analysis must still choose the ancestry of the current match.
+ try Runner.fill(p, 6);
+ try std.testing.expectEqual(analyses, p.locations_cache.analyses);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "const Outer") != null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn first") == null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn second") != null);
+ const match = file.location_rows[source.search_row.?];
+ try std.testing.expectEqual(.match, match.kind);
+ try std.testing.expectEqual(@as(usize, 6), match.at.line);
+
+ // The path stays identical, but an unsaved replacement changes its tree.
+ panes.File.setContent(p, &source.file.?, try p.gpa.dupe(u8, after));
+ try Runner.fill(p, 3);
+ try std.testing.expect(p.locations_cache.analyses > analyses);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn revised") != null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "const Outer") == null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn second") == null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") == null);
+
+ p.locations_config.context = 1;
+ try Runner.fill(p, 3);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn revised") != null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") != null);
+ const expanded = try p.gpa.dupe(u8, file.content);
+ defer p.gpa.free(expanded);
+ const expanded_analyses = p.locations_cache.analyses;
+ p.locations_config.context = 0;
+ try Runner.fill(p, 3);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") == null);
+ p.locations_config.context = 1;
+ try Runner.fill(p, 3);
+ try std.testing.expectEqualStrings(expanded, file.content);
+ try std.testing.expectEqual(expanded_analyses, p.locations_cache.analyses);
+ p.locations_config.tscontext = false;
+ try Runner.fill(p, 3);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "fn revised") == null);
+ try std.testing.expect(std.mem.indexOf(u8, file.content, "new neighbor") != null);
+ try std.testing.expectEqualStrings(after, source.file.?.content);
+}
+
+test "location cache eviction retains early colors and honors disk and unsaved replacements" {
+ if (!pardes.syntax.enabled) return error.SkipZigTest;
+ var tmp = std.testing.tmpDir(.{});
+ defer tmp.cleanup();
+ var dir_buf: [4096]u8 = undefined;
+ const dir = dir_buf[0..try tmp.dir.realPath(std.testing.io, &dir_buf)];
+ const comment = "/* open\nconst int value = 42;\n*/\nneedle();\n";
+ const code = "// open\nconst int value = 42;\n//\nneedle();\n";
+ try std.testing.expectEqual(comment.len, code.len);
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ const source = try p.setTestFile("notes\n");
+ p.locations_config = .{ .context = 3, .tscontext = false };
+ var input: std.Io.Writer.Allocating = .init(p.gpa);
+ defer input.deinit();
+ for (0..65) |index| {
+ var name_buf: [32]u8 = undefined;
+ const name = try std.fmt.bufPrint(&name_buf, "f{d:0>2}.c", .{index});
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = name, .data = if (index == 0) comment else code });
+ try input.writer.print("{s}:4:1-6 needle();\n", .{name});
+ }
+ try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, input.written()), null);
+ const id = source.search_pane.?;
+ const file = &p.panes[id].?.file.?;
+ try std.testing.expectEqual(@as(usize, 64), p.locations_cache.len);
+ try std.testing.expectEqual(@as(usize, 65), p.locations_cache.analyses);
+ const Color = struct {
+ fn valueColor(result: *const panes.File.State) !u8 {
+ for (result.location_rows, 0..) |row, index| {
+ if (row.kind != .context or row.at.line != 2 or !std.mem.endsWith(u8, row.path, "f00.c")) continue;
+ const line = modal.lineSlice(result.content, index);
+ const number = std.mem.indexOf(u8, line[row.code_start..], "42").?;
+ try std.testing.expectEqualStrings("const int value = 42;", line[row.code_start..]);
+ try std.testing.expectEqual(line.len - row.code_start, row.colors.len);
+ return row.colors[number];
+ }
+ return error.MissingEarlyContext;
+ }
+ };
+ try std.testing.expectEqual(@intFromEnum(pardes.syntax.Syn.comment), try Color.valueColor(file));
+ const single = "f00.c:4:1-6 needle();\n";
+ // Put the evicted file back before a same-size edit tests exact-byte keys.
+ try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, single), null);
+ const analyses = p.locations_cache.analyses;
+ try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "f00.c", .data = code });
+ try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, single), null);
+ try std.testing.expect(p.locations_cache.analyses > analyses);
+ try std.testing.expectEqual(@intFromEnum(pardes.syntax.Syn.number), try Color.valueColor(file));
+
+ // An open buffer has priority over that disk revision, even with equal size.
+ p.gpa.free(source.file.?.path);
+ source.file.?.path = try std.fs.path.join(p.gpa, &.{ dir, "f00.c" });
+ panes.File.setContent(p, &source.file.?, try p.gpa.dupe(u8, comment));
+ try panes.Output.fillResults(p, 0, dir, .search, "eviction", try p.gpa.dupe(u8, single), null);
+ try std.testing.expectEqual(@intFromEnum(pardes.syntax.Syn.comment), try Color.valueColor(file));
+ try std.testing.expectEqualStrings(comment, source.file.?.content);
+}