mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
The client ID was needed when a `MessageHandler` could handle messages from multiple connections. We don’t support this anymore (because it wasn’t needed) and so the client ID doesn’t need to get passed through as well.
229 lines
6.6 KiB
Swift
229 lines
6.6 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import LanguageServerProtocol
|
|
import LanguageServerProtocolJSONRPC
|
|
import SKSupport
|
|
import XCTest
|
|
|
|
import class Foundation.Pipe
|
|
|
|
public final class TestJSONRPCConnection {
|
|
public let clientToServer: Pipe = Pipe()
|
|
public let serverToClient: Pipe = Pipe()
|
|
public let client: TestMessageHandler
|
|
public let clientConnection: JSONRPCConnection
|
|
public let server: TestServer
|
|
public let serverConnection: JSONRPCConnection
|
|
|
|
public init(allowUnexpectedNotification: Bool = true) {
|
|
clientConnection = JSONRPCConnection(
|
|
name: "client",
|
|
protocol: testMessageRegistry,
|
|
inFD: serverToClient.fileHandleForReading,
|
|
outFD: clientToServer.fileHandleForWriting
|
|
)
|
|
|
|
serverConnection = JSONRPCConnection(
|
|
name: "server",
|
|
protocol: testMessageRegistry,
|
|
inFD: clientToServer.fileHandleForReading,
|
|
outFD: serverToClient.fileHandleForWriting
|
|
)
|
|
|
|
client = TestMessageHandler(server: clientConnection, allowUnexpectedNotification: allowUnexpectedNotification)
|
|
server = TestServer(client: serverConnection)
|
|
|
|
clientConnection.start(receiveHandler: client) {
|
|
// FIXME: keep the pipes alive until we close the connection. This
|
|
// should be fixed systemically.
|
|
withExtendedLifetime(self) {}
|
|
}
|
|
serverConnection.start(receiveHandler: server) {
|
|
// FIXME: keep the pipes alive until we close the connection. This
|
|
// should be fixed systemically.
|
|
withExtendedLifetime(self) {}
|
|
}
|
|
}
|
|
|
|
public func close() {
|
|
clientConnection.close()
|
|
serverConnection.close()
|
|
}
|
|
}
|
|
|
|
public struct TestLocalConnection {
|
|
public let client: TestMessageHandler
|
|
public let clientConnection: LocalConnection = .init()
|
|
public let server: TestServer
|
|
public let serverConnection: LocalConnection = .init()
|
|
|
|
public init(allowUnexpectedNotification: Bool = true) {
|
|
client = TestMessageHandler(server: serverConnection, allowUnexpectedNotification: allowUnexpectedNotification)
|
|
server = TestServer(client: clientConnection)
|
|
|
|
clientConnection.start(handler: client)
|
|
serverConnection.start(handler: server)
|
|
}
|
|
|
|
public func close() {
|
|
clientConnection.close()
|
|
serverConnection.close()
|
|
}
|
|
}
|
|
|
|
public actor TestMessageHandler: MessageHandler {
|
|
/// The connection to the language client.
|
|
public let server: Connection
|
|
|
|
private let messageHandlingQueue = AsyncQueue<Serial>()
|
|
|
|
private var oneShotNotificationHandlers: [((Any) -> Void)] = []
|
|
|
|
private let allowUnexpectedNotification: Bool
|
|
|
|
public init(server: Connection, allowUnexpectedNotification: Bool = true) {
|
|
self.server = server
|
|
self.allowUnexpectedNotification = allowUnexpectedNotification
|
|
}
|
|
|
|
public func appendOneShotNotificationHandler<N: NotificationType>(_ handler: @escaping (N) -> Void) {
|
|
oneShotNotificationHandlers.append({ anyNote in
|
|
guard let note = anyNote as? N else {
|
|
fatalError("received notification of the wrong type \(anyNote); expected \(N.self)")
|
|
}
|
|
handler(note)
|
|
})
|
|
}
|
|
|
|
/// The LSP server sent a notification to the client. Handle it.
|
|
public nonisolated func handle(_ notification: some NotificationType) {
|
|
messageHandlingQueue.async {
|
|
await self.handleNotificationImpl(notification)
|
|
}
|
|
}
|
|
|
|
public func handleNotificationImpl(_ notification: some NotificationType) {
|
|
guard !oneShotNotificationHandlers.isEmpty else {
|
|
if allowUnexpectedNotification { return }
|
|
fatalError("unexpected notification \(notification)")
|
|
}
|
|
let handler = oneShotNotificationHandlers.removeFirst()
|
|
handler(notification)
|
|
}
|
|
|
|
/// The LSP server sent a request to the client. Handle it.
|
|
public nonisolated func handle<Request: RequestType>(
|
|
_ request: Request,
|
|
id: RequestID,
|
|
reply: @escaping (LSPResult<Request.Response>) -> Void
|
|
) {
|
|
reply(.failure(.methodNotFound(Request.method)))
|
|
}
|
|
}
|
|
|
|
extension TestMessageHandler: Connection {
|
|
/// Send a notification to the LSP server.
|
|
public nonisolated func send(_ notification: some NotificationType) {
|
|
server.send(notification)
|
|
}
|
|
|
|
/// Send a request to the LSP server and (asynchronously) receive a reply.
|
|
public nonisolated func send<Request: RequestType>(
|
|
_ request: Request,
|
|
reply: @escaping (LSPResult<Request.Response>) -> Void
|
|
) -> RequestID {
|
|
return server.send(request, reply: reply)
|
|
}
|
|
}
|
|
|
|
public final class TestServer: MessageHandler {
|
|
public let client: Connection
|
|
|
|
init(client: Connection) {
|
|
self.client = client
|
|
}
|
|
|
|
public func handle(_ params: some NotificationType) {
|
|
if params is EchoNotification {
|
|
self.client.send(params)
|
|
} else {
|
|
fatalError("Unhandled notification")
|
|
}
|
|
}
|
|
|
|
public func handle<R: RequestType>(
|
|
_ params: R,
|
|
id: RequestID,
|
|
reply: @escaping (LSPResult<R.Response>) -> Void
|
|
) {
|
|
if let params = params as? EchoRequest {
|
|
reply(.success(params.string as! R.Response))
|
|
} else if let params = params as? EchoError {
|
|
if let code = params.code {
|
|
reply(.failure(ResponseError(code: code, message: params.message!)))
|
|
} else {
|
|
reply(.success(VoidResponse() as! R.Response))
|
|
}
|
|
} else {
|
|
fatalError("Unhandled request")
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: Test requests.
|
|
|
|
private let testMessageRegistry = MessageRegistry(
|
|
requests: [EchoRequest.self, EchoError.self],
|
|
notifications: [EchoNotification.self]
|
|
)
|
|
|
|
#if compiler(<5.11)
|
|
extension String: ResponseType {}
|
|
#else
|
|
extension String: @retroactive ResponseType {}
|
|
#endif
|
|
|
|
public struct EchoRequest: RequestType {
|
|
public static let method: String = "test_server/echo"
|
|
public typealias Response = String
|
|
|
|
public var string: String
|
|
|
|
public init(string: String) {
|
|
self.string = string
|
|
}
|
|
}
|
|
|
|
public struct EchoError: RequestType {
|
|
public static let method: String = "test_server/echo_error"
|
|
public typealias Response = VoidResponse
|
|
|
|
public var code: ErrorCode?
|
|
public var message: String?
|
|
|
|
public init(code: ErrorCode? = nil, message: String? = nil) {
|
|
self.code = code
|
|
self.message = message
|
|
}
|
|
}
|
|
|
|
public struct EchoNotification: NotificationType {
|
|
public static let method: String = "test_server/echo_note"
|
|
|
|
public var string: String
|
|
|
|
public init(string: String) {
|
|
self.string = string
|
|
}
|
|
}
|