summaryrefslogtreecommitdiff
path: root/src/lsp/lsp_zls.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-09 09:43:51 -0300
committerGabriel Schneider <[email protected]>2026-08-10 09:17:07 -0300
commit218d8577cbffb45a6ee80cea52418864a044e452 (patch)
treeb0b31c1e8091d6dea6fb767883e8c78e48cc385e /src/lsp/lsp_zls.zig
parent599dd82f96b9d091aae78300aa6c3fbc81f9eb69 (diff)
downloadpardes-218d8577cbffb45a6ee80cea52418864a044e452.tar.gz
pardes-218d8577cbffb45a6ee80cea52418864a044e452.zip
lsp rows: paths relative to the asking file, and the completion text
Diffstat (limited to 'src/lsp/lsp_zls.zig')
-rw-r--r--src/lsp/lsp_zls.zig99
1 files changed, 65 insertions, 34 deletions
diff --git a/src/lsp/lsp_zls.zig b/src/lsp/lsp_zls.zig
index dfc2db90..e1624df0 100644
--- a/src/lsp/lsp_zls.zig
+++ b/src/lsp/lsp_zls.zig
@@ -310,17 +310,20 @@ fn run(gpa: std.mem.Allocator, arena: std.mem.Allocator, req: lsp.Req, out: *std
const off: usize = @min(req.offset, req.source.len);
+ // the rows of the kinds that only ever name THIS file spell it once
+ const here = lsp.rel(req.root, req.path);
+
switch (kind) {
- .definition, .implementation => try goto(arena, &analyser, handle, off, .definition, out, tr),
- .declaration => try goto(arena, &analyser, handle, off, .declaration, out, tr),
- .type_definition => try goto(arena, &analyser, handle, off, .type_definition, out, tr),
+ .definition, .implementation => try goto(arena, &analyser, handle, off, .definition, req.root, out, tr),
+ .declaration => try goto(arena, &analyser, handle, off, .declaration, req.root, out, tr),
+ .type_definition => try goto(arena, &analyser, handle, off, .type_definition, req.root, out, tr),
.hover => try hover(arena, &analyser, handle, off, out),
- .document_symbols => try documentSymbols(gpa, arena, req.path, &handle.tree, out),
- .references, .select_refs => try references(arena, &analyser, handle, off, null, out),
- .rename => try references(arena, &analyser, handle, off, req.arg, out),
- .diagnostics => try diagnostics(gpa, arena, req.path, &handle.tree, out),
+ .document_symbols => try documentSymbols(gpa, arena, here, &handle.tree, out),
+ .references, .select_refs => try references(arena, &analyser, handle, off, null, req.root, out),
+ .rename => try references(arena, &analyser, handle, off, req.arg, req.root, out),
+ .diagnostics => try diagnostics(gpa, arena, here, &handle.tree, out),
.code_action => try codeActions(gpa, arena, &analyser, handle, off, out),
- .completion => try completion(arena, &analyser, handle, off, req.source, out, tr),
+ .completion => try completion(arena, &analyser, handle, off, req.root, req.source, out, tr),
else => {},
}
}
@@ -492,12 +495,13 @@ fn lineAt(src: []const u8, want: usize) []const u8 {
/// Emit one row for a token in whatever file it lives in — which for a goto
/// into `std` is not the file the cursor was in, so the path comes off the
-/// handle's uri rather than off `req`.
-fn rowForToken(arena: std.mem.Allocator, th: Analyser.TokenWithHandle, out: *std.Io.Writer) !void {
+/// handle's uri rather than off `req`. `base` is `req.root`, the directory the
+/// row is written relative to (lsp.rel).
+fn rowForToken(arena: std.mem.Allocator, base: []const u8, th: Analyser.TokenWithHandle, out: *std.Io.Writer) !void {
const tree = &th.handle.tree;
if (th.token >= tree.tokens.len) return;
const r = offsets.tokenToRange(tree, th.token, enc);
- const path = th.handle.uri.toFsPath(arena) catch return;
+ const path = lsp.rel(base, th.handle.uri.toFsPath(arena) catch return);
lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, lineAt(tree.source, r.start.line));
}
@@ -516,6 +520,8 @@ fn goto(
handle: *DocumentStore.Handle,
off: usize,
kind: GotoKind,
+ /// req.root: what the rows' paths are written relative to (lsp.rel)
+ base: []const u8,
out: *std.Io.Writer,
tr: *Trace,
) !void {
@@ -600,7 +606,7 @@ fn goto(
// knows the graph and baked it in; consult that.
if (moduleRoot(str)) |path| {
tr.note("`{s}` is a build.zig dependency; resolved from the compiled-in module map", .{str});
- lsp.row(out, path, 0, 0, str);
+ lsp.row(out, lsp.rel(base, path), 0, 0, str);
return;
}
tr.note("STOP: `{s}` does not resolve to a file. Relative paths, `std` and this", .{str});
@@ -608,8 +614,8 @@ fn goto(
tr.note(" OWN internal module names need the build graph we do not run.", .{});
return;
},
- .one => |u| lsp.row(out, u.toFsPath(arena) catch return, 0, 0, str),
- .many => |us| for (us) |u| lsp.row(out, u.toFsPath(arena) catch continue, 0, 0, str),
+ .one => |u| lsp.row(out, lsp.rel(base, u.toFsPath(arena) catch return), 0, 0, str),
+ .many => |us| for (us) |u| lsp.row(out, lsp.rel(base, u.toFsPath(arena) catch continue), 0, 0, str),
}
return;
},
@@ -625,8 +631,8 @@ fn goto(
// access can name several and a partial answer beats none.
for (decls.items) |d| {
switch (kind) {
- .declaration => try rowForToken(arena, d.definitionToken(analyser, false) catch continue, out),
- .definition => try rowForToken(arena, d.definitionToken(analyser, true) catch continue, out),
+ .declaration => try rowForToken(arena, base, d.definitionToken(analyser, false) catch continue, out),
+ .definition => try rowForToken(arena, base, d.definitionToken(analyser, true) catch continue, out),
.type_definition => {
// peel error unions / pointers / optionals the way ZLS does,
// then take the container's own name token
@@ -638,14 +644,14 @@ fn goto(
try analyser.resolveOptionalUnwrap(r) orelse break;
}
if (r.typeDefinitionToken()) |th| {
- try rowForToken(arena, th, out);
+ try rowForToken(arena, base, th, out);
continue;
}
}
const nd = try d.typeDeclarationNode() orelse continue;
const t2 = &nd.handle.tree;
const rr = offsets.nodeToRange(t2, nd.node, enc);
- const path = nd.handle.uri.toFsPath(arena) catch continue;
+ const path = lsp.rel(base, nd.handle.uri.toFsPath(arena) catch continue);
lsp.spanRow(out, path, rr.start.line, rr.start.character, rr.end.line, rr.end.character, lineAt(t2.source, rr.start.line));
},
}
@@ -741,6 +747,8 @@ fn completion(
analyser: *Analyser,
handle: *DocumentStore.Handle,
off: usize,
+ /// req.root: what the rows' paths are written relative to (lsp.rel)
+ base: []const u8,
/// the buffer as the USER has it, before completionSource repaired it. The
/// rows are read out of THIS one; the handle's tree is the repaired copy
/// and its text is not what is on screen.
@@ -833,20 +841,37 @@ fn completion(
lit = lit.resolveDeclLiteralResultType();
if (!(try lit.typeOf(analyser)).eql(try ty.typeOf(analyser))) continue;
}
+ // The WORD, off the decl's own name token. Not the definition token
+ // below it: `definitionToken` resolves an ALIAS through to what it
+ // names, so for `pub const base64 = @import("base64.zig")` it lands on
+ // base64.zig's first token and the "name" would come out as that file's
+ // doc comment. What goes after the dot is what the container calls it.
+ const name_tok = d.nameToken();
+ if (name_tok >= d.handle.tree.tokens.len) continue;
+ const name = offsets.tokenToSlice(&d.handle.tree, name_tok);
+
const th = d.definitionToken(analyser, true) catch continue;
const dtree = &th.handle.tree;
if (th.token >= dtree.tokens.len) continue;
var r = offsets.tokenToRange(dtree, th.token, enc);
- const path = th.handle.uri.toFsPath(arena) catch continue;
- if (th.handle != handle) {
- lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, lineAt(dtree.source, r.start.line));
- } else {
- if (pad != 0 and r.start.line == dot.line and @as(usize, r.start.character) >= dot.col + pad) {
- r.start.character -= @intCast(pad);
- if (r.end.line == r.start.line) r.end.character -= @intCast(pad);
- }
- lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, lineAt(orig, r.start.line));
+ const path = lsp.rel(base, th.handle.uri.toFsPath(arena) catch continue);
+ // The name, then its declaration line: "what goes here" before "where
+ // does it come from", which is the order the question was asked in —
+ // every other kind here answers a WHERE, this one answers a WHAT and
+ // the location is the evidence. Two spaces rather than a padded column,
+ // because the `path:LINE:COL-END` in front of it is already ragged and
+ // there is nothing to align to.
+ const text = try std.fmt.allocPrint(arena, "{s} {s}", .{
+ name,
+ std.mem.trim(u8, lineAt(if (th.handle == handle) orig else dtree.source, r.start.line), " \t\r\n"),
+ });
+ if (th.handle == handle and pad != 0 and r.start.line == dot.line and
+ @as(usize, r.start.character) >= dot.col + pad)
+ {
+ r.start.character -= @intCast(pad);
+ if (r.end.line == r.start.line) r.end.character -= @intCast(pad);
}
+ lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, text);
n += 1;
}
}
@@ -943,7 +968,7 @@ fn workspaceSymbols(
var tree: Ast = Ast.parse(arena, src, .zig) catch continue;
defer tree.deinit(arena);
const syms = zls.document_symbol.getDocumentSymbols(arena, &tree, enc) catch continue;
- try filterSymbols(gpa, arena, path, syms, "", req.arg, &n, out);
+ try filterSymbols(gpa, arena, lsp.rel(req.root, path), syms, "", req.arg, &n, out);
}
}
@@ -1006,6 +1031,8 @@ fn references(
handle: *DocumentStore.Handle,
off: usize,
new_name: ?[]const u8,
+ /// req.root: what the rows' paths are written relative to (lsp.rel)
+ base: []const u8,
out: *std.Io.Writer,
) !void {
const tree = &handle.tree;
@@ -1022,6 +1049,9 @@ fn references(
if (want.len == 0) return;
const lines: Lines = try .build(arena, tree.source);
+ // every row names THIS file (the walk is this file's tokens), so the path
+ // is spelled once rather than per hit
+ const path = lsp.rel(base, handle.uri.toFsPath(arena) catch return);
var n: usize = 0;
for (0..tree.tokens.len) |i| {
if (n >= max_rows) return;
@@ -1037,7 +1067,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.spanRow(out, handle.uri.toFsPath(arena) catch return, r.start.line, r.start.character, r.end.line, r.end.character, text);
+ lsp.spanRow(out, path, r.start.line, r.start.character, r.end.line, r.end.character, text);
}
}
@@ -1164,10 +1194,10 @@ fn workspaceDiagnostics(
const src = readFileZ(arena, io, path) catch continue;
var tree: Ast = Ast.parse(arena, src, .zig) catch continue;
defer tree.deinit(arena);
- total += treeDiagnostics(gpa, arena, path, &tree, out) catch continue;
+ total += treeDiagnostics(gpa, arena, lsp.rel(req.root, path), &tree, out) catch continue;
if (total >= max_rows) return;
}
- if (total == 0) lsp.row(out, req.path, 0, 0, try std.fmt.allocPrint(arena, "no diagnostics in {d} file(s)", .{files.items.len}));
+ if (total == 0) lsp.row(out, lsp.rel(req.root, req.path), 0, 0, try std.fmt.allocPrint(arena, "no diagnostics in {d} file(s)", .{files.items.len}));
}
// ----------------------------------------------------------- code actions
@@ -1220,10 +1250,11 @@ fn codeActions(
/// honest shape of "format" behind a read-only seam, and it is still the
/// answer to the question the keypress asks.
fn formatQuery(arena: std.mem.Allocator, req: lsp.Req, out: *std.Io.Writer) !void {
+ const path = lsp.rel(req.root, req.path);
var tree: Ast = try .parse(arena, req.source, .zig);
defer tree.deinit(arena);
if (tree.errors.len != 0) {
- lsp.row(out, req.path, 0, 0, "cannot format: file does not parse");
+ lsp.row(out, path, 0, 0, "cannot format: file does not parse");
return;
}
var buf: std.ArrayList(u8) = .empty;
@@ -1231,7 +1262,7 @@ fn formatQuery(arena: std.mem.Allocator, req: lsp.Req, out: *std.Io.Writer) !voi
tree.render(arena, &w.writer, .{}) catch return;
const formatted = w.written();
if (std.mem.eql(u8, formatted, req.source)) {
- lsp.row(out, req.path, 0, 0, "already formatted");
+ lsp.row(out, path, 0, 0, "already formatted");
return;
}
var a = std.mem.splitScalar(u8, req.source, '\n');
@@ -1246,7 +1277,7 @@ fn formatQuery(arena: std.mem.Allocator, req: lsp.Req, out: *std.Io.Writer) !voi
const ys = y orelse "";
if (std.mem.eql(u8, xs, ys)) continue;
n += 1;
- lsp.row(out, req.path, ln, 0, try std.fmt.allocPrint(arena, "- {s} + {s}", .{
+ lsp.row(out, path, ln, 0, try std.fmt.allocPrint(arena, "- {s} + {s}", .{
std.mem.trim(u8, xs, " \t"), std.mem.trim(u8, ys, " \t"),
}));
}