diff --git a/src/lsp/server.zig b/src/lsp/server.zig index f594295..c399714 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -68,6 +68,8 @@ pub const Server = struct { if (params) |p| self.handleDidClose(p); } else if (std.mem.eql(u8, method, "textDocument/definition")) { if (params) |p| self.handleDefinition(id, p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/references")) { + if (params) |p| self.handleReferences(id, p) catch |e| self.logError(method, e); } else if (std.mem.eql(u8, method, "textDocument/hover")) { if (params) |p| self.handleHover(id, p) catch |e| self.logError(method, e); } else if (std.mem.eql(u8, method, "textDocument/documentSymbol")) { @@ -173,6 +175,83 @@ pub const Server = struct { // ---- Go to definition ---- + fn appendRefLoc(self: *Server, buf: *std.ArrayList(u8), first: *bool, target_doc: *const Document, span: sx.ast.Span) !void { + if (!first.*) try buf.append(self.allocator, ','); + first.* = false; + const uri = try std.fmt.allocPrint(self.allocator, "file://{s}", .{target_doc.path}); + const range = spanToRange(target_doc.source, span); + const loc = try lsp.locationJson(self.allocator, uri, range); + try buf.appendSlice(self.allocator, loc); + } + + /// textDocument/references — all uses of the symbol under the cursor (a + /// reference or a definition). Same-file matches are precise (by symbol + /// index); cross-file matches a top-level name (functions/types/globals). + fn handleReferences(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { + const ctx = try self.extractRequest(id, params) orelse return; + const pos = extractPosition(params) orelse return; + const id_json = ctx.id_json; + const file_path = uriToFilePath(ctx.uri) orelse ""; + const doc = self.documents.get(file_path) orelse return try self.sendResponse(id_json, "null"); + const sema = doc.sema orelse doc.last_good_sema orelse return try self.sendResponse(id_json, "null"); + const offset = positionToOffset(doc.source, pos.line, pos.character) orelse return try self.sendResponse(id_json, "null"); + + var include_decl = true; + if (jsonGet(params, "context")) |c| { + if (jsonGet(c, "includeDeclaration")) |v| { + if (v == .bool) include_decl = v.bool; + } + } + + // Resolve the target symbol: a reference at the cursor, or a definition. + var target_idx: ?u32 = null; + if (sx.sema.findReferenceAtOffset(sema.references, offset)) |ri| { + target_idx = sema.references[ri].symbol_index; + } else if (findSymbolNameAtOffset(sema.symbols, doc.source, offset)) |si| { + target_idx = @intCast(si); + } + if (target_idx == null) return try self.sendResponse(id_json, "[]"); + const target = sema.symbols[target_idx.?]; + const cross_file = target.scope_depth == 0; // only top-level names span files + + var buf = std.ArrayList(u8).empty; + defer buf.deinit(self.allocator); + try buf.append(self.allocator, '['); + var first = true; + + // Current document — precise by symbol index. + if (include_decl) try self.appendRefLoc(&buf, &first, doc, target.def_span); + for (sema.references) |ref| { + if (ref.symbol_index == target_idx.?) try self.appendRefLoc(&buf, &first, doc, ref.span); + } + + // Other documents — match a top-level name by string. + if (cross_file) { + var it = self.documents.by_path.iterator(); + while (it.next()) |entry| { + const odoc = entry.value_ptr.*; + if (std.mem.eql(u8, odoc.path, doc.path)) continue; + const osema = odoc.sema orelse continue; + if (include_decl) { + for (osema.symbols) |sym| { + if (sym.scope_depth == 0 and sym.origin == null and std.mem.eql(u8, sym.name, target.name)) { + try self.appendRefLoc(&buf, &first, odoc, sym.def_span); + } + } + } + for (osema.references) |ref| { + const rs = osema.symbols[ref.symbol_index]; + if (rs.scope_depth == 0 and std.mem.eql(u8, rs.name, target.name)) { + try self.appendRefLoc(&buf, &first, odoc, ref.span); + } + } + } + } + + try buf.append(self.allocator, ']'); + try self.sendResponse(id_json, buf.items); + } + fn handleDefinition(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { const ctx = try self.extractRequest(id, params) orelse return; const pos = extractPosition(params) orelse return; diff --git a/src/lsp/types.zig b/src/lsp/types.zig index 8919337..112ce50 100644 --- a/src/lsp/types.zig +++ b/src/lsp/types.zig @@ -102,7 +102,7 @@ fn writeJsonValue(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, value: /// Build the initialize result JSON. pub fn initializeResultJson(allocator: std.mem.Allocator) ![]const u8 { return std.fmt.allocPrint(allocator, - "{{\"capabilities\":{{\"textDocumentSync\":1,\"definitionProvider\":true,\"hoverProvider\":true,\"documentSymbolProvider\":true," ++ + "{{\"capabilities\":{{\"textDocumentSync\":1,\"definitionProvider\":true,\"referencesProvider\":true,\"hoverProvider\":true,\"documentSymbolProvider\":true," ++ "\"completionProvider\":{{\"triggerCharacters\":[\".\"]}}," ++ "\"signatureHelpProvider\":{{\"triggerCharacters\":[\"(\",\",\"]}}," ++ "\"semanticTokensProvider\":{{\"legend\":{{" ++