summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 16:05:34 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commitb292808d75078c6b121e4873b150574308bcdc6f (patch)
tree5c213e1311de58f9259de5e416dfeba6b955b151
parent8ac4a48a2e8e66a9e72bcbc61c114be8db9215fd (diff)
downloadpardes-b292808d75078c6b121e4873b150574308bcdc6f.tar.gz
pardes-b292808d75078c6b121e4873b150574308bcdc6f.zip
Use result metadata when walking output locations with n and N
-rw-r--r--src/panes.zig5
-rw-r--r--src/pardes.zig14
-rw-r--r--test/output.zig67
3 files changed, 79 insertions, 7 deletions
diff --git a/src/panes.zig b/src/panes.zig
index 09225068..886d4b28 100644
--- a/src/panes.zig
+++ b/src/panes.zig
@@ -2600,8 +2600,9 @@ pub const Output = struct {
try openStepped(p, id, .{ .cmd = .ThemeSel }, content);
}
- pub fn isWalkRow(out: ?State, line: []const u8) bool {
- const state = out orelse return true;
+ pub fn isWalkRow(file: *const File.State, row: usize, line: []const u8) bool {
+ const state = file.output orelse return true;
+ if (usesLocationsConfig(state.from)) return isResultRow(file, row);
if (!std.meta.eql(state.from, Origin{ .cmd = .ThemeSel })) return true;
// Section labels are for reading, not stops in the command walk.
return std.mem.startsWith(u8, std.mem.trimStart(u8, line, " \t"), comptime config.Runtime.findAction(.theme).?.word ++ " ");
diff --git a/src/pardes.zig b/src/pardes.zig
index eb0a442f..294e6812 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -9636,7 +9636,7 @@ pub const Pardes = struct {
if (budget.* == 0) return null;
budget.* -= 1;
const ln = lines[@intCast(r)];
- if (pane.file) |f| if (!panes.Output.isWalkRow(f.output, ln)) continue;
+ if (pane.file) |*f| if (!panes.Output.isWalkRow(f, @intCast(r), ln)) continue;
const on_start = r == start;
switch (grain) {
.word => {
@@ -9658,10 +9658,14 @@ pub const Pardes = struct {
// one span per row, so there is nothing to keep and nothing to
// scan past: the row either offers it or it does not
.line, .whole => {
- const sp = (if (grain == .line)
- look.lineSpan(p, ln, dir, &realbuf)
- else
- wholeRowSpan(ln)) orelse continue;
+ const sp = span: {
+ if (grain == .whole) break :span wholeRowSpan(ln);
+ if (pane.file) |f| if (@as(usize, @intCast(r)) < f.location_rows.len) {
+ const end = f.location_rows[@intCast(r)].location_end;
+ if (end > 0) break :span @as(?look.Span, .{ .start = 0, .end = end });
+ };
+ break :span look.lineSpan(p, ln, dir, &realbuf);
+ } orelse continue;
const col0: i32 = @intCast(sp.start);
if (lookPast(col0, from, on_start, delta))
return .{ .row = r, .col0 = col0, .col1 = @intCast(sp.end - 1) };
diff --git a/test/output.zig b/test/output.zig
index e835a474..86ea287a 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -620,6 +620,73 @@ fn selectedOutputText(pane: *const pardes.Pane) ?[]const u8 {
return line[lo..@min(line.len, hi + 1)];
}
+test "n/N skips formatted context and selects comment match locations" {
+ if (!pardes.syntax.enabled) return error.SkipZigTest;
+ for ([_]panes.Output.Origin{ .search, .{ .query = .references } }) |origin| {
+ for ([_]bool{ false, true }) |tslocations| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 });
+ defer p.deinit();
+ const text = "const Box = struct {\n" ++
+ " pub fn run() void {\n" ++
+ " // /tmp neighboring path\n" ++
+ " // actual match /tmp\n" ++
+ " // / another neighbor\n" ++
+ " call();\n" ++
+ " // /tmp trailing context\n" ++
+ " }\n};\n";
+ const source = try p.setTestFile(text);
+ 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 };
+ 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),
+ });
+ try panes.Output.fillResults(p, 0, "/", origin, "walk", input, null);
+ const id = source.search_pane.?;
+ const result = p.panes[id].?;
+ const file = &result.file.?;
+ var matches: [2]usize = undefined;
+ var count: usize = 0;
+ for (file.location_rows, 0..) |row, index| {
+ if (row.kind != .match) continue;
+ matches[count] = index;
+ count += 1;
+ }
+ try std.testing.expectEqual(@as(usize, 2), count);
+ const available = p.freeSlot();
+ for ([_]struct { key: u21, match: usize }{
+ .{ .key = 'n', .match = 0 },
+ .{ .key = 'n', .match = 1 },
+ .{ .key = 'n', .match = 0 },
+ .{ .key = 'N', .match = 1 },
+ .{ .key = 'N', .match = 0 },
+ }) |step| {
+ p.update(.{ .key = .{ .cp = step.key } });
+ try std.testing.expectEqual(id, p.active);
+ const row = matches[step.match];
+ try std.testing.expectEqual(@as(i32, @intCast(row)), result.cur_row);
+ const line = modal.lineSlice(file.content, row);
+ try std.testing.expectEqualStrings(line[0..file.location_rows[row].location_end], selectedOutputText(result) orelse "");
+ try std.testing.expectEqual(available, p.freeSlot());
+ }
+ try p.dumpState();
+ const restored = try Pardes.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
+ defer restored.deinit();
+ restored.armLookWalk(id);
+ const restored_result = restored.panes[id].?;
+ restored.update(.{ .key = .{ .cp = 'n' } });
+ try std.testing.expectEqual(@as(i32, @intCast(matches[0])), restored_result.cur_row);
+ restored.update(.{ .key = .{ .cp = 'N' } });
+ try std.testing.expectEqual(@as(i32, @intCast(matches[1])), restored_result.cur_row);
+ const restored_file = &restored_result.file.?;
+ const last = modal.lineSlice(restored_file.content, matches[1]);
+ try std.testing.expectEqualStrings(last[0..restored_file.location_rows[matches[1]].location_end], selectedOutputText(restored_result) orelse "");
+ }
+ }
+}
+
test "n/N selects one output location per row and opens nothing" {
if (platform == .web) return;
var cwd_buf: [4096]u8 = undefined;