Skip to content

Commit f6b83db

Browse files
committed
Support completionItem/resolve to compute documentation of code completion item
Fixes #1935
1 parent 03da4a4 commit f6b83db

File tree

9 files changed

+238
-53
lines changed

9 files changed

+238
-53
lines changed

Sources/LanguageServerProtocol/SupportTypes/ClientCapabilities.swift

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,33 @@ public struct TextDocumentClientCapabilities: Hashable, Codable, Sendable {
254254

255255
/// Capabilities specific to the `textDocument/...` change notifications.
256256
public struct Completion: Hashable, Codable, Sendable {
257-
258257
/// Capabilities specific to `CompletionItem`.
259258
public struct CompletionItem: Hashable, Codable, Sendable {
259+
public struct TagSupportValueSet: Hashable, Codable, Sendable {
260+
/// The tags supported by the client.
261+
public var valueSet: [CompletionItemTag]
262+
263+
public init(valueSet: [CompletionItemTag]) {
264+
self.valueSet = valueSet
265+
}
266+
}
267+
268+
public struct ResolveSupportProperties: Hashable, Codable, Sendable {
269+
/// The properties that a client can resolve lazily.
270+
public var properties: [String]
271+
272+
public init(properties: [String]) {
273+
self.properties = properties
274+
}
275+
}
276+
277+
public struct InsertTextModeSupportValueSet: Hashable, Codable, Sendable {
278+
public var valueSet: [InsertTextMode]
279+
280+
public init(valueSet: [InsertTextMode]) {
281+
self.valueSet = valueSet
282+
}
283+
}
260284

261285
/// Whether the client supports rich snippets using placeholders, etc.
262286
public var snippetSupport: Bool? = nil
@@ -273,18 +297,48 @@ public struct TextDocumentClientCapabilities: Hashable, Codable, Sendable {
273297
/// Whether the client supports the `preselect` property on a CompletionItem.
274298
public var preselectSupport: Bool? = nil
275299

300+
/// Client supports the tag property on a completion item. Clients supporting tags have to handle unknown tags
301+
/// gracefully. Clients especially need to preserve unknown tags when sending a completion item back to the server
302+
/// in a resolve call.
303+
public var tagSupport: TagSupportValueSet?
304+
305+
/// Client supports insert replace edit to control different behavior if a completion item is inserted in the text
306+
/// or should replace text.
307+
public var insertReplaceSupport: Bool?
308+
309+
/// Indicates which properties a client can resolve lazily on a completion item. Before version 3.16.0 only the
310+
/// predefined properties `documentation` and `detail` could be resolved lazily.
311+
public var resolveSupport: ResolveSupportProperties?
312+
313+
/// The client supports the `insertTextMode` property on a completion item to override the whitespace handling mode
314+
/// as defined by the client (see `insertTextMode`).
315+
public var insertTextModeSupport: InsertTextModeSupportValueSet?
316+
317+
/// The client has support for completion item label details (see also `CompletionItemLabelDetails`).
318+
public var labelDetailsSupport: Bool?
319+
276320
public init(
277321
snippetSupport: Bool? = nil,
278322
commitCharactersSupport: Bool? = nil,
279323
documentationFormat: [MarkupKind]? = nil,
280324
deprecatedSupport: Bool? = nil,
281-
preselectSupport: Bool? = nil
325+
preselectSupport: Bool? = nil,
326+
tagSupport: TagSupportValueSet? = nil,
327+
insertReplaceSupport: Bool? = nil,
328+
resolveSupport: ResolveSupportProperties? = nil,
329+
insertTextModeSupport: InsertTextModeSupportValueSet? = nil,
330+
labelDetailsSupport: Bool? = nil
282331
) {
283332
self.snippetSupport = snippetSupport
284333
self.commitCharactersSupport = commitCharactersSupport
285334
self.documentationFormat = documentationFormat
286335
self.deprecatedSupport = deprecatedSupport
287336
self.preselectSupport = preselectSupport
337+
self.tagSupport = tagSupport
338+
self.insertReplaceSupport = insertReplaceSupport
339+
self.resolveSupport = resolveSupport
340+
self.insertTextModeSupport = insertTextModeSupport
341+
self.labelDetailsSupport = labelDetailsSupport
288342
}
289343
}
290344

Sources/SourceKitLSP/Clang/ClangLanguageService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,10 @@ extension ClangLanguageService {
496496
return try await forwardRequestToClangd(req)
497497
}
498498

499+
func completionItemResolve(_ req: CompletionItemResolveRequest) async throws -> CompletionItem {
500+
return try await forwardRequestToClangd(req)
501+
}
502+
499503
func hover(_ req: HoverRequest) async throws -> HoverResponse? {
500504
return try await forwardRequestToClangd(req)
501505
}

Sources/SourceKitLSP/Documentation/DocumentationLanguageService.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ package actor DocumentationLanguageService: LanguageService, Sendable {
103103
CompletionList(isIncomplete: false, items: [])
104104
}
105105

106+
package func completionItemResolve(_ req: CompletionItemResolveRequest) async throws -> CompletionItem {
107+
return req.item
108+
}
109+
106110
package func hover(_ req: HoverRequest) async throws -> HoverResponse? {
107111
nil
108112
}

Sources/SourceKitLSP/LanguageService.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ package protocol LanguageService: AnyObject, Sendable {
172172
// MARK: - Text Document
173173

174174
func completion(_ req: CompletionRequest) async throws -> CompletionList
175+
func completionItemResolve(_ req: CompletionItemResolveRequest) async throws -> CompletionItem
175176
func hover(_ req: HoverRequest) async throws -> HoverResponse?
176177
func symbolInfo(_ request: SymbolInfoRequest) async throws -> [SymbolDetails]
177178

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,16 @@ package actor SourceKitLSPServer {
281281
}.valuePropagatingCancellation
282282
}
283283

284+
private func documentService(for uri: DocumentURI) async throws -> LanguageService {
285+
guard let workspace = await self.workspaceForDocument(uri: uri) else {
286+
throw ResponseError.workspaceNotOpen(uri)
287+
}
288+
guard let languageService = workspace.documentService(for: uri) else {
289+
throw ResponseError.unknown("No language service for '\(uri)' found")
290+
}
291+
return languageService
292+
}
293+
284294
/// This method must be executed on `workspaceQueue` to ensure that the file handling capabilities of the
285295
/// workspaces don't change during the computation. Otherwise, we could run into a race condition like the following:
286296
/// 1. We don't have an entry for file `a.swift` in `workspaceForUri` and start the computation
@@ -754,6 +764,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
754764
await self.handleRequest(for: request, requestHandler: self.colorPresentation)
755765
case let request as RequestAndReply<CompletionRequest>:
756766
await self.handleRequest(for: request, requestHandler: self.completion)
767+
case let request as RequestAndReply<CompletionItemResolveRequest>:
768+
await request.reply { try await completionItemResolve(request: request.params) }
757769
case let request as RequestAndReply<DeclarationRequest>:
758770
await self.handleRequest(for: request, requestHandler: self.declaration)
759771
case let request as RequestAndReply<DefinitionRequest>:
@@ -1035,7 +1047,7 @@ extension SourceKitLSPServer {
10351047
await registry.clientHasDynamicCompletionRegistration
10361048
? nil
10371049
: LanguageServerProtocol.CompletionOptions(
1038-
resolveProvider: false,
1050+
resolveProvider: true,
10391051
triggerCharacters: [".", "("]
10401052
)
10411053

@@ -1432,6 +1444,15 @@ extension SourceKitLSPServer {
14321444
return try await languageService.completion(req)
14331445
}
14341446

1447+
func completionItemResolve(
1448+
request: CompletionItemResolveRequest
1449+
) async throws -> CompletionItem {
1450+
guard let completionItemData = CompletionItemData(fromLSPAny: request.item.data) else {
1451+
return request.item
1452+
}
1453+
return try await documentService(for: completionItemData.uri).completionItemResolve(request)
1454+
}
1455+
14351456
#if canImport(SwiftDocC)
14361457
func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse {
14371458
return try await documentationManager.convertDocumentation(
@@ -1624,18 +1645,12 @@ extension SourceKitLSPServer {
16241645
logger.error("Attempted to perform executeCommand request without an URL")
16251646
return nil
16261647
}
1627-
guard let workspace = await workspaceForDocument(uri: uri) else {
1628-
throw ResponseError.workspaceNotOpen(uri)
1629-
}
1630-
guard let languageService = workspace.documentService(for: uri) else {
1631-
return nil
1632-
}
16331648

16341649
let executeCommand = ExecuteCommandRequest(
16351650
command: req.command,
16361651
arguments: req.argumentsWithoutSourceKitMetadata
16371652
)
1638-
return try await languageService.executeCommand(executeCommand)
1653+
return try await documentService(for: uri).executeCommand(executeCommand)
16391654
}
16401655

16411656
func getReferenceDocument(_ req: GetReferenceDocumentRequest) async throws -> GetReferenceDocumentResponse {

Sources/SourceKitLSP/Swift/CodeCompletion.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ extension SwiftLanguageService {
3131
let completionPos = await adjustPositionToStartOfIdentifier(req.position, in: snapshot)
3232
let filterText = String(snapshot.text[snapshot.index(of: completionPos)..<snapshot.index(of: req.position)])
3333

34-
let clientSupportsSnippets =
35-
capabilityRegistry.clientCapabilities.textDocument?.completion?.completionItem?.snippetSupport ?? false
3634
let buildSettings = await buildSettings(for: snapshot.uri, fallbackAfterTimeout: false)
3735

3836
let inferredIndentationWidth = BasicFormat.inferIndentation(of: await syntaxTreeManager.syntaxTree(for: snapshot))
@@ -45,8 +43,12 @@ extension SwiftLanguageService {
4543
completionPosition: completionPos,
4644
cursorPosition: req.position,
4745
compileCommand: buildSettings,
48-
clientSupportsSnippets: clientSupportsSnippets,
46+
clientCapabilities: capabilityRegistry.clientCapabilities,
4947
filterText: filterText
5048
)
5149
}
50+
51+
package func completionItemResolve(_ req: CompletionItemResolveRequest) async throws -> CompletionItem {
52+
return try await CodeCompletionSession.completionItemResolve(item: req.item, sourcekitd: sourcekitd)
53+
}
5254
}

0 commit comments

Comments
 (0)