summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/config.md3
-rw-r--r--src/locations.zig16
-rw-r--r--test/output.zig64
3 files changed, 75 insertions, 8 deletions
diff --git a/docs/config.md b/docs/config.md
index f369d798..5b5641ba 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -157,7 +157,8 @@ LocationsConfig context:5 tscontext:on tslocations:off
`0`). Overlapping context is shown once. These neighboring lines omit their
locations and keep their code aligned with the matching result.
- `tscontext` includes enclosing tree-sitter declaration headers in source
- order (default `off`).
+ order (default `off`). Preceding neighboring context stops at the outermost
+ enclosing declaration, keeping unrelated lines above it out of the result.
- `tslocations` shows a location on the first line of each declaration header
(default `on`); continuation lines keep the same alignment without repeating
the location. Declaration headers use the same muted color as locations, with or
diff --git a/src/locations.zig b/src/locations.zig
index 34d1ad14..0f580e34 100644
--- a/src/locations.zig
+++ b/src/locations.zig
@@ -192,16 +192,11 @@ pub fn format(p: *pardes.Pardes, dir: []const u8, input: []const u8, anchor: ?us
for (pending.items[group_start..]) |*match| {
const row = match.at.line -| 1;
if (row >= count) continue;
- const lo = row -| p.locations_config.context;
- const hi = @min(count -| 1, row +| p.locations_config.context);
- var context_row = lo;
- while (context_row <= hi) : (context_row += 1) {
- if (context_row == row) continue;
- wanted[context_row] |= neighbor_context;
- }
+ var enclosing_start: ?usize = null;
for (declarations) |declaration| {
if (declaration.start_line > row) break;
if (declaration.end_line < row) continue;
+ enclosing_start = @min(enclosing_start orelse declaration.start_line, declaration.start_line);
match.depth += 1;
var header = declaration.start_line;
while (header <= declaration.header_end_line and header < row) : (header += 1) {
@@ -209,6 +204,13 @@ pub fn format(p: *pardes.Pardes, dir: []const u8, input: []const u8, anchor: ?us
if (header == declaration.start_line) wanted[header] |= declaration_start;
}
}
+ const lo = @max(row -| p.locations_config.context, enclosing_start orelse 0);
+ const hi = @min(count -| 1, row +| p.locations_config.context);
+ var context_row = lo;
+ while (context_row <= hi) : (context_row += 1) {
+ if (context_row == row) continue;
+ wanted[context_row] |= neighbor_context;
+ }
}
for (wanted, 0..) |kind, row| {
if (kind > 0) {
diff --git a/test/output.zig b/test/output.zig
index ede8e7dd..c162c1d1 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -1381,3 +1381,67 @@ test "LSP occurrence tint follows the supplied source span through context" {
try std.testing.expectEqualStrings(text, source.file.?.content);
}
}
+
+test "result context upper bound stops at earliest enclosing declaration" {
+ if (!pardes.syntax.enabled) return error.SkipZigTest;
+ const text = "// unrelated prelude\n" ++
+ "const unrelated = 1;\n" ++
+ "\n" ++
+ "const Outer = struct {\n" ++
+ " pub fn run(\n" ++
+ " value: u32,\n" ++
+ " ) void {\n" ++
+ " _ = value;\n" ++
+ " }\n" ++
+ "};\n" ++
+ "// trailing context remains available";
+ for ([_]panes.Output.Origin{ .search, .{ .query = .references } }) |origin| {
+ for ([_]bool{ false, true }) |tscontext| {
+ for ([_]bool{ false, true }) |visible| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ 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 };
+ 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.?;
+ const first: usize = if (tscontext) 4 else 1;
+ try std.testing.expectEqual(first, file.location_rows[0].at.line);
+ try std.testing.expectEqual(@as(usize, 11), file.location_rows[file.location_rows.len - 1].at.line);
+ try std.testing.expectEqual(@as(usize, 8) - first, source.search_row.?);
+ var matches: usize = 0;
+ for (file.location_rows, 0..) |row, index| {
+ try std.testing.expectEqual(first + index, row.at.line);
+ try std.testing.expectEqualStrings(modal.lineSlice(text, row.at.line - 1), modal.lineSlice(file.content, index)[row.code_start..]);
+ if (row.kind == .match) {
+ try std.testing.expectEqual(@as(usize, 8), row.at.line);
+ matches += 1;
+ }
+ }
+ try std.testing.expectEqual(@as(usize, 1), matches);
+ try std.testing.expectEqualStrings(text, source.file.?.content);
+ }
+ }
+ }
+
+ // A top-level comment has no enclosing declaration, and a plain-text
+ // file has no grammar. Neither should lose its preceding ordinary rows.
+ for ([_][]const u8{ "/no-ancestor.zig", "/no-grammar.txt" }) |path| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
+ defer p.deinit();
+ 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 };
+ 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.?;
+ try std.testing.expectEqual(@as(usize, 4), file.location_rows.len);
+ try std.testing.expectEqual(@as(usize, 1), file.location_rows[0].at.line);
+ try std.testing.expectEqual(@as(usize, 4), file.location_rows[3].at.line);
+ try std.testing.expectEqual(@as(usize, 2), source.search_row.?);
+ }
+}