diff --git a/Contributor Documentation/LSP Extensions.md b/Contributor Documentation/LSP Extensions.md index bbc53fca7..27a37158d 100644 --- a/Contributor Documentation/LSP Extensions.md +++ b/Contributor Documentation/LSP Extensions.md @@ -2,6 +2,14 @@ SourceKit-LSP extends the LSP protocol in the following ways. +To enable some of these extensions, the client needs to communicate that it can support them. To do so, it should pass a dictionary for the `capabilities.experimental` field in the `initialize` request. For each capability to enable, it should pass an entry as follows. + +```json +"": { + "supported": true +} +``` + ## `PublishDiagnosticsClientCapabilities` Added field (this is an extension from clangd that SourceKit-LSP re-exposes): diff --git a/Sources/SourceKitLSP/CapabilityRegistry.swift b/Sources/SourceKitLSP/CapabilityRegistry.swift index 4b44e1a20..118758c1e 100644 --- a/Sources/SourceKitLSP/CapabilityRegistry.swift +++ b/Sources/SourceKitLSP/CapabilityRegistry.swift @@ -103,7 +103,18 @@ package final actor CapabilityRegistry { guard case .dictionary(let experimentalCapabilities) = clientCapabilities.experimental else { return false } - return experimentalCapabilities[name] == .bool(true) + // Before Swift 6.3 we expected experimental client capabilities to be passed as `"capabilityName": true`. + // This proved to be insufficient for experimental capabilities that evolved over time. Since 6.3 we encourage + // clients to pass experimental capabilities as `"capabilityName": { "supported": true }`, which allows the addition + // of more configuration parameters to the capability. + switch experimentalCapabilities[name] { + case .bool(true): + return true + case .dictionary(let dict): + return dict["supported"] == .bool(true) + default: + return false + } } // MARK: Initializer diff --git a/Sources/SourceKitLSP/SourceKitLSPServer.swift b/Sources/SourceKitLSP/SourceKitLSPServer.swift index 1dd4aee1b..a684d6d38 100644 --- a/Sources/SourceKitLSP/SourceKitLSPServer.swift +++ b/Sources/SourceKitLSP/SourceKitLSPServer.swift @@ -938,12 +938,14 @@ extension SourceKitLSPServer { guard let experimentalCapability = initializationOptions[capabilityName] else { continue } - if case .dictionary(var experimentalCapabilities) = clientCapabilities.experimental { - experimentalCapabilities[capabilityName] = experimentalCapability - clientCapabilities.experimental = .dictionary(experimentalCapabilities) - } else { - clientCapabilities.experimental = .dictionary([capabilityName: experimentalCapability]) - } + var experimentalCapabilities: [String: LSPAny] = + if case .dictionary(let experimentalCapabilities) = clientCapabilities.experimental { + experimentalCapabilities + } else { + [:] + } + experimentalCapabilities[capabilityName] = experimentalCapability + clientCapabilities.experimental = .dictionary(experimentalCapabilities) } // The client announces what CodeLenses it supports, and the LSP will only return diff --git a/Sources/SourceKitLSP/Swift/OpenInterface.swift b/Sources/SourceKitLSP/Swift/OpenInterface.swift index 945c322b2..973643712 100644 --- a/Sources/SourceKitLSP/Swift/OpenInterface.swift +++ b/Sources/SourceKitLSP/Swift/OpenInterface.swift @@ -36,9 +36,7 @@ extension SwiftLanguageService { nil } - if case .dictionary(let experimentalCapabilities) = self.capabilityRegistry.clientCapabilities.experimental, - case .bool(true) = experimentalCapabilities["workspace/getReferenceDocument"] - { + if self.capabilityRegistry.clientHasExperimentalCapability(GetReferenceDocumentRequest.method) { return GeneratedInterfaceDetails(uri: try urlData.uri, position: position) } let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent(urlData.displayName) diff --git a/Tests/SourceKitLSPTests/ExpandMacroTests.swift b/Tests/SourceKitLSPTests/ExpandMacroTests.swift index 429c88b77..1679f1b57 100644 --- a/Tests/SourceKitLSPTests/ExpandMacroTests.swift +++ b/Tests/SourceKitLSPTests/ExpandMacroTests.swift @@ -79,8 +79,8 @@ final class ExpandMacroTests: XCTestCase { files: files, manifest: SwiftPMTestProject.macroPackageManifest, capabilities: ClientCapabilities(experimental: [ - "workspace/peekDocuments": .bool(peekDocuments), - "workspace/getReferenceDocument": .bool(getReferenceDocument), + PeekDocumentsRequest.method: .dictionary(["supported": .bool(peekDocuments)]), + GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(getReferenceDocument)]), ]), options: SourceKitLSPOptions.testDefault(), enableBackgroundIndexing: true @@ -268,8 +268,8 @@ final class ExpandMacroTests: XCTestCase { files: files, manifest: SwiftPMTestProject.macroPackageManifest, capabilities: ClientCapabilities(experimental: [ - PeekDocumentsRequest.method: .bool(peekDocuments), - GetReferenceDocumentRequest.method: .bool(getReferenceDocument), + PeekDocumentsRequest.method: .dictionary(["supported": .bool(peekDocuments)]), + GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(getReferenceDocument)]), ]), options: SourceKitLSPOptions.testDefault(), enableBackgroundIndexing: true @@ -464,8 +464,8 @@ final class ExpandMacroTests: XCTestCase { files: files, manifest: SwiftPMTestProject.macroPackageManifest, capabilities: ClientCapabilities(experimental: [ - "workspace/peekDocuments": .bool(true), - "workspace/getReferenceDocument": .bool(true), + PeekDocumentsRequest.method: .dictionary(["supported": .bool(true)]), + GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(true)]), ]), options: SourceKitLSPOptions.testDefault(), enableBackgroundIndexing: true diff --git a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift index e0eba1ce2..4997c5942 100644 --- a/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift +++ b/Tests/SourceKitLSPTests/SwiftInterfaceTests.swift @@ -45,7 +45,7 @@ final class SwiftInterfaceTests: XCTestCase { func testSystemModuleInterfaceReferenceDocument() async throws { let testClient = try await TestSourceKitLSPClient( capabilities: ClientCapabilities(experimental: [ - "workspace/getReferenceDocument": .bool(true) + GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(true)]) ]) ) let uri = DocumentURI(for: .swift) @@ -117,7 +117,7 @@ final class SwiftInterfaceTests: XCTestCase { } """, capabilities: ClientCapabilities(experimental: [ - "workspace/getReferenceDocument": .bool(true) + GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(true)]) ]), indexSystemModules: true ) @@ -210,7 +210,7 @@ final class SwiftInterfaceTests: XCTestCase { ) """, capabilities: ClientCapabilities(experimental: [ - "workspace/getReferenceDocument": .bool(true) + GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(true)]) ]), enableBackgroundIndexing: true ) @@ -292,7 +292,7 @@ final class SwiftInterfaceTests: XCTestCase { func testNoDiagnosticsInGeneratedInterface() async throws { let testClient = try await TestSourceKitLSPClient( capabilities: ClientCapabilities(experimental: [ - "workspace/getReferenceDocument": .bool(true) + GetReferenceDocumentRequest.method: .dictionary(["supported": .bool(true)]) ]) ) let uri = DocumentURI(for: .swift) diff --git a/Tests/SourceKitLSPTests/WorkspaceTests.swift b/Tests/SourceKitLSPTests/WorkspaceTests.swift index c2c76e562..d3d72f60c 100644 --- a/Tests/SourceKitLSPTests/WorkspaceTests.swift +++ b/Tests/SourceKitLSPTests/WorkspaceTests.swift @@ -1125,7 +1125,7 @@ final class WorkspaceTests: XCTestCase { ) """, capabilities: ClientCapabilities(experimental: [ - DidChangeActiveDocumentNotification.method: .bool(true) + DidChangeActiveDocumentNotification.method: .dictionary(["supported": .bool(true)]) ]), hooks: Hooks( indexHooks: IndexHooks(preparationTaskDidStart: { task in