summaryrefslogtreecommitdiff
path: root/src/lsp
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-31 11:42:18 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:08 -0300
commit911215846f1d5a9b2e37a00e74fb9266d4a0884a (patch)
tree31413ca3336cbbd1933d9dc3e24f09a3b8ef46b4 /src/lsp
parent0b7a480bef38b209741c520e2478d178767a9e51 (diff)
downloadpardes-911215846f1d5a9b2e37a00e74fb9266d4a0884a.tar.gz
pardes-911215846f1d5a9b2e37a00e74fb9266d4a0884a.zip
a Look path can name a range, and search selects what it found
file:LINE:COL-ENDLINE:ENDCOL, with the two short forms people actually type reading naturally: file:412:9-21 on one line, file:412-418 whole ones. Ends are inclusive. A path feature, not a search feature — a ranged path typed in a tag or middle-clicked out of a shell's output selects just the same; search is only its first consumer. The dash is the fussy part. `-` was already a file char, so a ranged word survives click expansion whole, but a range needs a number on BOTH sides or my-file:10, build-2 and 2026-07-30 would stop being paths. Table-driven test in look.zig for exactly that. Selecting goes through the cellRange/setPaneRange pair the multi-cursor work left, and hxOff clamps both ends, so a stale range selects what still exists rather than crashing or reaching past EOF — pinned with an 8:6-400:9 range in a nine-line file. Producers: / search, Grep, and five LSP sites through a new spanRow — goto, references, rename tokens and both symbol lists were throwing away real protocol ranges at path:line:col. Left alone deliberately: Find rows are bare paths with nothing to span, a jump is a spot not a span, and the diagnostic and format paths only ever have a point, where half a range would be worse than none. One knock-on worth knowing: n now leaves an EXPLICIT selection, so a topbar execute chords it. grep.snap's no-match step was silently becoming `Grep TARGET`; it runs from the leader path now, which never chords, and the dedicated chord steps stayed where they were.
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);
}
}