diff --git a/src/Server.zig b/src/Server.zig index 2b60c2a..a386f78 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -1135,6 +1135,7 @@ fn referencesDefinitionGlobal( handle: *DocumentStore.Handle, pos_index: usize, include_decl: bool, + comptime highlight: bool, ) !void { const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); @@ -1150,10 +1151,23 @@ fn referencesDefinitionGlobal( &locs, std.ArrayList(types.Location).append, server.config.skip_std_references, + !highlight, ); + + const result: types.ResponseParams = if (highlight) result: { + var highlights = try std.ArrayList(types.DocumentHighlight).initCapacity(arena.allocator(), locs.items.len); + for (locs.items) |loc| { + highlights.appendAssumeCapacity(.{ + .range = loc.range, + .kind = .Text, + }); + } + break :result .{ .DocumentHighlight = highlights.items }; + } else .{ .Locations = locs.items }; + try send(arena, types.Response{ .id = id, - .result = .{ .Locations = locs.items }, + .result = result, }); } @@ -1165,16 +1179,37 @@ fn referencesDefinitionFieldAccess( position: offsets.DocumentPosition, range: analysis.SourceRange, include_decl: bool, + comptime highlight: bool, ) !void { const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); const decl = (try server.getSymbolFieldAccess(handle, arena, position, range)) orelse return try respondGeneric(id, null_result_response); var locs = std.ArrayList(types.Location).init(arena.allocator()); - try references.symbolReferences(arena, &server.document_store, decl, server.offset_encoding, include_decl, &locs, std.ArrayList(types.Location).append, server.config.skip_std_references); + try references.symbolReferences( + arena, + &server.document_store, + decl, + server.offset_encoding, + include_decl, + &locs, + std.ArrayList(types.Location).append, + server.config.skip_std_references, + !highlight, + ); + const result: types.ResponseParams = if (highlight) result: { + var highlights = try std.ArrayList(types.DocumentHighlight).initCapacity(arena.allocator(), locs.items.len); + for (locs.items) |loc| { + highlights.appendAssumeCapacity(.{ + .range = loc.range, + .kind = .Text, + }); + } + break :result .{ .DocumentHighlight = highlights.items }; + } else .{ .Locations = locs.items }; try send(arena, types.Response{ .id = id, - .result = .{ .Locations = locs.items }, + .result = result, }); } @@ -1185,6 +1220,7 @@ fn referencesDefinitionLabel( handle: *DocumentStore.Handle, pos_index: usize, include_decl: bool, + comptime highlight: bool, ) !void { const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); @@ -1192,9 +1228,19 @@ fn referencesDefinitionLabel( const decl = (try getLabelGlobal(pos_index, handle)) orelse return try respondGeneric(id, null_result_response); var locs = std.ArrayList(types.Location).init(arena.allocator()); try references.labelReferences(arena, decl, server.offset_encoding, include_decl, &locs, std.ArrayList(types.Location).append); + const result: types.ResponseParams = if (highlight) result: { + var highlights = try std.ArrayList(types.DocumentHighlight).initCapacity(arena.allocator(), locs.items.len); + for (locs.items) |loc| { + highlights.appendAssumeCapacity(.{ + .range = loc.range, + .kind = .Text, + }); + } + break :result .{ .DocumentHighlight = highlights.items }; + } else .{ .Locations = locs.items }; try send(arena, types.Response{ .id = id, - .result = .{ .Locations = locs.items }, + .result = result, }); } @@ -1747,7 +1793,7 @@ fn initializeHandler(server: *Server, arena: *std.heap.ArenaAllocator, id: types .textDocumentSync = .Full, .renameProvider = true, .completionProvider = .{ .resolveProvider = false, .triggerCharacters = &[_][]const u8{ ".", ":", "@" }, .completionItem = .{ .labelDetailsSupport = true } }, - .documentHighlightProvider = false, + .documentHighlightProvider = true, .hoverProvider = true, .codeActionProvider = false, .declarationProvider = true, @@ -2268,9 +2314,33 @@ fn referencesHandler(server: *Server, arena: *std.heap.ArenaAllocator, id: types const include_decl = req.params.context.includeDeclaration; switch (pos_context) { - .var_access => try server.referencesDefinitionGlobal(arena, id, handle, doc_position.absolute_index, include_decl), - .field_access => |range| try server.referencesDefinitionFieldAccess(arena, id, handle, doc_position, range, include_decl), - .label => try server.referencesDefinitionLabel(arena, id, handle, doc_position.absolute_index, include_decl), + .var_access => try server.referencesDefinitionGlobal(arena, id, handle, doc_position.absolute_index, include_decl, false), + .field_access => |range| try server.referencesDefinitionFieldAccess(arena, id, handle, doc_position, range, include_decl, false), + .label => try server.referencesDefinitionLabel(arena, id, handle, doc_position.absolute_index, include_decl, false), + else => try respondGeneric(id, null_result_response), + } + } else { + try respondGeneric(id, null_result_response); + } +} + +fn documentHighlightHandler(server: *Server, arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.DocumentHighlight) !void { + const tracy_zone = tracy.trace(@src()); + defer tracy_zone.end(); + + const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { + logger.warn("Trying to highlight references in non existent document {s}", .{req.params.textDocument.uri}); + return try respondGeneric(id, null_result_response); + }; + + if (req.params.position.character >= 0) { + const doc_position = try offsets.documentPosition(handle.document, req.params.position, server.offset_encoding); + const pos_context = try analysis.documentPositionContext(arena, handle.document, doc_position); + + switch (pos_context) { + .var_access => try server.referencesDefinitionGlobal(arena, id, handle, doc_position.absolute_index, true, true), + .field_access => |range| try server.referencesDefinitionFieldAccess(arena, id, handle, doc_position, range, true, true), + .label => try server.referencesDefinitionLabel(arena, id, handle, doc_position.absolute_index, true, true), else => try respondGeneric(id, null_result_response), } } else { @@ -2375,6 +2445,7 @@ fn processJsonRpc(server: *Server, arena: *std.heap.ArenaAllocator, parser: *std .{ "textDocument/formatting", requests.Formatting, formattingHandler }, .{ "textDocument/rename", requests.Rename, renameHandler }, .{ "textDocument/references", requests.References, referencesHandler }, + .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, .{ "workspace/didChangeConfiguration", std.json.Value, didChangeConfigurationHandler }, }; @@ -2414,7 +2485,6 @@ fn processJsonRpc(server: *Server, arena: *std.heap.ArenaAllocator, parser: *std // needs a response) or false if the method is a notification (in which // case it should be silently ignored) const unimplemented_map = std.ComptimeStringMap(bool, .{ - .{ "textDocument/documentHighlight", true }, .{ "textDocument/codeAction", true }, .{ "textDocument/codeLens", true }, .{ "textDocument/documentLink", true }, diff --git a/src/references.zig b/src/references.zig index b5f58f7..fbd290f 100644 --- a/src/references.zig +++ b/src/references.zig @@ -492,7 +492,7 @@ fn symbolReferencesInternal(arena: *std.heap.ArenaAllocator, store: *DocumentSto } } -pub fn symbolReferences(arena: *std.heap.ArenaAllocator, store: *DocumentStore, decl_handle: analysis.DeclWithHandle, encoding: offsets.Encoding, include_decl: bool, context: anytype, comptime handler: anytype, skip_std_references: bool) !void { +pub fn symbolReferences(arena: *std.heap.ArenaAllocator, store: *DocumentStore, decl_handle: analysis.DeclWithHandle, encoding: offsets.Encoding, include_decl: bool, context: anytype, comptime handler: anytype, skip_std_references: bool, workspace: bool) !void { std.debug.assert(decl_handle.decl.* != .label_decl); const curr_handle = decl_handle.handle; if (include_decl) { @@ -503,41 +503,43 @@ pub fn symbolReferences(arena: *std.heap.ArenaAllocator, store: *DocumentStore, .ast_node => { try symbolReferencesInternal(arena, store, .{ .node = 0, .handle = curr_handle }, decl_handle, encoding, context, handler); - var imports = std.ArrayList(*DocumentStore.Handle).init(arena.allocator()); + if (workspace) { + var imports = std.ArrayList(*DocumentStore.Handle).init(arena.allocator()); - var handle_it = store.handles.iterator(); - while (handle_it.next()) |entry| { - if (skip_std_references and std.mem.indexOf(u8, entry.key_ptr.*, "std") != null) { - if (!include_decl or entry.value_ptr.* != curr_handle) - continue; - } + var handle_it = store.handles.iterator(); + while (handle_it.next()) |entry| { + if (skip_std_references and std.mem.indexOf(u8, entry.key_ptr.*, "std") != null) { + if (!include_decl or entry.value_ptr.* != curr_handle) + continue; + } - // Check entry's transitive imports - try imports.append(entry.value_ptr.*); - var i: usize = 0; - blk: while (i < imports.items.len) : (i += 1) { - const import = imports.items[i]; - for (import.imports_used.items) |uri| { - const h = store.getHandle(uri) orelse break; + // Check entry's transitive imports + try imports.append(entry.value_ptr.*); + var i: usize = 0; + blk: while (i < imports.items.len) : (i += 1) { + const import = imports.items[i]; + for (import.imports_used.items) |uri| { + const h = store.getHandle(uri) orelse break; - if (h == curr_handle) { - // entry does import curr_handle - try symbolReferencesInternal(arena, store, .{ .node = 0, .handle = entry.value_ptr.* }, decl_handle, encoding, context, handler); - break :blk; - } - - select: { - for (imports.items) |item| { - if (item == h) { - // already checked this import - break :select; - } + if (h == curr_handle) { + // entry does import curr_handle + try symbolReferencesInternal(arena, store, .{ .node = 0, .handle = entry.value_ptr.* }, decl_handle, encoding, context, handler); + break :blk; + } + + select: { + for (imports.items) |item| { + if (item == h) { + // already checked this import + break :select; + } + } + try imports.append(h); } - try imports.append(h); } } + try imports.resize(0); } - try imports.resize(0); } }, .param_decl => |param| { diff --git a/src/rename.zig b/src/rename.zig index e5b4c18..8250de7 100644 --- a/src/rename.zig +++ b/src/rename.zig @@ -31,7 +31,7 @@ pub fn renameSymbol(arena: *std.heap.ArenaAllocator, store: *DocumentStore, decl .edits = edits, .allocator = arena.allocator(), .new_name = new_name, - }, refHandler, true); + }, refHandler, true, true); } pub fn renameLabel(arena: *std.heap.ArenaAllocator, decl_handle: analysis.DeclWithHandle, new_name: []const u8, edits: *std.StringHashMap([]types.TextEdit), encoding: offsets.Encoding) !void { diff --git a/src/requests.zig b/src/requests.zig index 92470a5..ca5d67a 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -165,6 +165,7 @@ pub const Initialize = struct { documentationFormat: MaybeStringArray, }, }, + documentHighlight: Exists, }, offsetEncoding: MaybeStringArray, }; @@ -244,6 +245,7 @@ pub const GotoDeclaration = TextDocumentIdentifierPositionRequest; pub const Hover = TextDocumentIdentifierPositionRequest; pub const DocumentSymbols = TextDocumentIdentifierRequest; pub const Formatting = TextDocumentIdentifierRequest; +pub const DocumentHighlight = TextDocumentIdentifierPositionRequest; pub const Rename = struct { params: struct { textDocument: TextDocumentIdentifier, diff --git a/src/types.zig b/src/types.zig index c426267..21a08de 100644 --- a/src/types.zig +++ b/src/types.zig @@ -45,6 +45,7 @@ pub const ResponseParams = union(enum) { InitializeResult: InitializeResult, ConfigurationParams: ConfigurationParams, RegistrationParams: RegistrationParams, + DocumentHighlight: []DocumentHighlight, }; /// JSONRPC notifications @@ -412,3 +413,18 @@ pub const RegistrationParams = struct { // registerOptions?: LSPAny; }; }; + +pub const DocumentHighlightKind = enum(u8) { + Text = 1, + Read = 2, + Write = 3, + + pub fn jsonStringify(value: DocumentHighlightKind, options: std.json.StringifyOptions, out_stream: anytype) !void { + try std.json.stringify(@enumToInt(value), options, out_stream); + } +}; + +pub const DocumentHighlight = struct { + range: Range, + kind: ?DocumentHighlightKind, +};