diff --git a/Sources/BuildServerIntegration/BuildServerManager.swift b/Sources/BuildServerIntegration/BuildServerManager.swift index a19ed0576..0f648180e 100644 --- a/Sources/BuildServerIntegration/BuildServerManager.swift +++ b/Sources/BuildServerIntegration/BuildServerManager.swift @@ -795,6 +795,28 @@ package actor BuildServerManager: QueueBasedMessageHandler { return languageFromBuildServer ?? Language(inferredFromFileExtension: document) } + /// Returns the language that a document should be interpreted in for background tasks where the editor doesn't + /// specify the document's language. + /// + /// If the language could not be determined, this method throws an error. + package func defaultLanguageInCanonicalTarget(for document: DocumentURI) async throws -> Language { + struct UnableToInferLanguage: Error, CustomStringConvertible { + let document: DocumentURI + var description: String { "Unable to infer language for \(document)" } + } + + guard let canonicalTarget = await self.canonicalTarget(for: document) else { + guard let language = Language(inferredFromFileExtension: document) else { + throw UnableToInferLanguage(document: document) + } + return language + } + guard let language = await defaultLanguage(for: document, in: canonicalTarget) else { + throw UnableToInferLanguage(document: document) + } + return language + } + /// Retrieve information about the given source file within the build server. package func sourceFileInfo(for document: DocumentURI) async -> SourceFileInfo? { return await orLog("Getting targets for source file") { diff --git a/Sources/ClangLanguageService/ClangLanguageService.swift b/Sources/ClangLanguageService/ClangLanguageService.swift index 7bacff369..a5487a6db 100644 --- a/Sources/ClangLanguageService/ClangLanguageService.swift +++ b/Sources/ClangLanguageService/ClangLanguageService.swift @@ -496,14 +496,10 @@ extension ClangLanguageService { } package func doccDocumentation(_ req: DoccDocumentationRequest) async throws -> DoccDocumentationResponse { - guard let sourceKitLSPServer else { - throw ResponseError.unknown("Connection to the editor closed") + guard let language = openDocuments[req.textDocument.uri] else { + throw ResponseError.requestFailed("Documentation preview is not available for clang files") } - - let snapshot = try sourceKitLSPServer.documentManager.latestSnapshot(req.textDocument.uri) - throw ResponseError.requestFailed( - "Documentation preview is not available for \(snapshot.language.description) files" - ) + throw ResponseError.requestFailed("Documentation preview is not available for \(language.description) files") } package func symbolInfo(_ req: SymbolInfoRequest) async throws -> [SymbolDetails] { diff --git a/Sources/DocumentationLanguageService/DoccDocumentationError.swift b/Sources/DocumentationLanguageService/DoccDocumentationError.swift index ad1956884..07247a348 100644 --- a/Sources/DocumentationLanguageService/DoccDocumentationError.swift +++ b/Sources/DocumentationLanguageService/DoccDocumentationError.swift @@ -23,7 +23,7 @@ enum DocCDocumentationError: LocalizedError { case .unsupportedLanguage(let language): return "Documentation preview is not available for \(language.description) files" case .indexNotAvailable: - return "The index is not availble to complete the request" + return "The index is not available to complete the request" case .symbolNotFound(let symbolName): return "Could not find symbol \(symbolName) in the project" } diff --git a/Sources/DocumentationLanguageService/DoccDocumentationHandler.swift b/Sources/DocumentationLanguageService/DoccDocumentationHandler.swift index 5466019e9..2d3912ebd 100644 --- a/Sources/DocumentationLanguageService/DoccDocumentationHandler.swift +++ b/Sources/DocumentationLanguageService/DoccDocumentationHandler.swift @@ -104,47 +104,35 @@ extension DocumentationLanguageService { guard let index = workspace.index(checkedFor: .deletedFiles) else { throw ResponseError.requestFailed(doccDocumentationError: .indexNotAvailable) } - guard let symbolLink = DocCSymbolLink(linkString: symbolName), - let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence( - ofDocCSymbolLink: symbolLink, - fetchSymbolGraph: { location in - guard let symbolWorkspace = await sourceKitLSPServer.workspaceForDocument(uri: location.documentUri) else { - throw ResponseError.internalError("Unable to find language service for \(location.documentUri)") + return try await sourceKitLSPServer.withOnDiskDocumentManager { onDiskDocumentManager in + guard let symbolLink = DocCSymbolLink(linkString: symbolName), + let symbolOccurrence = try await index.primaryDefinitionOrDeclarationOccurrence( + ofDocCSymbolLink: symbolLink, + fetchSymbolGraph: { location in + return try await sourceKitLSPServer.primaryLanguageService( + for: location.documentUri, + workspace.buildServerManager.defaultLanguageInCanonicalTarget(for: location.documentUri), + in: workspace + ) + .symbolGraph(forOnDiskContentsAt: location, in: workspace, manager: onDiskDocumentManager) } - let languageService = try await sourceKitLSPServer.primaryLanguageService( - for: location.documentUri, - .swift, - in: symbolWorkspace - ) - return try await languageService.symbolGraph( - forOnDiskContentsOf: location.documentUri, - at: location - ) - } + ) + else { + throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName)) + } + let symbolGraph = try await sourceKitLSPServer.primaryLanguageService( + for: symbolOccurrence.location.documentUri, + workspace.buildServerManager.defaultLanguageInCanonicalTarget(for: symbolOccurrence.location.documentUri), + in: workspace + ).symbolGraph(forOnDiskContentsAt: symbolOccurrence.location, in: workspace, manager: onDiskDocumentManager) + return try await documentationManager.renderDocCDocumentation( + symbolUSR: symbolOccurrence.symbol.usr, + symbolGraph: symbolGraph, + markupFile: snapshot.text, + moduleName: moduleName, + catalogURL: catalogURL ) - else { - throw ResponseError.requestFailed(doccDocumentationError: .symbolNotFound(symbolName)) } - let symbolDocumentUri = symbolOccurrence.location.documentUri - guard let symbolWorkspace = await sourceKitLSPServer.workspaceForDocument(uri: symbolDocumentUri) else { - throw ResponseError.internalError("Unable to find language service for \(symbolDocumentUri)") - } - let languageService = try await sourceKitLSPServer.primaryLanguageService( - for: symbolDocumentUri, - .swift, - in: symbolWorkspace - ) - let symbolGraph = try await languageService.symbolGraph( - forOnDiskContentsOf: symbolDocumentUri, - at: symbolOccurrence.location - ) - return try await documentationManager.renderDocCDocumentation( - symbolUSR: symbolOccurrence.symbol.usr, - symbolGraph: symbolGraph, - markupFile: snapshot.text, - moduleName: moduleName, - catalogURL: catalogURL - ) } // This is a page representing the module itself. // Create a dummy symbol graph and tell SwiftDocC to convert the module name. @@ -175,24 +163,26 @@ extension DocumentationLanguageService { in: workspace ).symbolGraph(for: snapshot, at: position) // Locate the documentation extension and include it in the request if one exists - let markupExtensionFile = await orLog("Finding markup extension file for symbol \(symbolUSR)") { - try await findMarkupExtensionFile( - workspace: workspace, - documentationManager: documentationManager, - catalogURL: catalogURL, - for: symbolUSR, - fetchSymbolGraph: { location in - guard let symbolWorkspace = await sourceKitLSPServer.workspaceForDocument(uri: location.documentUri) else { - throw ResponseError.internalError("Unable to find language service for \(location.documentUri)") + let markupExtensionFile = await sourceKitLSPServer.withOnDiskDocumentManager { + [documentationManager, documentManager = try documentManager] onDiskDocumentManager in + await orLog("Finding markup extension file for symbol \(symbolUSR)") { + try await Self.findMarkupExtensionFile( + workspace: workspace, + documentationManager: documentationManager, + documentManager: documentManager, + catalogURL: catalogURL, + for: symbolUSR, + fetchSymbolGraph: { location in + try await sourceKitLSPServer.primaryLanguageService( + for: location.documentUri, + snapshot.language, + in: workspace + ) + .symbolGraph(forOnDiskContentsAt: location, in: workspace, manager: onDiskDocumentManager) + } - let languageService = try await sourceKitLSPServer.primaryLanguageService( - for: location.documentUri, - .swift, - in: symbolWorkspace - ) - return try await languageService.symbolGraph(forOnDiskContentsOf: location.documentUri, at: location) - } - ) + ) + } } return try await documentationManager.renderDocCDocumentation( symbolUSR: symbolUSR, @@ -204,9 +194,10 @@ extension DocumentationLanguageService { ) } - private func findMarkupExtensionFile( + private static func findMarkupExtensionFile( workspace: Workspace, documentationManager: DocCDocumentationManager, + documentManager: DocumentManager, catalogURL: URL?, for symbolUSR: String, fetchSymbolGraph: @Sendable (SymbolLocation) async throws -> String? @@ -215,16 +206,17 @@ extension DocumentationLanguageService { return nil } let catalogIndex = try await documentationManager.catalogIndex(for: catalogURL) - guard let index = workspace.index(checkedFor: .deletedFiles), - let symbolInformation = try await index.doccSymbolInformation( - ofUSR: symbolUSR, - fetchSymbolGraph: fetchSymbolGraph - ), - let markupExtensionFileURL = catalogIndex.documentationExtension(for: symbolInformation) - else { + guard let index = workspace.index(checkedFor: .deletedFiles) else { + return nil + } + let symbolInformation = try await index.doccSymbolInformation( + ofUSR: symbolUSR, + fetchSymbolGraph: fetchSymbolGraph + ) + guard let markupExtensionFileURL = catalogIndex.documentationExtension(for: symbolInformation) else { return nil } - return try? documentManager.latestSnapshotOrDisk( + return documentManager.latestSnapshotOrDisk( DocumentURI(markupExtensionFileURL), language: .markdown )?.text diff --git a/Sources/DocumentationLanguageService/IndexStoreDB+Extensions.swift b/Sources/DocumentationLanguageService/IndexStoreDB+Extensions.swift index bc817448a..fe5922ddd 100644 --- a/Sources/DocumentationLanguageService/IndexStoreDB+Extensions.swift +++ b/Sources/DocumentationLanguageService/IndexStoreDB+Extensions.swift @@ -40,7 +40,7 @@ extension CheckedIndex { var result: [SymbolOccurrence] = [] for occurrence in topLevelSymbolOccurrences { let info = try await doccSymbolInformation(ofUSR: occurrence.symbol.usr, fetchSymbolGraph: fetchSymbolGraph) - if let info, info.matches(symbolLink) { + if info.matches(symbolLink) { result.append(occurrence) } } @@ -60,9 +60,9 @@ extension CheckedIndex { func doccSymbolInformation( ofUSR usr: String, fetchSymbolGraph: (SymbolLocation) async throws -> String? - ) async throws -> DocCSymbolInformation? { + ) async throws -> DocCSymbolInformation { guard let topLevelSymbolOccurrence = primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else { - return nil + throw DocCCheckedIndexError.emptyDocCSymbolLink } let moduleName = topLevelSymbolOccurrence.location.moduleName var symbols = [topLevelSymbolOccurrence] diff --git a/Sources/SourceKitLSP/CMakeLists.txt b/Sources/SourceKitLSP/CMakeLists.txt index 1536bd6c5..7b9677fff 100644 --- a/Sources/SourceKitLSP/CMakeLists.txt +++ b/Sources/SourceKitLSP/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(SourceKitLSP STATIC LogMessageNotification+representingStructureUsingEmojiPrefixIfNecessary.swift MacroExpansionReferenceDocumentURLData.swift MessageHandlingDependencyTracker.swift + OnDiskDocumentManager.swift ReferenceDocumentURL.swift Rename.swift SemanticTokensLegend+SourceKitLSPLegend.swift diff --git a/Sources/SourceKitLSP/LanguageService.swift b/Sources/SourceKitLSP/LanguageService.swift index ddadbebd2..a8c27e43a 100644 --- a/Sources/SourceKitLSP/LanguageService.swift +++ b/Sources/SourceKitLSP/LanguageService.swift @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +package import BuildServerIntegration import Foundation package import IndexStoreDB package import LanguageServerProtocol @@ -148,6 +149,18 @@ package protocol LanguageService: AnyObject, Sendable { /// Sent to close a document on the Language Server. func closeDocument(_ notification: DidCloseTextDocumentNotification) async + /// Sent to open up a document on the Language Server whose contents are on-disk. + /// + /// The snapshot will have a synthesized name and the caller is responsible for synthesizing build settings for it. + /// + /// - Important: This should only be called by `OnDiskDocumentManager`. + func openOnDiskDocument(snapshot: DocumentSnapshot, buildSettings: FileBuildSettings) async throws + + /// Sent to close a document that was opened by `openOnDiskDocument`. + /// + /// - Important: This should only be called by `OnDiskDocumentManager`. + func closeOnDiskDocument(uri: DocumentURI) async throws + /// Re-open the given document, discarding any in-memory state and forcing an AST to be re-built after build settings /// have been changed. This needs to be handled via a notification to ensure that no other request for this document /// is executing at the same time. @@ -197,8 +210,9 @@ package protocol LanguageService: AnyObject, Sendable { /// Return the symbol graph at the given location for the contents of the document as they are on-disk (opposed to the /// in-memory modified version of the document). func symbolGraph( - forOnDiskContentsOf symbolDocumentUri: DocumentURI, - at location: SymbolLocation + forOnDiskContentsAt location: SymbolLocation, + in workspace: Workspace, + manager: OnDiskDocumentManager ) async throws -> String /// Request a generated interface of a module to display in the IDE. @@ -330,6 +344,14 @@ package extension LanguageService { func clientInitialized(_ initialized: LanguageServerProtocol.InitializedNotification) async {} + func openOnDiskDocument(snapshot: DocumentSnapshot, buildSettings: FileBuildSettings) async throws { + throw ResponseError.unknown("\(#function) not implemented in \(Self.self)") + } + + func closeOnDiskDocument(uri: DocumentURI) async throws { + throw ResponseError.unknown("\(#function) not implemented in \(Self.self)") + } + func willSaveDocument(_ notification: WillSaveTextDocumentNotification) async {} func didSaveDocument(_ notification: DidSaveTextDocumentNotification) async {} @@ -368,8 +390,9 @@ package extension LanguageService { } func symbolGraph( - forOnDiskContentsOf symbolDocumentUri: DocumentURI, - at location: SymbolLocation + forOnDiskContentsAt location: SymbolLocation, + in workspace: Workspace, + manager: OnDiskDocumentManager ) async throws -> String { throw ResponseError.internalError("\(#function) not implemented in \(Self.self)") } diff --git a/Sources/SourceKitLSP/OnDiskDocumentManager.swift b/Sources/SourceKitLSP/OnDiskDocumentManager.swift new file mode 100644 index 000000000..f66610e05 --- /dev/null +++ b/Sources/SourceKitLSP/OnDiskDocumentManager.swift @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 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 +// +//===----------------------------------------------------------------------===// + +package import BuildServerIntegration +import Foundation +package import LanguageServerProtocol +import SKLogging +import SKUtilities +import SwiftExtensions + +package actor OnDiskDocumentManager { + private let sourceKitLSPServer: SourceKitLSPServer + private var openSnapshots: + [DocumentURI: (snapshot: DocumentSnapshot, buildSettings: FileBuildSettings, workspace: Workspace)] + + fileprivate init(sourceKitLSPServer: SourceKitLSPServer) { + self.sourceKitLSPServer = sourceKitLSPServer + openSnapshots = [:] + } + + /// Opens a dummy ``DocumentSnapshot`` with contents from disk for a given ``DocumentURI`` and ``Language``. + /// + /// The snapshot will remain cached until ``closeAllDocuments()`` is called. + package func open( + uri: DocumentURI, + language: Language, + in workspace: Workspace + ) async throws -> (snapshot: DocumentSnapshot, buildSettings: FileBuildSettings) { + guard let fileURL = uri.fileURL else { + throw ResponseError.unknown("Cannot create snapshot with on-disk contents for non-file URI \(uri.forLogging)") + } + + if let cachedSnapshot = openSnapshots[uri] { + return (cachedSnapshot.snapshot, cachedSnapshot.buildSettings) + } + + let snapshot = DocumentSnapshot( + uri: try DocumentURI(filePath: "\(UUID().uuidString)/\(fileURL.filePath)", isDirectory: false), + language: language, + version: 0, + lineTable: LineTable(try String(contentsOf: fileURL, encoding: .utf8)) + ) + let languageService = try await sourceKitLSPServer.primaryLanguageService(for: uri, language, in: workspace) + + let originalBuildSettings = await workspace.buildServerManager.buildSettingsInferredFromMainFile( + for: uri, + language: language, + fallbackAfterTimeout: false + ) + guard let originalBuildSettings else { + throw ResponseError.unknown("Failed to infer build settings for \(uri)") + } + let patchedBuildSettings = originalBuildSettings.patching(newFile: snapshot.uri, originalFile: uri) + try await languageService.openOnDiskDocument(snapshot: snapshot, buildSettings: patchedBuildSettings) + openSnapshots[uri] = (snapshot, patchedBuildSettings, workspace) + return (snapshot, patchedBuildSettings) + } + + /// Close all of the ``DocumentSnapshot``s that were opened by this ``OnDiskDocumentManager``. + fileprivate func closeAllDocuments() async { + for (snapshot, _, workspace) in openSnapshots.values { + await orLog("Closing snapshot from on-disk contents: \(snapshot.uri.forLogging)") { + let languageService = + try await sourceKitLSPServer.primaryLanguageService(for: snapshot.uri, snapshot.language, in: workspace) + try await languageService.closeOnDiskDocument(uri: snapshot.uri) + } + } + openSnapshots = [:] + } +} + +package extension SourceKitLSPServer { + nonisolated func withOnDiskDocumentManager( + _ body: (OnDiskDocumentManager) async throws -> T + ) async rethrows -> T { + let manager = OnDiskDocumentManager(sourceKitLSPServer: self) + do { + let result = try await body(manager) + await manager.closeAllDocuments() + return result + } catch { + await manager.closeAllDocuments() + throw error + } + } +} diff --git a/Sources/SwiftLanguageService/CMakeLists.txt b/Sources/SwiftLanguageService/CMakeLists.txt index c08eaad6d..acf5d37f6 100644 --- a/Sources/SwiftLanguageService/CMakeLists.txt +++ b/Sources/SwiftLanguageService/CMakeLists.txt @@ -21,7 +21,6 @@ add_library(SwiftLanguageService STATIC SemanticRefactorCommand.swift DocumentFormatting.swift DiagnosticReportManager.swift - WithSnapshotFromDiskOpenedInSourcekitd.swift SemanticTokens.swift ExpandMacroCommand.swift Diagnostic.swift diff --git a/Sources/SwiftLanguageService/SwiftLanguageService.swift b/Sources/SwiftLanguageService/SwiftLanguageService.swift index 6ce44fdf8..759102a4e 100644 --- a/Sources/SwiftLanguageService/SwiftLanguageService.swift +++ b/Sources/SwiftLanguageService/SwiftLanguageService.swift @@ -294,29 +294,24 @@ package actor SwiftLanguageService: LanguageService, Sendable { } } - func buildSettings(for document: DocumentURI, fallbackAfterTimeout: Bool) async -> FileBuildSettings? { - let buildSettingsFile = document.buildSettingsFile - + func compileCommand(for document: DocumentURI, fallbackAfterTimeout: Bool) async -> SwiftCompileCommand? { guard let sourceKitLSPServer else { logger.fault("Cannot retrieve build settings because SourceKitLSPServer is no longer alive") return nil } - guard let workspace = await sourceKitLSPServer.workspaceForDocument(uri: buildSettingsFile) else { + guard let workspace = await sourceKitLSPServer.workspaceForDocument(uri: document.buildSettingsFile) else { return nil } - return await workspace.buildServerManager.buildSettingsInferredFromMainFile( - for: buildSettingsFile, + let settings = await workspace.buildServerManager.buildSettingsInferredFromMainFile( + for: document.buildSettingsFile, language: .swift, fallbackAfterTimeout: fallbackAfterTimeout ) - } - func compileCommand(for document: DocumentURI, fallbackAfterTimeout: Bool) async -> SwiftCompileCommand? { - if let settings = await self.buildSettings(for: document, fallbackAfterTimeout: fallbackAfterTimeout) { - return SwiftCompileCommand(settings) - } else { + guard let settings else { return nil } + return SwiftCompileCommand(settings) } func send( @@ -586,6 +581,22 @@ extension SwiftLanguageService { } } + package func openOnDiskDocument(snapshot: DocumentSnapshot, buildSettings: FileBuildSettings) async throws { + _ = try await send( + sourcekitdRequest: \.editorOpen, + self.openDocumentSourcekitdRequest(snapshot: snapshot, compileCommand: SwiftCompileCommand(buildSettings)), + snapshot: snapshot + ) + } + + package func closeOnDiskDocument(uri: DocumentURI) async throws { + _ = try await send( + sourcekitdRequest: \.editorClose, + self.closeDocumentSourcekitdRequest(uri: uri), + snapshot: nil + ) + } + /// Cancels any in-flight tasks to send a `PublishedDiagnosticsNotification` after edits. private func cancelInFlightPublishDiagnosticsTask(for document: DocumentURI) { if let inFlightTask = inFlightPublishDiagnosticsTasks[document] { diff --git a/Sources/SwiftLanguageService/SymbolGraph.swift b/Sources/SwiftLanguageService/SymbolGraph.swift index ad922f349..7becaed9a 100644 --- a/Sources/SwiftLanguageService/SymbolGraph.swift +++ b/Sources/SwiftLanguageService/SymbolGraph.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors +// Copyright (c) 2025 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 @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +import BuildServerIntegration import Foundation package import IndexStoreDB package import LanguageServerProtocol @@ -19,24 +20,22 @@ import SwiftSyntax extension SwiftLanguageService { package func symbolGraph( - forOnDiskContentsOf symbolDocumentUri: DocumentURI, - at location: SymbolLocation + forOnDiskContentsAt location: SymbolLocation, + in workspace: Workspace, + manager: OnDiskDocumentManager ) async throws -> String { - return try await withSnapshotFromDiskOpenedInSourcekitd( - uri: symbolDocumentUri, - fallbackSettingsAfterTimeout: false - ) { snapshot, compileCommand in - let symbolGraph = try await cursorInfo( - snapshot, - compileCommand: compileCommand, - Range(snapshot.position(of: location)), - includeSymbolGraph: true - ).symbolGraph - guard let symbolGraph else { - throw ResponseError.internalError("Unable to retrieve symbol graph") - } - return symbolGraph + let (snapshot, buildSettings) = try await manager.open(uri: location.documentUri, language: .swift, in: workspace) + + let symbolGraph = try await cursorInfo( + snapshot, + compileCommand: SwiftCompileCommand(buildSettings), + Range(snapshot.position(of: location)), + includeSymbolGraph: true + ).symbolGraph + guard let symbolGraph else { + throw ResponseError.internalError("Unable to retrieve symbol graph") } + return symbolGraph } package func symbolGraph( diff --git a/Sources/SwiftLanguageService/WithSnapshotFromDiskOpenedInSourcekitd.swift b/Sources/SwiftLanguageService/WithSnapshotFromDiskOpenedInSourcekitd.swift deleted file mode 100644 index 2a38725c5..000000000 --- a/Sources/SwiftLanguageService/WithSnapshotFromDiskOpenedInSourcekitd.swift +++ /dev/null @@ -1,70 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2018 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 BuildServerIntegration -import Foundation -import LanguageServerProtocol -import SKLogging -import SKUtilities -import SourceKitLSP -import SwiftExtensions - -extension SwiftLanguageService { - /// Open a unique dummy document in sourcekitd that has the contents of the file on disk for `uri` but an arbitrary - /// URI which doesn't exist on disk. Invoke `body` with a snapshot that contains the on-disk document contents and has - /// that dummy URI as well as build settings that were inferred from `uri` but have that URI replaced with the dummy - /// URI. Close the document in sourcekit after `body` has finished. - func withSnapshotFromDiskOpenedInSourcekitd( - uri: DocumentURI, - fallbackSettingsAfterTimeout: Bool, - body: (_ snapshot: DocumentSnapshot, _ patchedCompileCommand: SwiftCompileCommand?) async throws -> Result - ) async throws -> Result { - guard let fileURL = uri.fileURL else { - throw ResponseError.unknown("Cannot create snapshot with on-disk contents for non-file URI \(uri.forLogging)") - } - let snapshot = DocumentSnapshot( - uri: try DocumentURI(filePath: "\(UUID().uuidString)/\(fileURL.filePath)", isDirectory: false), - language: .swift, - version: 0, - lineTable: LineTable(try String(contentsOf: fileURL, encoding: .utf8)) - ) - let patchedCompileCommand: SwiftCompileCommand? = - if let buildSettings = await self.buildSettings( - for: uri, - fallbackAfterTimeout: fallbackSettingsAfterTimeout - ) { - SwiftCompileCommand(buildSettings.patching(newFile: snapshot.uri, originalFile: uri)) - } else { - nil - } - - _ = try await send( - sourcekitdRequest: \.editorOpen, - self.openDocumentSourcekitdRequest(snapshot: snapshot, compileCommand: patchedCompileCommand), - snapshot: snapshot - ) - let result: Swift.Result - do { - result = .success(try await body(snapshot, patchedCompileCommand)) - } catch { - result = .failure(error) - } - await orLog("Close helper document '\(snapshot.uri)' for cursorInfoFromDisk") { - _ = try await send( - sourcekitdRequest: \.editorClose, - self.closeDocumentSourcekitdRequest(uri: snapshot.uri), - snapshot: snapshot - ) - } - return try result.get() - } -}