diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-15 16:05:34 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-15 17:24:42 -0300 |
| commit | b292808d75078c6b121e4873b150574308bcdc6f (patch) | |
| tree | 5c213e1311de58f9259de5e416dfeba6b955b151 | |
| parent | 8ac4a48a2e8e66a9e72bcbc61c114be8db9215fd (diff) | |
| download | pardes-b292808d75078c6b121e4873b150574308bcdc6f.tar.gz pardes-b292808d75078c6b121e4873b150574308bcdc6f.zip | |
Use result metadata when walking output locations with n and N
| -rw-r--r-- | src/panes.zig | 5 | ||||
| -rw-r--r-- | src/pardes.zig | 14 | ||||
| -rw-r--r-- | test/output.zig | 67 |
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; |
