From 5b53171d500d655b67a36afc25f99fe49cce5b2d Mon Sep 17 00:00:00 2001 From: Matt <85322+mattmassicotte@users.noreply.github.com> Date: Wed, 28 Jun 2023 07:00:21 -0400 Subject: [PATCH] Real concurrency support --- .github/workflows/ci.yml | 23 +- Package.resolved | 4 +- Package.swift | 14 +- README.md | 4 +- .../Additions/JSONRPCServer.swift | 343 +++++++++++ .../Additions/MockServer.swift | 95 +++ .../NSRegularExpression+Matching.swift | 0 .../Additions/Protocol+Mutating.swift | 30 + .../Additions/SemanticTokensClient.swift | 49 -- .../Additions/Server.swift | 253 ++++++++ .../ServerCapabilities+Extensions.swift | 0 .../{ => Additions}/Snippet.swift | 0 .../Additions/TokenRepresentation.swift | 32 +- .../LanguageServerProtocol/BaseProtocol.swift | 10 +- .../BasicStructures.swift | 14 +- Sources/LanguageServerProtocol/Client.swift | 20 +- Sources/LanguageServerProtocol/General.swift | 4 +- .../JSONRPC+Extensions.swift | 31 - .../JSONRPCLanguageServer.swift | 426 -------------- .../LanguageFeatures/CallHeirarchy.swift | 8 +- .../LanguageFeatures/CodeAction.swift | 24 +- .../LanguageFeatures/CodeLens.swift | 2 +- .../LanguageFeatures/ColorPresentation.swift | 4 +- .../LanguageFeatures/Completion.swift | 12 +- .../LanguageFeatures/DocumentColor.swift | 6 +- .../LanguageFeatures/DocumentHighlight.swift | 8 +- .../LanguageFeatures/DocumentLink.swift | 7 + .../LanguageFeatures/DocumentSymbol.swift | 4 +- .../LanguageFeatures/Formatting.swift | 8 +- .../LanguageFeatures/Hover.swift | 12 +- .../LanguageFeatures/References.swift | 4 +- .../LanguageFeatures/SelectionRange.swift | 4 +- .../LanguageFeatures/SemanticTokens.swift | 20 +- .../LanguageFeatures/SignatureHelp.swift | 8 +- .../LanguageServerProtocol.swift | 148 +++-- Sources/LanguageServerProtocol/Server.swift | 549 ------------------ .../ServerCapabilities.swift | 62 +- .../TextSynchronization.swift | 20 +- .../ThreeTypeOption.swift | 8 +- Sources/LanguageServerProtocol/Window.swift | 16 +- .../Window/ShowMessageRequest.swift | 6 +- .../LanguageServerProtocol/Workspace.swift | 36 +- .../Workspace/ApplyEdit.swift | 4 +- .../Workspace/Configuration.swift | 4 +- .../Workspace/ExecuteCommand.swift | 4 +- .../Workspace/Folders.swift | 1 + .../Workspace/WillCreateFiles.swift | 14 +- .../Workspace/WillDeleteFiles.swift | 4 +- .../Workspace/WillRenameFiles.swift | 4 +- .../ServerTests.swift | 84 +-- 50 files changed, 1101 insertions(+), 1346 deletions(-) create mode 100644 Sources/LanguageServerProtocol/Additions/JSONRPCServer.swift create mode 100644 Sources/LanguageServerProtocol/Additions/MockServer.swift rename Sources/LanguageServerProtocol/{ => Additions}/NSRegularExpression+Matching.swift (100%) create mode 100644 Sources/LanguageServerProtocol/Additions/Protocol+Mutating.swift delete mode 100644 Sources/LanguageServerProtocol/Additions/SemanticTokensClient.swift create mode 100644 Sources/LanguageServerProtocol/Additions/Server.swift rename Sources/LanguageServerProtocol/{ => Additions}/ServerCapabilities+Extensions.swift (100%) rename Sources/LanguageServerProtocol/{ => Additions}/Snippet.swift (100%) delete mode 100644 Sources/LanguageServerProtocol/JSONRPC+Extensions.swift delete mode 100644 Sources/LanguageServerProtocol/JSONRPCLanguageServer.swift delete mode 100644 Sources/LanguageServerProtocol/Server.swift create mode 100644 Sources/LanguageServerProtocol/Workspace/Folders.swift diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 608503a..d3bc155 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,11 +8,28 @@ on: branches: - '**' +env: + DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer + jobs: - test: + apple_test: name: Test - runs-on: macOS-latest + runs-on: macOS-13 + strategy: + matrix: + destination: + - "platform=macOS" + - "platform=iOS Simulator,name=iPhone 12" + + steps: + - uses: actions/checkout@v3 + - name: Test platform ${{ matrix.destination }} + run: set -o pipefail && xcodebuild -scheme LanguageServerProtocol -destination "${{ matrix.destination }}" test | xcpretty + + linux_test: + name: Test Linux + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: swift test run: swift test diff --git a/Package.resolved b/Package.resolved index 75a0c3f..4fdb3d9 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/ChimeHQ/JSONRPC", "state": { "branch": null, - "revision": "aa785ad404bdcf7ec692908b3905dd4c87e87141", - "version": "0.7.1" + "revision": "306acbe0de2e87e4f6c1dc3901613aa9df293754", + "version": null } } ] diff --git a/Package.swift b/Package.swift index 558a46b..88c8ee0 100644 --- a/Package.swift +++ b/Package.swift @@ -2,23 +2,29 @@ import PackageDescription +let settings: [SwiftSetting] = [ +// .unsafeFlags(["-Xfrontend", "-strict-concurrency=complete"]) +] + let package = Package( name: "LanguageServerProtocol", - platforms: [.macOS(.v10_12), .iOS(.v10), .tvOS(.v10), .watchOS(.v3)], + platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)], products: [ .library( name: "LanguageServerProtocol", targets: ["LanguageServerProtocol"]), ], dependencies: [ - .package(url: "https://github.com/ChimeHQ/JSONRPC", "0.7.0"..."0.7.1"), + .package(url: "https://github.com/ChimeHQ/JSONRPC", revision: "306acbe0de2e87e4f6c1dc3901613aa9df293754"), ], targets: [ .target( name: "LanguageServerProtocol", - dependencies: ["JSONRPC"]), + dependencies: ["JSONRPC"], + swiftSettings: settings), .testTarget( name: "LanguageServerProtocolTests", - dependencies: ["LanguageServerProtocol"]), + dependencies: ["LanguageServerProtocol"], + swiftSettings: settings), ] ) diff --git a/README.md b/README.md index 51ac456..71e1d2d 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,10 @@ dependencies: [ For the most part, this library strives to be a straightforward version of the spec in Swift. There are a few places, however, where it just makes sense to pull in some extra functionality. +- `MockServer`: a stand-in that is useful for mocking a real server +- `Server`: a protocol that describes the essential server functionality - `Snippet`: makes it easier to interpret the contents of completion results -- `SemanticTokensClient`: helps to consume Semantic Token information +- `TokenRepresentation`: maintains the state of a document's semantic tokens ## Supported Features diff --git a/Sources/LanguageServerProtocol/Additions/JSONRPCServer.swift b/Sources/LanguageServerProtocol/Additions/JSONRPCServer.swift new file mode 100644 index 0000000..aaf7c0d --- /dev/null +++ b/Sources/LanguageServerProtocol/Additions/JSONRPCServer.swift @@ -0,0 +1,343 @@ +import Foundation +import JSONRPC + +enum ServerError: Error { + case unrecognizedMethod(String) + case missingParams + case unhandledRegisterationMethod(String) + case missingReply +} + +public actor JSONRPCServer: Server { + public let notificationSequence: NotificationSequence + public let requestSequence: RequestSequence + + private let notificationContinuation: NotificationSequence.Continuation + private let requestContinuation: RequestSequence.Continuation + + private let session: JSONRPCSession + private var notificationTask: Task? + private var requestTask: Task? + + public init(dataChannel: DataChannel) { + self.session = JSONRPCSession(channel: dataChannel) + + (self.notificationSequence, self.notificationContinuation) = NotificationSequence.makeStream() + (self.requestSequence, self.requestContinuation) = RequestSequence.makeStream() + + Task { + await startMonitoringSession() + } + } + + deinit { + notificationTask?.cancel() + notificationContinuation.finish() + + requestTask?.cancel() + requestContinuation.finish() + } + + private func startMonitoringSession() async { + let noteSequence = await session.notificationSequence + + self.notificationTask = Task { [weak self] in + for await (notification, data) in noteSequence { + guard let self = self else { break } + + await self.handleNotification(notification, data: data) + } + + self?.notificationContinuation.finish() + } + + let reqSequence = await session.requestSequence + + self.requestTask = Task { [weak self] in + for await (request, handler, data) in reqSequence { + guard let self = self else { break } + + await self.handleRequest(request, data: data, handler: handler) + } + + self?.requestContinuation.finish() + } + } + + public func sendNotification(_ notif: ClientNotification) async throws { + let method = notif.method.rawValue + + switch notif { + case .initialized(let params): + try await session.sendNotification(params, method: method) + case .exit: + try await session.sendNotification(method: method) + case .textDocumentDidChange(let params): + try await session.sendNotification(params, method: method) + case .didOpenTextDocument(let params): + try await session.sendNotification(params, method: method) + case .didChangeTextDocument(let params): + try await session.sendNotification(params, method: method) + case .didCloseTextDocument(let params): + try await session.sendNotification(params, method: method) + case .willSaveTextDocument(let params): + try await session.sendNotification(params, method: method) + case .didSaveTextDocument(let params): + try await session.sendNotification(params, method: method) + case .didChangeWatchedFiles(let params): + try await session.sendNotification(params, method: method) + case .protocolCancelRequest(let params): + try await session.sendNotification(params, method: method) + case .protocolSetTrace(let params): + try await session.sendNotification(params, method: method) + case .workspaceDidChangeWorkspaceFolders(let params): + try await session.sendNotification(params, method: method) + case .workspaceDidChangeConfiguration(let params): + try await session.sendNotification(params, method: method) + case .workspaceDidCreateFiles(let params): + try await session.sendNotification(params, method: method) + case .workspaceDidRenameFiles(let params): + try await session.sendNotification(params, method: method) + case .workspaceDidDeleteFiles(let params): + try await session.sendNotification(params, method: method) + case .windowWorkDoneProgressCancel(let params): + try await session.sendNotification(params, method: method) + } + } + + public func sendRequest(_ request: ClientRequest) async throws -> Response where Response : Decodable & Sendable { + let method = request.method.rawValue + + switch request { + case .initialize(let params): + return try await session.response(to: method, params: params) + case .shutdown: + return try await session.response(to: method) + case .workspaceExecuteCommand(let params): + return try await session.response(to: method, params: params) + case .workspaceWillCreateFiles(let params): + return try await session.response(to: method, params: params) + case .workspaceWillRenameFiles(let params): + return try await session.response(to: method, params: params) + case .workspaceWillDeleteFiles(let params): + return try await session.response(to: method, params: params) + case .workspaceSymbol(let params): + return try await session.response(to: method, params: params) + case .workspaceSymbolResolve(let params): + return try await session.response(to: method, params: params) + case .willSaveWaitUntilTextDocument(let params): + return try await session.response(to: method, params: params) + case .completion(let params): + return try await session.response(to: method, params: params) + case .completionItemResolve(let params): + return try await session.response(to: method, params: params) + case .hover(let params): + return try await session.response(to: method, params: params) + case .signatureHelp(let params): + return try await session.response(to: method, params: params) + case .declaration(let params): + return try await session.response(to: method, params: params) + case .definition(let params): + return try await session.response(to: method, params: params) + case .typeDefinition(let params): + return try await session.response(to: method, params: params) + case .implementation(let params): + return try await session.response(to: method, params: params) + case .documentHighlight(let params): + return try await session.response(to: method, params: params) + case .documentSymbol(let params): + return try await session.response(to: method, params: params) + case .codeAction(let params): + return try await session.response(to: method, params: params) + case .codeLens(let params): + return try await session.response(to: method, params: params) + case .codeLensResolve(let params): + return try await session.response(to: method, params: params) + case .selectionRange(let params): + return try await session.response(to: method, params: params) + case .prepareCallHierarchy(let params): + return try await session.response(to: method, params: params) + case .prepareRename(let params): + return try await session.response(to: method, params: params) + case .rename(let params): + return try await session.response(to: method, params: params) + case .documentLink(let params): + return try await session.response(to: method, params: params) + case .documentLinkResolve(let params): + return try await session.response(to: method, params: params) + case .documentColor(let params): + return try await session.response(to: method, params: params) + case .colorPresentation(let params): + return try await session.response(to: method, params: params) + case .formatting(let params): + return try await session.response(to: method, params: params) + case .rangeFormatting(let params): + return try await session.response(to: method, params: params) + case .onTypeFormatting(let params): + return try await session.response(to: method, params: params) + case .references(let params): + return try await session.response(to: method, params: params) + case .foldingRange(let params): + return try await session.response(to: method, params: params) + case .semanticTokensFull(let params): + return try await session.response(to: method, params: params) + case .semanticTokensFullDelta(let params): + return try await session.response(to: method, params: params) + case .semanticTokensRange(let params): + return try await session.response(to: method, params: params) + case .callHierarchyIncomingCalls(let params): + return try await session.response(to: method, params: params) + case .callHierarchyOutgoingCalls(let params): + return try await session.response(to: method, params: params) + case let .custom(method, params): + return try await session.response(to: method, params: params) + } + } + + private func decodeNotificationParams(_ type: Params.Type, from data: Data) throws -> Params where Params : Decodable { + let note = try JSONDecoder().decode(JSONRPCNotification.self, from: data) + + guard let params = note.params else { + throw ServerError.missingParams + } + + return params + } + + private func handleNotification(_ anyNotification: AnyJSONRPCNotification, data: Data) { + let methodName = anyNotification.method + + do { + guard let method = ServerNotification.Method(rawValue: methodName) else { + throw ServerError.unrecognizedMethod(methodName) + } + + switch method { + case .windowLogMessage: + let params = try decodeNotificationParams(LogMessageParams.self, from: data) + + notificationContinuation.yield(.windowLogMessage(params)) + case .windowShowMessage: + let params = try decodeNotificationParams(ShowMessageParams.self, from: data) + + notificationContinuation.yield(.windowShowMessage(params)) + case .textDocumentPublishDiagnostics: + let params = try decodeNotificationParams(PublishDiagnosticsParams.self, from: data) + + notificationContinuation.yield(.textDocumentPublishDiagnostics(params)) + case .telemetryEvent: + let params = anyNotification.params ?? .null + + notificationContinuation.yield(.telemetryEvent(params)) + case .protocolCancelRequest: + let params = try decodeNotificationParams(CancelParams.self, from: data) + + notificationContinuation.yield(.protocolCancelRequest(params)) + case .protocolProgress: + let params = try decodeNotificationParams(ProgressParams.self, from: data) + + notificationContinuation.yield(.protocolProgress(params)) + case .protocolLogTrace: + let params = try decodeNotificationParams(LogTraceParams.self, from: data) + + notificationContinuation.yield(.protocolLogTrace(params)) + } + } catch { + // should we backchannel this to the client somehow? + print("failed to relay notification: \(error)") + } + } + + private func decodeRequestParams(_ type: Params.Type, from data: Data) throws -> Params where Params : Decodable { + let req = try JSONDecoder().decode(JSONRPCRequest.self, from: data) + + guard let params = req.params else { + throw ServerError.missingParams + } + + return params + } + + private nonisolated func makeErrorOnlyHandler(_ handler: @escaping JSONRPCSession.RequestHandler) -> ServerRequest.ErrorOnlyHandler { + return { + if let error = $0 { + await handler(.failure(error)) + } else { + await handler(.success(JSONValue.null)) + } + } + } + + private nonisolated func makeHandler(_ handler: @escaping JSONRPCSession.RequestHandler) -> ServerRequest.Handler { + return { + let loweredResult = $0.map({ $0 as Encodable & Sendable }) + + await handler(loweredResult) + } + } + + private func handleRequest(_ anyRequest: AnyJSONRPCRequest, data: Data, handler: @escaping JSONRPCSession.RequestHandler) { + let methodName = anyRequest.method + + do { + guard let method = ServerRequest.Method(rawValue: methodName) else { + throw ServerError.unrecognizedMethod(methodName) + } + + switch method { + case .workspaceConfiguration: + let params = try decodeRequestParams(ConfigurationParams.self, from: data) + let reqHandler: ServerRequest.Handler<[LSPAny]> = makeHandler(handler) + + requestContinuation.yield(ServerRequest.workspaceConfiguration(params, reqHandler)) + case .workspaceFolders: + let reqHandler: ServerRequest.Handler = makeHandler(handler) + + requestContinuation.yield(ServerRequest.workspaceFolders(reqHandler)) + case .workspaceApplyEdit: + let params = try decodeRequestParams(ApplyWorkspaceEditParams.self, from: data) + let reqHandler: ServerRequest.Handler = makeHandler(handler) + + requestContinuation.yield(ServerRequest.workspaceApplyEdit(params, reqHandler)) + case .clientRegisterCapability: + let params = try decodeRequestParams(RegistrationParams.self, from: data) + let reqHandler = makeErrorOnlyHandler(handler) + + requestContinuation.yield(ServerRequest.clientRegisterCapability(params, reqHandler)) + case .clientUnregisterCapability: + let params = try decodeRequestParams(UnregistrationParams.self, from: data) + let reqHandler = makeErrorOnlyHandler(handler) + + requestContinuation.yield(ServerRequest.clientUnregisterCapability(params, reqHandler)) + case .workspaceCodeLensRefresh: + let reqHandler = makeErrorOnlyHandler(handler) + + requestContinuation.yield(ServerRequest.workspaceCodeLensRefresh(reqHandler)) + case .workspaceSemanticTokenRefresh: + let reqHandler = makeErrorOnlyHandler(handler) + + requestContinuation.yield(ServerRequest.workspaceSemanticTokenRefresh(reqHandler)) + case .windowShowMessageRequest: + let params = try decodeRequestParams(ShowMessageRequestParams.self, from: data) + let reqHandler: ServerRequest.Handler = makeHandler(handler) + + requestContinuation.yield(ServerRequest.windowShowMessageRequest(params, reqHandler)) + case .windowShowDocument: + let params = try decodeRequestParams(ShowDocumentParams.self, from: data) + let reqHandler: ServerRequest.Handler = makeHandler(handler) + + requestContinuation.yield(ServerRequest.windowShowDocument(params, reqHandler)) + case .windowWorkDoneProgressCreate: + let params = try decodeRequestParams(WorkDoneProgressCreateParams.self, from: data) + let reqHandler = makeErrorOnlyHandler(handler) + + requestContinuation.yield(ServerRequest.windowWorkDoneProgressCreate(params, reqHandler)) + + } + + } catch { + // should we backchannel this to the client somehow? + print("failed to relay request: \(error)") + } + } +} diff --git a/Sources/LanguageServerProtocol/Additions/MockServer.swift b/Sources/LanguageServerProtocol/Additions/MockServer.swift new file mode 100644 index 0000000..9c3e2b4 --- /dev/null +++ b/Sources/LanguageServerProtocol/Additions/MockServer.swift @@ -0,0 +1,95 @@ +import Foundation + +extension AsyncSequence { + func collect() async rethrows -> [Element] { + try await reduce(into: [Element]()) { $0.append($1) } + } +} + +/// Simulate LSP communication. +public actor MockServer: Server { + public enum ClientMessage: Sendable, Hashable { + case notification(ClientNotification) + case request(ClientRequest) + } + + public typealias ClientMessageSequence = AsyncStream + private typealias ResponseDataSequence = AsyncStream + + public let notificationSequence: NotificationSequence + public let requestSequence: RequestSequence + + private let notificationContinuation: NotificationSequence.Continuation + private let requestContinuation: RequestSequence.Continuation + + private var mockResponses = [Data]() + + public let sentMessageSequence: ClientMessageSequence + private let sentMessageContinuation: ClientMessageSequence.Continuation + + public init() { + (self.notificationSequence, self.notificationContinuation) = NotificationSequence.makeStream() + (self.requestSequence, self.requestContinuation) = RequestSequence.makeStream() + (self.sentMessageSequence, self.sentMessageContinuation) = ClientMessageSequence.makeStream() + } + + deinit { + sentMessageContinuation.finish() + notificationContinuation.finish() + requestContinuation.finish() + } + + public func sendNotification(_ notif: ClientNotification) async throws { + sentMessageContinuation.yield(.notification(notif)) + } + + public func sendRequest(_ request: ClientRequest) async throws -> Response where Response : Decodable, Response : Sendable { + sentMessageContinuation.yield(.request(request)) + + if mockResponses.isEmpty { + throw ServerError.missingReply + } + + let data = mockResponses.removeFirst() + + return try JSONDecoder().decode(Response.self, from: data) + } +} + +extension MockServer { + /// Returns an array of sent messages. + public func finishSession() async -> [ClientMessage] { + sentMessageContinuation.finish() + notificationContinuation.finish() + requestContinuation.finish() + + return await sentMessageSequence.collect() + } + + /// Simulate a server response. + public func sendMockResponse(_ data: Data) { + mockResponses.append(data) + } + + /// Simulate a server response. + public func sendMockResponse(_ string: String) { + sendMockResponse(string.data(using: .utf8)!) + } + + /// Simulate a server response. + public func sendMockResponse(_ response: Response) throws where Response : Encodable, Response : Sendable { + let data = try JSONEncoder().encode(response) + + sendMockResponse(data) + } + + /// Simulate a server request. + public func sendMockRequest(_ request: ServerRequest) { + requestContinuation.yield(request) + } + + /// Simulate a server notification. + public func sendMockNotification(_ note: ServerNotification) { + notificationContinuation.yield(note) + } +} diff --git a/Sources/LanguageServerProtocol/NSRegularExpression+Matching.swift b/Sources/LanguageServerProtocol/Additions/NSRegularExpression+Matching.swift similarity index 100% rename from Sources/LanguageServerProtocol/NSRegularExpression+Matching.swift rename to Sources/LanguageServerProtocol/Additions/NSRegularExpression+Matching.swift diff --git a/Sources/LanguageServerProtocol/Additions/Protocol+Mutating.swift b/Sources/LanguageServerProtocol/Additions/Protocol+Mutating.swift new file mode 100644 index 0000000..8d7f898 --- /dev/null +++ b/Sources/LanguageServerProtocol/Additions/Protocol+Mutating.swift @@ -0,0 +1,30 @@ +import Foundation + +extension ClientNotification { + public var mutatesServerState: Bool { + return true + } +} + +extension ClientRequest { + public var mutatesServerState: Bool { + switch self { + case .initialize: + return true + case .shutdown: + return true + case .workspaceWillCreateFiles: + return true + case .workspaceWillRenameFiles: + return true + case .workspaceWillDeleteFiles: + return true + case .willSaveWaitUntilTextDocument: + return true + case .custom: + return true + default: + return false + } + } +} diff --git a/Sources/LanguageServerProtocol/Additions/SemanticTokensClient.swift b/Sources/LanguageServerProtocol/Additions/SemanticTokensClient.swift deleted file mode 100644 index 59acfd6..0000000 --- a/Sources/LanguageServerProtocol/Additions/SemanticTokensClient.swift +++ /dev/null @@ -1,49 +0,0 @@ -import Foundation - -/// Handles requesting and decoding Semantic Token information. -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -public actor SemanticTokensClient { - public private(set) var lastResultId: String? = nil - private var tokenRepresentation: TokenRepresentation - public let textDocument: TextDocumentIdentifier - public let server: Server - - public init(legend: SemanticTokensLegend, textDocument: TextDocumentIdentifier, server: Server) { - self.tokenRepresentation = TokenRepresentation(legend: legend) - self.textDocument = textDocument - self.server = server - } - - public func tokens(in range: LSPRange, supportsDeltas: Bool = true) async throws -> [Token] { - let response = try await requestTokens(supportsDeltas: supportsDeltas) - - switch response { - case .optionA(let fullResponse): - self.lastResultId = fullResponse.resultId - - _ = tokenRepresentation.applyData(fullResponse.data) - case .optionB(let delta): - self.lastResultId = delta.resultId - - _ = tokenRepresentation.applyEdits(delta.edits) - case nil: - return [] - } - - return tokenRepresentation.decodeTokens(in: range) - } - - private func requestTokens(supportsDeltas: Bool) async throws -> SemanticTokensDeltaResponse { - if let resultId = self.lastResultId, supportsDeltas { - let params = SemanticTokensDeltaParams(textDocument: textDocument, previousResultId: resultId) - - return try await self.server.semanticTokensFullDelta(params: params) - } - - let params = SemanticTokensParams(textDocument: textDocument) - - // translate to a delta response first so we can use a uniform handler for both cases - return try await server.semanticTokensFull(params: params) - .map({ .optionA($0) }) - } -} diff --git a/Sources/LanguageServerProtocol/Additions/Server.swift b/Sources/LanguageServerProtocol/Additions/Server.swift new file mode 100644 index 0000000..f34bcc0 --- /dev/null +++ b/Sources/LanguageServerProtocol/Additions/Server.swift @@ -0,0 +1,253 @@ +import Foundation + +/// Handles the communication between an LSP client and server. +/// +/// This protocol defines all the messages that can be sent between an LSP client and server. It does **not** enforce correct ordering of those messages. +public protocol Server { + typealias NotificationSequence = AsyncStream + typealias RequestSequence = AsyncStream + + var notificationSequence: NotificationSequence { get } + var requestSequence: RequestSequence { get } + + func sendNotification(_ notif: ClientNotification) async throws + func sendRequest(_ request: ClientRequest) async throws -> Response +} + +extension Server { + func sendRequestWithErrorOnlyResult(_ request: ClientRequest) async throws { + let _: UnusedResult = try await sendRequest(request) + } +} + +public extension Server { + func initialize(params: InitializeParams) async throws -> InitializationResponse { + return try await sendRequest(.initialize(params)) + } + + func initialized(params: InitializedParams) async throws { + try await sendNotification(.initialized(params)) + } + + func shutdown() async throws { + try await sendRequestWithErrorOnlyResult(.shutdown) + } + + func exit() async throws { + try await sendNotification(.exit) + } + + func cancelRequest(params: CancelParams) async throws { + try await sendNotification(.protocolCancelRequest(params)) + } + + func setTrace(params: SetTraceParams) async throws { + try await sendNotification(.protocolSetTrace(params)) + } + + func didOpenTextDocument(params: DidOpenTextDocumentParams) async throws { + try await sendNotification(.didOpenTextDocument(params)) + } + + func didChangeTextDocument(params: DidChangeTextDocumentParams) async throws { + try await sendNotification(.didChangeTextDocument(params)) + } + + func didCloseTextDocument(params: DidCloseTextDocumentParams) async throws { + try await sendNotification(.didCloseTextDocument(params)) + } + + func willSaveTextDocument(params: WillSaveTextDocumentParams) async throws { + try await sendNotification(.willSaveTextDocument(params)) + } + + func willSaveWaitUntilTextDocument(params: WillSaveTextDocumentParams) async throws -> WillSaveWaitUntilResponse { + try await sendRequest(.willSaveWaitUntilTextDocument(params)) + } + + func didSaveTextDocument(params: DidSaveTextDocumentParams) async throws { + try await sendNotification(.didSaveTextDocument(params)) + } + + func didChangeWatchedFiles(params: DidChangeWatchedFilesParams) async throws { + try await sendNotification(.didChangeWatchedFiles(params)) + } + + func callHierarchyIncomingCalls(params: CallHierarchyIncomingCallsParams) async throws -> CallHierarchyIncomingCallsResponse { + try await sendRequest(.callHierarchyIncomingCalls(params)) + } + + func callHierarchyOutgoingCalls(params: CallHierarchyOutgoingCallsParams) async throws -> CallHierarchyOutgoingCallsResponse { + try await sendRequest(.callHierarchyOutgoingCalls(params)) + } + + func completion(params: CompletionParams) async throws -> CompletionResponse { + try await sendRequest(.completion(params)) + } + + func hover(params: TextDocumentPositionParams) async throws -> HoverResponse { + try await sendRequest(.hover(params)) + } + + func signatureHelp(params: TextDocumentPositionParams) async throws -> SignatureHelpResponse { + try await sendRequest(.signatureHelp(params)) + } + + func declaration(params: TextDocumentPositionParams) async throws -> DeclarationResponse { + try await sendRequest(.declaration(params)) + } + + func definition(params: TextDocumentPositionParams) async throws -> DefinitionResponse { + try await sendRequest(.definition(params)) + } + + func typeDefinition(params: TextDocumentPositionParams) async throws -> TypeDefinitionResponse { + try await sendRequest(.typeDefinition(params)) + } + + func implementation(params: TextDocumentPositionParams) async throws -> ImplementationResponse { + try await sendRequest(.implementation(params)) + } + + func documentSymbol(params: DocumentSymbolParams) async throws -> DocumentSymbolResponse { + try await sendRequest(.documentSymbol(params)) + } + + func codeAction(params: CodeActionParams) async throws -> CodeActionResponse { + try await sendRequest(.codeAction(params)) + } + + func prepareCallHierarchy(params: CallHierarchyPrepareParams) async throws -> CallHierarchyPrepareResponse { + try await sendRequest(.prepareCallHierarchy(params)) + } + + func prepareRename(params: PrepareRenameParams) async throws -> PrepareRenameResponse { + try await sendRequest(.prepareRename(params)) + } + + func rename(params: RenameParams) async throws -> RenameResponse { + try await sendRequest(.rename(params)) + } + + func formatting(params: DocumentFormattingParams) async throws -> FormattingResult { + try await sendRequest(.formatting(params)) + } + + func rangeFormatting(params: DocumentRangeFormattingParams) async throws -> FormattingResult { + try await sendRequest(.rangeFormatting(params)) + } + + func onTypeFormatting(params: DocumentOnTypeFormattingParams) async throws -> FormattingResult { + try await sendRequest(.onTypeFormatting(params)) + } + + func references(params: ReferenceParams) async throws -> ReferenceResponse { + try await sendRequest(.references(params)) + } + + func foldingRange(params: FoldingRangeParams) async throws -> FoldingRangeResponse { + try await sendRequest(.foldingRange(params)) + } + + func semanticTokensFull(params: SemanticTokensParams) async throws -> SemanticTokensResponse { + try await sendRequest(.semanticTokensFull(params)) + } + + func semanticTokensFullDelta(params: SemanticTokensDeltaParams) async throws -> SemanticTokensDeltaResponse { + try await sendRequest(.semanticTokensFullDelta(params)) + } + + func semanticTokensRange(params: SemanticTokensRangeParams) async throws -> SemanticTokensResponse { + try await sendRequest(.semanticTokensRange(params)) + } + + + func customRequest(method: String, params: LSPAny) async throws -> Response { + try await sendRequest(.custom(method, params)) + } +} + +// Workspace notifications +public extension Server { + func didChangeWorkspaceFolders(params: DidChangeWorkspaceFoldersParams) async throws { + try await sendNotification(.workspaceDidChangeWorkspaceFolders(params)) + } + + func didChangeConfiguration(params: DidChangeConfigurationParams) async throws { + try await sendNotification(.workspaceDidChangeConfiguration(params)) + } + + func didCreateFiles(params: CreateFilesParams) async throws { + try await sendNotification(.workspaceDidCreateFiles(params)) + } + + func didRenameFiles(params: RenameFilesParams) async throws { + try await sendNotification(.workspaceDidRenameFiles(params)) + } + + func didDeleteFiles(params: DeleteFilesParams) async throws { + try await sendNotification(.workspaceDidDeleteFiles(params)) + } +} + +// Workspace Requests +public extension Server { + func willCreateFiles(params: CreateFilesParams) async throws -> WorkspaceWillCreateFilesResponse { + try await sendRequest(.workspaceWillCreateFiles(params)) + } + + func willRenameFiles(params: RenameFilesParams) async throws -> WorkspaceWillRenameFilesResponse { + try await sendRequest(.workspaceWillRenameFiles(params)) + } + + func willDeleteFiles(params: DeleteFilesParams) async throws -> WorkspaceWillDeleteFilesResponse { + try await sendRequest(.workspaceWillDeleteFiles(params)) + } + + func executeCommand(params: ExecuteCommandParams) async throws -> ExecuteCommandResponse { + try await sendRequest(.workspaceExecuteCommand(params)) + } + + func workspaceSymbol(params: WorkspaceSymbolParams) async throws -> WorkspaceSymbolResponse { + try await sendRequest(.workspaceSymbol(params)) + } + + func workspaceSymbolResolve(params: WorkspaceSymbol) async throws -> WorkspaceSymbolResponse { + try await sendRequest(.workspaceSymbolResolve(params)) + } +} + +// Language Features +public extension Server { + func documentHighlight(params: DocumentHighlightParams) async throws -> DocumentHighlightResponse { + try await sendRequest(.documentHighlight(params)) + } + + func codeLens(params: CodeLensParams) async throws -> CodeLensResponse { + try await sendRequest(.codeLens(params)) + } + + func codeLensResolve(params: CodeLens) async throws -> CodeLensResolveResponse { + try await sendRequest(.codeLensResolve(params)) + } + + func selectionRange(params: SelectionRangeParams) async throws -> SelectionRangeResponse { + try await sendRequest(.selectionRange(params)) + } + + func documentLink(params: DocumentLinkParams) async throws -> DocumentLinkResponse { + try await sendRequest(.documentLink(params)) + } + + func documentLinkResolve(params: DocumentLink) async throws -> DocumentLink { + try await sendRequest(.documentLinkResolve(params)) + } + + func documentColor(params: DocumentColorParams) async throws -> DocumentColorResponse { + try await sendRequest(.documentColor(params)) + } + + func colorPresentation(params: ColorPresentationParams) async throws -> ColorPresentationResponse { + try await sendRequest(.colorPresentation(params)) + } +} diff --git a/Sources/LanguageServerProtocol/ServerCapabilities+Extensions.swift b/Sources/LanguageServerProtocol/Additions/ServerCapabilities+Extensions.swift similarity index 100% rename from Sources/LanguageServerProtocol/ServerCapabilities+Extensions.swift rename to Sources/LanguageServerProtocol/Additions/ServerCapabilities+Extensions.swift diff --git a/Sources/LanguageServerProtocol/Snippet.swift b/Sources/LanguageServerProtocol/Additions/Snippet.swift similarity index 100% rename from Sources/LanguageServerProtocol/Snippet.swift rename to Sources/LanguageServerProtocol/Additions/Snippet.swift diff --git a/Sources/LanguageServerProtocol/Additions/TokenRepresentation.swift b/Sources/LanguageServerProtocol/Additions/TokenRepresentation.swift index bafab6a..de16b09 100644 --- a/Sources/LanguageServerProtocol/Additions/TokenRepresentation.swift +++ b/Sources/LanguageServerProtocol/Additions/TokenRepresentation.swift @@ -14,8 +14,9 @@ public struct Token: Codable, Hashable, Sendable { } /// Stores and updates raw Semantic Token data and converts it into Tokens. -public class TokenRepresentation { +public final class TokenRepresentation { private var data: [UInt32] + public private(set) var lastResultId: String? public let legend: SemanticTokensLegend public init(legend: SemanticTokensLegend) { @@ -128,3 +129,32 @@ public class TokenRepresentation { } } +extension TokenRepresentation { + public func applyResponse(_ response: SemanticTokensDeltaResponse) -> [LSPRange] { + switch response { + case .optionA(let fullResponse): + self.lastResultId = fullResponse.resultId + + return applyData(fullResponse.data) + case .optionB(let delta): + self.lastResultId = delta.resultId + + return applyEdits(delta.edits) + case nil: + return [] + } + } + + public func applyResponse(_ response: SemanticTokensResponse) -> [LSPRange] { + guard let response = response else { + self.lastResultId = nil + self.data = [] + + return [] + } + + self.lastResultId = response.resultId + + return applyData(response.data) + } +} diff --git a/Sources/LanguageServerProtocol/BaseProtocol.swift b/Sources/LanguageServerProtocol/BaseProtocol.swift index 30cf30b..1760f3c 100644 --- a/Sources/LanguageServerProtocol/BaseProtocol.swift +++ b/Sources/LanguageServerProtocol/BaseProtocol.swift @@ -9,7 +9,7 @@ public typealias DocumentUri = String public typealias ProgressToken = TwoTypeOption -public struct CancelParams: Hashable, Codable { +public struct CancelParams: Hashable, Codable, Sendable { public var id: TwoTypeOption public init(id: Int) { @@ -21,23 +21,23 @@ public struct CancelParams: Hashable, Codable { } } -public struct ProgressParams: Hashable, Codable { +public struct ProgressParams: Hashable, Codable, Sendable { public var token: ProgressToken public var value: LSPAny? } -public struct LogTraceParams: Hashable, Codable { +public struct LogTraceParams: Hashable, Codable, Sendable { public var string: String public var verbose: String? } -public enum TraceValue: String, Hashable, Codable { +public enum TraceValue: String, Hashable, Codable, Sendable { case off case messages case verbose } -public struct SetTraceParams: Hashable, Codable { +public struct SetTraceParams: Hashable, Codable, Sendable { public var value: TraceValue public init(value: TraceValue) { diff --git a/Sources/LanguageServerProtocol/BasicStructures.swift b/Sources/LanguageServerProtocol/BasicStructures.swift index 5e30056..08ae1ba 100644 --- a/Sources/LanguageServerProtocol/BasicStructures.swift +++ b/Sources/LanguageServerProtocol/BasicStructures.swift @@ -35,7 +35,7 @@ extension Position: Comparable { } public struct LSPRange: Codable, Hashable, Sendable { - static let zero = LSPRange(start: .zero, end: .zero) + public static let zero = LSPRange(start: .zero, end: .zero) public let start: Position public let end: Position @@ -69,7 +69,7 @@ extension LSPRange: CustomStringConvertible { } } -public struct TextDocumentItem: Codable, Hashable { +public struct TextDocumentItem: Codable, Hashable, Sendable { public let uri: DocumentUri public let languageId: String public let version: Int @@ -90,7 +90,7 @@ public struct TextDocumentItem: Codable, Hashable { } } -public struct VersionedTextDocumentIdentifier: Codable, Hashable { +public struct VersionedTextDocumentIdentifier: Codable, Hashable, Sendable { public let uri: DocumentUri public let version: Int? @@ -153,7 +153,7 @@ public enum MarkupKind: String, Codable, Hashable, Sendable { case markdown } -public struct TextDocumentPositionParams: Codable, Hashable { +public struct TextDocumentPositionParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let position: Position @@ -169,7 +169,7 @@ public struct TextDocumentPositionParams: Codable, Hashable { } } -public struct LanguageStringPair: Codable, Hashable { +public struct LanguageStringPair: Codable, Hashable, Sendable { public let language: LanguageIdentifier public let value: String } @@ -187,12 +187,12 @@ public extension MarkedString { } } -public struct MarkupContent: Codable, Hashable { +public struct MarkupContent: Codable, Hashable, Sendable { public let kind: MarkupKind public let value: String } -public struct LocationLink: Codable, Hashable { +public struct LocationLink: Codable, Hashable, Sendable { public let originSelectionRange: LSPRange? public let targetUri: String public let targetRange: LSPRange diff --git a/Sources/LanguageServerProtocol/Client.swift b/Sources/LanguageServerProtocol/Client.swift index 426d554..7895d02 100644 --- a/Sources/LanguageServerProtocol/Client.swift +++ b/Sources/LanguageServerProtocol/Client.swift @@ -1,9 +1,15 @@ import Foundation -public struct Registration: Codable { +public struct Registration: Codable, Hashable, Sendable { public var id: String public var method: String public var registerOptions: LSPAny? + + public init(id: String, method: String, registerOptions: LSPAny? = nil) { + self.id = id + self.method = method + self.registerOptions = registerOptions + } } extension Registration { @@ -19,7 +25,7 @@ extension Registration { func decodeServerRegistration() throws -> ServerRegistration { guard let regMethod = ServerRegistration.Method(rawValue: method) else { - throw ServerError.unhandledMethod(method) + throw ServerError.unhandledRegisterationMethod(method) } switch regMethod { @@ -44,19 +50,23 @@ extension Registration { } } -public struct RegistrationParams: Codable { +public struct RegistrationParams: Codable, Hashable, Sendable { public var registrations: [Registration] + public init(registrations: [Registration]) { + self.registrations = registrations + } + public var serverRegistrations: [ServerRegistration] { return registrations.compactMap({ $0.serverRegistration }) } } -public struct Unregistration: Codable { +public struct Unregistration: Codable, Hashable, Sendable { public var id: String public var method: String } -public struct UnregistrationParams: Codable { +public struct UnregistrationParams: Codable, Hashable, Sendable { public var unregistrations: [Unregistration] } diff --git a/Sources/LanguageServerProtocol/General.swift b/Sources/LanguageServerProtocol/General.swift index c60f693..befbf93 100644 --- a/Sources/LanguageServerProtocol/General.swift +++ b/Sources/LanguageServerProtocol/General.swift @@ -48,11 +48,11 @@ public struct InitializeParams: Codable, Hashable, Sendable { } } -public struct InitializationResponse: Codable, Hashable { +public struct InitializationResponse: Codable, Hashable, Sendable { public let capabilities: ServerCapabilities } -public struct InitializedParams: Codable, Hashable { +public struct InitializedParams: Codable, Hashable, Sendable { public init() { } } diff --git a/Sources/LanguageServerProtocol/JSONRPC+Extensions.swift b/Sources/LanguageServerProtocol/JSONRPC+Extensions.swift deleted file mode 100644 index c0dc66c..0000000 --- a/Sources/LanguageServerProtocol/JSONRPC+Extensions.swift +++ /dev/null @@ -1,31 +0,0 @@ -import Foundation -import JSONRPC - -public extension JSONRPCResponse { - func getServerResult() -> Result { - switch self { - case .failure(_, let error): - return .failure(.responseError(error)) - case .result(_, let value): - return .success(value) - } - } -} - -public extension JSONRPCRequest { - func getParamsResult() -> Result { - if let params = params { - return .success(params) - } else { - return .failure(.missingExpectedParameter) - } - } -} - -public extension AnyJSONRPCRequest { - func response(with error: Error) -> AnyJSONRPCResponse { - let error = AnyJSONRPCResponseError(code: JSONRPCErrors.internalError, - message: error.localizedDescription) - return AnyJSONRPCResponse.failure(id, error) - } -} diff --git a/Sources/LanguageServerProtocol/JSONRPCLanguageServer.swift b/Sources/LanguageServerProtocol/JSONRPCLanguageServer.swift deleted file mode 100644 index bed1d83..0000000 --- a/Sources/LanguageServerProtocol/JSONRPCLanguageServer.swift +++ /dev/null @@ -1,426 +0,0 @@ -import Foundation -import JSONRPC - -public class JSONRPCLanguageServer: Server { - typealias ProtocolResponse = ProtocolTransport.ResponseResult - - private let protocolTransport: ProtocolTransport - - public var requestHandler: RequestHandler? - public var notificationHandler: NotificationHandler? - - public init(protocolTransport: ProtocolTransport) { - self.protocolTransport = protocolTransport - - // These can be racy, where some data comes in just after deallocation. Happens during shutdown most - // commonly. Making these weak, along with unsetting then in deinit should be safe. - let requestHandler: ProtocolTransport.Handlers.RequestHandler = { [weak self] in - self?.handleRequest($0, data: $1, callback: $2) - } - - let notificationHandler: ProtocolTransport.Handlers.NotificationHandler = { [weak self] in - self?.handleNotification($0, data: $1, block: $2) - } - - let errorHandler: ProtocolTransport.Handlers.ErrorHandler = { error in - // We're intentionally doing nothing here but logging because - // there's a reasonable expectation that future interactions might - // succeed. - // - // We leave it to higher-level implemenations to Do The Right Thing - // with failures - print("protocol level error: \(error.localizedDescription)") - } - - protocolTransport.setHandlers(.init(request: requestHandler, - notification: notificationHandler, - error: errorHandler)) - } - - public convenience init(dataTransport: DataTransport) { - let framing = SeperatedHTTPHeaderMessageFraming() - let messageTransport = MessageTransport(dataTransport: dataTransport, messageProtocol: framing) - - self.init(protocolTransport: ProtocolTransport(dataTransport: messageTransport)) - } - - deinit { - protocolTransport.setHandlers(.init(request: nil, notification: nil, error: nil)) - } - - public var logMessages: Bool { - get { return protocolTransport.logMessages } - set { protocolTransport.logMessages = newValue } - } - - public func setHandlers(_ handlers: ServerHandlers, completionHandler: @escaping (ServerError?) -> Void) { - self.notificationHandler = handlers.notificationHandler - self.requestHandler = handlers.requestHandler - - completionHandler(nil) - } -} - -extension JSONRPCLanguageServer { - private func decodeNotificationParams(data: Data) -> ServerResult { - do { - let resultType = JSONRPCNotification.self - let result = try JSONDecoder().decode(resultType, from: data) - - switch result.params { - case nil: - return .failure(.missingExpectedParameter) - case let params?: - return .success(params) - } - } catch { - return .failure(.unableToDecodeRequest(error)) - } - } - - private func relayNotification(data: Data, block: (T) -> Void) throws { - let result: ServerResult = decodeNotificationParams(data: data) - - block(try result.get()) - } - - private func handleNotification(_ anyNotification: AnyJSONRPCNotification, data: Data, block: @escaping (Error?) -> Void) { - let methodName = anyNotification.method - - guard let method = ServerNotification.Method(rawValue: methodName) else { - block(ServerError.unhandledMethod(methodName)) - return - } - - guard let handler = notificationHandler else { - block(ServerError.handlerUnavailable(methodName)) - return - } - - do { - switch method { - case .windowLogMessage: - try relayNotification(data: data) { (params: LogMessageParams) in - handler(.windowLogMessage(params), block) - } - case .windowShowMessage: - try relayNotification(data: data) { (params: ShowMessageParams) in - handler(.windowShowMessage(params), block) - } - case .textDocumentPublishDiagnostics: - try relayNotification(data: data) { (params: PublishDiagnosticsParams) in - handler(.textDocumentPublishDiagnostics(params), block) - } - case .telemetryEvent: - try relayNotification(data: data) { (params: LSPAny) in - handler(.telemetryEvent(params), block) - } - case .protocolCancelRequest: - try relayNotification(data: data) { (params: CancelParams) in - handler(.protocolCancelRequest(params), block) - } - case .protocolProgress: - try relayNotification(data: data) { (params: ProgressParams) in - handler(.protocolProgress(params), block) - } - case .protocolLogTrace: - try relayNotification(data: data) { (params: LogTraceParams) in - handler(.protocolLogTrace(params), block) - } - } - } catch { - block(error) - } - } - - public func sendNotification(_ notif: ClientNotification, completionHandler: @escaping (ServerError?) -> Void) { - let method = notif.method.rawValue - - switch notif { - case .initialized(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .exit: - let params: String? = nil // stand-in for null - - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .textDocumentDidChange(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .didOpenTextDocument(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .didChangeTextDocument(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .didCloseTextDocument(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .willSaveTextDocument(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .didSaveTextDocument(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .didChangeWatchedFiles(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .protocolCancelRequest(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .protocolSetTrace(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .workspaceDidChangeWorkspaceFolders(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .workspaceDidChangeConfiguration(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .workspaceDidCreateFiles(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .workspaceDidRenameFiles(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - case .workspaceDidDeleteFiles(let params): - protocolTransport.sendNotification(params, method: method) { error in - completionHandler(error.map({ .unableToSendNotification($0) })) - } - } - } -} - -extension JSONRPCLanguageServer { - private func decodeRequest(data: Data) -> Result, ServerError> { - let resultType = JSONRPCRequest.self - - do { - let result = try JSONDecoder().decode(resultType, from: data) - - return .success(result) - } catch { - return .failure(.unableToDecodeRequest(error)) - } - } - - private func decodeRequestWithParams(data: Data) -> Result { - let requestResult: Result, ServerError> = self.decodeRequest(data: data) - - return requestResult.flatMap({ $0.getParamsResult() }) - } - - private func relayRequest(request: Result, id: JSONId, block: @escaping (Result) -> Void) { - switch (request, requestHandler) { - case (.failure, nil): - block(.failure(.handlerUnavailable("unknown"))) - case (.success(let request), nil): - block(.failure(.handlerUnavailable(request.method.rawValue))) - case (.failure(let error), .some): - block(.failure(error)) - case (.success(let request), let handler?): - handler(request, { result in - let mappedResult = result.map { anyCodable in - AnyJSONRPCResponse(id: id, result: anyCodable) - } - - block(mappedResult) - }) - } - } - - private func handleRequestResult(_ anyRequest: AnyJSONRPCRequest, data: Data, block: @escaping (Result) -> Void) { - guard let method = ServerRequest.Method(rawValue: anyRequest.method) else { - block(.failure(ServerError.unhandledMethod(anyRequest.method))) - return - } - - let id = anyRequest.id - - switch method { - case .workspaceConfiguration: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.workspaceConfiguration($0) } - - relayRequest(request: request, id: id, block: block) - case .workspaceFolders: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { _ in ServerRequest.workspaceFolders } - - relayRequest(request: request, id: id, block: block) - case .workspaceApplyEdit: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.workspaceApplyEdit($0) } - - relayRequest(request: request, id: id, block: block) - case .clientRegisterCapability: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.clientRegisterCapability($0) } - - relayRequest(request: request, id: id, block: block) - case .clientUnregisterCapability: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.clientUnregisterCapability($0) } - - relayRequest(request: request, id: id, block: block) - case .workspaceCodeLensRefresh: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { _ in ServerRequest.workspaceFolders } - - relayRequest(request: request, id: id, block: block) - case .workspaceSemanticTokenRefresh: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { _ in ServerRequest.workspaceSemanticTokenRefresh } - - relayRequest(request: request, id: id, block: block) - case .windowShowMessageRequest: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.windowShowMessageRequest($0) } - - relayRequest(request: request, id: id, block: block) - case .windowShowDocument: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.windowShowDocument($0) } - - relayRequest(request: request, id: id, block: block) - case .windowWorkDoneProgressCreate: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.windowWorkDoneProgressCreate($0) } - - relayRequest(request: request, id: id, block: block) - case .windowWorkDoneProgressCancel: - let requestResult: ServerResult = self.decodeRequestWithParams(data: data) - let request = requestResult.map { ServerRequest.windowWorkDoneProgressCancel($0) } - - relayRequest(request: request, id: id, block: block) - } - } - - private func handleRequest(_ request: AnyJSONRPCRequest, data: Data, callback: @escaping (AnyJSONRPCResponse) -> Void) { - handleRequestResult(request, data: data) { result in - switch result { - case .failure(let error): - callback(request.response(with: error)) - case .success(let response): - callback(response) - } - } - } -} - -extension JSONRPCLanguageServer { - private func sendRequestWithHandler(_ params: Params, method: String, handler: @escaping (ServerResult) -> Void) { - protocolTransport.sendRequest(params, method: method) { (result: ProtocolResponse) in - let newResult = result - .mapError { ServerError.unableToSendRequest($0) } - .flatMap { $0.getServerResult() } - - handler(newResult) - } - } - - public func sendRequest(_ request: ClientRequest, completionHandler: @escaping (ServerResult) -> Void) { - let method = request.method.rawValue - - switch request { - case .initialize(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .shutdown: - sendRequestWithHandler(UnusedParam(nil), method: method, handler: completionHandler) - case .willSaveWaitUntilTextDocument(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .workspaceWillRenameFiles(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .completion(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .completionItemResolve(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .hover(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .signatureHelp(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .declaration(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .definition(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .typeDefinition(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .implementation(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .documentHighlight(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .documentSymbol(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .codeAction(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .codeLens(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .codeLensResolve(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .prepareCallHierarchy(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .prepareRename(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .documentLink(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .documentLinkResolve(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .documentColor(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .colorPresentation(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .rename(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .formatting(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .rangeFormatting(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .onTypeFormatting(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .references(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .foldingRange(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .selectionRange(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .semanticTokensFull(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .semanticTokensFullDelta(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .semanticTokensRange(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .workspaceExecuteCommand(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .workspaceWillCreateFiles(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .workspaceWillDeleteFiles(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .callHierarchyIncomingCalls(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .callHierarchyOutgoingCalls(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .custom(let method, let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .workspaceSymbol(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - case .workspaceSymbolResolve(let params): - sendRequestWithHandler(params, method: method, handler: completionHandler) - } - } -} diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/CallHeirarchy.swift b/Sources/LanguageServerProtocol/LanguageFeatures/CallHeirarchy.swift index 2fb9c65..48fa4c3 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/CallHeirarchy.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/CallHeirarchy.swift @@ -18,7 +18,7 @@ public struct CallHierarchyPrepareParams: Codable, Hashable, Sendable { } } -public struct CallHierarchyItem: Codable, Hashable { +public struct CallHierarchyItem: Codable, Hashable, Sendable { public let name: String public let kind: SymbolKind public let tag: [SymbolTag]? @@ -49,7 +49,7 @@ public struct CallHierarchyItem: Codable, Hashable { public typealias CallHierarchyPrepareResponse = [CallHierarchyItem]? -public struct CallHierarchyIncomingCallsParams: Codable, Hashable { +public struct CallHierarchyIncomingCallsParams: Codable, Hashable, Sendable { public let workDoneToken: ProgressToken? public let partialResultToken: ProgressToken? @@ -62,7 +62,7 @@ public struct CallHierarchyIncomingCallsParams: Codable, Hashable { } } -public struct CallHierarchyIncomingCall: Codable, Hashable { +public struct CallHierarchyIncomingCall: Codable, Hashable, Sendable { public let from: CallHierarchyItem public let fromRanges: [LSPRange] } @@ -71,7 +71,7 @@ public typealias CallHierarchyIncomingCallsResponse = [CallHierarchyIncomingCall public typealias CallHierarchyOutgoingCallsParams = CallHierarchyIncomingCallsParams -public struct CallHierarchyOutgoingCall: Codable, Hashable { +public struct CallHierarchyOutgoingCall: Codable, Hashable, Sendable { public let to: CallHierarchyItem public let fromRanges: [LSPRange] } diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/CodeAction.swift b/Sources/LanguageServerProtocol/LanguageFeatures/CodeAction.swift index 5cba0f8..6d264aa 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/CodeAction.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/CodeAction.swift @@ -3,14 +3,14 @@ import Foundation public typealias CodeActionKind = String extension CodeActionKind { - public static var Empty: CodeActionKind = "" - public static var Quickfix: CodeActionKind = "quickfix" - public static var Refactor: CodeActionKind = "refactor" - public static var RefactorExtract: CodeActionKind = "refactor.extract" - public static var RefactorInline: CodeActionKind = "refactor.inline" - public static var RefactorRewrite: CodeActionKind = "refactor.rewrite" - public static var Source: CodeActionKind = "source" - public static var SourceOrganizeImports: CodeActionKind = "source.organizeImports" + public static let Empty: CodeActionKind = "" + public static let Quickfix: CodeActionKind = "quickfix" + public static let Refactor: CodeActionKind = "refactor" + public static let RefactorExtract: CodeActionKind = "refactor.extract" + public static let RefactorInline: CodeActionKind = "refactor.inline" + public static let RefactorRewrite: CodeActionKind = "refactor.rewrite" + public static let Source: CodeActionKind = "source" + public static let SourceOrganizeImports: CodeActionKind = "source.organizeImports" } public struct CodeActionClientCapabilities: Codable, Hashable, Sendable { @@ -55,7 +55,7 @@ public struct CodeActionClientCapabilities: Codable, Hashable, Sendable { } } -public struct CodeActionContext: Codable, Hashable { +public struct CodeActionContext: Codable, Hashable, Sendable { public let diagnostics: [Diagnostic] public let only: [CodeActionKind]? @@ -65,7 +65,7 @@ public struct CodeActionContext: Codable, Hashable { } } -public struct CodeActionParams: Codable, Hashable { +public struct CodeActionParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let range: LSPRange public let context: CodeActionContext @@ -77,8 +77,8 @@ public struct CodeActionParams: Codable, Hashable { } } -public struct CodeAction: Codable, Hashable { - public struct Disabled: Codable, Hashable { +public struct CodeAction: Codable, Hashable, Sendable { + public struct Disabled: Codable, Hashable, Sendable { public var disabled: Bool } diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/CodeLens.swift b/Sources/LanguageServerProtocol/LanguageFeatures/CodeLens.swift index 10d3ba9..7a5d9f2 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/CodeLens.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/CodeLens.swift @@ -33,7 +33,7 @@ public struct CodeLensParams: Codable, Hashable, Sendable { } } -public struct CodeLens: Codable, Sendable { +public struct CodeLens: Codable, Hashable, Sendable { public var range: LSPRange public var command: Command? public var data: LSPAny? diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/ColorPresentation.swift b/Sources/LanguageServerProtocol/LanguageFeatures/ColorPresentation.swift index 55e3868..ff76b3d 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/ColorPresentation.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/ColorPresentation.swift @@ -1,6 +1,6 @@ import Foundation -public struct ColorPresentationParams: Codable, Hashable { +public struct ColorPresentationParams: Codable, Hashable, Sendable { public let workDoneToken: ProgressToken? public let partialResultToken: ProgressToken? public let textDocument: TextDocumentIdentifier @@ -16,7 +16,7 @@ public struct ColorPresentationParams: Codable, Hashable { } } -public struct ColorPresentation: Codable, Hashable { +public struct ColorPresentation: Codable, Hashable, Sendable { public let label: String public let textEdit: TextEdit? public let additionalTextEdits: [TextEdit]? diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/Completion.swift b/Sources/LanguageServerProtocol/LanguageFeatures/Completion.swift index da1288b..f880c74 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/Completion.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/Completion.swift @@ -75,7 +75,7 @@ public struct CompletionClientCapabilities: Codable, Hashable, Sendable { } } -public enum CompletionTriggerKind: Int, Codable, Hashable { +public enum CompletionTriggerKind: Int, Codable, Hashable, Sendable { case invoked = 1 case triggerCharacter = 2 case triggerForIncompleteCompletions = 3 @@ -113,7 +113,7 @@ public enum CompletionItemTag: Int, CaseIterable, Codable, Hashable, Sendable { case deprecated = 1 } -public struct CompletionContext: Codable, Hashable { +public struct CompletionContext: Codable, Hashable, Sendable { public let triggerKind: CompletionTriggerKind public let triggerCharacter: String? @@ -123,7 +123,7 @@ public struct CompletionContext: Codable, Hashable { } } -public struct CompletionParams: Codable, Hashable { +public struct CompletionParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let position: Position public let context: CompletionContext? @@ -142,12 +142,12 @@ public struct CompletionParams: Codable, Hashable { } } -public enum InsertTextFormat: Int, Codable, Hashable { +public enum InsertTextFormat: Int, Codable, Hashable, Sendable { case plaintext = 1 case snippet = 2 } -public struct CompletionItem: Codable, Hashable { +public struct CompletionItem: Codable, Hashable, Sendable { public let label: String public let kind: CompletionItemKind? public let detail: String? @@ -197,7 +197,7 @@ public struct CompletionItem: Codable, Hashable { } } -public struct CompletionList: Codable, Hashable { +public struct CompletionList: Codable, Hashable, Sendable { public let isIncomplete: Bool public let items: [CompletionItem] diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentColor.swift b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentColor.swift index a23427c..623c850 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentColor.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentColor.swift @@ -2,7 +2,7 @@ import Foundation public typealias DocumentColorClientCapabilities = DynamicRegistrationClientCapabilities -public struct DocumentColorParams: Codable, Hashable { +public struct DocumentColorParams: Codable, Hashable, Sendable { public let workDoneToken: ProgressToken? public let partialResultToken: ProgressToken? public let textDocument: TextDocumentIdentifier @@ -14,14 +14,14 @@ public struct DocumentColorParams: Codable, Hashable { } } -public struct Color: Codable, Hashable { +public struct Color: Codable, Hashable, Sendable { public let red: Float public let green: Float public let blue: Float public let alpha: Float } -public struct ColorInformation: Codable, Hashable { +public struct ColorInformation: Codable, Hashable, Sendable { public let range: LSPRange public let color: Color } diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentHighlight.swift b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentHighlight.swift index 0555332..d02dc83 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentHighlight.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentHighlight.swift @@ -4,12 +4,12 @@ public typealias DocumentHighlightClientCapabilities = DynamicRegistrationClient public typealias DocumentHighlightOptions = WorkDoneProgressOptions -public struct DocumentHighlightRegistrationOptions: Codable, Hashable { +public struct DocumentHighlightRegistrationOptions: Codable, Hashable, Sendable { public var documentSelector: DocumentSelector? public var workDoneProgress: Bool? } -public struct DocumentHighlightParams: Codable, Hashable { +public struct DocumentHighlightParams: Codable, Hashable, Sendable { public var textDocument: TextDocumentIdentifier public var position: Position public var workDoneToken: ProgressToken? @@ -23,13 +23,13 @@ public struct DocumentHighlightParams: Codable, Hashable { } } -public enum DocumentHighlightKind: Int, CaseIterable, Codable, Hashable { +public enum DocumentHighlightKind: Int, CaseIterable, Codable, Hashable, Sendable { case Text = 1 case Read = 2 case Write = 3 } -public struct DocumentHighlight: Codable, Hashable { +public struct DocumentHighlight: Codable, Hashable, Sendable { public var range: LSPRange public var kind: DocumentHighlightKind? } diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentLink.swift b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentLink.swift index 9990be6..092ad50 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentLink.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentLink.swift @@ -44,6 +44,13 @@ public struct DocumentLink: Codable, Hashable, Sendable { public var target: DocumentUri? public var tooltip: String? public var data: LSPAny? + + public init(range: LSPRange, target: DocumentUri?, tooltip: String?, data: LSPAny?) { + self.range = range + self.target = target + self.tooltip = tooltip + self.data = data + } } public typealias DocumentLinkResponse = [DocumentLink]? diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentSymbol.swift b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentSymbol.swift index d4babd4..f4fb872 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/DocumentSymbol.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/DocumentSymbol.swift @@ -16,7 +16,7 @@ public struct DocumentSymbolClientCapabilities: Codable, Hashable, Sendable { } } -public struct DocumentSymbolParams: Codable, Hashable { +public struct DocumentSymbolParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public init(textDocument: TextDocumentIdentifier) { @@ -24,7 +24,7 @@ public struct DocumentSymbolParams: Codable, Hashable { } } -public struct DocumentSymbol: Codable, Hashable { +public struct DocumentSymbol: Codable, Hashable, Sendable { public let name: String public let detail: String? public let kind: SymbolKind diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/Formatting.swift b/Sources/LanguageServerProtocol/LanguageFeatures/Formatting.swift index 9dab9a4..0ba4a55 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/Formatting.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/Formatting.swift @@ -3,7 +3,7 @@ import Foundation public typealias DocumentFormattingClientCapabilities = DynamicRegistrationClientCapabilities public typealias DocumentRangeFormattingClientCapabilities = DynamicRegistrationClientCapabilities -public struct FormattingOptions: Codable, Hashable { +public struct FormattingOptions: Codable, Hashable, Sendable { public let tabSize: Int public let insertSpaces: Bool @@ -13,7 +13,7 @@ public struct FormattingOptions: Codable, Hashable { } } -public struct DocumentFormattingParams: Codable, Hashable { +public struct DocumentFormattingParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let options: FormattingOptions @@ -23,7 +23,7 @@ public struct DocumentFormattingParams: Codable, Hashable { } } -public struct DocumentRangeFormattingParams: Codable, Hashable { +public struct DocumentRangeFormattingParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let range: LSPRange public let options: FormattingOptions @@ -35,7 +35,7 @@ public struct DocumentRangeFormattingParams: Codable, Hashable { } } -public struct DocumentOnTypeFormattingParams: Codable, Hashable { +public struct DocumentOnTypeFormattingParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let position: Position public let ch: String diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/Hover.swift b/Sources/LanguageServerProtocol/LanguageFeatures/Hover.swift index 363991b..7454a33 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/Hover.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/Hover.swift @@ -10,9 +10,19 @@ public struct HoverClientCapabilities: Codable, Hashable, Sendable { } } -public struct Hover: Codable, Hashable { +public struct Hover: Codable, Hashable, Sendable { public let contents: ThreeTypeOption public let range: LSPRange? + + public init(contents: ThreeTypeOption, range: LSPRange?) { + self.contents = contents + self.range = range + } + + public init(contents: String, range: LSPRange? = nil) { + self.contents = .optionA(.optionA(contents)) + self.range = range + } } public typealias HoverResponse = Hover? diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/References.swift b/Sources/LanguageServerProtocol/LanguageFeatures/References.swift index e963161..0aaa02d 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/References.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/References.swift @@ -2,7 +2,7 @@ import Foundation public typealias ReferenceClientCapabilities = DynamicRegistrationClientCapabilities -public struct ReferenceContext: Codable, Hashable { +public struct ReferenceContext: Codable, Hashable, Sendable { public let includeDeclaration: Bool public init(includeDeclaration: Bool) { @@ -10,7 +10,7 @@ public struct ReferenceContext: Codable, Hashable { } } -public struct ReferenceParams: Codable, Hashable { +public struct ReferenceParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let position: Position public let context: ReferenceContext diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/SelectionRange.swift b/Sources/LanguageServerProtocol/LanguageFeatures/SelectionRange.swift index 8daebe3..7e66395 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/SelectionRange.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/SelectionRange.swift @@ -6,7 +6,7 @@ public typealias SelectionRangeOptions = WorkDoneProgressOptions public typealias SelectionRangeRegistrationOptions = StaticRegistrationWorkDoneProgressTextDocumentRegistrationOptions -public struct SelectionRangeParams: Codable, Hashable { +public struct SelectionRangeParams: Codable, Hashable, Sendable { public let workDoneToken: ProgressToken? public let partialResultToken: ProgressToken? public let textDocument: TextDocumentIdentifier @@ -20,7 +20,7 @@ public struct SelectionRangeParams: Codable, Hashable { } } -public class SelectionRange: Codable { +public final class SelectionRange: Codable, Sendable { public let range: LSPRange public let parent: SelectionRange? } diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/SemanticTokens.swift b/Sources/LanguageServerProtocol/LanguageFeatures/SemanticTokens.swift index d2dc3b8..0dd06d5 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/SemanticTokens.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/SemanticTokens.swift @@ -70,12 +70,12 @@ public struct SemanticTokensClientCapabilities: Codable, Hashable, Sendable { } } -public struct SemanticTokensLegend: Codable, Hashable { +public struct SemanticTokensLegend: Codable, Hashable, Sendable { public var tokenTypes: [String] public var tokenModifiers: [String] } -public enum SemanticTokenTypes: String, Codable, Hashable, CaseIterable { +public enum SemanticTokenTypes: String, Codable, Hashable, CaseIterable, Sendable { case namespace = "namespace" case type = "type" case `class` = "class" @@ -104,7 +104,7 @@ public enum SemanticTokenTypes: String, Codable, Hashable, CaseIterable { } } -public enum SemanticTokenModifiers: String, Codable, Hashable, CaseIterable { +public enum SemanticTokenModifiers: String, Codable, Hashable, CaseIterable, Sendable { case declaration = "declaration" case definition = "definition" case readonly = "readonly" @@ -121,7 +121,7 @@ public enum SemanticTokenModifiers: String, Codable, Hashable, CaseIterable { } } -public struct SemanticTokensParams: Codable, Hashable { +public struct SemanticTokensParams: Codable, Hashable, Sendable { public var workDoneToken: ProgressToken? public var partialResultToken: ProgressToken? public var textDocument: TextDocumentIdentifier @@ -131,18 +131,18 @@ public struct SemanticTokensParams: Codable, Hashable { } } -public struct SemanticTokens: Codable { +public struct SemanticTokens: Codable, Hashable, Sendable { public var resultId: String? public var data: [UInt32] } public typealias SemanticTokensResponse = SemanticTokens? -public struct SemanticTokensPartialResult: Codable { +public struct SemanticTokensPartialResult: Codable, Hashable, Sendable { public var data: [UInt32] } -public struct SemanticTokensDeltaParams: Codable { +public struct SemanticTokensDeltaParams: Codable, Hashable, Sendable { public var workDoneToken: ProgressToken? public var partialResultToken: ProgressToken? public var textDocument: TextDocumentIdentifier @@ -154,20 +154,20 @@ public struct SemanticTokensDeltaParams: Codable { } } -public struct SemanticTokensEdit: Codable { +public struct SemanticTokensEdit: Codable, Hashable, Sendable { public var start: UInt public var deleteCount: UInt public var data: [UInt32]? } -public struct SemanticTokensDelta: Codable { +public struct SemanticTokensDelta: Codable, Hashable, Sendable { public var resultId: String? public var edits: [SemanticTokensEdit] } public typealias SemanticTokensDeltaResponse = TwoTypeOption? -public struct SemanticTokensRangeParams: Codable { +public struct SemanticTokensRangeParams: Codable, Hashable, Sendable { public var workDoneToken: ProgressToken? public var partialResultToken: ProgressToken? public var textDocument: TextDocumentIdentifier diff --git a/Sources/LanguageServerProtocol/LanguageFeatures/SignatureHelp.swift b/Sources/LanguageServerProtocol/LanguageFeatures/SignatureHelp.swift index ae2c723..b4912b7 100644 --- a/Sources/LanguageServerProtocol/LanguageFeatures/SignatureHelp.swift +++ b/Sources/LanguageServerProtocol/LanguageFeatures/SignatureHelp.swift @@ -38,25 +38,25 @@ public struct SignatureHelpClientCapabilities: Codable, Hashable, Sendable { } } -public struct ParameterInformation: Codable, Hashable { +public struct ParameterInformation: Codable, Hashable, Sendable { public var label: TwoTypeOption public var documentation: TwoTypeOption? } -public struct SignatureInformation: Codable, Hashable { +public struct SignatureInformation: Codable, Hashable, Sendable { public var label: String public var documentation: TwoTypeOption? public var parameters: [ParameterInformation]? public var activeParameter: UInt? } -public struct SignatureHelp: Codable, Hashable { +public struct SignatureHelp: Codable, Hashable, Sendable { public let signatures: [SignatureInformation] public let activeSignature: Int? public let activeParameter: Int? } -public struct SignatureHelpRegistrationOptions: Codable, Hashable { +public struct SignatureHelpRegistrationOptions: Codable, Hashable, Sendable { public let documentSelector: DocumentSelector? public let triggerCharacters: [String]? } diff --git a/Sources/LanguageServerProtocol/LanguageServerProtocol.swift b/Sources/LanguageServerProtocol/LanguageServerProtocol.swift index ee70fc4..c7087f8 100644 --- a/Sources/LanguageServerProtocol/LanguageServerProtocol.swift +++ b/Sources/LanguageServerProtocol/LanguageServerProtocol.swift @@ -3,10 +3,11 @@ import JSONRPC typealias UnusedResult = String? typealias UnusedParam = String? -public enum ClientNotification { - public enum Method: String { +public enum ClientNotification: Sendable, Hashable { + public enum Method: String, Hashable, Sendable { case initialized case exit + case windowWorkDoneProgressCancel = "window/workDoneProgress/cancel" case workspaceDidChangeWatchedFiles = "workspace/didChangeWatchedFiles" case workspaceDidChangeConfiguration = "workspace/didChangeConfiguration" case workspaceDidChangeWorkspaceFolders = "workspace/didChangeWorkspaceFolders" @@ -33,6 +34,7 @@ public enum ClientNotification { case didChangeWatchedFiles(DidChangeWatchedFilesParams) case protocolCancelRequest(CancelParams) case protocolSetTrace(SetTraceParams) + case windowWorkDoneProgressCancel(WorkDoneProgressCancelParams) case workspaceDidChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams) case workspaceDidChangeConfiguration(DidChangeConfigurationParams) case workspaceDidCreateFiles(CreateFilesParams) @@ -63,6 +65,8 @@ public enum ClientNotification { return .protocolCancelRequest case .protocolSetTrace: return .protocolSetTrace + case .windowWorkDoneProgressCancel: + return .windowWorkDoneProgressCancel case .workspaceDidChangeWorkspaceFolders: return .workspaceDidChangeWorkspaceFolders case .workspaceDidChangeConfiguration: @@ -77,8 +81,8 @@ public enum ClientNotification { } } -public enum ClientRequest { - public enum Method: String { +public enum ClientRequest: Sendable, Hashable { + public enum Method: String, Hashable, Sendable { case initialize case shutdown case workspaceExecuteCommand = "workspace/executeCommand" @@ -125,47 +129,47 @@ public enum ClientRequest { case custom } - case initialize(InitializeParams) - case shutdown - case workspaceExecuteCommand(ExecuteCommandParams) - case workspaceWillCreateFiles(CreateFilesParams) - case workspaceWillRenameFiles(RenameFilesParams) - case workspaceWillDeleteFiles(DeleteFilesParams) - case workspaceSymbol(WorkspaceSymbolParams) - case workspaceSymbolResolve(WorkspaceSymbol) - case willSaveWaitUntilTextDocument(WillSaveTextDocumentParams) - case completion(CompletionParams) - case completionItemResolve(CompletionItem) - case hover(TextDocumentPositionParams) - case signatureHelp(TextDocumentPositionParams) - case declaration(TextDocumentPositionParams) - case definition(TextDocumentPositionParams) - case typeDefinition(TextDocumentPositionParams) - case implementation(TextDocumentPositionParams) - case documentHighlight(DocumentHighlightParams) - case documentSymbol(DocumentSymbolParams) - case codeAction(CodeActionParams) - case codeLens(CodeLensParams) - case codeLensResolve(CodeLens) - case selectionRange(SelectionRangeParams) + case initialize(InitializeParams) + case shutdown + case workspaceExecuteCommand(ExecuteCommandParams) + case workspaceWillCreateFiles(CreateFilesParams) + case workspaceWillRenameFiles(RenameFilesParams) + case workspaceWillDeleteFiles(DeleteFilesParams) + case workspaceSymbol(WorkspaceSymbolParams) + case workspaceSymbolResolve(WorkspaceSymbol) + case willSaveWaitUntilTextDocument(WillSaveTextDocumentParams) + case completion(CompletionParams) + case completionItemResolve(CompletionItem) + case hover(TextDocumentPositionParams) + case signatureHelp(TextDocumentPositionParams) + case declaration(TextDocumentPositionParams) + case definition(TextDocumentPositionParams) + case typeDefinition(TextDocumentPositionParams) + case implementation(TextDocumentPositionParams) + case documentHighlight(DocumentHighlightParams) + case documentSymbol(DocumentSymbolParams) + case codeAction(CodeActionParams) + case codeLens(CodeLensParams) + case codeLensResolve(CodeLens) + case selectionRange(SelectionRangeParams) case prepareCallHierarchy(CallHierarchyPrepareParams) - case prepareRename(PrepareRenameParams) - case rename(RenameParams) - case documentLink(DocumentLinkParams) - case documentLinkResolve(DocumentLink) - case documentColor(DocumentColorParams) - case colorPresentation(ColorPresentationParams) - case formatting(DocumentFormattingParams) - case rangeFormatting(DocumentRangeFormattingParams) - case onTypeFormatting(DocumentOnTypeFormattingParams) - case references(ReferenceParams) - case foldingRange(FoldingRangeParams) - case semanticTokensFull(SemanticTokensParams) - case semanticTokensFullDelta(SemanticTokensDeltaParams) - case semanticTokensRange(SemanticTokensRangeParams) + case prepareRename(PrepareRenameParams) + case rename(RenameParams) + case documentLink(DocumentLinkParams) + case documentLinkResolve(DocumentLink) + case documentColor(DocumentColorParams) + case colorPresentation(ColorPresentationParams) + case formatting(DocumentFormattingParams) + case rangeFormatting(DocumentRangeFormattingParams) + case onTypeFormatting(DocumentOnTypeFormattingParams) + case references(ReferenceParams) + case foldingRange(FoldingRangeParams) + case semanticTokensFull(SemanticTokensParams) + case semanticTokensFullDelta(SemanticTokensDeltaParams) + case semanticTokensRange(SemanticTokensRangeParams) case callHierarchyIncomingCalls(CallHierarchyIncomingCallsParams) case callHierarchyOutgoingCalls(CallHierarchyOutgoingCallsParams) - case custom(String, LSPAny) + case custom(String, LSPAny) public var method: Method { switch self { @@ -255,8 +259,8 @@ public enum ClientRequest { } } -public enum ServerNotification { - public enum Method: String { +public enum ServerNotification: Sendable, Hashable { + public enum Method: String, Hashable, Sendable { case windowLogMessage = "window/logMessage" case windowShowMessage = "window/showMessage" case textDocumentPublishDiagnostics = "textDocument/publishDiagnostics" @@ -294,7 +298,10 @@ public enum ServerNotification { } } -public enum ServerRequest { +public enum ServerRequest: Sendable { + public typealias Handler = @Sendable (Result) async -> Void + public typealias ErrorOnlyHandler = @Sendable (AnyJSONRPCResponseError?) async -> Void + public enum Method: String { case workspaceConfiguration = "workspace/configuration" case workspaceFolders = "workspace/workspaceFolders" @@ -306,20 +313,18 @@ public enum ServerRequest { case windowShowMessageRequest = "window/showMessageRequest" case windowShowDocument = "window/showDocument" case windowWorkDoneProgressCreate = "window/workDoneProgress/create" - case windowWorkDoneProgressCancel = "window/workDoneProgress/cancel" } - case workspaceConfiguration(ConfigurationParams) - case workspaceFolders - case workspaceApplyEdit(ApplyWorkspaceEditParams) - case clientRegisterCapability(RegistrationParams) - case clientUnregisterCapability(UnregistrationParams) - case workspaceCodeLensRefresh - case workspaceSemanticTokenRefresh - case windowShowMessageRequest(ShowMessageRequestParams) - case windowShowDocument(ShowDocumentParams) - case windowWorkDoneProgressCreate(WorkDoneProgressCreateParams) - case windowWorkDoneProgressCancel(WorkDoneProgressCancelParams) + case workspaceConfiguration(ConfigurationParams, Handler<[LSPAny]>) + case workspaceFolders(Handler) + case workspaceApplyEdit(ApplyWorkspaceEditParams, Handler) + case clientRegisterCapability(RegistrationParams, ErrorOnlyHandler) + case clientUnregisterCapability(UnregistrationParams, ErrorOnlyHandler) + case workspaceCodeLensRefresh(ErrorOnlyHandler) + case workspaceSemanticTokenRefresh(ErrorOnlyHandler) + case windowShowMessageRequest(ShowMessageRequestParams, Handler) + case windowShowDocument(ShowDocumentParams, Handler) + case windowWorkDoneProgressCreate(WorkDoneProgressCreateParams, ErrorOnlyHandler) public var method: Method { switch self { @@ -343,10 +348,35 @@ public enum ServerRequest { return .windowShowDocument case .windowWorkDoneProgressCreate: return .windowWorkDoneProgressCreate - case .windowWorkDoneProgressCancel: - return .windowWorkDoneProgressCancel } } + + public func relyWithError(_ error: Error) async { + let protocolError = AnyJSONRPCResponseError(code: JSONRPCErrors.internalError, message: "unsupported", data: nil) + + switch self { + case let .workspaceConfiguration(_, handler): + await handler(.failure(protocolError)) + case let .workspaceFolders(handler): + await handler(.failure(protocolError)) + case let .workspaceApplyEdit(_, handler): + await handler(.failure(protocolError)) + case let .clientRegisterCapability(_, handler): + await handler(protocolError) + case let .clientUnregisterCapability(_, handler): + await handler(protocolError) + case let .workspaceCodeLensRefresh(handler): + await handler(protocolError) + case let .workspaceSemanticTokenRefresh(handler): + await handler(protocolError) + case let .windowShowMessageRequest(_, handler): + await handler(.failure(protocolError)) + case let .windowShowDocument(_, handler): + await handler(.failure(protocolError)) + case let .windowWorkDoneProgressCreate(_, handler): + await handler(protocolError) + } + } } public enum ServerRegistration { diff --git a/Sources/LanguageServerProtocol/Server.swift b/Sources/LanguageServerProtocol/Server.swift deleted file mode 100644 index 7b2eea1..0000000 --- a/Sources/LanguageServerProtocol/Server.swift +++ /dev/null @@ -1,549 +0,0 @@ -import Foundation -import JSONRPC - -public enum ServerError: LocalizedError { - case handlerUnavailable(String) - case unhandledMethod(String) - case notificationDispatchFailed(Error) - case requestDispatchFailed(Error) - case clientDataUnavailable(Error) - case serverUnavailable - case missingExpectedParameter - case missingExpectedResult - case unableToDecodeRequest(Error) - case unableToSendRequest(Error) - case unableToSendNotification(Error) - case serverError(code: Int, message: String, data: Codable?) - case invalidRequest(Error?) - case timeout - - static func responseError(_ error: AnyJSONRPCResponseError) -> ServerError { - return ServerError.serverError(code: error.code, - message: error.message, - data: error.data) - } -} - -public typealias ServerResult = Result - -public struct ServerHandlers { - public var requestHandler: Server.RequestHandler? - public var notificationHandler: Server.NotificationHandler? - - public init(requestHandler: Server.RequestHandler? = nil, notificationHandler: Server.NotificationHandler? = nil) { - self.requestHandler = requestHandler - self.notificationHandler = notificationHandler - } -} - -public protocol Server { - typealias RequestHandler = (ServerRequest, @escaping (ServerResult) -> Void) -> Void - typealias NotificationHandler = (ServerNotification, @escaping (ServerError?) -> Void) -> Void - typealias ResponseHandler = (ServerResult>) -> Void - - func setHandlers(_ handlers: ServerHandlers, completionHandler: @escaping (ServerError?) -> Void) - func sendNotification(_ notif: ClientNotification, completionHandler: @escaping (ServerError?) -> Void) - func sendRequest(_ request: ClientRequest, completionHandler: @escaping (ServerResult) -> Void) -} - -public extension Server { - func setHandlers(_ handlers: ServerHandlers) { - setHandlers(handlers, completionHandler: { _ in }) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func setHandlers(_ handlers: ServerHandlers) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - setHandlers(handlers) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } -} - -extension Server { - func sendRequestWithErrorOnlyResult(_ request: ClientRequest, completionHandler: @escaping (ServerError?) -> Void) { - sendRequest(request) { (result: ServerResult) in - switch result { - case .failure(let error): - completionHandler(error) - case .success: - completionHandler(nil) - } - } - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func sendRequestWithErrorOnlyResult(_ request: ClientRequest) async throws { - let _: UnusedResult = try await sendRequest(request) - } -} - -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -public extension Server { - func sendNotification(_ notif: ClientNotification) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.sendNotification(notif) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } - - func sendRequest(_ request: ClientRequest) async throws -> Response { - return try await withCheckedThrowingContinuation { continuation in - self.sendRequest(request) { result in - continuation.resume(with: result) - } - } - } -} - -public extension Server { - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func initialize(params: InitializeParams) async throws -> InitializationResponse { - return try await withCheckedThrowingContinuation { continuation in - self.initialize(params: params) { result in - continuation.resume(with: result) - } - } - } - - func initialize(params: InitializeParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.initialize(params), completionHandler: block) - } - - func initialized(params: InitializedParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.initialized(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func initialized(params: InitializedParams) async throws { - try await sendNotification(.initialized(params)) - } - - func shutdown(block: @escaping (ServerError?) -> Void) { - sendRequestWithErrorOnlyResult(.shutdown, completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func shutdown() async throws { - try await sendRequestWithErrorOnlyResult(.shutdown) - } - - func exit(block: @escaping (ServerError?) -> Void) { - sendNotification(.exit, completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func exit() async throws { - try await sendNotification(.exit) - } - - func cancelRequest(params: CancelParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.protocolCancelRequest(params), completionHandler: block) - } - - func setTrace(params: SetTraceParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.protocolSetTrace(params), completionHandler: block) - } - - func didOpenTextDocument(params: DidOpenTextDocumentParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.didOpenTextDocument(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func didOpenTextDocument(params: DidOpenTextDocumentParams) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.didOpenTextDocument(params: params) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } - - func didChangeTextDocument(params: DidChangeTextDocumentParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.didChangeTextDocument(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func didChangeTextDocument(params: DidChangeTextDocumentParams) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.didChangeTextDocument(params: params) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } - - func didCloseTextDocument(params: DidCloseTextDocumentParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.didCloseTextDocument(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func didCloseTextDocument(params: DidCloseTextDocumentParams) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.didCloseTextDocument(params: params) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } - - func willSaveTextDocument(params: WillSaveTextDocumentParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.willSaveTextDocument(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func willSaveTextDocument(params: WillSaveTextDocumentParams) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.willSaveTextDocument(params: params) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } - - func willSaveWaitUntilTextDocument(params: WillSaveTextDocumentParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.willSaveWaitUntilTextDocument(params), completionHandler: block) - } - - func didSaveTextDocument(params: DidSaveTextDocumentParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.didSaveTextDocument(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func didSaveTextDocument(params: DidSaveTextDocumentParams) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.didSaveTextDocument(params: params) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } - - func didChangeWatchedFiles(params: DidChangeWatchedFilesParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.didChangeWatchedFiles(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func didChangeWatchedFiles(params: DidChangeWatchedFilesParams) async throws { - try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in - self.didChangeWatchedFiles(params: params) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } - } - - func callHierarchyIncomingCalls(params: CallHierarchyIncomingCallsParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.callHierarchyIncomingCalls(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func completion(params: CallHierarchyIncomingCallsParams) async throws -> CallHierarchyIncomingCallsResponse { - try await withCheckedThrowingContinuation { continuation in - self.callHierarchyIncomingCalls(params: params) { result in - continuation.resume(with: result) - } - } - } - - func callHierarchyOutgoingCalls(params: CallHierarchyOutgoingCallsParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.callHierarchyOutgoingCalls(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func callHierarchyOutgoingCalls(params: CallHierarchyOutgoingCallsParams) async throws -> CallHierarchyOutgoingCallsResponse { - try await withCheckedThrowingContinuation { continuation in - self.callHierarchyOutgoingCalls(params: params) { result in - continuation.resume(with: result) - } - } - } - - func completion(params: CompletionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.completion(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func completion(params: CompletionParams) async throws -> CompletionResponse { - try await withCheckedThrowingContinuation { continuation in - self.completion(params: params) { result in - continuation.resume(with: result) - } - } - } - - func hover(params: TextDocumentPositionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.hover(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func hover(params: TextDocumentPositionParams) async throws -> HoverResponse { - try await withCheckedThrowingContinuation { continuation in - self.hover(params: params) { result in - continuation.resume(with: result) - } - } - } - - func signatureHelp(params: TextDocumentPositionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.signatureHelp(params), completionHandler: block) - } - - func declaration(params: TextDocumentPositionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.declaration(params), completionHandler: block) - } - - func definition(params: TextDocumentPositionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.definition(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func definition(params: TextDocumentPositionParams) async throws -> DefinitionResponse { - try await withCheckedThrowingContinuation { continuation in - self.definition(params: params) { result in - continuation.resume(with: result) - } - } - } - - func typeDefinition(params: TextDocumentPositionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.typeDefinition(params), completionHandler: block) - } - - func implementation(params: TextDocumentPositionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.implementation(params), completionHandler: block) - } - - func documentSymbol(params: DocumentSymbolParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.documentSymbol(params), completionHandler: block) - } - - func codeAction(params: CodeActionParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.codeAction(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func codeAction(params: CodeActionParams) async throws -> CodeActionResponse { - try await withCheckedThrowingContinuation { continuation in - codeAction(params: params) { result in - continuation.resume(with: result) - } - } - } - - func prepareCallHierarchy(params: CallHierarchyPrepareParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.prepareCallHierarchy(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func prepareCallHierarchy(params: CallHierarchyPrepareParams) async throws -> CallHierarchyPrepareResponse { - try await withCheckedThrowingContinuation { continuation in - prepareCallHierarchy(params: params) { result in - continuation.resume(with: result) - } - } - } - - func prepareRename(params: PrepareRenameParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.prepareRename(params), completionHandler: block) - } - - func rename(params: RenameParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.rename(params), completionHandler: block) - } - - func formatting(params: DocumentFormattingParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.formatting(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func formatting(params: DocumentFormattingParams) async throws -> FormattingResult { - try await withCheckedThrowingContinuation { continuation in - self.formatting(params: params) { result in - continuation.resume(with: result) - } - } - } - - func rangeFormatting(params: DocumentRangeFormattingParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.rangeFormatting(params), completionHandler: block) - } - - func onTypeFormatting(params: DocumentOnTypeFormattingParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.onTypeFormatting(params), completionHandler: block) - } - - func references(params: ReferenceParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.references(params), completionHandler: block) - } - - func foldingRange(params: FoldingRangeParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.foldingRange(params), completionHandler: block) - } - - func semanticTokensFull(params: SemanticTokensParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.semanticTokensFull(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func semanticTokensFull(params: SemanticTokensParams) async throws -> SemanticTokensResponse { - try await withCheckedThrowingContinuation { continuation in - self.semanticTokensFull(params: params) { result in - continuation.resume(with: result) - } - } - } - - func semanticTokensFullDelta(params: SemanticTokensDeltaParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.semanticTokensFullDelta(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func semanticTokensFullDelta(params: SemanticTokensDeltaParams) async throws -> SemanticTokensDeltaResponse { - try await withCheckedThrowingContinuation { continuation in - self.semanticTokensFullDelta(params: params) { result in - continuation.resume(with: result) - } - } - } - - func semanticTokensRange(params: SemanticTokensRangeParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.semanticTokensRange(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func semanticTokensRange(params: SemanticTokensRangeParams) async throws -> SemanticTokensResponse { - try await withCheckedThrowingContinuation { continuation in - self.semanticTokensRange(params: params) { result in - continuation.resume(with: result) - } - } - } - - func customRequest(method: String, params: LSPAny, block: @escaping (ServerResult) -> Void) { - sendRequest(.custom(method, params), completionHandler: block) - } -} - -// Workspace notifications -public extension Server { - func didChangeWorkspaceFolders(params: DidChangeWorkspaceFoldersParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.workspaceDidChangeWorkspaceFolders(params), completionHandler: block) - } - - func didChangeConfiguration(params: DidChangeConfigurationParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.workspaceDidChangeConfiguration(params), completionHandler: block) - } - - func didCreateFiles(params: CreateFilesParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.workspaceDidCreateFiles(params), completionHandler: block) - } - - func didRenameFiles(params: RenameFilesParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.workspaceDidRenameFiles(params), completionHandler: block) - } - - func didDeleteFiles(params: DeleteFilesParams, block: @escaping (ServerError?) -> Void) { - sendNotification(.workspaceDidDeleteFiles(params), completionHandler: block) - } -} - -// Workspace Requests -public extension Server { - func willCreateFiles(params: CreateFilesParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.workspaceWillCreateFiles(params), completionHandler: block) - } - - func willRenameFiles(params: RenameFilesParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.workspaceWillRenameFiles(params), completionHandler: block) - } - - func willDeleteFiles(params: DeleteFilesParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.workspaceWillDeleteFiles(params), completionHandler: block) - } - - func executeCommand(params: ExecuteCommandParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.workspaceExecuteCommand(params), completionHandler: block) - } - - func workspaceSymbol(params: WorkspaceSymbolParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.workspaceSymbol(params), completionHandler: block) - } - - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - func workspaceSymbol(params: WorkspaceSymbolParams) async throws -> WorkspaceSymbolResponse { - try await withCheckedThrowingContinuation { continuation in - self.workspaceSymbol(params: params) { result in - continuation.resume(with: result) - } - } - } - - - func workspaceSymbolResolve(params: WorkspaceSymbol, block: @escaping (ServerResult) -> Void) { - sendRequest(.workspaceSymbolResolve(params), completionHandler: block) - } -} - -// Language Features -public extension Server { - func documentHighlight(params: DocumentHighlightParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.documentHighlight(params), completionHandler: block) - } - - func codeLens(params: CodeLensParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.codeLens(params), completionHandler: block) - } - - func codeLensResolve(params: CodeLens, block: @escaping (ServerResult) -> Void) { - sendRequest(.codeLensResolve(params), completionHandler: block) - } - - func selectionRange(params: SelectionRangeParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.selectionRange(params), completionHandler: block) - } - - func documentLink(params: DocumentLinkParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.documentLink(params), completionHandler: block) - } - - func documentLinkResolve(params: DocumentLink, block: @escaping (ServerResult) -> Void) { - sendRequest(.documentLinkResolve(params), completionHandler: block) - } - - func documentColor(params: DocumentColorParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.documentColor(params), completionHandler: block) - } - - func colorPresentation(params: ColorPresentationParams, block: @escaping (ServerResult) -> Void) { - sendRequest(.colorPresentation(params), completionHandler: block) - } -} diff --git a/Sources/LanguageServerProtocol/ServerCapabilities.swift b/Sources/LanguageServerProtocol/ServerCapabilities.swift index 7f70ce0..49a57e5 100644 --- a/Sources/LanguageServerProtocol/ServerCapabilities.swift +++ b/Sources/LanguageServerProtocol/ServerCapabilities.swift @@ -1,6 +1,6 @@ import Foundation -public struct StaticRegistrationWorkDoneProgressTextDocumentRegistrationOptions: Codable, Hashable { +public struct StaticRegistrationWorkDoneProgressTextDocumentRegistrationOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? public var textDocument: TextDocumentIdentifier public var position: Position @@ -8,7 +8,7 @@ public struct StaticRegistrationWorkDoneProgressTextDocumentRegistrationOptions: public var id: String? } -public struct PartialResultsWorkDoneProgressTextDocumentRegistrationOptions: Codable, Hashable { +public struct PartialResultsWorkDoneProgressTextDocumentRegistrationOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? public var textDocument: TextDocumentIdentifier public var position: Position @@ -16,21 +16,21 @@ public struct PartialResultsWorkDoneProgressTextDocumentRegistrationOptions: Cod public var partialResultToken: ProgressToken? } -public struct WorkDoneProgressOptions: Codable, Hashable { +public struct WorkDoneProgressOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? } -public struct SaveOptions: Codable, Hashable { +public struct SaveOptions: Codable, Hashable, Sendable { public let includeText: Bool? } -public enum TextDocumentSyncKind: Int, Codable, Hashable { +public enum TextDocumentSyncKind: Int, Codable, Hashable, Sendable { case none = 0 case full = 1 case incremental = 2 } -public struct TextDocumentSyncOptions: Codable, Hashable { +public struct TextDocumentSyncOptions: Codable, Hashable, Sendable { public var openClose: Bool? public var change: TextDocumentSyncKind? public var willSave: Bool? @@ -49,14 +49,14 @@ public struct TextDocumentSyncOptions: Codable, Hashable { } } -public struct CompletionOptions: Codable, Hashable { +public struct CompletionOptions: Codable, Hashable, Sendable { public var resolveProvider: Bool? public var triggerCharacters: [String] } public typealias HoverOptions = WorkDoneProgressOptions -public struct SignatureHelpOptions: Codable, Hashable { +public struct SignatureHelpOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? public var triggerCharacters: [String]? public var retriggerCharacters: [String]? @@ -78,12 +78,12 @@ public typealias ImplementationRegistrationOptions = StaticRegistrationWorkDoneP public typealias ReferenceOptions = WorkDoneProgressOptions -public struct DocumentSymbolOptions: Codable, Hashable { +public struct DocumentSymbolOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? public var label: String? } -public struct CodeActionOptions: Codable, Hashable { +public struct CodeActionOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? public var codeActionKinds: [CodeActionKind]? public var resolveProvider: Bool? @@ -97,7 +97,7 @@ public typealias DocumentFormattingOptions = WorkDoneProgressOptions public typealias DocumentRangeFormattingOptions = WorkDoneProgressOptions -public struct DocumentOnTypeFormattingOptions: Codable, Hashable { +public struct DocumentOnTypeFormattingOptions: Codable, Hashable, Sendable { public var firstTriggerCharacter: String public var moreTriggerCharacter: [String]? } @@ -110,19 +110,19 @@ public typealias LinkedEditingRangeOptions = WorkDoneProgressOptions public typealias LinkedEditingRangeRegistrationOptions = StaticRegistrationWorkDoneProgressTextDocumentRegistrationOptions -public struct SemanticTokensOptions: Codable, Hashable { +public struct SemanticTokensOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? public var legend: SemanticTokensLegend - public var range: SemanticTokensClientCapabilities.Requests.RangeOption - public var full: SemanticTokensClientCapabilities.Requests.FullOption + public var range: SemanticTokensClientCapabilities.Requests.RangeOption? + public var full: SemanticTokensClientCapabilities.Requests.FullOption? } -public struct SemanticTokensRegistrationOptions: Codable, Hashable { +public struct SemanticTokensRegistrationOptions: Codable, Hashable, Sendable { public var documentSelector: DocumentSelector? public var workDoneProgress: Bool? public var legend: SemanticTokensLegend - public var range: SemanticTokensClientCapabilities.Requests.RangeOption - public var full: SemanticTokensClientCapabilities.Requests.FullOption + public var range: SemanticTokensClientCapabilities.Requests.RangeOption? + public var full: SemanticTokensClientCapabilities.Requests.FullOption? public var id: String? } @@ -130,24 +130,44 @@ public typealias MonikerOptions = WorkDoneProgressOptions public typealias MonikerRegistrationOptions = PartialResultsWorkDoneProgressTextDocumentRegistrationOptions -public struct WorkspaceFoldersServerCapabilities: Codable, Hashable { +public struct WorkspaceFoldersServerCapabilities: Codable, Hashable, Sendable { public var supported: Bool? public var changeNotifications: TwoTypeOption? } -public struct ServerCapabilities: Codable, Hashable { - public struct Workspace: Codable, Hashable { - public struct FileOperations: Codable, Hashable { +public struct ServerCapabilities: Codable, Hashable, Sendable { + public struct Workspace: Codable, Hashable, Sendable { + public struct FileOperations: Codable, Hashable, Sendable { public var didCreate: FileOperationRegistrationOptions? public var willCreate: FileOperationRegistrationOptions? public var didRename: FileOperationRegistrationOptions? public var willRename: FileOperationRegistrationOptions? public var didDelete: FileOperationRegistrationOptions? public var willDelete: FileOperationRegistrationOptions? + + public init(didCreate: FileOperationRegistrationOptions? = nil, + willCreate: FileOperationRegistrationOptions? = nil, + didRename: FileOperationRegistrationOptions? = nil, + willRename: FileOperationRegistrationOptions? = nil, + didDelete: FileOperationRegistrationOptions? = nil, + willDelete: FileOperationRegistrationOptions? = nil) { + self.didCreate = didCreate + self.willCreate = willCreate + self.didRename = didRename + self.willRename = willRename + self.didDelete = didDelete + self.willDelete = willDelete + } } public var workspaceFolders: WorkspaceFoldersServerCapabilities? public var fileOperations: FileOperations? + + public init(workspaceFolders: WorkspaceFoldersServerCapabilities? = nil, + fileOperations: FileOperations? = nil) { + self.workspaceFolders = workspaceFolders + self.fileOperations = fileOperations + } } public var textDocumentSync: TwoTypeOption? diff --git a/Sources/LanguageServerProtocol/TextSynchronization.swift b/Sources/LanguageServerProtocol/TextSynchronization.swift index 2519adc..829c0ad 100644 --- a/Sources/LanguageServerProtocol/TextSynchronization.swift +++ b/Sources/LanguageServerProtocol/TextSynchronization.swift @@ -1,6 +1,6 @@ import Foundation -public struct DidOpenTextDocumentParams: Codable, Hashable { +public struct DidOpenTextDocumentParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentItem public init(textDocument: TextDocumentItem) { @@ -8,7 +8,7 @@ public struct DidOpenTextDocumentParams: Codable, Hashable { } } -public struct TextDocumentContentChangeEvent: Codable, Hashable { +public struct TextDocumentContentChangeEvent: Codable, Hashable, Sendable { public let range: LSPRange? public let rangeLength: Int? public let text: String @@ -20,7 +20,7 @@ public struct TextDocumentContentChangeEvent: Codable, Hashable { } } -public struct DidChangeTextDocumentParams: Codable, Hashable { +public struct DidChangeTextDocumentParams: Codable, Hashable, Sendable { public let textDocument: VersionedTextDocumentIdentifier public let contentChanges: [TextDocumentContentChangeEvent] @@ -40,12 +40,12 @@ public struct DidChangeTextDocumentParams: Codable, Hashable { } } -public struct TextDocumentChangeRegistrationOptions: Codable, Hashable { +public struct TextDocumentChangeRegistrationOptions: Codable, Hashable, Sendable { public let documentSelector: DocumentSelector? public let syncKind: TextDocumentSyncKind } -public struct DidSaveTextDocumentParams: Codable, Hashable { +public struct DidSaveTextDocumentParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let text: String? @@ -62,12 +62,12 @@ public struct DidSaveTextDocumentParams: Codable, Hashable { } } -public struct TextDocumentSaveRegistrationOptions: Codable, Hashable { +public struct TextDocumentSaveRegistrationOptions: Codable, Hashable, Sendable { public let documentSelector: DocumentSelector? public let includeText: Bool? } -public struct DidCloseTextDocumentParams: Codable { +public struct DidCloseTextDocumentParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public init(textDocument: TextDocumentIdentifier) { @@ -81,13 +81,13 @@ public struct DidCloseTextDocumentParams: Codable { } } -public enum TextDocumentSaveReason: Int, Codable, Hashable { +public enum TextDocumentSaveReason: Int, Codable, Hashable, Sendable { case manual = 1 case afterDelay = 2 case focusOut = 3 } -public struct WillSaveTextDocumentParams: Codable, Hashable { +public struct WillSaveTextDocumentParams: Codable, Hashable, Sendable { public let textDocument: TextDocumentIdentifier public let reason: TextDocumentSaveReason @@ -99,7 +99,7 @@ public struct WillSaveTextDocumentParams: Codable, Hashable { public typealias WillSaveWaitUntilResponse = [TextEdit]? -public struct TextEdit: Codable, Hashable { +public struct TextEdit: Codable, Hashable, Sendable { public let range: LSPRange public let newText: String diff --git a/Sources/LanguageServerProtocol/ThreeTypeOption.swift b/Sources/LanguageServerProtocol/ThreeTypeOption.swift index b08ea62..f12ba55 100644 --- a/Sources/LanguageServerProtocol/ThreeTypeOption.swift +++ b/Sources/LanguageServerProtocol/ThreeTypeOption.swift @@ -38,8 +38,6 @@ extension ThreeTypeOption: Codable where T: Codable, U: Codable, V: Codable { } } -extension ThreeTypeOption: Equatable where T: Equatable, U: Equatable, V: Equatable { -} - -extension ThreeTypeOption: Hashable where T: Hashable, U: Hashable, V: Hashable { -} +extension ThreeTypeOption: Equatable where T: Equatable, U: Equatable, V: Equatable {} +extension ThreeTypeOption: Hashable where T: Hashable, U: Hashable, V: Hashable {} +extension ThreeTypeOption: Sendable where T: Sendable, U: Sendable, V: Sendable {} diff --git a/Sources/LanguageServerProtocol/Window.swift b/Sources/LanguageServerProtocol/Window.swift index 50c71d5..07c9055 100644 --- a/Sources/LanguageServerProtocol/Window.swift +++ b/Sources/LanguageServerProtocol/Window.swift @@ -1,6 +1,6 @@ import Foundation -public enum MessageType: Int, Codable, Hashable { +public enum MessageType: Int, Codable, Hashable, Sendable { case error = 1 case warning = 2 case info = 3 @@ -22,7 +22,7 @@ extension MessageType: CustomStringConvertible { } } -public struct LogMessageParams: Codable, Hashable { +public struct LogMessageParams: Codable, Hashable, Sendable { public let type: MessageType public let message: String } @@ -35,7 +35,7 @@ extension LogMessageParams: CustomStringConvertible { public typealias ShowMessageParams = LogMessageParams -public struct ShowDocumentParams: Hashable, Codable { +public struct ShowDocumentParams: Hashable, Codable, Sendable { public var uri: URI public var external: Bool? public var takeFocus: Bool? @@ -49,7 +49,7 @@ public struct ShowDocumentParams: Hashable, Codable { } } -public struct WorkDoneProgressCreateParams: Hashable, Codable { +public struct WorkDoneProgressCreateParams: Hashable, Codable, Sendable { public var token: ProgressToken public init(token: ProgressToken) { @@ -58,3 +58,11 @@ public struct WorkDoneProgressCreateParams: Hashable, Codable { } public typealias WorkDoneProgressCancelParams = WorkDoneProgressCreateParams + +public struct ShowDocumentResult: Hashable, Codable, Sendable { + public let success: Bool + + public init(success: Bool) { + self.success = success + } +} diff --git a/Sources/LanguageServerProtocol/Window/ShowMessageRequest.swift b/Sources/LanguageServerProtocol/Window/ShowMessageRequest.swift index a51ab32..57a8985 100644 --- a/Sources/LanguageServerProtocol/Window/ShowMessageRequest.swift +++ b/Sources/LanguageServerProtocol/Window/ShowMessageRequest.swift @@ -1,10 +1,10 @@ import Foundation -public struct MessageActionItem: Codable, Hashable { +public struct MessageActionItem: Codable, Hashable, Sendable { public var title: String } -public struct ShowMessageRequestParams: Codable, Hashable { +public struct ShowMessageRequestParams: Codable, Hashable, Sendable { public var type: MessageType public var message: String public var actions: [MessageActionItem]? @@ -15,3 +15,5 @@ extension ShowMessageRequestParams: CustomStringConvertible { return "\(type): \(message)" } } + +public typealias ShowMessageRequestResponse = MessageActionItem? diff --git a/Sources/LanguageServerProtocol/Workspace.swift b/Sources/LanguageServerProtocol/Workspace.swift index beac86e..c3f0fa6 100644 --- a/Sources/LanguageServerProtocol/Workspace.swift +++ b/Sources/LanguageServerProtocol/Workspace.swift @@ -1,6 +1,6 @@ import Foundation -public struct WatchKind: OptionSet, Codable, Hashable { +public struct WatchKind: OptionSet, Codable, Hashable, Sendable { public let rawValue: Int public init(rawValue: Int) { @@ -14,7 +14,7 @@ public struct WatchKind: OptionSet, Codable, Hashable { public static let all: WatchKind = [.create, .change, .delete] } -public struct FileSystemWatcher: Codable, Hashable { +public struct FileSystemWatcher: Codable, Hashable, Sendable { public var globPattern: String public var kind: WatchKind? @@ -24,17 +24,17 @@ public struct FileSystemWatcher: Codable, Hashable { } } -public struct DidChangeWatchedFilesRegistrationOptions: Codable, Hashable { +public struct DidChangeWatchedFilesRegistrationOptions: Codable, Hashable, Sendable { public var watchers: [FileSystemWatcher] } -public enum FileChangeType: Int, Codable, Hashable { +public enum FileChangeType: Int, Codable, Hashable, Sendable { case created = 1 case changed = 2 case deleted = 3 } -public struct FileEvent: Codable, Hashable { +public struct FileEvent: Codable, Hashable, Sendable { public var uri: DocumentUri public var type: FileChangeType @@ -44,7 +44,7 @@ public struct FileEvent: Codable, Hashable { } } -public struct DidChangeWatchedFilesParams: Codable, Hashable { +public struct DidChangeWatchedFilesParams: Codable, Hashable, Sendable { public var changes: [FileEvent] public init(changes: [FileEvent]) { @@ -62,7 +62,7 @@ public struct WorkspaceFolder: Codable, Hashable, Sendable { } } -public struct WorkspaceFoldersChangeEvent: Codable, Hashable { +public struct WorkspaceFoldersChangeEvent: Codable, Hashable, Sendable { public var added: [WorkspaceFolder] public var removed: [WorkspaceFolder] @@ -72,7 +72,7 @@ public struct WorkspaceFoldersChangeEvent: Codable, Hashable { } } -public struct DidChangeWorkspaceFoldersParams: Codable, Hashable { +public struct DidChangeWorkspaceFoldersParams: Codable, Hashable, Sendable { public var event: WorkspaceFoldersChangeEvent public init(event: WorkspaceFoldersChangeEvent) { @@ -80,7 +80,7 @@ public struct DidChangeWorkspaceFoldersParams: Codable, Hashable { } } -public struct DidChangeConfigurationParams: Codable, Hashable { +public struct DidChangeConfigurationParams: Codable, Hashable, Sendable { public var settings: LSPAny? public init(settings: LSPAny) { @@ -92,7 +92,7 @@ public enum SymbolTag: Int, Codable, Hashable, CaseIterable, Sendable { case Deprecated = 1 } -public struct SymbolInformation: Codable, Hashable { +public struct SymbolInformation: Codable, Hashable, Sendable { public let name: String public let kind: SymbolKind public let tags: [SymbolTag]? @@ -101,12 +101,12 @@ public struct SymbolInformation: Codable, Hashable { public let containerName: String? } -public struct CreateFileOptions: Codable, Hashable { +public struct CreateFileOptions: Codable, Hashable, Sendable { public let overwrite: Bool? public let ignoreIfExists: Bool? } -public struct CreateFile: Codable, Hashable { +public struct CreateFile: Codable, Hashable, Sendable { public let kind: String public let uri: DocumentUri public let options: CreateFileOptions? @@ -114,30 +114,30 @@ public struct CreateFile: Codable, Hashable { public typealias RenameFileOptions = CreateFileOptions -public struct RenameFile: Codable, Hashable { +public struct RenameFile: Codable, Hashable, Sendable { public let kind: String public let oldUri: DocumentUri public let newUri: DocumentUri public let options: RenameFileOptions } -public struct DeleteFileOptions: Codable, Hashable { +public struct DeleteFileOptions: Codable, Hashable, Sendable { public let recursive: Bool? public let ignoreIfNotExists: Bool? } -public struct DeleteFile: Codable, Hashable { +public struct DeleteFile: Codable, Hashable, Sendable { public let kind: String public let uri: DocumentUri public let options: DeleteFileOptions } -public struct TextDocumentEdit: Codable, Hashable { +public struct TextDocumentEdit: Codable, Hashable, Sendable { public let textDocument: VersionedTextDocumentIdentifier public let edits: [TextEdit] } -public enum WorkspaceEditDocumentChange: Codable, Hashable { +public enum WorkspaceEditDocumentChange: Codable, Hashable, Sendable { case textDocumentEdit(TextDocumentEdit) case createFile(CreateFile) case renameFile(RenameFile) @@ -174,7 +174,7 @@ public enum WorkspaceEditDocumentChange: Codable, Hashable { } } -public struct WorkspaceEdit: Codable, Hashable { +public struct WorkspaceEdit: Codable, Hashable, Sendable { public let changes: [DocumentUri : [TextEdit]]? public let documentChanges: [WorkspaceEditDocumentChange]? } diff --git a/Sources/LanguageServerProtocol/Workspace/ApplyEdit.swift b/Sources/LanguageServerProtocol/Workspace/ApplyEdit.swift index afbf9ea..194ef9b 100644 --- a/Sources/LanguageServerProtocol/Workspace/ApplyEdit.swift +++ b/Sources/LanguageServerProtocol/Workspace/ApplyEdit.swift @@ -1,6 +1,6 @@ import Foundation -public struct ApplyWorkspaceEditParams: Codable, Hashable { +public struct ApplyWorkspaceEditParams: Codable, Hashable, Sendable { public var label: String? public var edit: WorkspaceEdit @@ -10,7 +10,7 @@ public struct ApplyWorkspaceEditParams: Codable, Hashable { } } -public struct ApplyWorkspaceEditResult: Codable, Hashable { +public struct ApplyWorkspaceEditResult: Codable, Hashable, Sendable { public var applied: Bool public var failureReason: String? public var failedChange: UInt? diff --git a/Sources/LanguageServerProtocol/Workspace/Configuration.swift b/Sources/LanguageServerProtocol/Workspace/Configuration.swift index a863c1f..d2c91b3 100644 --- a/Sources/LanguageServerProtocol/Workspace/Configuration.swift +++ b/Sources/LanguageServerProtocol/Workspace/Configuration.swift @@ -1,6 +1,6 @@ import Foundation -public struct ConfigurationItem: Codable, Hashable { +public struct ConfigurationItem: Codable, Hashable, Sendable { public var scopeUri: DocumentUri? public var section: String? @@ -10,7 +10,7 @@ public struct ConfigurationItem: Codable, Hashable { } } -public struct ConfigurationParams: Codable, Hashable { +public struct ConfigurationParams: Codable, Hashable, Sendable { public var items: [ConfigurationItem] public init(items: [ConfigurationItem]) { diff --git a/Sources/LanguageServerProtocol/Workspace/ExecuteCommand.swift b/Sources/LanguageServerProtocol/Workspace/ExecuteCommand.swift index 173e821..fded77b 100644 --- a/Sources/LanguageServerProtocol/Workspace/ExecuteCommand.swift +++ b/Sources/LanguageServerProtocol/Workspace/ExecuteCommand.swift @@ -2,7 +2,7 @@ import Foundation public typealias ExecuteCommandClientCapabilities = DynamicRegistrationClientCapabilities -public struct ExecuteCommandOptions: Codable, Hashable { +public struct ExecuteCommandOptions: Codable, Hashable, Sendable { public var workDoneProgress: Bool? public var commands: [String] @@ -14,7 +14,7 @@ public struct ExecuteCommandOptions: Codable, Hashable { public typealias ExecuteCommandRegistrationOptions = ExecuteCommandOptions -public struct ExecuteCommandParams: Codable, Hashable { +public struct ExecuteCommandParams: Codable, Hashable, Sendable { public var workDoneToken: ProgressToken? public var command: String public var arguments: [LSPAny]? diff --git a/Sources/LanguageServerProtocol/Workspace/Folders.swift b/Sources/LanguageServerProtocol/Workspace/Folders.swift new file mode 100644 index 0000000..b68e492 --- /dev/null +++ b/Sources/LanguageServerProtocol/Workspace/Folders.swift @@ -0,0 +1 @@ +public typealias WorkspaceFoldersResponse = [WorkspaceFolder]? diff --git a/Sources/LanguageServerProtocol/Workspace/WillCreateFiles.swift b/Sources/LanguageServerProtocol/Workspace/WillCreateFiles.swift index c83d2f9..27c437c 100644 --- a/Sources/LanguageServerProtocol/Workspace/WillCreateFiles.swift +++ b/Sources/LanguageServerProtocol/Workspace/WillCreateFiles.swift @@ -1,11 +1,11 @@ import Foundation -public enum FileOperationPatternKind: String, Codable, Hashable { +public enum FileOperationPatternKind: String, Codable, Hashable, Sendable { case file = "file" case folder = "folder" } -public struct FileOperationPatternOptions: Codable, Hashable { +public struct FileOperationPatternOptions: Codable, Hashable, Sendable { public var ignoreCase: Bool? public init(ignoreCase: Bool? = nil) { @@ -13,7 +13,7 @@ public struct FileOperationPatternOptions: Codable, Hashable { } } -public struct FileOperationPattern: Codable, Hashable { +public struct FileOperationPattern: Codable, Hashable, Sendable { public let glob: String public let matches: FileOperationPatternKind? public let options: FileOperationPatternOptions? @@ -25,7 +25,7 @@ public struct FileOperationPattern: Codable, Hashable { } } -public struct FileOperationFilter: Codable, Hashable { +public struct FileOperationFilter: Codable, Hashable, Sendable { public var scheme: String? public var pattern: FileOperationPattern @@ -35,7 +35,7 @@ public struct FileOperationFilter: Codable, Hashable { } } -public struct FileOperationRegistrationOptions: Codable, Hashable { +public struct FileOperationRegistrationOptions: Codable, Hashable, Sendable { public var filters: [FileOperationFilter] public init(filters: [FileOperationFilter]) { @@ -43,7 +43,7 @@ public struct FileOperationRegistrationOptions: Codable, Hashable { } } -public struct CreateFilesParams: Codable, Hashable { +public struct CreateFilesParams: Codable, Hashable, Sendable { public var files: [FileCreate] public init(files: [FileCreate]) { @@ -51,7 +51,7 @@ public struct CreateFilesParams: Codable, Hashable { } } -public struct FileCreate: Codable, Hashable { +public struct FileCreate: Codable, Hashable, Sendable { public var uri: String public init(uri: String) { diff --git a/Sources/LanguageServerProtocol/Workspace/WillDeleteFiles.swift b/Sources/LanguageServerProtocol/Workspace/WillDeleteFiles.swift index 6df30b6..8400a7a 100644 --- a/Sources/LanguageServerProtocol/Workspace/WillDeleteFiles.swift +++ b/Sources/LanguageServerProtocol/Workspace/WillDeleteFiles.swift @@ -1,10 +1,10 @@ import Foundation -public struct DeleteFilesParams: Codable, Hashable { +public struct DeleteFilesParams: Codable, Hashable, Sendable { public var files: [FileDelete] } -public struct FileDelete: Codable, Hashable { +public struct FileDelete: Codable, Hashable, Sendable { public var uri: String public init(uri: String) { diff --git a/Sources/LanguageServerProtocol/Workspace/WillRenameFiles.swift b/Sources/LanguageServerProtocol/Workspace/WillRenameFiles.swift index 74a1061..9c08da3 100644 --- a/Sources/LanguageServerProtocol/Workspace/WillRenameFiles.swift +++ b/Sources/LanguageServerProtocol/Workspace/WillRenameFiles.swift @@ -1,6 +1,6 @@ import Foundation -public struct RenameFilesParams: Codable, Hashable { +public struct RenameFilesParams: Codable, Hashable, Sendable { public var files: [FileRename] public init(files: [FileRename]) { @@ -8,7 +8,7 @@ public struct RenameFilesParams: Codable, Hashable { } } -public struct FileRename: Codable, Hashable { +public struct FileRename: Codable, Hashable, Sendable { public var oldUri: String public var newUri: String diff --git a/Tests/LanguageServerProtocolTests/ServerTests.swift b/Tests/LanguageServerProtocolTests/ServerTests.swift index ff93d16..afb971e 100644 --- a/Tests/LanguageServerProtocolTests/ServerTests.swift +++ b/Tests/LanguageServerProtocolTests/ServerTests.swift @@ -1,98 +1,46 @@ import XCTest -import JSONRPC -@testable import LanguageServerProtocol - -class MockServer: Server { - var responseData: Data? - - func setHandlers(_ handlers: LanguageServerProtocol.ServerHandlers, completionHandler: @escaping (ServerError?) -> Void) { - completionHandler(nil) - } - - func sendNotification(_ notif: ClientNotification, completionHandler: @escaping (ServerError?) -> Void) { - completionHandler(.missingExpectedParameter) - } - - func sendRequest(_ request: ClientRequest, completionHandler: @escaping (ServerResult) -> Void) where Response : Decodable, Response : Encodable { - guard let data = responseData else { - completionHandler(.failure(.missingExpectedResult)) - return - } - - do { - let response = try JSONDecoder().decode(Response.self, from: data) - - completionHandler(.success(response)) - } catch { - completionHandler(.failure(.requestDispatchFailed(error))) - } - } -} +import LanguageServerProtocol final class ServerTests: XCTestCase { - func testNonOptionalSend() throws { + func testNonOptionalSend() async throws { let server = MockServer() // nearly all requests have optional results - let response = DocumentLink(range: .zero, target: "something", tooltip: nil, data: nil) + let link = DocumentLink(range: .zero, target: "something", tooltip: nil, data: nil) - server.responseData = try JSONEncoder().encode(response) + try await server.sendMockResponse(link) let param = DocumentLink(range: .zero, target: nil, tooltip: nil, data: nil) - server.documentLinkResolve(params: param) { result in - guard case .success(let link) = result else { - XCTFail() - return - } + let response = try await server.documentLinkResolve(params: param) - XCTAssertEqual(link, response) - } + XCTAssertEqual(link, response) } - func testOptionalSend() throws { - let server = MockServer() + func testOptionalSend() async throws { + let server = MockServer() - let response: CodeActionResponse = nil + let expectedResponse: CodeActionResponse = nil - server.responseData = try JSONEncoder().encode(response) + try await server.sendMockResponse(expectedResponse) let params = CodeActionParams(textDocument: TextDocumentIdentifier(path: "abc"), range: .zero, context: CodeActionContext(diagnostics: [], only: [.SourceOrganizeImports])) - server.codeAction(params: params) { result in - guard case .success(nil) = result else { - XCTFail() - return - } - } + let response = try await server.codeAction(params: params) + + XCTAssertNil(response) } - func testAsyncSendWithNoResponse() async throws { + func testSendWithNoResponse() async throws { let server = MockServer() - let response: String? = nil + let expectedResponse: String? = nil - server.responseData = try JSONEncoder().encode(response) + try await server.sendMockResponse(expectedResponse) try await server.shutdown() - - // check to make sure this throws correctly - server.responseData = nil - - do { - try await server.shutdown() - - XCTFail() - } catch { - switch error { - case ServerError.missingExpectedResult: - break - default: - throw error - } - } } }