1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import BuildSystemIntegration
public import Foundation
public import LanguageServerProtocol
import LanguageServerProtocolExtensions
import SKLogging
package import SKOptions
package import SourceKitLSP
import SwiftExtensions
import TSCExtensions
package import ToolchainRegistry
import struct TSCBasic.AbsolutePath
/// Launches a `SourceKitLSPServer` in-process and allows sending messages to it.
public final class InProcessSourceKitLSPClient: Sendable {
private let server: SourceKitLSPServer
private let nextRequestID = AtomicUInt32(initialValue: 0)
public convenience init(
toolchainPath: URL?,
capabilities: ClientCapabilities = ClientCapabilities(),
workspaceFolders: [WorkspaceFolder],
messageHandler: any MessageHandler
) async throws {
try await self.init(
toolchainRegistry: ToolchainRegistry(installPath: toolchainPath),
options: SourceKitLSPOptions(),
capabilities: capabilities,
workspaceFolders: workspaceFolders,
messageHandler: messageHandler
)
}
/// Create a new `SourceKitLSPServer`. An `InitializeRequest` is automatically sent to the server.
///
/// `messageHandler` handles notifications and requests sent from the SourceKit-LSP server to the client.
package init(
toolchainRegistry: ToolchainRegistry,
options: SourceKitLSPOptions = SourceKitLSPOptions(),
hooks: Hooks = Hooks(),
capabilities: ClientCapabilities = ClientCapabilities(),
workspaceFolders: [WorkspaceFolder],
messageHandler: any MessageHandler
) async throws {
let serverToClientConnection = LocalConnection(receiverName: "client")
self.server = SourceKitLSPServer(
client: serverToClientConnection,
toolchainRegistry: toolchainRegistry,
options: options,
hooks: hooks,
onExit: {
serverToClientConnection.close()
}
)
serverToClientConnection.start(handler: messageHandler)
_ = try await self.send(
InitializeRequest(
processId: nil,
rootPath: nil,
rootURI: nil,
initializationOptions: nil,
capabilities: capabilities,
trace: .off,
workspaceFolders: workspaceFolders
)
)
}
/// Send the request to `server` and return the request result.
///
/// - Important: Because this is an async function, Swift concurrency makes no guarantees about the execution ordering
/// of this request with regard to other requests to the server. If execution of requests in a particular order is
/// necessary and the response of the request is not awaited, use the version of the function that takes a
/// completion handler
public func send<R: RequestType>(_ request: R) async throws -> R.Response {
let requestId = ThreadSafeBox<RequestID?>(initialValue: nil)
return try await withTaskCancellationHandler {
return try await withCheckedThrowingContinuation { continuation in
if Task.isCancelled {
// Check if the task has been cancelled before we send the request to LSP to avoid any kind of work if
// possible.
return continuation.resume(throwing: CancellationError())
}
requestId.value = self.send(request) {
continuation.resume(with: $0)
}
if Task.isCancelled, let requestId = requestId.takeValue() {
// The task might have been cancelled after the above cancellation check but before `requestId` was assigned
// a value. To cover that case, check for cancellation here again. Note that we won't cancel twice from here
// and the `onCancel` handler because we take the request ID out of the `ThreadSafeBox` before sending the
// `CancelRequestNotification`.
self.send(CancelRequestNotification(id: requestId))
}
}
} onCancel: {
if let requestId = requestId.takeValue() {
self.send(CancelRequestNotification(id: requestId))
}
}
}
/// Send the request to `server` and return the request result via a completion handler.
@discardableResult
public func send<R: RequestType>(
_ request: R,
reply: @Sendable @escaping (LSPResult<R.Response>) -> Void
) -> RequestID {
let requestID = RequestID.string("sk-\(Int(nextRequestID.fetchAndIncrement()))")
server.handle(request, id: requestID, reply: reply)
return requestID
}
/// Send the request to `server` and return the request result via a completion handler.
///
/// The request ID must not start with `sk-` to avoid conflicting with the request IDs that are created by
/// `send(:reply:)`.
public func send<R: RequestType>(
_ request: R,
id: RequestID,
reply: @Sendable @escaping (LSPResult<R.Response>) -> Void
) {
if case .string(let string) = id {
if string.starts(with: "sk-") {
logger.fault("Manually specified request ID must not have reserved prefix 'sk-'")
}
}
server.handle(request, id: id, reply: reply)
}
/// Send the notification to `server`.
public func send(_ notification: some NotificationType) {
server.handle(notification)
}
}
|