mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
Quite a few of these were reminders to clean things up once we no longer need to support testing using compilers and sourcekitd from older toolchains.
313 lines
11 KiB
Swift
313 lines
11 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 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 Foundation
|
|
import SourceKitD
|
|
import SwiftExtensions
|
|
import SwiftSourceKitPluginCommon
|
|
|
|
#if compiler(>=6)
|
|
public import Csourcekitd
|
|
#else
|
|
import Csourcekitd
|
|
#endif
|
|
|
|
private func useNewAPI(for dict: SKDRequestDictionaryReader) -> Bool {
|
|
guard let opts: SKDRequestDictionaryReader = dict[dict.sourcekitd.keys.codeCompleteOptions],
|
|
opts[dict.sourcekitd.keys.useNewAPI] == 1
|
|
else {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
final class RequestHandler: Sendable {
|
|
enum HandleRequestResult {
|
|
/// `handleRequest` will call `receiver`.
|
|
case requestHandled
|
|
|
|
/// `handleRequest` will not call `receiver` and a request response should be produced by sourcekitd (not the plugin).
|
|
case handleInSourceKitD
|
|
}
|
|
|
|
let requestHandlingQueue = AsyncQueue<Serial>()
|
|
let sourcekitd: SourceKitD
|
|
let completionProvider: CompletionProvider
|
|
|
|
init(params: sourcekitd_api_plugin_initialize_params_t, completionResultsBufferKind: UInt64, sourcekitd: SourceKitD) {
|
|
let ideInspectionInstance = sourcekitd.servicePluginApi.plugin_initialize_get_swift_ide_inspection_instance(params)
|
|
|
|
self.sourcekitd = sourcekitd
|
|
self.completionProvider = CompletionProvider(
|
|
completionResultsBufferKind: completionResultsBufferKind,
|
|
opaqueIDEInspectionInstance: OpaqueIDEInspectionInstance(ideInspectionInstance),
|
|
sourcekitd: sourcekitd
|
|
)
|
|
}
|
|
|
|
func handleRequest(
|
|
_ dict: SKDRequestDictionaryReader,
|
|
handle: RequestHandle?,
|
|
receiver: @Sendable @escaping (SKDResponse) -> Void
|
|
) -> HandleRequestResult {
|
|
func produceResult(
|
|
body: @escaping @Sendable () async throws -> SKDResponseDictionaryBuilder
|
|
) -> HandleRequestResult {
|
|
requestHandlingQueue.async {
|
|
do {
|
|
receiver(try await body().response)
|
|
} catch {
|
|
receiver(SKDResponse.from(error: error, sourcekitd: self.sourcekitd))
|
|
}
|
|
}
|
|
return .requestHandled
|
|
}
|
|
|
|
func sourcekitdProducesResult(body: @escaping @Sendable () async -> ()) -> HandleRequestResult {
|
|
requestHandlingQueue.async {
|
|
await body()
|
|
}
|
|
return .handleInSourceKitD
|
|
}
|
|
|
|
switch dict[sourcekitd.keys.request] as sourcekitd_api_uid_t? {
|
|
case sourcekitd.requests.editorOpen:
|
|
return sourcekitdProducesResult {
|
|
await self.completionProvider.handleDocumentOpen(dict)
|
|
}
|
|
case sourcekitd.requests.editorReplaceText:
|
|
return sourcekitdProducesResult {
|
|
await self.completionProvider.handleDocumentEdit(dict)
|
|
}
|
|
case sourcekitd.requests.editorClose:
|
|
return sourcekitdProducesResult {
|
|
await self.completionProvider.handleDocumentClose(dict)
|
|
}
|
|
|
|
case sourcekitd.requests.codeCompleteOpen:
|
|
guard useNewAPI(for: dict) else {
|
|
return .handleInSourceKitD
|
|
}
|
|
return produceResult {
|
|
try await self.completionProvider.handleCompleteOpen(dict, handle: handle)
|
|
}
|
|
case sourcekitd.requests.codeCompleteUpdate:
|
|
guard useNewAPI(for: dict) else {
|
|
return .handleInSourceKitD
|
|
}
|
|
return produceResult {
|
|
try await self.completionProvider.handleCompleteUpdate(dict)
|
|
}
|
|
case sourcekitd.requests.codeCompleteClose:
|
|
guard useNewAPI(for: dict) else {
|
|
return .handleInSourceKitD
|
|
}
|
|
return produceResult {
|
|
try await self.completionProvider.handleCompleteClose(dict)
|
|
}
|
|
case sourcekitd.requests.codeCompleteDocumentation:
|
|
return produceResult {
|
|
try await self.completionProvider.handleCompletionDocumentation(dict)
|
|
}
|
|
case sourcekitd.requests.codeCompleteDiagnostic:
|
|
return produceResult {
|
|
try await self.completionProvider.handleCompletionDiagnostic(dict)
|
|
}
|
|
case sourcekitd.requests.codeCompleteSetPopularAPI:
|
|
guard useNewAPI(for: dict) else {
|
|
return .handleInSourceKitD
|
|
}
|
|
return produceResult {
|
|
await self.completionProvider.handleSetPopularAPI(dict)
|
|
}
|
|
case sourcekitd.requests.dependencyUpdated:
|
|
return sourcekitdProducesResult {
|
|
await self.completionProvider.handleDependencyUpdated()
|
|
}
|
|
default:
|
|
return .handleInSourceKitD
|
|
}
|
|
}
|
|
|
|
func cancel(_ handle: RequestHandle) {
|
|
self.completionProvider.cancel(handle: handle)
|
|
}
|
|
}
|
|
|
|
#if compiler(>=6.3)
|
|
#warning("Remove sourcekitd_plugin_initialize when we no longer support toolchains that call it")
|
|
#endif
|
|
|
|
/// Legacy plugin initialization logic in which sourcekitd does not inform the plugin about the sourcekitd path it was
|
|
/// loaded from.
|
|
@_cdecl("sourcekitd_plugin_initialize")
|
|
public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initialize_params_t) {
|
|
#if canImport(Darwin)
|
|
var dlInfo = Dl_info()
|
|
dladdr(#dsohandle, &dlInfo)
|
|
let path = String(cString: dlInfo.dli_fname)
|
|
var url = URL(fileURLWithPath: path, isDirectory: false)
|
|
while url.pathExtension != "framework" && url.lastPathComponent != "/" {
|
|
url.deleteLastPathComponent()
|
|
}
|
|
url =
|
|
url
|
|
.deletingLastPathComponent()
|
|
.appendingPathComponent("sourcekitd.framework")
|
|
.appendingPathComponent("sourcekitd")
|
|
if FileManager.default.fileExists(at: url) {
|
|
try! url.filePath.withCString { sourcekitdPath in
|
|
sourcekitd_plugin_initialize_2(params, sourcekitdPath)
|
|
}
|
|
} else {
|
|
// When using a SourceKit plugin from the build directory, we can't find sourcekitd relative to the plugin.
|
|
// Since sourcekitd_plugin_initialize is only called on Darwin from Xcode toolchains, we know that we are getting
|
|
// called from an XPC sourcekitd. Thus, all sourcekitd symbols that we need should be loaded in the current process
|
|
// already and we can use `RTLD_DEFAULT` for the sourcekitd library.
|
|
sourcekitd_plugin_initialize_2(params, "SOURCEKIT_LSP_PLUGIN_PARENT_LIBRARY_RTLD_DEFAULT")
|
|
}
|
|
#else
|
|
fatalError("sourcekitd_plugin_initialize is not supported on non-Darwin platforms")
|
|
#endif
|
|
}
|
|
|
|
#if canImport(Darwin)
|
|
private extension DynamicallyLoadedSourceKitD {
|
|
/// When a plugin is initialized, it gets passed the library it was loaded from to `sourcekitd_plugin_initialize_2`.
|
|
///
|
|
/// Since the plugin wants to interact with sourcekitd in-process, it needs to load `sourcekitdInProc`. This function
|
|
/// loads `sourcekitdInProc` relative to the parent library path, if it exists, or `sourcekitd` if `sourcekitdInProc`
|
|
/// doesn't exist (eg. on Linux where `sourcekitd` is already in-process).
|
|
static func inProcLibrary(relativeTo parentLibraryPath: URL) throws -> DynamicallyLoadedSourceKitD {
|
|
var frameworkUrl = parentLibraryPath
|
|
|
|
// Remove path components until we reach the `sourcekitd.framework` directory. The plugin might have been loaded
|
|
// from an XPC service, in which case `parentLibraryPath` is
|
|
// `sourcekitd.framework/XPCServices/SourceKitService.xpc/Contents/MacOS/SourceKitService`.
|
|
while frameworkUrl.pathExtension != "framework" {
|
|
guard frameworkUrl.pathComponents.count > 1 else {
|
|
struct NoFrameworkPathError: Error, CustomStringConvertible {
|
|
var parentLibraryPath: URL
|
|
var description: String { "Could not find .framework directory relative to '\(parentLibraryPath)'" }
|
|
}
|
|
throw NoFrameworkPathError(parentLibraryPath: parentLibraryPath)
|
|
}
|
|
frameworkUrl.deleteLastPathComponent()
|
|
}
|
|
frameworkUrl.deleteLastPathComponent()
|
|
|
|
let inProcUrl =
|
|
frameworkUrl
|
|
.appendingPathComponent("sourcekitdInProc.framework")
|
|
.appendingPathComponent("sourcekitdInProc")
|
|
if FileManager.default.fileExists(at: inProcUrl) {
|
|
return try DynamicallyLoadedSourceKitD(
|
|
dylib: inProcUrl,
|
|
pluginPaths: nil,
|
|
initialize: false
|
|
)
|
|
}
|
|
|
|
let sourcekitdUrl =
|
|
frameworkUrl
|
|
.appendingPathComponent("sourcekitd.framework")
|
|
.appendingPathComponent("sourcekitd")
|
|
return try DynamicallyLoadedSourceKitD(
|
|
dylib: sourcekitdUrl,
|
|
pluginPaths: nil,
|
|
initialize: false
|
|
)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
@_cdecl("sourcekitd_plugin_initialize_2")
|
|
public func sourcekitd_plugin_initialize_2(
|
|
_ params: sourcekitd_api_plugin_initialize_params_t,
|
|
_ parentLibraryPath: UnsafePointer<CChar>
|
|
) {
|
|
let parentLibraryPath = String(cString: parentLibraryPath)
|
|
#if canImport(Darwin)
|
|
if parentLibraryPath == "SOURCEKIT_LSP_PLUGIN_PARENT_LIBRARY_RTLD_DEFAULT" {
|
|
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD(
|
|
dlhandle: .rtldDefault,
|
|
path: URL(string: "rtld-default://")!,
|
|
pluginPaths: nil,
|
|
initialize: false
|
|
)
|
|
} else {
|
|
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD.inProcLibrary(
|
|
relativeTo: URL(fileURLWithPath: parentLibraryPath)
|
|
)
|
|
}
|
|
#else
|
|
// On other platforms, sourcekitd is always in process, so we can load it straight away.
|
|
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD(
|
|
dylib: URL(fileURLWithPath: parentLibraryPath),
|
|
pluginPaths: nil,
|
|
initialize: false
|
|
)
|
|
#endif
|
|
let sourcekitd = DynamicallyLoadedSourceKitD.forPlugin
|
|
|
|
let completionResultsBufferKind = sourcekitd.pluginApi.plugin_initialize_custom_buffer_start(params)
|
|
let isClientOnly = sourcekitd.pluginApi.plugin_initialize_is_client_only(params)
|
|
|
|
let uidFromCString = sourcekitd.pluginApi.plugin_initialize_uid_get_from_cstr(params)
|
|
let uidGetCString = sourcekitd.pluginApi.plugin_initialize_uid_get_string_ptr(params)
|
|
|
|
// Depending on linking and loading configuration, we may need to chain the global UID handlers back to the UID
|
|
// handlers in the caller. The extra hop should not matter, since we cache the results.
|
|
if unsafeBitCast(uidFromCString, to: UnsafeRawPointer.self)
|
|
!= unsafeBitCast(sourcekitd.api.uid_get_from_cstr, to: UnsafeRawPointer.self)
|
|
{
|
|
sourcekitd.api.set_uid_handlers(uidFromCString, uidGetCString)
|
|
}
|
|
|
|
sourcekitd.pluginApi.plugin_initialize_register_custom_buffer(
|
|
params,
|
|
completionResultsBufferKind,
|
|
CompletionResultsArray.arrayFuncs.rawValue
|
|
)
|
|
|
|
if isClientOnly {
|
|
return
|
|
}
|
|
|
|
let requestHandler = RequestHandler(
|
|
params: params,
|
|
completionResultsBufferKind: completionResultsBufferKind,
|
|
sourcekitd: sourcekitd
|
|
)
|
|
|
|
sourcekitd.servicePluginApi.plugin_initialize_register_cancellation_handler(params) { handle in
|
|
if let handle = RequestHandle(handle) {
|
|
requestHandler.cancel(handle)
|
|
}
|
|
}
|
|
|
|
sourcekitd.servicePluginApi.plugin_initialize_register_cancellable_request_handler(params) {
|
|
(request, handle, receiver) -> Bool in
|
|
guard let receiver, let request, let dict = SKDRequestDictionaryReader(request, sourcekitd: sourcekitd) else {
|
|
return false
|
|
}
|
|
let handle = RequestHandle(handle)
|
|
|
|
let handledRequest = requestHandler.handleRequest(dict, handle: handle) { receiver($0.underlyingValueRetained()) }
|
|
|
|
switch handledRequest {
|
|
case .requestHandled: return true
|
|
case .handleInSourceKitD: return false
|
|
}
|
|
}
|
|
}
|