mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
153 lines
5.3 KiB
Swift
153 lines
5.3 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#if compiler(>=6)
|
|
package import Csourcekitd
|
|
import Dispatch
|
|
import Foundation
|
|
import SwiftExtensions
|
|
#else
|
|
import Csourcekitd
|
|
import Dispatch
|
|
import Foundation
|
|
import SwiftExtensions
|
|
#endif
|
|
|
|
fileprivate struct SourceKitDRequestHandle: Sendable {
|
|
/// `nonisolated(unsafe)` is fine because we just use the handle as an opaque value.
|
|
nonisolated(unsafe) let handle: sourcekitd_api_request_handle_t
|
|
}
|
|
|
|
/// Access to sourcekitd API, taking care of initialization, shutdown, and notification handler
|
|
/// multiplexing.
|
|
///
|
|
/// *Users* of this protocol should not call the api functions `initialize`, `shutdown`, or
|
|
/// `set_notification_handler`, which are global state managed internally by this class.
|
|
///
|
|
/// *Implementors* are expected to handle initialization and shutdown, e.g. during `init` and
|
|
/// `deinit` or by wrapping an existing sourcekitd session that outlives this object.
|
|
package protocol SourceKitD: AnyObject, Sendable {
|
|
/// The sourcekitd API functions.
|
|
var api: sourcekitd_api_functions_t { get }
|
|
|
|
/// Convenience for accessing known keys.
|
|
var keys: sourcekitd_api_keys { get }
|
|
|
|
/// Convenience for accessing known keys.
|
|
var requests: sourcekitd_api_requests { get }
|
|
|
|
/// Convenience for accessing known keys.
|
|
var values: sourcekitd_api_values { get }
|
|
|
|
/// Adds a new notification handler, which will be weakly referenced.
|
|
func addNotificationHandler(_ handler: SKDNotificationHandler) async
|
|
|
|
/// Removes a previously registered notification handler.
|
|
func removeNotificationHandler(_ handler: SKDNotificationHandler) async
|
|
|
|
/// Log the given request.
|
|
///
|
|
/// This log call is issued during normal operation. It is acceptable for the logger to truncate the log message
|
|
/// to achieve good performance.
|
|
func log(request: SKDRequestDictionary)
|
|
|
|
/// Log the given request and file contents, ensuring they do not get truncated.
|
|
///
|
|
/// This log call is used when a request has crashed. In this case we want the log to contain the entire request to be
|
|
/// able to reproduce it.
|
|
func log(crashedRequest: SKDRequestDictionary, fileContents: String?)
|
|
|
|
/// Log the given response.
|
|
///
|
|
/// This log call is issued during normal operation. It is acceptable for the logger to truncate the log message
|
|
/// to achieve good performance.
|
|
func log(response: SKDResponse)
|
|
|
|
/// Log that the given request has been cancelled.
|
|
func logRequestCancellation(request: SKDRequestDictionary)
|
|
}
|
|
|
|
package enum SKDError: Error, Equatable {
|
|
/// The service has crashed.
|
|
case connectionInterrupted
|
|
|
|
/// The request was unknown or had an invalid or missing parameter.
|
|
case requestInvalid(String)
|
|
|
|
/// The request failed.
|
|
case requestFailed(String)
|
|
|
|
/// The request was cancelled.
|
|
case requestCancelled
|
|
|
|
/// The request exceeded the maximum allowed duration.
|
|
case timedOut
|
|
|
|
/// Loading a required symbol from the sourcekitd library failed.
|
|
case missingRequiredSymbol(String)
|
|
}
|
|
|
|
extension SourceKitD {
|
|
// MARK: - Convenience API for requests.
|
|
|
|
/// - Parameters:
|
|
/// - request: The request to send to sourcekitd.
|
|
/// - timeout: The maximum duration how long to wait for a response. If no response is returned within this time,
|
|
/// declare the request as having timed out.
|
|
/// - fileContents: The contents of the file that the request operates on. If sourcekitd crashes, the file contents
|
|
/// will be logged.
|
|
package func send(
|
|
_ request: SKDRequestDictionary,
|
|
timeout: Duration,
|
|
fileContents: String?
|
|
) async throws -> SKDResponseDictionary {
|
|
log(request: request)
|
|
|
|
let sourcekitdResponse = try await withTimeout(timeout) {
|
|
return try await withCancellableCheckedThrowingContinuation { (continuation) -> SourceKitDRequestHandle? in
|
|
var handle: sourcekitd_api_request_handle_t? = nil
|
|
self.api.send_request(request.dict, &handle) { response in
|
|
continuation.resume(returning: SKDResponse(response!, sourcekitd: self))
|
|
}
|
|
if let handle {
|
|
return SourceKitDRequestHandle(handle: handle)
|
|
}
|
|
return nil
|
|
} cancel: { (handle: SourceKitDRequestHandle?) in
|
|
if let handle {
|
|
self.logRequestCancellation(request: request)
|
|
self.api.cancel_request(handle.handle)
|
|
}
|
|
}
|
|
}
|
|
|
|
log(response: sourcekitdResponse)
|
|
|
|
guard let dict = sourcekitdResponse.value else {
|
|
if sourcekitdResponse.error == .connectionInterrupted {
|
|
log(crashedRequest: request, fileContents: fileContents)
|
|
}
|
|
if sourcekitdResponse.error == .requestCancelled && !Task.isCancelled {
|
|
throw SKDError.timedOut
|
|
}
|
|
throw sourcekitdResponse.error!
|
|
}
|
|
|
|
return dict
|
|
}
|
|
}
|
|
|
|
/// A sourcekitd notification handler in a class to allow it to be uniquely referenced.
|
|
package protocol SKDNotificationHandler: AnyObject, Sendable {
|
|
func notification(_: SKDResponse) -> Void
|
|
}
|