From af435ba7e4fd34dd5eab17bd21ce535b6f9b8413 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 15 Sep 2026 17:44:34 -0300 Subject: Align search and LSP results in groups of eight matches --- src/locations.zig | 94 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 23 deletions(-) (limited to 'src/locations.zig') diff --git a/src/locations.zig b/src/locations.zig index ebc87a2e..43d99005 100644 --- a/src/locations.zig +++ b/src/locations.zig @@ -46,14 +46,31 @@ pub fn sameInput(input: []const u8, rendered: []const u8, rows: []const Row) boo if (std.mem.endsWith(u8, input, "\n") != std.mem.endsWith(u8, rendered, "\n")) return false; var raw_lines = std.mem.splitScalar(u8, input, '\n'); var shown_lines = std.mem.splitScalar(u8, rendered, '\n'); - for (rows) |row| { + var width: usize = 0; + for (rows, 0..) |row, index| { if (row.kind != .match) return false; + if (index % alignment_entries == 0) { + width = 0; + var chunk = raw_lines; + for (0..alignment_entries) |_| { + const line = chunk.next() orelse break; + const target = parseRaw(line); + width = @max(width, pardes.panes.File.displayWidth(line[0..target.end])); + } + } const raw = raw_lines.next() orelse return false; const shown = shown_lines.next() orelse return false; const target = parseRaw(raw); if (target.end != row.location_end or row.code_start > shown.len or !std.mem.eql(u8, raw[0..target.end], shown[0..row.location_end]) or !std.mem.eql(u8, raw[target.code_start..], shown[row.code_start..])) return false; + // A restored result may still have the old pane-wide padding. Do not + // let the identical-input shortcut retain that layout when rerun. + if (row.code_start <= row.location_end) return false; + const padding = shown[row.location_end..row.code_start]; + if (padding.len != width - pardes.panes.File.displayWidth(raw[0..target.end]) + 1 or + padding[padding.len - 1] != '\t') return false; + for (padding[0 .. padding.len - 1]) |byte| if (byte != ' ') return false; } if (raw_lines.next()) |tail| if (tail.len != 0) return false; return raw_lines.next() == null; @@ -136,7 +153,7 @@ fn sourceText(p: *pardes.Pardes, arena: std.mem.Allocator, dir: []const u8, path } /// Inputs are sorted results. Expand each source group, merge context with -/// matches, then align once across the final rows. All temporary source data +/// matches, then align groups of eight matches. All temporary source data /// stays in the caller's scratch arena; returned text and rows are owned. pub fn format(p: *pardes.Pardes, dir: []const u8, input: []const u8, anchor: ?usize, expand: bool) !Result { const arena = p.scratch.allocator(); @@ -210,8 +227,6 @@ pub fn format(p: *pardes.Pardes, dir: []const u8, input: []const u8, anchor: ?us pending.items.len = write; first = end; } - var width: usize = 0; - for (pending.items) |row| width = @max(width, labelWidth(row)); var out: std.Io.Writer.Allocating = .init(p.gpa); errdefer out.deinit(); var rows: std.ArrayList(Row) = .empty; @@ -223,32 +238,65 @@ pub fn format(p: *pardes.Pardes, dir: []const u8, input: []const u8, anchor: ?us rows.deinit(p.gpa); } var mapped = anchor; - for (pending.items, 0..) |row, index| { - const before = out.written().len; - const context = row.original == null; - try out.writer.writeAll(row.label); - const location_end = if (row.hidden) 0 else out.written().len - before; - if (row.depth > 0) { - try out.writer.writeByte(' '); - try out.writer.splatByteAll('*', row.depth); + first = 0; + while (first < pending.items.len) { + const end = alignmentEnd(pending.items, first); + var width: usize = 0; + for (pending.items[first..end]) |row| width = @max(width, labelWidth(row)); + for (pending.items[first..end], first..) |row, index| { + const before = out.written().len; + const context = row.original == null; + try out.writer.writeAll(row.label); + const location_end = if (row.hidden) 0 else out.written().len - before; + if (row.depth > 0) { + try out.writer.writeByte(' '); + try out.writer.splatByteAll('*', row.depth); + } + try out.writer.splatByteAll(' ', width - labelWidth(row)); + try out.writer.writeByte('\t'); + const code_start = out.written().len - before; + try out.writer.writeAll(row.code); + if (index + 1 < pending.items.len or std.mem.endsWith(u8, input, "\n")) try out.writer.writeByte('\n'); + const owned_path = try p.gpa.dupe(u8, row.path); + errdefer p.gpa.free(owned_path); + const owned_colors = try p.gpa.dupe(u8, row.colors); + errdefer p.gpa.free(owned_colors); + try rows.append(p.gpa, .{ .kind = if (context) .context else .match, .declaration = row.declaration, .path = owned_path, .at = row.at, .location_end = location_end, .code_start = code_start, .colors = owned_colors }); + if (anchor != null and row.original == anchor) mapped = index; } - try out.writer.splatByteAll(' ', width - labelWidth(row)); - try out.writer.writeByte('\t'); - const code_start = out.written().len - before; - try out.writer.writeAll(row.code); - if (index + 1 < pending.items.len or std.mem.endsWith(u8, input, "\n")) try out.writer.writeByte('\n'); - const owned_path = try p.gpa.dupe(u8, row.path); - errdefer p.gpa.free(owned_path); - const owned_colors = try p.gpa.dupe(u8, row.colors); - errdefer p.gpa.free(owned_colors); - try rows.append(p.gpa, .{ .kind = if (context) .context else .match, .declaration = row.declaration, .path = owned_path, .at = row.at, .location_end = location_end, .code_start = code_start, .colors = owned_colors }); - if (anchor != null and row.original == anchor) mapped = index; + first = end; } const owned_rows = try rows.toOwnedSlice(p.gpa); errdefer freeRows(p.gpa, owned_rows); return .{ .content = try out.toOwnedSlice(), .rows = owned_rows, .anchor = mapped }; } +const alignment_entries = 8; + +/// Keep neighboring context with the nearer match at a chunk boundary. Shared +/// context stays deduplicated; ties belong to the preceding chunk. +fn alignmentEnd(rows: []const Pending, first: usize) usize { + var matches: usize = 0; + var previous: usize = first; + for (rows[first..], first..) |row, index| { + if (row.original == null) continue; + if (matches == alignment_entries) { + var end = index; + while (end > previous + 1) { + const context = rows[end - 1]; + if (!std.mem.eql(u8, context.path, row.path)) break; + if (std.mem.eql(u8, rows[previous].path, row.path) and + row.at.line -| context.at.line >= context.at.line -| rows[previous].at.line) break; + end -= 1; + } + return end; + } + matches += 1; + previous = index; + } + return rows.len; +} + fn labelWidth(row: Pending) usize { return pardes.panes.File.displayWidth(row.label) + @as(usize, if (row.depth > 0) row.depth + 1 else 0); -- cgit v1.3