diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-15 18:27:49 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 4a3c5a48224cf542429504e8728e10c802e1fd52 (patch) | |
| tree | 92b9b0922d8e37abd0af68cc014433449be29f05 /src/lsp/lsp_client.zig | |
| parent | f3a9a1a9005024923f32e1acae5ecd509e9bbfd2 (diff) | |
| download | pardes-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.zig | 37 |
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); + } + } +} |
