summaryrefslogtreecommitdiff
path: root/src/lsp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lsp')
-rw-r--r--src/lsp/lsp.zig36
-rw-r--r--src/lsp/lsp_zls.zig10
2 files changed, 37 insertions, 9 deletions
diff --git a/src/lsp/lsp.zig b/src/lsp/lsp.zig
index abb91874..b8356724 100644
--- a/src/lsp/lsp.zig
+++ b/src/lsp/lsp.zig
@@ -7,10 +7,12 @@
//! language query is just another thing that answers later.
//!
//! Every backend renders into ONE format: `+Search` rows. A location is
-//! `path:LINE:COL text`, which is what look.zig already resolves and what n/N
-//! already steps, so a multi-result answer IS helix's picker and a single
-//! result IS a jump — with no picker UI written for it. Free text (hover,
-//! a rename's diff) rides the same buffer as plain lines.
+//! `path:LINE:COL text` — or `path:LINE:COL-ENDCOL text` where the protocol
+//! answered with a real range, which a look then SELECTS — and that is what
+//! look.zig already resolves and what n/N already steps, so a multi-result
+//! answer IS helix's picker and a single result IS a jump, with no picker UI
+//! written for it. Free text (hover, a rename's diff) rides the same buffer as
+//! plain lines.
//!
//! `query` is the ONLY thing an implementation supplies. Swapping backends is
//! swapping this one function, which is also how the three competing
@@ -101,6 +103,32 @@ pub fn row(
}) catch {};
}
+/// The same row for a protocol RANGE: `path:LINE:COL-ENDCOL`, which a look
+/// SELECTS rather than parking on its first cell — so `gd` lands on the whole
+/// name and a references list steps symbol by symbol with each one highlighted
+/// (config.range_sep spells the dash; `-` is written out here for the same
+/// reason `:` is).
+///
+/// `end_col` is the protocol's own EXCLUSIVE end character, which is already
+/// the 1-based inclusive column pardes wants, so the conversion is the absence
+/// of one. A span that is empty or crosses lines falls back to the point row:
+/// the only multi-line ranges here are whole declarations, and a goto onto one
+/// wants the cursor at its name, not its body painted.
+pub fn spanRow(
+ out: *std.Io.Writer,
+ path: []const u8,
+ line: usize,
+ col: usize,
+ end_line: usize,
+ end_col: usize,
+ text: []const u8,
+) void {
+ if (end_line != line or end_col <= col) return row(out, path, line, col, text);
+ out.print("{s}:{d}:{d}-{d} {s}\n", .{
+ path, line + 1, col + 1, end_col, std.mem.trim(u8, text, " \t\r\n"),
+ }) catch {};
+}
+
/// Byte offset -> (line, column), both 0-based. Every backend needs it to turn
/// an AST token into a row, so it lives here rather than three times over.
pub fn lineCol(source: []const u8, offset: usize) struct { line: usize, col: usize } {
diff --git a/src/lsp/lsp_zls.zig b/src/lsp/lsp_zls.zig
index 8a36a151..6090af1a 100644
--- a/src/lsp/lsp_zls.zig
+++ b/src/lsp/lsp_zls.zig
@@ -484,7 +484,7 @@ fn rowForToken(arena: std.mem.Allocator, th: Analyser.TokenWithHandle, out: *std
if (th.token >= tree.tokens.len) return;
const r = offsets.tokenToRange(tree, th.token, enc);
const path = th.handle.uri.toFsPath(arena) catch return;
- lsp.row(out, path, r.start.line, r.start.character, lineAt(tree.source, r.start.line));
+ lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, lineAt(tree.source, r.start.line));
}
// ---------------------------------------------------------------- goto
@@ -632,7 +632,7 @@ fn goto(
const t2 = &nd.handle.tree;
const rr = offsets.nodeToRange(t2, nd.node, enc);
const path = nd.handle.uri.toFsPath(arena) catch continue;
- lsp.row(out, path, rr.start.line, rr.start.character, lineAt(t2.source, rr.start.line));
+ lsp.spanRow(out, path, rr.start.line, rr.start.character, rr.end.line, rr.end.character, lineAt(t2.source, rr.start.line));
},
}
}
@@ -703,7 +703,7 @@ fn emitSymbols(
try std.fmt.allocPrint(arena, "{s} {s}", .{ name, d })
else
name;
- lsp.row(out, path, s.selectionRange.start.line, s.selectionRange.start.character, text);
+ lsp.spanRow(out, path, s.selectionRange.start.line, s.selectionRange.start.character, s.selectionRange.end.line, s.selectionRange.end.character, text);
if (s.children) |kids| try emitSymbols(gpa, arena, path, kids, name, n, out);
}
}
@@ -752,7 +752,7 @@ fn filterSymbols(
try std.fmt.allocPrint(arena, "{s}.{s}", .{ prefix, s.name });
if (containsIgnoreCase(s.name, needle)) {
n.* += 1;
- lsp.row(out, path, s.selectionRange.start.line, s.selectionRange.start.character, name);
+ lsp.spanRow(out, path, s.selectionRange.start.line, s.selectionRange.start.character, s.selectionRange.end.line, s.selectionRange.end.character, name);
}
if (s.children) |kids| try filterSymbols(gpa, arena, path, kids, name, needle, n, out);
}
@@ -824,7 +824,7 @@ fn references(
try std.fmt.allocPrint(arena, "{s} -> {s} {s}", .{ want, nn, std.mem.trim(u8, lines.line(r.start.line), " \t") })
else
lines.line(r.start.line);
- lsp.row(out, handle.uri.toFsPath(arena) catch return, r.start.line, r.start.character, text);
+ lsp.spanRow(out, handle.uri.toFsPath(arena) catch return, r.start.line, r.start.character, r.end.line, r.end.character, text);
}
}