diff --git a/Sources/LanguageServerProtocol/Requests/CodeActionRequest.swift b/Sources/LanguageServerProtocol/Requests/CodeActionRequest.swift index 205456f0..4a777f62 100644 --- a/Sources/LanguageServerProtocol/Requests/CodeActionRequest.swift +++ b/Sources/LanguageServerProtocol/Requests/CodeActionRequest.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// public typealias CodeActionProviderCompletion = (LSPResult<[CodeAction]>) -> Void -public typealias CodeActionProvider = (CodeActionRequest, @escaping CodeActionProviderCompletion) async -> Void +public typealias CodeActionProvider = (CodeActionRequest, @escaping CodeActionProviderCompletion) -> Void /// Request for returning all possible code actions for a given text document and range. /// diff --git a/Sources/SKCore/BuildServerBuildSystem.swift b/Sources/SKCore/BuildServerBuildSystem.swift index b9dd23c1..7bbef467 100644 --- a/Sources/SKCore/BuildServerBuildSystem.swift +++ b/Sources/SKCore/BuildServerBuildSystem.swift @@ -65,9 +65,6 @@ public final class BuildServerBuildSystem: MessageHandler { /// Delegate to handle any build system events. public weak var delegate: BuildSystemDelegate? - /// The build settings that have been received from the build server. - private var buildSettings: [DocumentURI: FileBuildSettings] = [:] - public init(projectRoot: AbsolutePath, buildFolder: AbsolutePath?, fileSystem: FileSystem = localFileSystem) throws { let configPath = projectRoot.appending(component: "buildServer.json") let config = try loadBuildServerConfig(path: configPath, fileSystem: fileSystem) @@ -198,18 +195,7 @@ public final class BuildServerBuildSystem: MessageHandler { let result = notification.params.updatedOptions let settings = FileBuildSettings( compilerArguments: result.options, workingDirectory: result.workingDirectory) - self.buildSettingsChanged(for: notification.params.uri, settings: settings) - } - - /// Record the new build settings for the given document and inform the delegate - /// about the changed build settings. - private func buildSettingsChanged(for document: DocumentURI, settings: FileBuildSettings?) { - buildSettings[document] = settings - if let settings { - self.delegate?.fileBuildSettingsChanged([document: .modified(settings)]) - } else { - self.delegate?.fileBuildSettingsChanged([document: .removedOrUnavailable]) - } + self.delegate?.fileBuildSettingsChanged([notification.params.uri: .modified(settings)]) } } @@ -222,14 +208,19 @@ private func readReponseDataKey(data: LSPAny?, key: String) -> String? { return nil } -extension BuildServerBuildSystem: BuildSystem { - /// The build settings for the given file. - /// - /// Returns `nil` if no build settings have been received from the build - /// server yet or if no build settings are available for this file. - public func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? { - return buildSettings[document] +extension BuildServerBuildSystem { + /// Exposed for *testing*. + public func _settings(for uri: DocumentURI) -> FileBuildSettings? { + if let response = try? self.buildServer?.sendSync(SourceKitOptions(uri: uri)) { + return FileBuildSettings( + compilerArguments: response.options, + workingDirectory: response.workingDirectory) + } + return nil } +} + +extension BuildServerBuildSystem: BuildSystem { public func registerForChangeNotifications(for uri: DocumentURI, language: Language) { let request = RegisterForChanges(uri: uri, action: .register) @@ -239,7 +230,7 @@ extension BuildServerBuildSystem: BuildSystem { // BuildServer registration failed, so tell our delegate that no build // settings are available. - self.buildSettingsChanged(for: uri, settings: nil) + self.delegate?.fileBuildSettingsChanged([uri: .removedOrUnavailable]) } }) } diff --git a/Sources/SKCore/BuildSystem.swift b/Sources/SKCore/BuildSystem.swift index 05d76572..dee92d51 100644 --- a/Sources/SKCore/BuildSystem.swift +++ b/Sources/SKCore/BuildSystem.swift @@ -51,13 +51,6 @@ public protocol BuildSystem: AnyObject { /// initial reports as well as changes. var delegate: BuildSystemDelegate? { get set } - /// Retrieve build settings for the given document with the given source - /// language. - /// - /// Returns `nil` if the build system can't provide build settings for this - /// file or if it hasn't computed build settings for the file yet. - func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? - /// Register the given file for build-system level change notifications, such /// as command line flag changes, dependency changes, etc. /// diff --git a/Sources/SKCore/BuildSystemManager.swift b/Sources/SKCore/BuildSystemManager.swift index 4c29920c..1e811f6d 100644 --- a/Sources/SKCore/BuildSystemManager.swift +++ b/Sources/SKCore/BuildSystemManager.swift @@ -146,41 +146,6 @@ extension BuildSystemManager { set { queue.sync { _mainFilesProvider = newValue } } } - /// Get the build settings for the given document, assuming it has the given - /// language. - /// - /// Returns `nil` if no build settings are availabe in the build system and - /// no fallback build settings can be computed. - /// - /// `isFallback` is `true` if the build settings couldn't be computed and - /// fallback settings are used. These fallback settings are most likely not - /// correct and provide limited semantic functionality. - public func buildSettings( - for document: DocumentURI, - language: Language - ) async -> (buildSettings: FileBuildSettings, isFallback: Bool)? { - do { - // FIXME: (async) We should only wait `fallbackSettingsTimeout` for build - // settings and return fallback afterwards. I am not sure yet, how best to - // implement that with Swift concurrency. - // For now, this should be fine because all build systems return - // very quickly from `settings(for:language:)`. - if let settings = try await buildSystem?.buildSettings(for: document, language: language) { - return (buildSettings: settings, isFallback: false) - } - } catch { - log("Getting build settings failed: \(error)") - } - if let settings = fallbackBuildSystem?.buildSettings(for: document, language: language) { - // If there is no build system and we onlyl have the fallback build system, - // we will never get real build settings. Consider the build settings - // non-fallback. - return (buildSettings: settings, isFallback: buildSystem != nil) - } else { - return nil - } - } - public func registerForChangeNotifications(for uri: DocumentURI, language: Language) { return queue.async { log("registerForChangeNotifications(\(uri.pseudoPath))") @@ -257,7 +222,7 @@ extension BuildSystemManager { } else if let fallback = self.fallbackBuildSystem { // Only have a fallback build system. We consider it be a primary build // system that functions synchronously. - if let settings = fallback.buildSettings(for: mainFile, language: language) { + if let settings = fallback.settings(for: mainFile, language) { newStatus = .primary(settings) } else { newStatus = .unsupported @@ -318,7 +283,7 @@ extension BuildSystemManager { guard let status = self.mainFileStatuses[mainFile], status == .waiting else { return } - if let settings = fallback.buildSettings(for: mainFile, language: language) { + if let settings = fallback.settings(for: mainFile, language) { self.updateAndNotifyStatuses(changes: [mainFile: .waitingUsingFallback(settings)]) } else { // Keep the status as waiting. @@ -373,7 +338,7 @@ extension BuildSystemManager: BuildSystemDelegate { // FIXME: we need to stop threading the language everywhere, or we need the build system // itself to pass it in here. Or alteratively cache the fallback settings/language earlier? let language = firstWatch.value.language - if let settings = fallback.buildSettings(for: mainFile, language: language) { + if let settings = fallback.settings(for: mainFile, language) { newStatus = .fallback(settings) } else { newStatus = .unsupported diff --git a/Sources/SKCore/CompilationDatabaseBuildSystem.swift b/Sources/SKCore/CompilationDatabaseBuildSystem.swift index 34f36f48..10735178 100644 --- a/Sources/SKCore/CompilationDatabaseBuildSystem.swift +++ b/Sources/SKCore/CompilationDatabaseBuildSystem.swift @@ -93,15 +93,6 @@ extension CompilationDatabaseBuildSystem: BuildSystem { public var indexPrefixMappings: [PathPrefixMapping] { return [] } - public func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? { - // FIXME: (async) Convert this to an async function once `CompilationDatabaseBuildSystem` is an actor. - return await withCheckedContinuation { continuation in - self.queue.async { - continuation.resume(returning: self.settings(for: document)) - } - } - } - public func registerForChangeNotifications(for uri: DocumentURI, language: Language) { queue.async { self.watchedFiles[uri] = language diff --git a/Sources/SKCore/FallbackBuildSystem.swift b/Sources/SKCore/FallbackBuildSystem.swift index 67c81423..30183e04 100644 --- a/Sources/SKCore/FallbackBuildSystem.swift +++ b/Sources/SKCore/FallbackBuildSystem.swift @@ -44,7 +44,7 @@ public final class FallbackBuildSystem: BuildSystem { public var indexPrefixMappings: [PathPrefixMapping] { return [] } - public func buildSettings(for uri: DocumentURI, language: Language) -> FileBuildSettings? { + public func settings(for uri: DocumentURI, _ language: Language) -> FileBuildSettings? { switch language { case .swift: return settingsSwift(uri.pseudoPath) @@ -58,7 +58,7 @@ public final class FallbackBuildSystem: BuildSystem { public func registerForChangeNotifications(for uri: DocumentURI, language: Language) { guard let delegate = self.delegate else { return } - let settings = self.buildSettings(for: uri, language: language) + let settings = self.settings(for: uri, language) DispatchQueue.global().async { delegate.fileBuildSettingsChanged([uri: FileBuildSettingsChange(settings)]) } diff --git a/Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift b/Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift index 940d3e28..2ca18dc5 100644 --- a/Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift +++ b/Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift @@ -290,18 +290,6 @@ extension SwiftPMWorkspace: SKCore.BuildSystem { } } - public func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? { - return try await withCheckedThrowingContinuation { continuation in - queue.async { - do { - continuation.resume(returning: try self.settings(for: document, language)) - } catch { - continuation.resume(throwing: error) - } - } - } - } - /// Must only be called on `queue`. private func settings( for uri: DocumentURI, diff --git a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift index 953a5d18..01b17dd8 100644 --- a/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift +++ b/Sources/SourceKitLSP/Clang/ClangLanguageServer.swift @@ -118,8 +118,7 @@ final class ClangLanguageServerShim: ToolchainLanguageServer, MessageHandler { toolchain: Toolchain, options: SourceKitServer.Options, workspace: Workspace, - reopenDocuments: @escaping (ToolchainLanguageServer) -> Void, - workspaceForDocument: @escaping (DocumentURI) async -> Workspace? + reopenDocuments: @escaping (ToolchainLanguageServer) -> Void ) throws { guard let clangdPath = toolchain.clangd else { return nil diff --git a/Sources/SourceKitLSP/SourceKitServer.swift b/Sources/SourceKitLSP/SourceKitServer.swift index f32d4532..3b1826c9 100644 --- a/Sources/SourceKitLSP/SourceKitServer.swift +++ b/Sources/SourceKitLSP/SourceKitServer.swift @@ -414,10 +414,6 @@ public actor SourceKitServer { Task { await self.reopenDocuments(for: toolchainLanguageServer) } - }, - workspaceForDocument: { [weak self] document in - guard let self else { return nil } - return await self.workspaceForDocument(uri: document) } ) @@ -2012,8 +2008,7 @@ func languageService( options: SourceKitServer.Options, client: MessageHandler, in workspace: Workspace, - reopenDocuments: @escaping (ToolchainLanguageServer) -> Void, - workspaceForDocument: @escaping (DocumentURI) async -> Workspace? + reopenDocuments: @escaping (ToolchainLanguageServer) -> Void ) throws -> ToolchainLanguageServer? { let connectionToClient = LocalConnection() @@ -2022,8 +2017,7 @@ func languageService( toolchain: toolchain, options: options, workspace: workspace, - reopenDocuments: reopenDocuments, - workspaceForDocument: workspaceForDocument + reopenDocuments: reopenDocuments ) connectionToClient.start(handler: client) return server diff --git a/Sources/SourceKitLSP/Swift/CodeCompletion.swift b/Sources/SourceKitLSP/Swift/CodeCompletion.swift index bdce13c5..b95e01ec 100644 --- a/Sources/SourceKitLSP/Swift/CodeCompletion.swift +++ b/Sources/SourceKitLSP/Swift/CodeCompletion.swift @@ -17,7 +17,7 @@ import Foundation extension SwiftLanguageServer { - public func completion(_ req: Request) async { + public func completion(_ req: Request) { guard let snapshot = documentManager.latestSnapshot(req.params.textDocument.uri) else { log("failed to find snapshot for url \(req.params.textDocument.uri)") req.reply(CompletionList(isIncomplete: true, items: [])) @@ -39,13 +39,14 @@ extension SwiftLanguageServer { let options = req.params.sourcekitlspOptions ?? serverOptions.completionOptions if options.serverSideFiltering { - await _completionWithServerFiltering(offset: offset, completionPos: completionPos, snapshot: snapshot, request: req, options: options) + _completionWithServerFiltering(offset: offset, completionPos: completionPos, snapshot: snapshot, request: req, options: options) } else { - await _completionWithClientFiltering(offset: offset, completionPos: completionPos, snapshot: snapshot, request: req, options: options) + _completionWithClientFiltering(offset: offset, completionPos: completionPos, snapshot: snapshot, request: req, options: options) } } - func _completionWithServerFiltering(offset: Int, completionPos: Position, snapshot: DocumentSnapshot, request req: Request, options: SKCompletionOptions) async { + /// Must be called on `queue`. + func _completionWithServerFiltering(offset: Int, completionPos: Position, snapshot: DocumentSnapshot, request req: Request, options: SKCompletionOptions) { guard let start = snapshot.indexOf(utf8Offset: offset), let end = snapshot.index(of: req.params.position) else { log("invalid completion position \(req.params.position)") @@ -73,7 +74,7 @@ extension SwiftLanguageServer { snapshot: snapshot, utf8Offset: offset, position: completionPos, - compileCommand: await buildSettings(for: snapshot.document.uri)) + compileCommand: commandsByFile[snapshot.document.uri]) currentCompletionSession?.close() currentCompletionSession = session @@ -82,7 +83,8 @@ extension SwiftLanguageServer { session.update(filterText: filterText, position: req.params.position, in: snapshot, options: options, completion: req.reply) } - func _completionWithClientFiltering(offset: Int, completionPos: Position, snapshot: DocumentSnapshot, request req: Request, options: SKCompletionOptions) async { + /// Must be called on `queue`. + func _completionWithClientFiltering(offset: Int, completionPos: Position, snapshot: DocumentSnapshot, request req: Request, options: SKCompletionOptions) { let skreq = SKDRequestDictionary(sourcekitd: sourcekitd) skreq[keys.request] = requests.codecomplete skreq[keys.offset] = offset @@ -94,7 +96,7 @@ extension SwiftLanguageServer { skreq[keys.codecomplete_options] = skreqOptions // FIXME: SourceKit should probably cache this for us. - if let compileCommand = await buildSettings(for: snapshot.document.uri) { + if let compileCommand = commandsByFile[snapshot.document.uri] { skreq[keys.compilerargs] = compileCommand.compilerArgs } diff --git a/Sources/SourceKitLSP/Swift/CursorInfo.swift b/Sources/SourceKitLSP/Swift/CursorInfo.swift index 7701896e..69472f64 100644 --- a/Sources/SourceKitLSP/Swift/CursorInfo.swift +++ b/Sources/SourceKitLSP/Swift/CursorInfo.swift @@ -84,7 +84,7 @@ extension SwiftLanguageServer { _ uri: DocumentURI, _ range: Range, additionalParameters appendAdditionalParameters: ((SKDRequestDictionary) -> Void)? = nil, - _ completion: @escaping (Swift.Result) -> Void) async + _ completion: @escaping (Swift.Result) -> Void) { guard let snapshot = documentManager.latestSnapshot(uri) else { return completion(.failure(.unknownDocument(uri))) @@ -105,7 +105,7 @@ extension SwiftLanguageServer { skreq[keys.sourcefile] = snapshot.document.uri.pseudoPath // FIXME: SourceKit should probably cache this for us. - if let compileCommand = await self.buildSettings(for: uri) { + if let compileCommand = self.commandsByFile[uri] { skreq[keys.compilerargs] = compileCommand.compilerArgs } diff --git a/Sources/SourceKitLSP/Swift/ExpressionTypeInfo.swift b/Sources/SourceKitLSP/Swift/ExpressionTypeInfo.swift index 68aac791..bc1ade78 100644 --- a/Sources/SourceKitLSP/Swift/ExpressionTypeInfo.swift +++ b/Sources/SourceKitLSP/Swift/ExpressionTypeInfo.swift @@ -57,7 +57,7 @@ extension SwiftLanguageServer { func expressionTypeInfos( _ uri: DocumentURI, _ completion: @escaping (Swift.Result<[ExpressionTypeInfo], ExpressionTypeInfoError>) -> Void - ) async { + ) { guard let snapshot = documentManager.latestSnapshot(uri) else { return completion(.failure(.unknownDocument(uri))) } @@ -69,7 +69,7 @@ extension SwiftLanguageServer { skreq[keys.sourcefile] = snapshot.document.uri.pseudoPath // FIXME: SourceKit should probably cache this for us. - if let compileCommand = await self.buildSettings(for: uri) { + if let compileCommand = self.commandsByFile[uri] { skreq[keys.compilerargs] = compileCommand.compilerArgs } diff --git a/Sources/SourceKitLSP/Swift/OpenInterface.swift b/Sources/SourceKitLSP/Swift/OpenInterface.swift index 0d36cf94..d488ee93 100644 --- a/Sources/SourceKitLSP/Swift/OpenInterface.swift +++ b/Sources/SourceKitLSP/Swift/OpenInterface.swift @@ -25,7 +25,7 @@ struct FindUSRInfo { } extension SwiftLanguageServer { - public func openInterface(_ request: LanguageServerProtocol.Request) async { + public func openInterface(_ request: LanguageServerProtocol.Request) { let uri = request.params.textDocument.uri let moduleName = request.params.moduleName let name = request.params.name @@ -37,7 +37,7 @@ extension SwiftLanguageServer { self._findUSRAndRespond(request: request, uri: interfaceDocURI, snapshot: snapshot, symbol: symbol) } else { // generate interface - await self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in + self._openInterface(request: request, uri: uri, name: moduleName, interfaceURI: interfaceDocURI) { result in switch result { case .success(let interfaceInfo): do { @@ -69,7 +69,7 @@ extension SwiftLanguageServer { uri: DocumentURI, name: String, interfaceURI: DocumentURI, - completion: @escaping (Swift.Result) -> Void) async { + completion: @escaping (Swift.Result) -> Void) { let keys = self.keys let skreq = SKDRequestDictionary(sourcekitd: sourcekitd) skreq[keys.request] = requests.editor_open_interface @@ -79,7 +79,7 @@ extension SwiftLanguageServer { } skreq[keys.name] = interfaceURI.pseudoPath skreq[keys.synthesizedextensions] = 1 - if let compileCommand = await self.buildSettings(for: uri) { + if let compileCommand = self.commandsByFile[uri] { skreq[keys.compilerargs] = compileCommand.compilerArgs } diff --git a/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift b/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift index 700cc720..167b53de 100644 --- a/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift +++ b/Sources/SourceKitLSP/Swift/SemanticRefactoring.swift @@ -126,7 +126,7 @@ extension SwiftLanguageServer { /// - completion: Completion block to asynchronously receive the SemanticRefactoring data, or error. func semanticRefactoring( _ refactorCommand: SemanticRefactorCommand, - _ completion: @escaping (Result) -> Void) async + _ completion: @escaping (Result) -> Void) { let keys = self.keys @@ -156,7 +156,7 @@ extension SwiftLanguageServer { skreq[keys.actionuid] = self.sourcekitd.api.uid_get_from_cstr(refactorCommand.actionString)! // FIXME: SourceKit should probably cache this for us. - if let compileCommand = await self.buildSettings(for: snapshot.document.uri) { + if let compileCommand = self.commandsByFile[snapshot.document.uri] { skreq[keys.compilerargs] = compileCommand.compilerArgs } diff --git a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift index 5a5a7f34..c17489ad 100644 --- a/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift +++ b/Sources/SourceKitLSP/Swift/SwiftLanguageServer.swift @@ -124,6 +124,8 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { var currentCompletionSession: CodeCompletionSession? = nil + var commandsByFile: [DocumentURI: SwiftCompileCommand] = [:] + /// *For Testing* public var reusedNodeCallback: ReusedNodeCallback? @@ -152,12 +154,6 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { /// A callback with which `SwiftLanguageServer` can request its owner to reopen all documents in case it has crashed. private let reopenDocuments: (ToolchainLanguageServer) -> Void - /// Get the workspace that the document with the given URI belongs to. - /// - /// This is used to find the `BuildSystemManager` that is able to deliver - /// build settings for this document. - private let workspaceForDocument: (DocumentURI) async -> Workspace? - /// Creates a language server for the given client using the sourcekitd dylib specified in `toolchain`. /// `reopenDocuments` is a closure that will be called if sourcekitd crashes and the `SwiftLanguageServer` asks its parent server to reopen all of its documents. /// Returns `nil` if `sourcektid` couldn't be found. @@ -166,8 +162,7 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { toolchain: Toolchain, options: SourceKitServer.Options, workspace: Workspace, - reopenDocuments: @escaping (ToolchainLanguageServer) -> Void, - workspaceForDocument: @escaping (DocumentURI) async -> Workspace? + reopenDocuments: @escaping (ToolchainLanguageServer) -> Void ) throws { guard let sourcekitd = toolchain.sourcekitd else { return nil } self.client = client @@ -177,22 +172,10 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { self.documentManager = DocumentManager() self.state = .connected self.reopenDocuments = reopenDocuments - self.workspaceForDocument = workspaceForDocument self.generatedInterfacesPath = options.generatedInterfacesPath.asURL try FileManager.default.createDirectory(at: generatedInterfacesPath, withIntermediateDirectories: true) } - func buildSettings(for document: DocumentURI) async -> SwiftCompileCommand? { - guard let workspace = await self.workspaceForDocument(document) else { - return nil - } - if let settings = await workspace.buildSystemManager.buildSettings(for: document, language: .swift) { - return SwiftCompileCommand(settings.buildSettings, isFallback: settings.isFallback) - } else { - return nil - } - } - public nonisolated func canHandle(workspace: Workspace) -> Bool { // We have a single sourcekitd instance for all workspaces. return true @@ -305,15 +288,10 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { /// Register the diagnostics returned from sourcekitd in `currentDiagnostics` /// and returns the corresponding LSP diagnostics. - /// - /// If `isFromFallbackBuildSettings` is `true`, then only parse diagnostics are - /// stored and any semantic diagnostics are ignored since they are probably - /// incorrect in the absence of build settings. private func registerDiagnostics( sourcekitdDiagnostics: SKDResponseArray?, snapshot: DocumentSnapshot, - stage: DiagnosticStage, - isFromFallbackBuildSettings: Bool + stage: DiagnosticStage ) -> [Diagnostic] { let supportsCodeDescription = capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport @@ -329,7 +307,7 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { old: currentDiagnostics[snapshot.document.uri] ?? [], new: newDiags, stage: stage, - isFallback: isFromFallbackBuildSettings + isFallback: self.commandsByFile[snapshot.document.uri]?.isFallback ?? true ) currentDiagnostics[snapshot.document.uri] = result @@ -356,8 +334,7 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { let diagnostics = registerDiagnostics( sourcekitdDiagnostics: response[keys.diagnostics], snapshot: snapshot, - stage: stage, - isFromFallbackBuildSettings: compileCommand?.isFallback ?? true + stage: stage ) client.send( @@ -369,11 +346,11 @@ public actor SwiftLanguageServer: ToolchainLanguageServer { ) } - func handleDocumentUpdate(uri: DocumentURI) async { + func handleDocumentUpdate(uri: DocumentURI) { guard let snapshot = documentManager.latestSnapshot(uri) else { return } - let compileCommand = await self.buildSettings(for: uri) + let compileCommand = self.commandsByFile[uri] // Make the magic 0,0 replacetext request to update diagnostics and semantic tokens. @@ -488,7 +465,16 @@ extension SwiftLanguageServer { self.updateSyntacticTokens(for: snapshot) } - public func documentUpdatedBuildSettings(_ uri: DocumentURI, change: FileBuildSettingsChange) async { + public func documentUpdatedBuildSettings(_ uri: DocumentURI, change: FileBuildSettingsChange) { + let compileCommand = SwiftCompileCommand(change: change) + // Confirm that the compile commands actually changed, otherwise we don't need to do anything. + // This includes when the compiler arguments are the same but the command is no longer + // considered to be fallback. + guard self.commandsByFile[uri] != compileCommand else { + return + } + self.commandsByFile[uri] = compileCommand + // We may not have a snapshot if this is called just before `openDocument`. guard let snapshot = self.documentManager.latestSnapshot(uri) else { return @@ -496,22 +482,22 @@ extension SwiftLanguageServer { // Close and re-open the document internally to inform sourcekitd to update the compile // command. At the moment there's no better way to do this. - self.reopenDocument(snapshot, await self.buildSettings(for: uri)) + self.reopenDocument(snapshot, compileCommand) } - public func documentDependenciesUpdated(_ uri: DocumentURI) async { + public func documentDependenciesUpdated(_ uri: DocumentURI) { guard let snapshot = self.documentManager.latestSnapshot(uri) else { return } // Forcefully reopen the document since the `BuildSystem` has informed us // that the dependencies have changed and the AST needs to be reloaded. - await self.reopenDocument(snapshot, self.buildSettings(for: uri)) + self.reopenDocument(snapshot, self.commandsByFile[uri]) } // MARK: - Text synchronization - public func openDocument(_ note: DidOpenTextDocumentNotification) async { + public func openDocument(_ note: DidOpenTextDocumentNotification) { let keys = self.keys guard let snapshot = self.documentManager.open(note) else { @@ -525,7 +511,7 @@ extension SwiftLanguageServer { req[keys.name] = note.textDocument.uri.pseudoPath req[keys.sourcetext] = snapshot.text - let compileCommand = await self.buildSettings(for: uri) + let compileCommand = self.commandsByFile[uri] if let compilerArgs = compileCommand?.compilerArgs { req[keys.compilerargs] = compilerArgs @@ -551,12 +537,13 @@ extension SwiftLanguageServer { req[keys.name] = uri.pseudoPath // Clear settings that should not be cached for closed documents. + self.commandsByFile[uri] = nil self.currentDiagnostics[uri] = nil _ = try? self.sourcekitd.sendSync(req) } - public func changeDocument(_ note: DidChangeTextDocumentNotification) async { + public func changeDocument(_ note: DidChangeTextDocumentNotification) { let keys = self.keys var edits: [IncrementalEdit] = [] @@ -602,7 +589,7 @@ extension SwiftLanguageServer { } if let dict = lastResponse, let snapshot = snapshot { - let compileCommand = await self.buildSettings(for: note.textDocument.uri) + let compileCommand = self.commandsByFile[note.textDocument.uri] self.publishDiagnostics(response: dict, for: snapshot, compileCommand: compileCommand) } } @@ -628,10 +615,10 @@ extension SwiftLanguageServer { return false } - public func hover(_ req: Request) async { + public func hover(_ req: Request) { let uri = req.params.textDocument.uri let position = req.params.position - await cursorInfo(uri, position..) async { + public func symbolInfo(_ req: Request) { let uri = req.params.textDocument.uri let position = req.params.position - await cursorInfo(uri, position..) async { + public func documentSymbolHighlight(_ req: Request) { let keys = self.keys guard let snapshot = self.documentManager.latestSnapshot(req.params.textDocument.uri) else { @@ -949,7 +936,7 @@ extension SwiftLanguageServer { skreq[keys.sourcefile] = snapshot.document.uri.pseudoPath // FIXME: SourceKit should probably cache this for us. - if let compileCommand = await self.buildSettings(for: snapshot.document.uri) { + if let compileCommand = self.commandsByFile[snapshot.document.uri] { skreq[keys.compilerargs] = compileCommand.compilerArgs } @@ -1241,14 +1228,12 @@ extension SwiftLanguageServer { // FIXME: (async) Migrate `CodeActionProvider` to be async so that we // don't need to do the `withCheckedContinuation` dance here. await withCheckedContinuation { continuation in - Task { - await provider(req.params) { - switch $0 { - case .success(let actions): - continuation.resume(returning: actions) - case .failure: - continuation.resume(returning: []) - } + provider(req.params) { + switch $0 { + case .success(let actions): + continuation.resume(returning: actions) + case .failure: + continuation.resume(returning: []) } } } @@ -1263,12 +1248,12 @@ extension SwiftLanguageServer { completion(.success(codeActions)) } - func retrieveRefactorCodeActions(_ params: CodeActionRequest, completion: @escaping CodeActionProviderCompletion) async { + func retrieveRefactorCodeActions(_ params: CodeActionRequest, completion: @escaping CodeActionProviderCompletion) { let additionalCursorInfoParameters: ((SKDRequestDictionary) -> Void) = { skreq in skreq[self.keys.retrieve_refactor_actions] = 1 } - await cursorInfo( + cursorInfo( params.textDocument.uri, params.range, additionalParameters: additionalCursorInfoParameters) @@ -1356,9 +1341,9 @@ extension SwiftLanguageServer { completion(.success(codeActions)) } - public func inlayHint(_ req: Request) async { + public func inlayHint(_ req: Request) { let uri = req.params.textDocument.uri - await variableTypeInfos(uri, req.params.range) { infosResult in + variableTypeInfos(uri, req.params.range) { infosResult in do { let infos = try infosResult.get() let hints = infos @@ -1393,7 +1378,7 @@ extension SwiftLanguageServer { public func documentDiagnostic( _ uri: DocumentURI, _ completion: @escaping (Result<[Diagnostic], ResponseError>) -> Void - ) async { + ) { guard let snapshot = documentManager.latestSnapshot(uri) else { let msg = "failed to find snapshot for url \(uri)" log(msg) @@ -1407,12 +1392,8 @@ extension SwiftLanguageServer { skreq[keys.sourcefile] = snapshot.document.uri.pseudoPath // FIXME: SourceKit should probably cache this for us. - let areFallbackBuildSettings: Bool - if let buildSettings = await self.buildSettings(for: uri) { - skreq[keys.compilerargs] = buildSettings.compilerArgs - areFallbackBuildSettings = buildSettings.isFallback - } else { - areFallbackBuildSettings = true + if let compileCommand = self.commandsByFile[uri] { + skreq[keys.compilerargs] = compileCommand.compilerArgs } let handle = self.sourcekitd.send(skreq, self.queue) { response in @@ -1423,8 +1404,7 @@ extension SwiftLanguageServer { let diagnostics = self.registerDiagnostics( sourcekitdDiagnostics: dict[keys.diagnostics], snapshot: snapshot, - stage: .sema, - isFromFallbackBuildSettings: areFallbackBuildSettings + stage: .sema ) completion(.success(diagnostics)) @@ -1434,9 +1414,9 @@ extension SwiftLanguageServer { _ = handle } - public func documentDiagnostic(_ req: Request) async { + public func documentDiagnostic(_ req: Request) { let uri = req.params.textDocument.uri - await documentDiagnostic(req.params.textDocument.uri) { result in + documentDiagnostic(req.params.textDocument.uri) { result in switch result { case .success(let diagnostics): req.reply(.full(.init(items: diagnostics))) @@ -1449,7 +1429,7 @@ extension SwiftLanguageServer { } } - public func executeCommand(_ req: Request) async { + public func executeCommand(_ req: Request) { let params = req.params //TODO: If there's support for several types of commands, we might need to structure this similarly to the code actions request. guard let swiftCommand = params.swiftCommand(ofType: SemanticRefactorCommand.self) else { @@ -1458,7 +1438,7 @@ extension SwiftLanguageServer { return req.reply(.failure(.unknown(message))) } let uri = swiftCommand.textDocument.uri - await semanticRefactoring(swiftCommand) { result in + semanticRefactoring(swiftCommand) { result in switch result { case .success(let refactor): let edit = refactor.edit @@ -1511,7 +1491,7 @@ extension SwiftLanguageServer: SKDNotificationHandler { } } - public func notificationImpl(_ notification: SKDResponse) async { + public func notificationImpl(_ notification: SKDResponse) { // Check if we need to update our `state` based on the contents of the notification. if notification.value?[self.keys.notification] == self.values.notification_sema_enabled { self.state = .connected @@ -1569,7 +1549,7 @@ extension SwiftLanguageServer: SKDNotificationHandler { } else { uri = DocumentURI(string: name) } - await self.handleDocumentUpdate(uri: uri) + self.handleDocumentUpdate(uri: uri) } } } diff --git a/Sources/SourceKitLSP/Swift/VariableTypeInfo.swift b/Sources/SourceKitLSP/Swift/VariableTypeInfo.swift index 172ca0a5..43c227d9 100644 --- a/Sources/SourceKitLSP/Swift/VariableTypeInfo.swift +++ b/Sources/SourceKitLSP/Swift/VariableTypeInfo.swift @@ -93,7 +93,7 @@ extension SwiftLanguageServer { _ uri: DocumentURI, _ range: Range? = nil, _ completion: @escaping (Swift.Result<[VariableTypeInfo], VariableTypeInfoError>) -> Void - ) async { + ) { guard let snapshot = documentManager.latestSnapshot(uri) else { return completion(.failure(.unknownDocument(uri))) } @@ -112,7 +112,7 @@ extension SwiftLanguageServer { } // FIXME: SourceKit should probably cache this for us - if let compileCommand = await self.buildSettings(for: uri) { + if let compileCommand = self.commandsByFile[uri] { skreq[keys.compilerargs] = compileCommand.compilerArgs } diff --git a/Sources/SourceKitLSP/ToolchainLanguageServer.swift b/Sources/SourceKitLSP/ToolchainLanguageServer.swift index e2da6bf3..2a67dfd4 100644 --- a/Sources/SourceKitLSP/ToolchainLanguageServer.swift +++ b/Sources/SourceKitLSP/ToolchainLanguageServer.swift @@ -34,8 +34,7 @@ public protocol ToolchainLanguageServer: AnyObject { toolchain: Toolchain, options: SourceKitServer.Options, workspace: Workspace, - reopenDocuments: @escaping (ToolchainLanguageServer) -> Void, - workspaceForDocument: @escaping (DocumentURI) async -> Workspace? + reopenDocuments: @escaping (ToolchainLanguageServer) -> Void ) throws /// Returns `true` if this instance of the language server can handle opening documents in `workspace`. diff --git a/Tests/SKCoreTests/BuildServerBuildSystemTests.swift b/Tests/SKCoreTests/BuildServerBuildSystemTests.swift index a4324425..aafaa689 100644 --- a/Tests/SKCoreTests/BuildServerBuildSystemTests.swift +++ b/Tests/SKCoreTests/BuildServerBuildSystemTests.swift @@ -40,6 +40,24 @@ final class BuildServerBuildSystemTests: XCTestCase { ) } + func testSettings() throws { +#if os(Windows) + try XCTSkipIf(true, "hang") +#endif + let buildSystem = try BuildServerBuildSystem(projectRoot: root, buildFolder: buildFolder) + + // test settings with a response + let fileURL = URL(fileURLWithPath: "/path/to/some/file.swift") + let settings = buildSystem._settings(for: DocumentURI(fileURL)) + XCTAssertNotNil(settings) + XCTAssertEqual(settings?.compilerArguments, ["-a", "-b"]) + XCTAssertEqual(settings?.workingDirectory, fileURL.deletingLastPathComponent().path) + + // test error + let missingFileURL = URL(fileURLWithPath: "/path/to/some/missingfile.missing") + XCTAssertNil(buildSystem._settings(for: DocumentURI(missingFileURL))) + } + func testFileRegistration() throws { let buildSystem = try BuildServerBuildSystem(projectRoot: root, buildFolder: buildFolder) diff --git a/Tests/SKCoreTests/BuildSystemManagerTests.swift b/Tests/SKCoreTests/BuildSystemManagerTests.swift index 99724f25..837133b4 100644 --- a/Tests/SKCoreTests/BuildSystemManagerTests.swift +++ b/Tests/SKCoreTests/BuildSystemManagerTests.swift @@ -149,7 +149,7 @@ final class BuildSystemManagerTests: XCTestCase { mainFilesProvider: mainFiles) defer { withExtendedLifetime(bsm) {} } // Keep BSM alive for callbacks. let del = BSMDelegate(bsm) - let fallbackSettings = fallback.buildSettings(for: a, language: .swift) + let fallbackSettings = fallback.settings(for: a, .swift) let initial = expectation(description: "initial fallback settings") del.expected = [(a, fallbackSettings, initial, #file, #line)] bsm.registerForChangeNotifications(for: a, language: .swift) @@ -475,12 +475,12 @@ class ManualBuildSystem: BuildSystem { var delegate: BuildSystemDelegate? = nil - func buildSettings(for uri: DocumentURI, language: Language) -> FileBuildSettings? { + func settings(for uri: DocumentURI, _ language: Language) -> FileBuildSettings? { return map[uri] } func registerForChangeNotifications(for uri: DocumentURI, language: Language) { - let settings = self.buildSettings(for: uri, language: language) + let settings = self.settings(for: uri, language) self.delegate?.fileBuildSettingsChanged([uri: FileBuildSettingsChange(settings)]) } diff --git a/Tests/SKCoreTests/FallbackBuildSystemTests.swift b/Tests/SKCoreTests/FallbackBuildSystemTests.swift index 6d103a3b..68b54ef1 100644 --- a/Tests/SKCoreTests/FallbackBuildSystemTests.swift +++ b/Tests/SKCoreTests/FallbackBuildSystemTests.swift @@ -29,7 +29,7 @@ final class FallbackBuildSystemTests: XCTestCase { XCTAssertNil(bs.indexStorePath) XCTAssertNil(bs.indexDatabasePath) - let settings = bs.buildSettings(for: source.asURI, language: .swift)! + let settings = bs.settings(for: source.asURI, .swift)! XCTAssertNil(settings.workingDirectory) let args = settings.compilerArguments @@ -41,7 +41,7 @@ final class FallbackBuildSystemTests: XCTestCase { bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .swift)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .swift)?.compilerArguments, [ source.pathString, ]) } @@ -57,7 +57,7 @@ final class FallbackBuildSystemTests: XCTestCase { let bs = FallbackBuildSystem(buildSetup: buildSetup) bs.sdkpath = sdk - let args = bs.buildSettings(for: source.asURI, language: .swift)?.compilerArguments + let args = bs.settings(for: source.asURI, .swift)?.compilerArguments XCTAssertEqual(args, [ "-Xfrontend", "-debug-constraints", @@ -68,7 +68,7 @@ final class FallbackBuildSystemTests: XCTestCase { bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .swift)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .swift)?.compilerArguments, [ "-Xfrontend", "-debug-constraints", source.pathString, @@ -88,7 +88,7 @@ final class FallbackBuildSystemTests: XCTestCase { let bs = FallbackBuildSystem(buildSetup: buildSetup) bs.sdkpath = sdk - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .swift)!.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .swift)!.compilerArguments, [ "-sdk", "/some/custom/sdk", "-Xfrontend", @@ -98,7 +98,7 @@ final class FallbackBuildSystemTests: XCTestCase { bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .swift)!.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .swift)!.compilerArguments, [ "-sdk", "/some/custom/sdk", "-Xfrontend", @@ -114,7 +114,7 @@ final class FallbackBuildSystemTests: XCTestCase { let bs = FallbackBuildSystem(buildSetup: .default) bs.sdkpath = sdk - let settings = bs.buildSettings(for: source.asURI, language: .cpp)! + let settings = bs.settings(for: source.asURI, .cpp)! XCTAssertNil(settings.workingDirectory) let args = settings.compilerArguments @@ -126,7 +126,7 @@ final class FallbackBuildSystemTests: XCTestCase { bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .cpp)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .cpp)?.compilerArguments, [ source.pathString, ]) } @@ -141,7 +141,7 @@ final class FallbackBuildSystemTests: XCTestCase { let bs = FallbackBuildSystem(buildSetup: buildSetup) bs.sdkpath = sdk - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .cpp)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .cpp)?.compilerArguments, [ "-v", "-isysroot", sdk.pathString, @@ -150,7 +150,7 @@ final class FallbackBuildSystemTests: XCTestCase { bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .cpp)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .cpp)?.compilerArguments, [ "-v", source.pathString, ]) @@ -168,7 +168,7 @@ final class FallbackBuildSystemTests: XCTestCase { let bs = FallbackBuildSystem(buildSetup: buildSetup) bs.sdkpath = sdk - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .cpp)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .cpp)?.compilerArguments, [ "-isysroot", "/my/custom/sdk", "-v", @@ -177,7 +177,7 @@ final class FallbackBuildSystemTests: XCTestCase { bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .cpp)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .cpp)?.compilerArguments, [ "-isysroot", "/my/custom/sdk", "-v", @@ -189,7 +189,7 @@ final class FallbackBuildSystemTests: XCTestCase { let source = try AbsolutePath(validating: "/my/source.c") let bs = FallbackBuildSystem(buildSetup: .default) bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .c)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .c)?.compilerArguments, [ source.pathString, ]) } @@ -202,7 +202,7 @@ final class FallbackBuildSystemTests: XCTestCase { ])) let bs = FallbackBuildSystem(buildSetup: buildSetup) bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .c)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .c)?.compilerArguments, [ "-v", source.pathString, ]) @@ -212,7 +212,7 @@ final class FallbackBuildSystemTests: XCTestCase { let source = try AbsolutePath(validating: "/my/source.m") let bs = FallbackBuildSystem(buildSetup: .default) bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .objective_c)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .objective_c)?.compilerArguments, [ source.pathString, ]) } @@ -221,7 +221,7 @@ final class FallbackBuildSystemTests: XCTestCase { let source = try AbsolutePath(validating: "/my/source.mm") let bs = FallbackBuildSystem(buildSetup: .default) bs.sdkpath = nil - XCTAssertEqual(bs.buildSettings(for: source.asURI, language: .objective_cpp)?.compilerArguments, [ + XCTAssertEqual(bs.settings(for: source.asURI, .objective_cpp)?.compilerArguments, [ source.pathString, ]) } @@ -229,6 +229,6 @@ final class FallbackBuildSystemTests: XCTestCase { func testUnknown() throws { let source = try AbsolutePath(validating: "/my/source.mm") let bs = FallbackBuildSystem(buildSetup: .default) - XCTAssertNil(bs.buildSettings(for: source.asURI, language: Language(rawValue: "unknown"))) + XCTAssertNil(bs.settings(for: source.asURI, Language(rawValue: "unknown"))) } } diff --git a/Tests/SourceKitLSPTests/BuildSystemTests.swift b/Tests/SourceKitLSPTests/BuildSystemTests.swift index 95f1aa4a..ac05bedd 100644 --- a/Tests/SourceKitLSPTests/BuildSystemTests.swift +++ b/Tests/SourceKitLSPTests/BuildSystemTests.swift @@ -43,10 +43,6 @@ final class TestBuildSystem: BuildSystem { /// Files currently being watched by our delegate. var watchedFiles: Set = [] - func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? { - return buildSettingsByFile[document] - } - func registerForChangeNotifications(for uri: DocumentURI, language: Language) { watchedFiles.insert(uri) @@ -200,7 +196,7 @@ final class BuildSystemTests: XCTestCase { func testSwiftDocumentUpdatedBuildSettings() async throws { let url = URL(fileURLWithPath: "/\(UUID())/a.swift") let doc = DocumentURI(url) - let args = FallbackBuildSystem(buildSetup: .default).buildSettings(for: doc, language: .swift)!.compilerArguments + let args = FallbackBuildSystem(buildSetup: .default).settings(for: doc, .swift)!.compilerArguments buildSystem.buildSettingsByFile[doc] = FileBuildSettings(compilerArguments: args) @@ -310,7 +306,7 @@ final class BuildSystemTests: XCTestCase { let doc = DocumentURI(url) // Primary settings must be different than the fallback settings. - var primarySettings = FallbackBuildSystem(buildSetup: .default).buildSettings(for: doc, language: .swift)! + var primarySettings = FallbackBuildSystem(buildSetup: .default).settings(for: doc, .swift)! primarySettings.compilerArguments.append("-DPRIMARY") let text = """