From 02ea4db25f9802e76112f3b4e69aa75e5a9464dc Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 15 Sep 2026 18:41:15 -0300 Subject: Default location results to stacked addresses and source previews --- src/panes.zig | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 4 deletions(-) (limited to 'src/panes.zig') diff --git a/src/panes.zig b/src/panes.zig index 7855866d..59993148 100644 --- a/src/panes.zig +++ b/src/panes.zig @@ -1082,6 +1082,11 @@ pub const File = struct { return saved; } + fn sameSavedLocation(a: dump.LocationRow, b: dump.LocationRow) bool { + return std.mem.eql(u8, a.path, b.path) and a.line == b.line and a.col == b.col and + a.end_line == b.end_line and a.end_col == b.end_col; + } + fn restoreLocationRows(gpa: std.mem.Allocator, content: []const u8, saved: []const dump.LocationRow) ![]locations.Row { if (saved.len == 0) return &.{}; if (saved.len > std.mem.count(u8, content, "\n") + 1) return error.InvalidLocationRows; @@ -1095,16 +1100,33 @@ pub const File = struct { gpa.free(rows); } var lines = std.mem.splitScalar(u8, content, '\n'); - for (saved, rows) |row, *out| { + var previous_line: []const u8 = ""; + for (saved, rows, 0..) |row, *out, index| { const line = lines.next() orelse return error.InvalidLocationRows; if (row.path.len == 0 or row.path.len >= 4096 or - row.location_end > row.code_start or row.code_start == 0 or row.code_start > line.len or - line[row.code_start - 1] != '\t' or + row.location_end > row.code_start or row.code_start > line.len or (row.kind == .match and row.location_end == 0) or (row.declaration and row.kind != .context) or row.line > std.math.maxInt(i32) or row.col > std.math.maxInt(i32) or row.end_line > std.math.maxInt(i32) or row.end_col > std.math.maxInt(i32)) return error.InvalidLocationRows; + const inline_row = row.code_start > row.location_end and row.code_start > 0 and line[row.code_start - 1] == '\t'; + const address_row = !inline_row and row.location_end > 0 and row.code_start == line.len; + const source_row = row.location_end == 0 and row.code_start == 0; + if (!inline_row and !address_row and !source_row) return error.InvalidLocationRows; + if (row.kind == .preview) { + if (!source_row or index == 0) return error.InvalidLocationRows; + const previous = saved[index - 1]; + if (previous.kind != .match or previous.location_end == 0 or previous.code_start != previous_line.len or previous_line[previous_line.len - 1] == '\t' or + !sameSavedLocation(previous, row)) return error.InvalidLocationRows; + } + if (address_row) { + if (index + 1 >= saved.len) return error.InvalidLocationRows; + const following = saved[index + 1]; + const expected: @TypeOf(row.kind) = if (row.kind == .match) .preview else .context; + if (following.kind != expected or following.code_start != 0 or following.location_end != 0 or + following.declaration != row.declaration or !sameSavedLocation(row, following)) return error.InvalidLocationRows; + } const code_len = line.len - row.code_start; if (row.colors_b64.len > std.base64.standard.Encoder.calcSize(code_len)) return error.InvalidLocationRows; const colors = try dump.decodeBytes(gpa, row.colors_b64); @@ -1122,6 +1144,7 @@ pub const File = struct { .colors = colors, }; initialized += 1; + previous_line = line; } if (lines.next()) |remaining| if (remaining.len != 0 or lines.next() != null) return error.InvalidLocationRows; return rows; @@ -1149,6 +1172,32 @@ pub const File = struct { try std.testing.expectError(error.InvalidLocationRows, restoreLocationRows(gpa, "| \tx\n", &.{row})); } + test "stacked location metadata requires matching adjacent preview ownership" { + const gpa = std.testing.allocator; + const header = "x.c:2:1-3"; + const content = header ++ "\nfoo\n"; + const match: dump.LocationRow = .{ .kind = .match, .path = "x.c", .line = 2, .col = 1, .end_line = 2, .end_col = 3, .location_end = header.len, .code_start = header.len }; + const preview: dump.LocationRow = .{ .kind = .preview, .path = "x.c", .line = 2, .col = 1, .end_line = 2, .end_col = 3, .location_end = 0, .code_start = 0 }; + const restored = try restoreLocationRows(gpa, content, &.{ match, preview }); + defer locations.freeRows(gpa, restored); + try std.testing.expectEqual(.preview, restored[1].kind); + try std.testing.expectEqual(restored[0].at, restored[1].at); + try std.testing.expectError(error.InvalidLocationRows, restoreLocationRows(gpa, "foo\n", &.{preview})); + try std.testing.expectError(error.InvalidLocationRows, restoreLocationRows(gpa, header ++ "\n", &.{match})); + var inline_match = match; + inline_match.code_start += 1; + try std.testing.expectError(error.InvalidLocationRows, restoreLocationRows(gpa, header ++ "\t\nfoo\n", &.{ inline_match, preview })); + var wrong = preview; + wrong.path = "other.c"; + try std.testing.expectError(error.InvalidLocationRows, restoreLocationRows(gpa, content, &.{ match, wrong })); + wrong = preview; + wrong.line = 3; + try std.testing.expectError(error.InvalidLocationRows, restoreLocationRows(gpa, content, &.{ match, wrong })); + wrong = preview; + wrong.code_start = 1; + try std.testing.expectError(error.InvalidLocationRows, restoreLocationRows(gpa, content, &.{ match, wrong })); + } + pub fn graphemeDisplayWidth(grapheme: []const u8) usize { if (std.mem.eql(u8, grapheme, "\t")) return config.tab_width; if (grapheme.len == 1 and grapheme[0] >= 0x20 and grapheme[0] < 0x7f) return 1; @@ -2258,6 +2307,18 @@ pub const Output = struct { } } + test "stacked empty previews distinguish separators from trailing newlines" { + var path = [_]u8{ 'x', '.', 'c' }; + const rows = [_]locations.Row{ + .{ .kind = .match, .path = &path, .at = .{ .line = 1, .col = 1 }, .location_end = 7, .code_start = 7 }, + .{ .kind = .preview, .path = &path, .at = .{ .line = 1, .col = 1 }, .location_end = 0, .code_start = 0 }, + }; + try std.testing.expect(locations.sameInput("x.c:1:1 ", "x.c:1:1\n", &rows, .stacked)); + try std.testing.expect(locations.sameInput("x.c:1:1 \n", "x.c:1:1\n\n", &rows, .stacked)); + try std.testing.expect(!locations.sameInput("x.c:1:1 ", "x.c:1:1\n\n", &rows, .stacked)); + try std.testing.expect(!locations.sameInput("x.c:1:1 \n", "x.c:1:1\n", &rows, .stacked)); + } + test "plain output does not opt into location decoration" { const report = rowDecoration(.{ .from = .{ .cmd = .LocationsConfig } }, "LocationsConfig context:5 tscontext:on"); try std.testing.expectEqualDeep(RowDecoration{}, report); @@ -2527,7 +2588,7 @@ pub const Output = struct { if (!std.meta.eql(output.from, from)) continue; if (by_arg and !std.mem.eql(u8, output.arg(), arg)) continue; if (!std.mem.eql(u8, std.fs.path.dirname(rf.path) orelse "", dir)) continue; - if (!locations.sameInput(content, rf.content, rf.location_rows)) continue; + if (!locations.sameInput(content, rf.content, rf.location_rows, p.locations_config.layout)) continue; try setArg(output, arg); p.gpa.free(content); p.active = id; -- cgit v1.3