Files
sourcekit-lsp/Sources/SourceKitLSP/Swift/OpenInterface.swift
Alex Hoppen 5bae73fca8 Use fallback build settings if build system doesn’t provide build settings within a timeout
When we receive build settings after hitting the timeout, we call `fileBuildSettingsChanged` on the delegate, which should cause the document to get re-opened in sourcekitd and diagnostics to get refreshed.

rdar://136332685
Fixes #1693
2024-10-16 10:55:29 -07:00

139 lines
4.7 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2022 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)
import Foundation
package import LanguageServerProtocol
import SKLogging
import SKSupport
import SourceKitD
#else
import Foundation
import LanguageServerProtocol
import SKLogging
import SKSupport
import SourceKitD
#endif
struct GeneratedInterfaceInfo {
var contents: String
}
extension SwiftLanguageService {
package func openGeneratedInterface(
document: DocumentURI,
moduleName: String,
groupName: String?,
symbolUSR symbol: String?
) async throws -> GeneratedInterfaceDetails? {
// Name of interface module name with group names appended
let name =
if let groupName {
"\(moduleName).\(groupName.replacing("/", with: "."))"
} else {
moduleName
}
let interfaceFilePath = self.generatedInterfacesPath.appendingPathComponent("\(name).swiftinterface")
let interfaceDocURI = DocumentURI(interfaceFilePath)
// has interface already been generated
if let snapshot = try? await self.latestSnapshot(for: interfaceDocURI) {
return await self.generatedInterfaceDetails(
uri: interfaceDocURI,
snapshot: snapshot,
symbol: symbol
)
} else {
let interfaceInfo = try await self.generatedInterfaceInfo(
document: document,
moduleName: moduleName,
groupName: groupName,
interfaceURI: interfaceDocURI
)
try interfaceInfo.contents.write(to: interfaceFilePath, atomically: true, encoding: String.Encoding.utf8)
let snapshot = DocumentSnapshot(
uri: interfaceDocURI,
language: .swift,
version: 0,
lineTable: LineTable(interfaceInfo.contents)
)
let result = await self.generatedInterfaceDetails(
uri: interfaceDocURI,
snapshot: snapshot,
symbol: symbol
)
_ = await orLog("Closing generated interface") {
try await sendSourcekitdRequest(closeDocumentSourcekitdRequest(uri: interfaceDocURI), fileContents: nil)
}
return result
}
}
/// Open the Swift interface for a module.
///
/// - Parameters:
/// - document: The document whose compiler arguments should be used to generate the interface.
/// - moduleName: The module to generate an index for.
/// - groupName: The module group name.
/// - interfaceURI: The file where the generated interface should be written.
///
/// - Important: This opens a document with name `interfaceURI.pseudoPath` in sourcekitd. The caller is responsible
/// for ensuring that the document will eventually get closed in sourcekitd again.
private func generatedInterfaceInfo(
document: DocumentURI,
moduleName: String,
groupName: String?,
interfaceURI: DocumentURI
) async throws -> GeneratedInterfaceInfo {
let keys = self.keys
let skreq = sourcekitd.dictionary([
keys.request: requests.editorOpenInterface,
keys.moduleName: moduleName,
keys.groupName: groupName,
keys.name: interfaceURI.pseudoPath,
keys.synthesizedExtension: 1,
keys.compilerArgs: await self.buildSettings(for: document, fallbackAfterTimeout: false)?.compilerArgs
as [SKDRequestValue]?,
])
let dict = try await sendSourcekitdRequest(skreq, fileContents: nil)
return GeneratedInterfaceInfo(contents: dict[keys.sourceText] ?? "")
}
private func generatedInterfaceDetails(
uri: DocumentURI,
snapshot: DocumentSnapshot,
symbol: String?
) async -> GeneratedInterfaceDetails {
do {
guard let symbol = symbol else {
return GeneratedInterfaceDetails(uri: uri, position: nil)
}
let keys = self.keys
let skreq = sourcekitd.dictionary([
keys.request: requests.editorFindUSR,
keys.sourceFile: uri.sourcekitdSourceFile,
keys.primaryFile: uri.primaryFile?.pseudoPath,
keys.usr: symbol,
])
let dict = try await sendSourcekitdRequest(skreq, fileContents: snapshot.text)
if let offset: Int = dict[keys.offset] {
return GeneratedInterfaceDetails(uri: uri, position: snapshot.positionOf(utf8Offset: offset))
} else {
return GeneratedInterfaceDetails(uri: uri, position: nil)
}
} catch {
return GeneratedInterfaceDetails(uri: uri, position: nil)
}
}
}