diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-31 11:42:18 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:08 -0300 |
| commit | 911215846f1d5a9b2e37a00e74fb9266d4a0884a (patch) | |
| tree | 31413ca3336cbbd1933d9dc3e24f09a3b8ef46b4 /src/lsp | |
| parent | 0b7a480bef38b209741c520e2478d178767a9e51 (diff) | |
| download | pardes-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.zig | 36 | ||||
| -rw-r--r-- | src/lsp/lsp_zls.zig | 10 |
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); } } |
