Skip to content

Commit af5cd53

Browse files
authored
Merge pull request #751 from tristanlabelle/pull-diagnostics-on-5.9
Implement pull-model documentDiagnostics (cherry-pick from main)
2 parents ff667c2 + 2f0209f commit af5cd53

12 files changed

+315
-30
lines changed

Sources/LanguageServerProtocol/SupportTypes/ClientCapabilities.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,16 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
526526
}
527527
}
528528

529+
/// Capabilities specific to 'textDocument/diagnostic'. Since LSP 3.17.0.
530+
public struct Diagnostic: Equatable, Hashable, Codable {
531+
532+
/// Whether implementation supports dynamic registration.
533+
public var dynamicRegistration: Bool?
534+
535+
/// Whether the clients supports related documents for document diagnostic pulls.
536+
public var relatedDocumentSupport: Bool?
537+
}
538+
529539
// MARK: Properties
530540

531541
public var synchronization: Synchronization? = nil
@@ -575,6 +585,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
575585
public var semanticTokens: SemanticTokens? = nil
576586

577587
public var inlayHint: InlayHint? = nil
588+
589+
public var diagnostic: Diagnostic? = nil
578590

579591
public init(synchronization: Synchronization? = nil,
580592
completion: Completion? = nil,
@@ -599,7 +611,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
599611
foldingRange: FoldingRange? = nil,
600612
callHierarchy: DynamicRegistrationCapability? = nil,
601613
semanticTokens: SemanticTokens? = nil,
602-
inlayHint: InlayHint? = nil) {
614+
inlayHint: InlayHint? = nil,
615+
diagnostic: Diagnostic? = nil) {
603616
self.synchronization = synchronization
604617
self.completion = completion
605618
self.hover = hover
@@ -624,5 +637,6 @@ public struct TextDocumentClientCapabilities: Hashable, Codable {
624637
self.callHierarchy = callHierarchy
625638
self.semanticTokens = semanticTokens
626639
self.inlayHint = inlayHint
640+
self.diagnostic = diagnostic
627641
}
628642
}

Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,25 @@ public struct InlayHintRegistrationOptions: RegistrationOptions, TextDocumentReg
156156
}
157157
}
158158

159+
/// Describe options to be used when registering for pull diagnostics. Since LSP 3.17.0
160+
public struct DiagnosticRegistrationOptions: RegistrationOptions, TextDocumentRegistrationOptionsProtocol {
161+
public var textDocumentRegistrationOptions: TextDocumentRegistrationOptions
162+
public var diagnosticOptions: DiagnosticOptions
163+
164+
public init(
165+
documentSelector: DocumentSelector? = nil,
166+
diagnosticOptions: DiagnosticOptions
167+
) {
168+
textDocumentRegistrationOptions = TextDocumentRegistrationOptions(documentSelector: documentSelector)
169+
self.diagnosticOptions = diagnosticOptions
170+
}
171+
172+
public func encodeIntoLSPAny(dict: inout [String: LSPAny]) {
173+
textDocumentRegistrationOptions.encodeIntoLSPAny(dict: &dict)
174+
diagnosticOptions.encodeIntoLSPAny(dict: &dict)
175+
}
176+
}
177+
159178
/// Describe options to be used when registering for file system change events.
160179
public struct DidChangeWatchedFilesRegistrationOptions: RegistrationOptions {
161180
/// The watchers to register.

Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ public struct ServerCapabilities: Codable, Hashable {
108108
/// Whether the server supports the `textDocument/inlayHint` family of requests.
109109
public var inlayHintProvider: ValueOrBool<InlayHintOptions>?
110110

111+
/// Whether the server supports the `textDocument/diagnostic` request.
112+
public var diagnosticProvider: DiagnosticOptions?
113+
111114
/// Whether the server provides selection range support.
112115
public var selectionRangeProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>?
113116

@@ -152,6 +155,7 @@ public struct ServerCapabilities: Codable, Hashable {
152155
typeHierarchyProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
153156
semanticTokensProvider: SemanticTokensOptions? = nil,
154157
inlayHintProvider: ValueOrBool<InlayHintOptions>? = nil,
158+
diagnosticProvider: DiagnosticOptions? = nil,
155159
selectionRangeProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
156160
linkedEditingRangeProvider: ValueOrBool<TextDocumentAndStaticRegistrationOptions>? = nil,
157161
monikerProvider: ValueOrBool<MonikerOptions>? = nil,
@@ -188,6 +192,7 @@ public struct ServerCapabilities: Codable, Hashable {
188192
self.typeHierarchyProvider = typeHierarchyProvider
189193
self.semanticTokensProvider = semanticTokensProvider
190194
self.inlayHintProvider = inlayHintProvider
195+
self.diagnosticProvider = diagnosticProvider
191196
self.selectionRangeProvider = selectionRangeProvider
192197
self.linkedEditingRangeProvider = linkedEditingRangeProvider
193198
self.experimental = experimental
@@ -863,9 +868,6 @@ public struct DiagnosticOptions: WorkDoneProgressOptions, Codable, Hashable {
863868
/// The server provides support for workspace diagnostics as well.
864869
public var workspaceDiagnostics: Bool
865870

866-
/// A document selector to identify the scope of the registration. If set to null the document selector provided on the client side will be used.
867-
public var documentSelector: DocumentSelector?
868-
869871
/// The id used to register the request. The id can be used to deregister the request again. See also Registration#id
870872
public var id: String?
871873

@@ -875,17 +877,29 @@ public struct DiagnosticOptions: WorkDoneProgressOptions, Codable, Hashable {
875877
identifier: String? = nil,
876878
interFileDependencies: Bool,
877879
workspaceDiagnostics: Bool,
878-
documentSelector: DocumentSelector? = nil,
879880
id: String? = nil,
880881
workDoneProgress: Bool? = nil
881882
) {
882883
self.identifier = identifier
883884
self.interFileDependencies = interFileDependencies
884885
self.workspaceDiagnostics = workspaceDiagnostics
885-
self.documentSelector = documentSelector
886886
self.id = id
887887
self.workDoneProgress = workDoneProgress
888888
}
889+
890+
public func encodeIntoLSPAny(dict: inout [String: LSPAny]) {
891+
if let identifier = identifier {
892+
dict["identifier"] = .string(identifier)
893+
}
894+
dict["interFileDependencies"] = .bool(interFileDependencies)
895+
dict["workspaceDiagnostics"] = .bool(workspaceDiagnostics)
896+
if let id = id {
897+
dict["id"] = .string(id)
898+
}
899+
if let workDoneProgress = workDoneProgress {
900+
dict["workDoneProgress"] = .bool(workDoneProgress)
901+
}
902+
}
889903
}
890904

891905
public struct WorkspaceServerCapabilities: Codable, Hashable {

Sources/SourceKitD/sourcekitd_uids.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ public struct sourcekitd_requests {
177177
public let codecomplete_update: sourcekitd_uid_t
178178
public let codecomplete_close: sourcekitd_uid_t
179179
public let cursorinfo: sourcekitd_uid_t
180+
public let diagnostics: sourcekitd_uid_t
180181
public let expression_type: sourcekitd_uid_t
181182
public let find_usr: sourcekitd_uid_t
182183
public let variable_type: sourcekitd_uid_t
@@ -194,6 +195,7 @@ public struct sourcekitd_requests {
194195
codecomplete_update = api.uid_get_from_cstr("source.request.codecomplete.update")!
195196
codecomplete_close = api.uid_get_from_cstr("source.request.codecomplete.close")!
196197
cursorinfo = api.uid_get_from_cstr("source.request.cursorinfo")!
198+
diagnostics = api.uid_get_from_cstr("source.request.diagnostics")!
197199
expression_type = api.uid_get_from_cstr("source.request.expression.type")!
198200
find_usr = api.uid_get_from_cstr("source.request.editor.find_usr")!
199201
variable_type = api.uid_get_from_cstr("source.request.variable.type")!

Sources/SourceKitLSP/CapabilityRegistry.swift

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public final class CapabilityRegistry {
3131

3232
/// Dynamically registered inlay hint options.
3333
private var inlayHint: [CapabilityRegistration: InlayHintRegistrationOptions] = [:]
34+
35+
/// Dynamically registered pull diagnostics options.
36+
private var pullDiagnostics: [CapabilityRegistration: DiagnosticRegistrationOptions] = [:]
3437

3538
/// Dynamically registered file watchers.
3639
private var didChangeWatchedFiles: DidChangeWatchedFilesRegistrationOptions?
@@ -59,7 +62,10 @@ public final class CapabilityRegistry {
5962
public var clientHasDynamicInlayHintRegistration: Bool {
6063
clientCapabilities.textDocument?.inlayHint?.dynamicRegistration == true
6164
}
62-
65+
public var clientHasDynamicDocumentDiagnosticsRegistration: Bool {
66+
clientCapabilities.textDocument?.diagnostic?.dynamicRegistration == true
67+
}
68+
6369
public var clientHasDynamicExecuteCommandRegistration: Bool {
6470
clientCapabilities.workspace?.executeCommand?.dynamicRegistration == true
6571
}
@@ -68,6 +74,14 @@ public final class CapabilityRegistry {
6874
clientCapabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration == true
6975
}
7076

77+
public var clientHasSemanticTokenRefreshSupport: Bool {
78+
clientCapabilities.workspace?.semanticTokens?.refreshSupport == true
79+
}
80+
81+
public var clientHasDiagnosticsCodeDescriptionSupport: Bool {
82+
clientCapabilities.textDocument?.publishDiagnostics?.codeDescriptionSupport == true
83+
}
84+
7185
/// Dynamically register completion capabilities if the client supports it and
7286
/// we haven't yet registered any completion capabilities for the given
7387
/// languages.
@@ -202,6 +216,33 @@ public final class CapabilityRegistry {
202216
registerOnClient(registration)
203217
}
204218

219+
/// Dynamically register (pull model) diagnostic capabilities,
220+
/// if the client supports it.
221+
public func registerDiagnosticIfNeeded(
222+
options: DiagnosticOptions,
223+
for languages: [Language],
224+
registerOnClient: ClientRegistrationHandler
225+
) {
226+
guard clientHasDynamicDocumentDiagnosticsRegistration else { return }
227+
if let registration = registration(for: languages, in: pullDiagnostics) {
228+
if options != registration.diagnosticOptions {
229+
log("Unable to register new pull diagnostics options \(options) for " +
230+
"\(languages) due to pre-existing options \(registration.diagnosticOptions)", level: .warning)
231+
}
232+
return
233+
}
234+
let registrationOptions = DiagnosticRegistrationOptions(
235+
documentSelector: self.documentSelector(for: languages),
236+
diagnosticOptions: options)
237+
let registration = CapabilityRegistration(
238+
method: DocumentDiagnosticsRequest.method,
239+
registerOptions: self.encode(registrationOptions))
240+
241+
self.pullDiagnostics[registration] = registrationOptions
242+
243+
registerOnClient(registration)
244+
}
245+
205246
/// Dynamically register executeCommand with the given IDs if the client supports
206247
/// it and we haven't yet registered the given command IDs yet.
207248
public func registerExecuteCommandIfNeeded(
@@ -232,13 +273,26 @@ public final class CapabilityRegistry {
232273
if registration.method == CompletionRequest.method {
233274
completion.removeValue(forKey: registration)
234275
}
276+
if registration.method == FoldingRangeRequest.method {
277+
foldingRange.removeValue(forKey: registration)
278+
}
235279
if registration.method == SemanticTokensRegistrationOptions.method {
236280
semanticTokens.removeValue(forKey: registration)
237281
}
282+
if registration.method == InlayHintRequest.method {
283+
inlayHint.removeValue(forKey: registration)
284+
}
285+
if registration.method == DocumentDiagnosticsRequest.method {
286+
pullDiagnostics.removeValue(forKey: registration)
287+
}
288+
}
289+
290+
public func pullDiagnosticsRegistration(for language: Language) -> DiagnosticRegistrationOptions? {
291+
registration(for: [language], in: pullDiagnostics)
238292
}
239293

240-
private func documentSelector(for langauges: [Language]) -> DocumentSelector {
241-
return DocumentSelector(langauges.map { DocumentFilter(language: $0.rawValue) })
294+
private func documentSelector(for languages: [Language]) -> DocumentSelector {
295+
return DocumentSelector(languages.map { DocumentFilter(language: $0.rawValue) })
242296
}
243297

244298
private func encode<T: RegistrationOptions>(_ options: T) -> LSPAny {

Sources/SourceKitLSP/Clang/ClangLanguageServer.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ final class ClangLanguageServerShim: LanguageServer, ToolchainLanguageServer {
103103
public init?(
104104
client: LocalConnection,
105105
toolchain: Toolchain,
106-
clientCapabilities: ClientCapabilities?,
107106
options: SourceKitServer.Options,
108107
workspace: Workspace,
109108
reopenDocuments: @escaping (ToolchainLanguageServer) -> Void
@@ -515,6 +514,10 @@ extension ClangLanguageServerShim {
515514
forwardRequestToClangdOnQueue(req)
516515
}
517516

517+
func documentDiagnostic(_ req: Request<DocumentDiagnosticsRequest>) {
518+
forwardRequestToClangdOnQueue(req)
519+
}
520+
518521
func foldingRange(_ req: Request<FoldingRangeRequest>) {
519522
queue.async {
520523
if self.capabilities?.foldingRangeProvider?.isSupported == true {

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ public final class SourceKitServer: LanguageServer {
202202
registerToolchainTextDocumentRequest(SourceKitServer.colorPresentation, [])
203203
registerToolchainTextDocumentRequest(SourceKitServer.codeAction, nil)
204204
registerToolchainTextDocumentRequest(SourceKitServer.inlayHint, [])
205+
registerToolchainTextDocumentRequest(SourceKitServer.documentDiagnostic,
206+
.full(.init(items: [])))
205207
}
206208

207209
/// Register a `TextDocumentRequest` that requires a valid `Workspace`, `ToolchainLanguageServer`,
@@ -709,6 +711,11 @@ extension SourceKitServer {
709711
self.dynamicallyRegisterCapability($0, registry)
710712
}
711713
}
714+
if let diagnosticOptions = server.diagnosticProvider {
715+
registry.registerDiagnosticIfNeeded(options: diagnosticOptions, for: languages) {
716+
self.dynamicallyRegisterCapability($0, registry)
717+
}
718+
}
712719
if let commandOptions = server.executeCommandProvider {
713720
registry.registerExecuteCommandIfNeeded(commands: commandOptions.commands) {
714721
self.dynamicallyRegisterCapability($0, registry)
@@ -1209,6 +1216,14 @@ extension SourceKitServer {
12091216
languageService.inlayHint(req)
12101217
}
12111218

1219+
func documentDiagnostic(
1220+
_ req: Request<DocumentDiagnosticsRequest>,
1221+
workspace: Workspace,
1222+
languageService: ToolchainLanguageServer
1223+
) {
1224+
languageService.documentDiagnostic(req)
1225+
}
1226+
12121227
/// Converts a location from the symbol index to an LSP location.
12131228
///
12141229
/// - Parameter location: The symbol index location
@@ -1751,7 +1766,6 @@ func languageService(
17511766
let server = try languageServerType.serverType.init(
17521767
client: connectionToClient,
17531768
toolchain: toolchain,
1754-
clientCapabilities: workspace.capabilityRegistry.clientCapabilities,
17551769
options: options,
17561770
workspace: workspace,
17571771
reopenDocuments: reopenDocuments

Sources/SourceKitLSP/Swift/CodeCompletion.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ extension SwiftLanguageServer {
139139
let typeName: String? = value[self.keys.typename]
140140
let docBrief: String? = value[self.keys.doc_brief]
141141

142-
let clientCompletionCapabilities = self.clientCapabilities.textDocument?.completion
143-
let clientSupportsSnippets = clientCompletionCapabilities?.completionItem?.snippetSupport == true
142+
let completionCapabilities = self.capabilityRegistry.clientCapabilities.textDocument?.completion
143+
let clientSupportsSnippets = completionCapabilities?.completionItem?.snippetSupport == true
144144
let text = insertText.map {
145145
rewriteSourceKitPlaceholders(inString: $0, clientSupportsSnippets: clientSupportsSnippets)
146146
}

0 commit comments

Comments
 (0)