Files
sourcekit-lsp/Sources/SourceKitD/SourceKitDImpl.swift
Saleem Abdulrasool 7d1bcee1b0 explicitly import TSCBasic types
Explicitly import interfaces from TSCBasic which now allows us to
identify all the swift-tools-support-core interfaces which are in
use in SourceKit-LSP.
2022-09-10 12:53:30 -07:00

113 lines
3.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 SKSupport
import Foundation
import struct TSCBasic.AbsolutePath
/// Wrapper for sourcekitd, taking care of initialization, shutdown, and notification handler
/// multiplexing.
///
/// Users of this class should not call the api functions `initialize`, `shutdown`, or
/// `set_notification_handler`, which are global state managed internally by this class.
public final class SourceKitDImpl: SourceKitD {
/// The path to the sourcekitd dylib.
public let path: AbsolutePath
/// The handle to the dylib.
let dylib: DLHandle
/// The sourcekitd API functions.
public let api: sourcekitd_functions_t
/// Convenience for accessing known keys.
public let keys: sourcekitd_keys
/// Convenience for accessing known keys.
public let requests: sourcekitd_requests
/// Convenience for accessing known keys.
public let values: sourcekitd_values
/// Lock protecting private state.
let lock: NSLock = NSLock()
/// List of notification handlers that will be called for each notification.
private var _notificationHandlers: [WeakSKDNotificationHandler] = []
var notificationHandlers: [SKDNotificationHandler] {
lock.withLock {
_notificationHandlers.compactMap { $0.value }
}
}
public static func getOrCreate(dylibPath: AbsolutePath) throws -> SourceKitD {
try SourceKitDRegistry.shared
.getOrAdd(dylibPath, create: { try SourceKitDImpl(dylib: dylibPath) })
}
init(dylib path: AbsolutePath) throws {
self.path = path
#if os(Windows)
self.dylib = try dlopen(path.pathString, mode: [])
#else
self.dylib = try dlopen(path.pathString, mode: [.lazy, .local, .first])
#endif
self.api = try sourcekitd_functions_t(self.dylib)
self.keys = sourcekitd_keys(api: self.api)
self.requests = sourcekitd_requests(api: self.api)
self.values = sourcekitd_values(api: self.api)
self.api.initialize()
self.api.set_notification_handler { [weak self] rawResponse in
guard let self = self else { return }
let handlers = self.lock.withLock { self._notificationHandlers.compactMap(\.value) }
let response = SKDResponse(rawResponse, sourcekitd: self)
for handler in handlers {
handler.notification(response)
}
}
}
deinit {
self.api.set_notification_handler(nil)
self.api.shutdown()
// FIXME: is it safe to dlclose() sourcekitd? If so, do that here. For now, let the handle leak.
dylib.leak()
}
/// Adds a new notification handler (referenced weakly).
public func addNotificationHandler(_ handler: SKDNotificationHandler) {
lock.withLock {
_notificationHandlers.removeAll(where: { $0.value == nil })
_notificationHandlers.append(.init(handler))
}
}
/// Removes a previously registered notification handler.
public func removeNotificationHandler(_ handler: SKDNotificationHandler) {
lock.withLock {
_notificationHandlers.removeAll(where: { $0.value == nil || $0.value === handler})
}
}
}
struct WeakSKDNotificationHandler {
weak private(set) var value: SKDNotificationHandler?
init(_ value: SKDNotificationHandler) {
self.value = value
}
}