From 1077ea39a76524e2c024829c9672b39f90585f84 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Wed, 3 May 2023 11:25:28 -0400 Subject: [PATCH] Add boilerplate for pull-model diagnostics --- .../SupportTypes/ClientCapabilities.swift | 16 ++++++++- .../SupportTypes/RegistrationOptions.swift | 24 +++++++++++++ .../SupportTypes/ServerCapabilities.swift | 5 +++ Sources/SourceKitLSP/CapabilityRegistry.swift | 34 +++++++++++++++++++ .../Clang/ClangLanguageServer.swift | 4 +++ Sources/SourceKitLSP/SourceKitServer.swift | 15 ++++++++ .../Swift/SwiftLanguageServer.swift | 5 +++ .../ToolchainLanguageServer.swift | 1 + 8 files changed, 103 insertions(+), 1 deletion(-) diff --git a/Sources/LanguageServerProtocol/SupportTypes/ClientCapabilities.swift b/Sources/LanguageServerProtocol/SupportTypes/ClientCapabilities.swift index 648c11653..a3a8cc7a4 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/ClientCapabilities.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/ClientCapabilities.swift @@ -526,6 +526,16 @@ public struct TextDocumentClientCapabilities: Hashable, Codable { } } + /// Capabilities specific to 'textDocument/diagnostic'. Since LSP 3.17.0. + public struct Diagnostic: Equatable, Hashable, Codable { + + /// Whether implementation supports dynamic registration. + public var dynamicRegistration: Bool? + + /// Whether the clients supports related documents for document diagnostic pulls. + public var relatedDocumentSupport: Bool? + } + // MARK: Properties public var synchronization: Synchronization? = nil @@ -575,6 +585,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable { public var semanticTokens: SemanticTokens? = nil public var inlayHint: InlayHint? = nil + + public var diagnostic: Diagnostic? = nil public init(synchronization: Synchronization? = nil, completion: Completion? = nil, @@ -599,7 +611,8 @@ public struct TextDocumentClientCapabilities: Hashable, Codable { foldingRange: FoldingRange? = nil, callHierarchy: DynamicRegistrationCapability? = nil, semanticTokens: SemanticTokens? = nil, - inlayHint: InlayHint? = nil) { + inlayHint: InlayHint? = nil, + diagnostic: Diagnostic? = nil) { self.synchronization = synchronization self.completion = completion self.hover = hover @@ -624,5 +637,6 @@ public struct TextDocumentClientCapabilities: Hashable, Codable { self.callHierarchy = callHierarchy self.semanticTokens = semanticTokens self.inlayHint = inlayHint + self.diagnostic = diagnostic } } diff --git a/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift b/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift index cb1b19fd4..cfec220bd 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift @@ -156,6 +156,30 @@ public struct InlayHintRegistrationOptions: RegistrationOptions, TextDocumentReg } } +/// Describe options to be used when registering for pull diagnostics. Since LSP 3.17.0 +public struct DiagnosticRegistrationOptions: RegistrationOptions, TextDocumentRegistrationOptionsProtocol { + public var textDocumentRegistrationOptions: TextDocumentRegistrationOptions + public var diagnosticOptions: DiagnosticOptions + + public init( + documentSelector: DocumentSelector? = nil, + diagnosticOptions: DiagnosticOptions + ) { + textDocumentRegistrationOptions = TextDocumentRegistrationOptions(documentSelector: documentSelector) + self.diagnosticOptions = diagnosticOptions + } + + public func encodeIntoLSPAny(dict: inout [String: LSPAny]) { + textDocumentRegistrationOptions.encodeIntoLSPAny(dict: &dict) + + dict["interFileDependencies"] = .bool(diagnosticOptions.interFileDependencies) + dict["workspaceDiagnostics"] = .bool(diagnosticOptions.workspaceDiagnostics) + if let workDoneProgress = diagnosticOptions.workDoneProgress { + dict["workDoneProgress"] = .bool(workDoneProgress) + } + } +} + /// Describe options to be used when registering for file system change events. public struct DidChangeWatchedFilesRegistrationOptions: RegistrationOptions { /// The watchers to register. diff --git a/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift b/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift index 0c5278cc7..4db49892c 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift @@ -108,6 +108,9 @@ public struct ServerCapabilities: Codable, Hashable { /// Whether the server supports the `textDocument/inlayHint` family of requests. public var inlayHintProvider: ValueOrBool? + /// Whether the server supports the `textDocument/diagnostic` request. + public var diagnosticProvider: DiagnosticOptions? + /// Whether the server provides selection range support. public var selectionRangeProvider: ValueOrBool? @@ -152,6 +155,7 @@ public struct ServerCapabilities: Codable, Hashable { typeHierarchyProvider: ValueOrBool? = nil, semanticTokensProvider: SemanticTokensOptions? = nil, inlayHintProvider: ValueOrBool? = nil, + diagnosticProvider: DiagnosticOptions? = nil, selectionRangeProvider: ValueOrBool? = nil, linkedEditingRangeProvider: ValueOrBool? = nil, monikerProvider: ValueOrBool? = nil, @@ -188,6 +192,7 @@ public struct ServerCapabilities: Codable, Hashable { self.typeHierarchyProvider = typeHierarchyProvider self.semanticTokensProvider = semanticTokensProvider self.inlayHintProvider = inlayHintProvider + self.diagnosticProvider = diagnosticProvider self.selectionRangeProvider = selectionRangeProvider self.linkedEditingRangeProvider = linkedEditingRangeProvider self.experimental = experimental diff --git a/Sources/SourceKitLSP/CapabilityRegistry.swift b/Sources/SourceKitLSP/CapabilityRegistry.swift index fa684b364..268b57954 100644 --- a/Sources/SourceKitLSP/CapabilityRegistry.swift +++ b/Sources/SourceKitLSP/CapabilityRegistry.swift @@ -31,6 +31,9 @@ public final class CapabilityRegistry { /// Dynamically registered inlay hint options. private var inlayHint: [CapabilityRegistration: InlayHintRegistrationOptions] = [:] + + /// Dynamically registered pull diagnostics options. + private var pullDiagnostics: [CapabilityRegistration: DiagnosticRegistrationOptions] = [:] /// Dynamically registered file watchers. private var didChangeWatchedFiles: DidChangeWatchedFilesRegistrationOptions? @@ -60,6 +63,10 @@ public final class CapabilityRegistry { clientCapabilities.textDocument?.inlayHint?.dynamicRegistration == true } + public var clientHasDocumentDiagnosticsRegistration: Bool { + clientCapabilities.textDocument?.diagnostic?.dynamicRegistration == true + } + public var clientHasDynamicExecuteCommandRegistration: Bool { clientCapabilities.workspace?.executeCommand?.dynamicRegistration == true } @@ -202,6 +209,33 @@ public final class CapabilityRegistry { registerOnClient(registration) } + /// Dynamically register (pull model) diagnostic capabilities, + /// if the client supports it. + public func registerDiagnosticIfNeeded( + options: DiagnosticOptions, + for languages: [Language], + registerOnClient: ClientRegistrationHandler + ) { + guard clientHasDocumentDiagnosticsRegistration else { return } + if let registration = registration(for: languages, in: pullDiagnostics) { + if options != registration.diagnosticOptions { + log("Unable to register new pull diagnostics options \(options) for " + + "\(languages) due to pre-existing options \(registration.diagnosticOptions)", level: .warning) + } + return + } + let registrationOptions = DiagnosticRegistrationOptions( + documentSelector: self.documentSelector(for: languages), + diagnosticOptions: options) + let registration = CapabilityRegistration( + method: DocumentDiagnosticsRequest.method, + registerOptions: self.encode(registrationOptions)) + + self.pullDiagnostics[registration] = registrationOptions + + registerOnClient(registration) + } + /// Dynamically register executeCommand with the given IDs if the client supports /// it and we haven't yet registered the given command IDs yet. public func registerExecuteCommandIfNeeded( diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift index e271c2001..9141f38af 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift @@ -515,6 +515,10 @@ extension ClangLanguageServerShim { forwardRequestToClangdOnQueue(req) } + func documentDiagnostic(_ req: Request) { + forwardRequestToClangdOnQueue(req) + } + func foldingRange(_ req: Request) { queue.async { if self.capabilities?.foldingRangeProvider?.isSupported == true { diff --git a/Sources/SourceKitLSP/SourceKitServer.swift b/Sources/SourceKitLSP/SourceKitServer.swift index 5dbf88fdf..9e9156094 100644 --- a/Sources/SourceKitLSP/SourceKitServer.swift +++ b/Sources/SourceKitLSP/SourceKitServer.swift @@ -202,6 +202,8 @@ public final class SourceKitServer: LanguageServer { registerToolchainTextDocumentRequest(SourceKitServer.colorPresentation, []) registerToolchainTextDocumentRequest(SourceKitServer.codeAction, nil) registerToolchainTextDocumentRequest(SourceKitServer.inlayHint, []) + registerToolchainTextDocumentRequest(SourceKitServer.documentDiagnostic, + .full(.init(items: []))) } /// Register a `TextDocumentRequest` that requires a valid `Workspace`, `ToolchainLanguageServer`, @@ -709,6 +711,11 @@ extension SourceKitServer { self.dynamicallyRegisterCapability($0, registry) } } + if let diagnosticOptions = server.diagnosticProvider { + registry.registerDiagnosticIfNeeded(options: diagnosticOptions, for: languages) { + self.dynamicallyRegisterCapability($0, registry) + } + } if let commandOptions = server.executeCommandProvider { registry.registerExecuteCommandIfNeeded(commands: commandOptions.commands) { self.dynamicallyRegisterCapability($0, registry) @@ -1209,6 +1216,14 @@ extension SourceKitServer { languageService.inlayHint(req) } + func documentDiagnostic( + _ req: Request, + workspace: Workspace, + languageService: ToolchainLanguageServer + ) { + languageService.documentDiagnostic(req) + } + /// Converts a location from the symbol index to an LSP location. /// /// - Parameter location: The symbol index location diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift index d3848437c..e9ca27268 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift @@ -1326,6 +1326,11 @@ extension SwiftLanguageServer { } } } + + public func documentDiagnostic(_ req: Request) { + // TODO: Return provider object in initializeSync and implement pull-model document diagnostics here. + req.reply(.failure(.unknown("Pull-model diagnostics not implemented yet."))) + } public func executeCommand(_ req: Request) { let params = req.params diff --git a/Sources/SourceKitLSP/ToolchainLanguageServer.swift b/Sources/SourceKitLSP/ToolchainLanguageServer.swift index c98ed715f..13d01706e 100644 --- a/Sources/SourceKitLSP/ToolchainLanguageServer.swift +++ b/Sources/SourceKitLSP/ToolchainLanguageServer.swift @@ -97,6 +97,7 @@ public protocol ToolchainLanguageServer: AnyObject { func colorPresentation(_ req: Request) func codeAction(_ req: Request) func inlayHint(_ req: Request) + func documentDiagnostic(_ req: Request) // MARK: - Other