From 542a29db2b3614962ff45bd46b613a1dad73dece Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Thu, 11 May 2023 17:07:53 -0400 Subject: [PATCH 01/12] Implement pull-model documentDiagnostics --- .../SupportTypes/RegistrationOptions.swift | 7 +- .../SupportTypes/ServerCapabilities.swift | 19 ++-- Sources/SourceKitD/sourcekitd_uids.swift | 2 + .../Swift/SwiftLanguageServer.swift | 90 ++++++++++++++++++- 4 files changed, 103 insertions(+), 15 deletions(-) diff --git a/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift b/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift index cfec220bd..8e1f2bf1b 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/RegistrationOptions.swift @@ -171,12 +171,7 @@ public struct DiagnosticRegistrationOptions: RegistrationOptions, TextDocumentRe 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) - } + diagnosticOptions.encodeIntoLSPAny(dict: &dict) } } diff --git a/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift b/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift index 4db49892c..80adbb612 100644 --- a/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift +++ b/Sources/LanguageServerProtocol/SupportTypes/ServerCapabilities.swift @@ -868,9 +868,6 @@ public struct DiagnosticOptions: WorkDoneProgressOptions, Codable, Hashable { /// The server provides support for workspace diagnostics as well. public var workspaceDiagnostics: Bool - /// 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. - public var documentSelector: DocumentSelector? - /// The id used to register the request. The id can be used to deregister the request again. See also Registration#id public var id: String? @@ -880,17 +877,29 @@ public struct DiagnosticOptions: WorkDoneProgressOptions, Codable, Hashable { identifier: String? = nil, interFileDependencies: Bool, workspaceDiagnostics: Bool, - documentSelector: DocumentSelector? = nil, id: String? = nil, workDoneProgress: Bool? = nil ) { self.identifier = identifier self.interFileDependencies = interFileDependencies self.workspaceDiagnostics = workspaceDiagnostics - self.documentSelector = documentSelector self.id = id self.workDoneProgress = workDoneProgress } + + public func encodeIntoLSPAny(dict: inout [String: LSPAny]) { + if let identifier = identifier { + dict["identifier"] = .string(identifier) + } + dict["interFileDependencies"] = .bool(interFileDependencies) + dict["workspaceDiagnostics"] = .bool(workspaceDiagnostics) + if let id = id { + dict["id"] = .string(id) + } + if let workDoneProgress = workDoneProgress { + dict["workDoneProgress"] = .bool(workDoneProgress) + } + } } public struct WorkspaceServerCapabilities: Codable, Hashable { diff --git a/Sources/SourceKitD/sourcekitd_uids.swift b/Sources/SourceKitD/sourcekitd_uids.swift index b88dd9390..052bf0557 100644 --- a/Sources/SourceKitD/sourcekitd_uids.swift +++ b/Sources/SourceKitD/sourcekitd_uids.swift @@ -177,6 +177,7 @@ public struct sourcekitd_requests { public let codecomplete_update: sourcekitd_uid_t public let codecomplete_close: sourcekitd_uid_t public let cursorinfo: sourcekitd_uid_t + public let diagnostics: sourcekitd_uid_t public let expression_type: sourcekitd_uid_t public let find_usr: sourcekitd_uid_t public let variable_type: sourcekitd_uid_t @@ -194,6 +195,7 @@ public struct sourcekitd_requests { codecomplete_update = api.uid_get_from_cstr("source.request.codecomplete.update")! codecomplete_close = api.uid_get_from_cstr("source.request.codecomplete.close")! cursorinfo = api.uid_get_from_cstr("source.request.cursorinfo")! + diagnostics = api.uid_get_from_cstr("source.request.diagnostics")! expression_type = api.uid_get_from_cstr("source.request.expression.type")! find_usr = api.uid_get_from_cstr("source.request.editor.find_usr")! variable_type = api.uid_get_from_cstr("source.request.variable.type")! diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift index e9ca27268..bc5277b11 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift @@ -122,6 +122,14 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { var keys: sourcekitd_keys { return sourcekitd.keys } var requests: sourcekitd_requests { return sourcekitd.requests } var values: sourcekitd_values { return sourcekitd.values } + + var enablePublishDiagnostics: Bool { + // Since LSP 3.17.0, diagnostics can be reported through pull-based requests, + // in addition to the existing push-based publish notifications. + // If the client supports pull diagnostics, we report the capability + // and we should disable the publish notifications to avoid double-reporting. + return clientCapabilities.textDocument?.diagnostic == nil + } private var state: LanguageServerState { didSet { @@ -326,7 +334,10 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { req[keys.sourcetext] = "" if let dict = try? self.sourcekitd.sendSync(req) { - publishDiagnostics(response: dict, for: snapshot, compileCommand: compileCommand) + if (enablePublishDiagnostics) { + publishDiagnostics(response: dict, for: snapshot, compileCommand: compileCommand) + } + if dict[keys.diagnostic_stage] as sourcekitd_uid_t? == sourcekitd.values.diag_stage_sema { // Only update semantic tokens if the 0,0 replacetext request returned semantic information. updateSemanticTokens(response: dict, for: snapshot) @@ -370,7 +381,10 @@ extension SwiftLanguageServer { range: .bool(true), full: .bool(true)), inlayHintProvider: .value(InlayHintOptions( - resolveProvider: false)) + resolveProvider: false)), + diagnosticProvider: DiagnosticOptions( + interFileDependencies: true, + workspaceDiagnostics: false) )) } @@ -1326,10 +1340,78 @@ extension SwiftLanguageServer { } } } + + // Must be called on self.queue + public func _documentDiagnostic( + _ uri: DocumentURI, + _ completion: @escaping (Result<[Diagnostic], ResponseError>) -> Void + ) { + dispatchPrecondition(condition: .onQueue(queue)) + + guard let snapshot = documentManager.latestSnapshot(uri) else { + let msg = "failed to find snapshot for url \(uri)" + log(msg) + return completion(.failure(.unknown(msg))) + } + + let keys = self.keys + + let skreq = SKDRequestDictionary(sourcekitd: self.sourcekitd) + skreq[keys.request] = requests.diagnostics + skreq[keys.sourcefile] = snapshot.document.uri.pseudoPath + + // FIXME: SourceKit should probably cache this for us. + if let compileCommand = self.commandsByFile[uri] { + skreq[keys.compilerargs] = compileCommand.compilerArgs + } + + let supportsCodeDescription = + (clientCapabilities.textDocument?.publishDiagnostics?.codeDescriptionSupport == true) + + let handle = self.sourcekitd.send(skreq, self.queue) { [weak self] response in + guard let self = self else { return } + guard let dict = response.success else { + return completion(.failure(ResponseError(response.failure!))) + } + + var diagnostics: [Diagnostic] = [] + dict[keys.diagnostics]?.forEach { _, diag in + if var diagnostic = Diagnostic(diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) { + diagnostic.source = "pulldiag" + diagnostics.append(diagnostic) + } + return true + } + + completion(.success(diagnostics)) + } + + // FIXME: cancellation + _ = handle + } + public func documentDiagnostic( + _ uri: DocumentURI, + _ completion: @escaping (Result<[Diagnostic], ResponseError>) -> Void + ) { + self.queue.async { + self._documentDiagnostic(uri, completion) + } + } + 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."))) + let uri = req.params.textDocument.uri + documentDiagnostic(req.params.textDocument.uri) { result in + switch result { + case .success(let diagnostics): + req.reply(.full(.init(items: diagnostics))) + + case .failure(let error): + let message = "document diagnostic failed \(uri): \(error)" + log(message, level: .warning) + return req.reply(.failure(.unknown(message))) + } + } } public func executeCommand(_ req: Request) { From e778ca24a6626cc0a7b15eef108c4611f8514bb1 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Thu, 11 May 2023 17:11:05 -0400 Subject: [PATCH 02/12] Remove testing code --- Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift index bc5277b11..2939be03f 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift @@ -1377,7 +1377,6 @@ extension SwiftLanguageServer { var diagnostics: [Diagnostic] = [] dict[keys.diagnostics]?.forEach { _, diag in if var diagnostic = Diagnostic(diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) { - diagnostic.source = "pulldiag" diagnostics.append(diagnostic) } return true From bc444519c8fc833359fe861c4990698ca2a8dad7 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Mon, 15 May 2023 10:22:15 -0400 Subject: [PATCH 03/12] Report diagnostics support in SourceKitServer. --- Sources/SourceKitLSP/SourceKitServer.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/SourceKitLSP/SourceKitServer.swift b/Sources/SourceKitLSP/SourceKitServer.swift index 7a0f69c36..3084f8fd0 100644 --- a/Sources/SourceKitLSP/SourceKitServer.swift +++ b/Sources/SourceKitLSP/SourceKitServer.swift @@ -674,7 +674,10 @@ extension SourceKitServer { changeNotifications: .bool(true) )), callHierarchyProvider: .bool(true), - typeHierarchyProvider: .bool(true) + typeHierarchyProvider: .bool(true), + diagnosticProvider: DiagnosticOptions( + interFileDependencies: true, + workspaceDiagnostics: false) ) } From aae1058865570bb642c47b7ceea3d4b73f3ac13d Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Mon, 15 May 2023 10:22:28 -0400 Subject: [PATCH 04/12] Add test for pul diagnostics --- ...ts.swift => PublishDiagnosticsTests.swift} | 4 +- .../PullDiagnosticsTests.swift | 79 +++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) rename Tests/SourceKitLSPTests/{DiagnosticsTests.swift => PublishDiagnosticsTests.swift} (98%) create mode 100644 Tests/SourceKitLSPTests/PullDiagnosticsTests.swift diff --git a/Tests/SourceKitLSPTests/DiagnosticsTests.swift b/Tests/SourceKitLSPTests/PublishDiagnosticsTests.swift similarity index 98% rename from Tests/SourceKitLSPTests/DiagnosticsTests.swift rename to Tests/SourceKitLSPTests/PublishDiagnosticsTests.swift index 6cf7433b4..cc5286338 100644 --- a/Tests/SourceKitLSPTests/DiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PublishDiagnosticsTests.swift @@ -15,7 +15,7 @@ import LSPTestSupport import SKTestSupport import XCTest -final class DiagnosticsTests: XCTestCase { +final class PublishDiagnosticsTests: XCTestCase { /// Connection and lifetime management for the service. var connection: TestSourceKitServer! = nil @@ -28,7 +28,7 @@ final class DiagnosticsTests: XCTestCase { override func setUp() { version = 0 - uri = DocumentURI(URL(fileURLWithPath: "/DiagnosticsTests/\(UUID()).swift")) + uri = DocumentURI(URL(fileURLWithPath: "/PublishDiagnosticsTests/\(UUID()).swift")) connection = TestSourceKitServer() sk = connection.client let documentCapabilities = TextDocumentClientCapabilities() diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift new file mode 100644 index 000000000..b119ab388 --- /dev/null +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import LanguageServerProtocol +import LSPTestSupport +import SKTestSupport +import XCTest + +final class PullDiagnosticsTests: XCTestCase { + /// Connection and lifetime management for the service. + var connection: TestSourceKitServer! = nil + + /// The primary interface to make requests to the SourceKitServer. + var sk: TestClient! = nil + + override func setUp() { + connection = TestSourceKitServer() + sk = connection.client + _ = try! sk.sendSync(InitializeRequest( + processId: nil, + rootPath: nil, + rootURI: nil, + initializationOptions: nil, + capabilities: ClientCapabilities(workspace: nil, textDocument: nil), + trace: .off, + workspaceFolders: nil + )) + } + + override func tearDown() { + sk = nil + connection = nil + } + + func performDiagnosticRequest(text: String) throws -> [Diagnostic] { + let url = URL(fileURLWithPath: "/PullDiagnostics/\(UUID()).swift") + + sk.send(DidOpenTextDocumentNotification(textDocument: TextDocumentItem( + uri: DocumentURI(url), + language: .swift, + version: 17, + text: text + ))) + + let request = DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(url)) + + let report: DocumentDiagnosticReport + do { + report = try sk.sendSync(request) + } catch let error as ResponseError where error.message.contains("unknown request: source.request.diagnostics") { + throw XCTSkip("toolchain does not support source.request.diagnostics request") + } + + guard case .full(let fullReport) = report else { + XCTFail("Unexpected diagnostics report type: \(report)") + } + + return fullReport.diagnostics + } + + func testUnknownIdentifierDiagnostic() { + let diagnostics = performDiagnosticRequest(text: """ + func foo() { + invalid + } + """) + XCTAssertEqual(diagnostics.count, 1) + XCTAssertEqual(diagnostics[0].range, Position(line: 1, utf16index: 2).. Date: Mon, 22 May 2023 14:33:03 -0400 Subject: [PATCH 05/12] Fix compilation errors in test. --- Tests/SourceKitLSPTests/PullDiagnosticsTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index b119ab388..6d0776644 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -64,11 +64,11 @@ final class PullDiagnosticsTests: XCTestCase { XCTFail("Unexpected diagnostics report type: \(report)") } - return fullReport.diagnostics + return fullReport.items } - func testUnknownIdentifierDiagnostic() { - let diagnostics = performDiagnosticRequest(text: """ + func testUnknownIdentifierDiagnostic() throws { + let diagnostics = try performDiagnosticRequest(text: """ func foo() { invalid } From 0da6291d2279d8e199742d7bcbd79d4e517fd880 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Mon, 22 May 2023 15:05:51 -0400 Subject: [PATCH 06/12] Use CapabilityRegistry in ToolchainLanguageServer implementations --- Sources/SourceKitLSP/CapabilityRegistry.swift | 25 +++++++++++++++++-- .../Clang/ClangLanguageServer.swift | 1 - Sources/SourceKitLSP/SourceKitServer.swift | 1 - .../Swift/SwiftLanguageServer.swift | 18 ++++++------- .../ToolchainLanguageServer.swift | 1 - 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Sources/SourceKitLSP/CapabilityRegistry.swift b/Sources/SourceKitLSP/CapabilityRegistry.swift index 268b57954..b46f9d173 100644 --- a/Sources/SourceKitLSP/CapabilityRegistry.swift +++ b/Sources/SourceKitLSP/CapabilityRegistry.swift @@ -75,6 +75,14 @@ public final class CapabilityRegistry { clientCapabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration == true } + public var clientHasSemanticTokenRefreshSupport: Bool { + clientCapabilities.workspace?.semanticTokens?.refreshSupport == true + } + + public var clientHasDiagnosticsCodeDescriptionSupport: Bool { + clientCapabilities.textDocument?.publishDiagnostics?.codeDescriptionSupport == true + } + /// Dynamically register completion capabilities if the client supports it and /// we haven't yet registered any completion capabilities for the given /// languages. @@ -266,13 +274,26 @@ public final class CapabilityRegistry { if registration.method == CompletionRequest.method { completion.removeValue(forKey: registration) } + if registration.method == FoldingRangeRegistrationOptions.method { + foldingRange.removeValue(forKey: registration) + } if registration.method == SemanticTokensRegistrationOptions.method { semanticTokens.removeValue(forKey: registration) } + if registration.method == InlayHintRegistrationOptions.method { + inlayHint.removeValue(forKey: registration) + } + if registration.method == DiagnosticRegistrationOptions.method { + pullDiagnostics.removeValue(forKey: registration) + } + } + + public func pullDiagnosticsRegistration(for language: Language) -> DiagnosticRegistrationOptions? { + registration(for: language, in: pullDiagnostics) } - private func documentSelector(for langauges: [Language]) -> DocumentSelector { - return DocumentSelector(langauges.map { DocumentFilter(language: $0.rawValue) }) + private func documentSelector(for languages: [Language]) -> DocumentSelector { + return DocumentSelector(languages.map { DocumentFilter(language: $0.rawValue) }) } private func encode(_ options: T) -> LSPAny { diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift index 9141f38af..f6acc61bf 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift @@ -103,7 +103,6 @@ final class ClangLanguageServerShim: LanguageServer, ToolchainLanguageServer { public init?( client: LocalConnection, toolchain: Toolchain, - clientCapabilities: ClientCapabilities?, options: SourceKitServer.Options, workspace: Workspace, reopenDocuments: @escaping (ToolchainLanguageServer) -> Void diff --git a/Sources/SourceKitLSP/SourceKitServer.swift b/Sources/SourceKitLSP/SourceKitServer.swift index 3084f8fd0..eb4c44cb0 100644 --- a/Sources/SourceKitLSP/SourceKitServer.swift +++ b/Sources/SourceKitLSP/SourceKitServer.swift @@ -1769,7 +1769,6 @@ func languageService( let server = try languageServerType.serverType.init( client: connectionToClient, toolchain: toolchain, - clientCapabilities: workspace.capabilityRegistry.clientCapabilities, options: options, workspace: workspace, reopenDocuments: reopenDocuments diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift index 2939be03f..c60f86bde 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift @@ -103,7 +103,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { let sourcekitd: SourceKitD - let clientCapabilities: ClientCapabilities + let capabilityRegistry: CapabilityRegistry let serverOptions: SourceKitServer.Options @@ -128,7 +128,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { // in addition to the existing push-based publish notifications. // If the client supports pull diagnostics, we report the capability // and we should disable the publish notifications to avoid double-reporting. - return clientCapabilities.textDocument?.diagnostic == nil + return capabilityRegistry.pullDiagnosticsRegistration(.swift) == nil } private var state: LanguageServerState { @@ -160,7 +160,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { guard let sourcekitd = toolchain.sourcekitd else { return nil } self.client = client self.sourcekitd = try SourceKitDImpl.getOrCreate(dylibPath: sourcekitd) - self.clientCapabilities = clientCapabilities ?? ClientCapabilities(workspace: nil, textDocument: nil) + self.capabilityRegistry = workspace.capabilityRegistry self.serverOptions = options self.documentManager = DocumentManager() self.state = .connected @@ -250,7 +250,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { /// Inform the client about changes to the syntax highlighting tokens. private func requestTokensRefresh() { - if clientCapabilities.workspace?.semanticTokens?.refreshSupport ?? false { + if capabilityRegistry.clientHasSemanticTokenRefreshSupport { _ = client.send(WorkspaceSemanticTokensRefreshRequest(), queue: queue) { result in if let error = result.failure { log("refreshing tokens failed: \(error)", level: .warning) @@ -293,8 +293,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { let stageUID: sourcekitd_uid_t? = response[sourcekitd.keys.diagnostic_stage] let stage = stageUID.flatMap { DiagnosticStage($0, sourcekitd: sourcekitd) } ?? .sema - let supportsCodeDescription = - (clientCapabilities.textDocument?.publishDiagnostics?.codeDescriptionSupport == true) + let supportsCodeDescription = capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport // Note: we make the notification even if there are no diagnostics to clear the current state. var newDiags: [CachedDiagnostic] = [] @@ -1156,7 +1155,7 @@ extension SwiftLanguageServer { } } - let capabilities = self.clientCapabilities.textDocument?.foldingRange + let capabilities = capabilityRegistry.clientCapabilities.textDocument?.foldingRange // If the limit is less than one, do nothing. if let limit = capabilities?.rangeLimit, limit <= 0 { req.reply([]) @@ -1184,7 +1183,7 @@ extension SwiftLanguageServer { retrieveCodeActions(req, providers: providers.map { $0.provider }) { result in switch result { case .success(let codeActions): - let capabilities = self.clientCapabilities.textDocument?.codeAction + let capabilities = capabilityRegistry.clientCapabilities.textDocument?.codeAction let response = CodeActionRequestResponse(codeActions: codeActions, clientCapabilities: capabilities) req.reply(response) @@ -1365,8 +1364,7 @@ extension SwiftLanguageServer { skreq[keys.compilerargs] = compileCommand.compilerArgs } - let supportsCodeDescription = - (clientCapabilities.textDocument?.publishDiagnostics?.codeDescriptionSupport == true) + let supportsCodeDescription = capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport let handle = self.sourcekitd.send(skreq, self.queue) { [weak self] response in guard let self = self else { return } diff --git a/Sources/SourceKitLSP/ToolchainLanguageServer.swift b/Sources/SourceKitLSP/ToolchainLanguageServer.swift index 13d01706e..23482a62b 100644 --- a/Sources/SourceKitLSP/ToolchainLanguageServer.swift +++ b/Sources/SourceKitLSP/ToolchainLanguageServer.swift @@ -32,7 +32,6 @@ public protocol ToolchainLanguageServer: AnyObject { init?( client: LocalConnection, toolchain: Toolchain, - clientCapabilities: ClientCapabilities?, options: SourceKitServer.Options, workspace: Workspace, reopenDocuments: @escaping (ToolchainLanguageServer) -> Void From 22234a0c444b743e76416aa39402a24e6a27cb4d Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Mon, 22 May 2023 16:24:05 -0400 Subject: [PATCH 07/12] Compilation fixes --- Sources/SourceKitLSP/CapabilityRegistry.swift | 8 ++++---- .../Swift/SwiftLanguageServer.swift | 20 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Sources/SourceKitLSP/CapabilityRegistry.swift b/Sources/SourceKitLSP/CapabilityRegistry.swift index b46f9d173..0a22b7899 100644 --- a/Sources/SourceKitLSP/CapabilityRegistry.swift +++ b/Sources/SourceKitLSP/CapabilityRegistry.swift @@ -274,22 +274,22 @@ public final class CapabilityRegistry { if registration.method == CompletionRequest.method { completion.removeValue(forKey: registration) } - if registration.method == FoldingRangeRegistrationOptions.method { + if registration.method == FoldingRangeRequest.method { foldingRange.removeValue(forKey: registration) } if registration.method == SemanticTokensRegistrationOptions.method { semanticTokens.removeValue(forKey: registration) } - if registration.method == InlayHintRegistrationOptions.method { + if registration.method == InlayHintRequest.method { inlayHint.removeValue(forKey: registration) } - if registration.method == DiagnosticRegistrationOptions.method { + if registration.method == DocumentDiagnosticsRequest.method { pullDiagnostics.removeValue(forKey: registration) } } public func pullDiagnosticsRegistration(for language: Language) -> DiagnosticRegistrationOptions? { - registration(for: language, in: pullDiagnostics) + registration(for: [language], in: pullDiagnostics) } private func documentSelector(for languages: [Language]) -> DocumentSelector { diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift index c60f86bde..f416f692e 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift @@ -128,7 +128,7 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { // in addition to the existing push-based publish notifications. // If the client supports pull diagnostics, we report the capability // and we should disable the publish notifications to avoid double-reporting. - return capabilityRegistry.pullDiagnosticsRegistration(.swift) == nil + return capabilityRegistry.pullDiagnosticsRegistration(for: .swift) == nil } private var state: LanguageServerState { @@ -152,7 +152,6 @@ public final class SwiftLanguageServer: ToolchainLanguageServer { public init?( client: LocalConnection, toolchain: Toolchain, - clientCapabilities: ClientCapabilities?, options: SourceKitServer.Options, workspace: Workspace, reopenDocuments: @escaping (ToolchainLanguageServer) -> Void @@ -977,6 +976,7 @@ extension SwiftLanguageServer { } public func foldingRange(_ req: Request) { + let foldingRangeCapabilities = capabilityRegistry.clientCapabilities.textDocument?.foldingRange queue.async { guard let snapshot = self.documentManager.latestSnapshot(req.params.textDocument.uri) else { log("failed to find snapshot for url \(req.params.textDocument.uri)") @@ -1155,17 +1155,16 @@ extension SwiftLanguageServer { } } - let capabilities = capabilityRegistry.clientCapabilities.textDocument?.foldingRange // If the limit is less than one, do nothing. - if let limit = capabilities?.rangeLimit, limit <= 0 { + if let limit = foldingRangeCapabilities?.rangeLimit, limit <= 0 { req.reply([]) return } let rangeFinder = FoldingRangeFinder( snapshot: snapshot, - rangeLimit: capabilities?.rangeLimit, - lineFoldingOnly: capabilities?.lineFoldingOnly ?? false) + rangeLimit: foldingRangeCapabilities?.rangeLimit, + lineFoldingOnly: foldingRangeCapabilities?.lineFoldingOnly ?? false) rangeFinder.walk(sourceFile) let ranges = rangeFinder.finalize() @@ -1180,12 +1179,12 @@ extension SwiftLanguageServer { ] let wantedActionKinds = req.params.context.only let providers = providersAndKinds.filter { wantedActionKinds?.contains($0.1) != false } + let codeActionCapabilities = capabilityRegistry.clientCapabilities.textDocument?.codeAction retrieveCodeActions(req, providers: providers.map { $0.provider }) { result in switch result { case .success(let codeActions): - let capabilities = capabilityRegistry.clientCapabilities.textDocument?.codeAction let response = CodeActionRequestResponse(codeActions: codeActions, - clientCapabilities: capabilities) + clientCapabilities: codeActionCapabilities) req.reply(response) case .failure(let error): req.reply(.failure(error)) @@ -1366,15 +1365,14 @@ extension SwiftLanguageServer { let supportsCodeDescription = capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport - let handle = self.sourcekitd.send(skreq, self.queue) { [weak self] response in - guard let self = self else { return } + let handle = self.sourcekitd.send(skreq, self.queue) { response in guard let dict = response.success else { return completion(.failure(ResponseError(response.failure!))) } var diagnostics: [Diagnostic] = [] dict[keys.diagnostics]?.forEach { _, diag in - if var diagnostic = Diagnostic(diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) { + if let diagnostic = Diagnostic(diag, in: snapshot, useEducationalNoteAsCode: supportsCodeDescription) { diagnostics.append(diagnostic) } return true From 94a62c1e3386510e7b2595f7e193e8fcdbec8010 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Wed, 31 May 2023 07:38:13 -0400 Subject: [PATCH 08/12] Fixed CodeCompletion build error --- Sources/SourceKitLSP/CapabilityRegistry.swift | 4 ++-- Sources/SourceKitLSP/Swift/CodeCompletion.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/SourceKitLSP/CapabilityRegistry.swift b/Sources/SourceKitLSP/CapabilityRegistry.swift index 0a22b7899..6cac156c0 100644 --- a/Sources/SourceKitLSP/CapabilityRegistry.swift +++ b/Sources/SourceKitLSP/CapabilityRegistry.swift @@ -63,7 +63,7 @@ public final class CapabilityRegistry { clientCapabilities.textDocument?.inlayHint?.dynamicRegistration == true } - public var clientHasDocumentDiagnosticsRegistration: Bool { + public var clientHasDynamicDocumentDiagnosticsRegistration: Bool { clientCapabilities.textDocument?.diagnostic?.dynamicRegistration == true } @@ -224,7 +224,7 @@ public final class CapabilityRegistry { for languages: [Language], registerOnClient: ClientRegistrationHandler ) { - guard clientHasDocumentDiagnosticsRegistration else { return } + guard clientHasDynamicDocumentDiagnosticsRegistration else { return } if let registration = registration(for: languages, in: pullDiagnostics) { if options != registration.diagnosticOptions { log("Unable to register new pull diagnostics options \(options) for " + diff --git a/Sources/SourceKitLSP/Swift/CodeCompletion.swift b/Sources/SourceKitLSP/Swift/CodeCompletion.swift index 241cffddf..89ba7b2ca 100644 --- a/Sources/SourceKitLSP/Swift/CodeCompletion.swift +++ b/Sources/SourceKitLSP/Swift/CodeCompletion.swift @@ -139,8 +139,8 @@ extension SwiftLanguageServer { let typeName: String? = value[self.keys.typename] let docBrief: String? = value[self.keys.doc_brief] - let clientCompletionCapabilities = self.clientCapabilities.textDocument?.completion - let clientSupportsSnippets = clientCompletionCapabilities?.completionItem?.snippetSupport == true + let completionCapabilities = self.capabilityRegistry.clientCapabilities.textDocument?.completion + let clientSupportsSnippets = completionCapabilities?.completionItem?.snippetSupport == true let text = insertText.map { rewriteSourceKitPlaceholders(inString: $0, clientSupportsSnippets: clientSupportsSnippets) } From e91c83fd722052582c5b3f1c8a33985c797bdaed Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Wed, 31 May 2023 10:30:54 -0400 Subject: [PATCH 09/12] Remove reporting diagnostic support at SourceKitServer initialization --- Sources/SourceKitLSP/SourceKitServer.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/SourceKitLSP/SourceKitServer.swift b/Sources/SourceKitLSP/SourceKitServer.swift index eb4c44cb0..3ebe5ec26 100644 --- a/Sources/SourceKitLSP/SourceKitServer.swift +++ b/Sources/SourceKitLSP/SourceKitServer.swift @@ -674,10 +674,7 @@ extension SourceKitServer { changeNotifications: .bool(true) )), callHierarchyProvider: .bool(true), - typeHierarchyProvider: .bool(true), - diagnosticProvider: DiagnosticOptions( - interFileDependencies: true, - workspaceDiagnostics: false) + typeHierarchyProvider: .bool(true) ) } From e9c069d21dae15bb19d54d44b5bc5baa95b772b2 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Wed, 31 May 2023 14:48:02 -0400 Subject: [PATCH 10/12] Fix indentation Co-authored-by: Alex Hoppen --- Tests/SourceKitLSPTests/PullDiagnosticsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index 6d0776644..0726474f4 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -61,7 +61,7 @@ final class PullDiagnosticsTests: XCTestCase { } guard case .full(let fullReport) = report else { - XCTFail("Unexpected diagnostics report type: \(report)") + XCTFail("Unexpected diagnostics report type: \(report)") } return fullReport.items From 5e1f667c807a3925981b994d9f9a909b2eefc615 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Wed, 31 May 2023 14:51:06 -0400 Subject: [PATCH 11/12] fix guard clause in test not returning --- Tests/SourceKitLSPTests/PullDiagnosticsTests.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index 0726474f4..fa2b43f87 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -62,6 +62,7 @@ final class PullDiagnosticsTests: XCTestCase { guard case .full(let fullReport) = report else { XCTFail("Unexpected diagnostics report type: \(report)") + return } return fullReport.items From ee043b5dfa206a46e0a113ddc6d9c7ef54269f1d Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Wed, 31 May 2023 16:19:37 -0400 Subject: [PATCH 12/12] Fix unit test --- Tests/SourceKitLSPTests/PullDiagnosticsTests.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index fa2b43f87..36e7c59a8 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -16,6 +16,10 @@ import SKTestSupport import XCTest final class PullDiagnosticsTests: XCTestCase { + enum Error: Swift.Error { + case unexpectedDiagnosticReport + } + /// Connection and lifetime management for the service. var connection: TestSourceKitServer! = nil @@ -61,8 +65,7 @@ final class PullDiagnosticsTests: XCTestCase { } guard case .full(let fullReport) = report else { - XCTFail("Unexpected diagnostics report type: \(report)") - return + throw Error.unexpectedDiagnosticReport } return fullReport.items