summaryrefslogtreecommitdiff
path: root/src/lsp/lsp_client.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 18:27:49 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit4a3c5a48224cf542429504e8728e10c802e1fd52 (patch)
tree92b9b0922d8e37abd0af68cc014433449be29f05 /src/lsp/lsp_client.zig
parentf3a9a1a9005024923f32e1acae5ecd509e9bbfd2 (diff)
downloadpardes-4a3c5a48224cf542429504e8728e10c802e1fd52.tar.gz
pardes-4a3c5a48224cf542429504e8728e10c802e1fd52.zip
Highlight exact source occurrences in LSP references and goto results
Diffstat (limited to 'src/lsp/lsp_client.zig')
-rw-r--r--src/lsp/lsp_client.zig37
1 files changed, 29 insertions, 8 deletions
diff --git a/src/lsp/lsp_client.zig b/src/lsp/lsp_client.zig
index 8c6a0da9..c3762f08 100644
--- a/src/lsp/lsp_client.zig
+++ b/src/lsp/lsp_client.zig
@@ -948,15 +948,13 @@ fn emitRange(cx: *Cx, enc: Enc, uri: []const u8, range: ?std.json.Value, note: [
const col = colBytes(lntext, r.sc, enc);
const shown = lsp.rel(cx.base, path);
- const rowtext = if (note.len > 0) note else lntext;
- if (r.el == r.sl and r.ec > r.sc) {
- // spanRow wants the protocol's EXCLUSIVE end as a 1-based inclusive
- // byte column; converting the exclusive utf-16 end unit yields the
- // exclusive byte column, which is the same number.
- const end_col = colBytes(lntext, r.ec, enc);
- try lsp.spanRow(cx.out, shown, r.sl, col, r.el, end_col, rowtext);
+ // A same-line exclusive end byte offset is also the displayed inclusive
+ // one-based column. Multi-line ranges display only their starting address.
+ const end_col = if (r.el == r.sl and r.ec > r.sc) colBytes(lntext, r.ec, enc) else col;
+ if (note.len == 0) {
+ try lsp.sourceSpanRow(cx.out, shown, r.sl, col, r.el, end_col, lntext);
} else {
- try lsp.row(cx.out, shown, r.sl, col, rowtext);
+ try lsp.spanRow(cx.out, shown, r.sl, col, r.el, end_col, note);
}
cx.rows += 1;
}
@@ -2208,3 +2206,26 @@ test "the transport this host actually gives us is a pair, and both ends are clo
try std.testing.expectEqual(@as(isize, msg.len), libc.read(sv[1], &got, got.len));
try std.testing.expectEqualStrings(msg, got[0..msg.len]);
}
+
+test "LSP source snippets preserve indentation with UTF16 byte ranges and prose stays trimmed" {
+ const gpa = std.testing.allocator;
+ const source = "\t const 𝕏 = target; \t\r\n";
+ for ([_]bool{ false, true }) |multiline| {
+ var arena: std.heap.ArenaAllocator = .init(gpa);
+ defer arena.deinit();
+ const json = try std.fmt.allocPrint(arena.allocator(), "{{\"start\":{{\"line\":0,\"character\":14}},\"end\":{{\"line\":{d},\"character\":20}}}}", .{@as(u8, if (multiline) 1 else 0)});
+ const parsed = try std.json.parseFromSlice(std.json.Value, arena.allocator(), json, .{});
+ for ([_]bool{ false, true }) |prose| {
+ var buffer: [256]u8 = undefined;
+ var out: std.Io.Writer = .fixed(&buffer);
+ var cx: Cx = .{ .arena = arena.allocator(), .base = "/", .cur_path = "/file.c", .cur_src = source, .out = &out };
+ try emitRange(&cx, .utf16, "file:///file.c", parsed.value, if (prose) " explanatory label \t" else "");
+ const expected = try std.fmt.allocPrint(arena.allocator(), "file.c:1:17{s} {s}\n", .{
+ if (multiline) "" else "-22",
+ if (prose) "explanatory label" else "\t const 𝕏 = target; \t",
+ });
+ try std.testing.expectEqualStrings(expected, out.buffered());
+ try std.testing.expectEqual(@as(usize, 1), cx.rows);
+ }
+ }
+}