@@ -357,6 +357,22 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
357357 }
358358 }
359359
360+ /// Debounces calls to `delegate.fileBuildSettingsChanged`.
361+ ///
362+ /// This helps in the following situation: A build system takes 5s to return build settings for a file and we have 10
363+ /// requests for those build settings coming in that time period. Once we get build settings, we get 10 calls to
364+ /// `resultReceivedAfterTimeout` in `buildSettings(for:in:language:fallbackAfterTimeout:)`, all for the same document.
365+ /// But calling `fileBuildSettingsChanged` once is totally sufficient.
366+ ///
367+ /// Force-unwrapped optional because initializing it requires access to `self`.
368+ private var filesBuildSettingsChangedDebouncer : Debouncer < Set < DocumentURI > > ! = nil {
369+ didSet {
370+ // Must only be set once
371+ precondition ( oldValue == nil )
372+ precondition ( filesBuildSettingsChangedDebouncer != nil )
373+ }
374+ }
375+
360376 private var cachedAdjustedSourceKitOptions = RequestCache < TextDocumentSourceKitOptionsRequest > ( )
361377
362378 private var cachedBuildTargets = Cache < WorkspaceBuildTargetsRequest , [ BuildTargetIdentifier : BuildTargetInfo ] > ( )
@@ -421,6 +437,24 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
421437 }
422438 }
423439
440+ // We don't need a large debounce duration here. It just needs to be big enough to accumulate
441+ // `resultReceivedAfterTimeout` calls for the same document (see comment on `filesBuildSettingsChangedDebouncer`).
442+ // Since they should all come in at the same time, a couple of milliseconds should be sufficient here, an 20ms be
443+ // plenty while still not causing a noticeable delay to the user.
444+ self . filesBuildSettingsChangedDebouncer = Debouncer (
445+ debounceDuration: . milliseconds( 20 ) ,
446+ combineResults: { $0. union ( $1) }
447+ ) {
448+ [ weak self] ( filesWithChangedBuildSettings) in
449+ guard let self, let delegate = await self . delegate else {
450+ logger. fault ( " Not calling fileBuildSettingsChanged because no delegate exists in SwiftPMBuildSystem " )
451+ return
452+ }
453+ if !filesWithChangedBuildSettings. isEmpty {
454+ await delegate. fileBuildSettingsChanged ( filesWithChangedBuildSettings)
455+ }
456+ }
457+
424458 // TODO: Forward file watch patterns from this initialize request to the client
425459 // (https://github.com/swiftlang/sourcekit-lsp/issues/1671)
426460 initializeResult = Task { ( ) -> InitializeBuildResponse ? in
@@ -579,7 +613,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
579613 self . cachedSourceFilesAndDirectories. clearAll ( isolation: self )
580614
581615 await delegate? . buildTargetsChanged ( notification. changes)
582- await delegate ? . fileBuildSettingsChanged ( Set ( watchedFiles. keys) )
616+ await filesBuildSettingsChangedDebouncer . scheduleCall ( Set ( watchedFiles. keys) )
583617 }
584618
585619 private func logMessage( notification: BuildServerProtocol . OnBuildLogMessageNotification ) async {
@@ -837,7 +871,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
837871 try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
838872 return try await self . buildSettingsFromBuildSystem ( for: document, in: target, language: language)
839873 } resultReceivedAfterTimeout: {
840- await self . delegate ? . fileBuildSettingsChanged ( [ document] )
874+ await self . filesBuildSettingsChangedDebouncer . scheduleCall ( [ document] )
841875 }
842876 } else {
843877 try await self . buildSettingsFromBuildSystem ( for: document, in: target, language: language)
@@ -895,7 +929,7 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
895929 try await withTimeout ( options. buildSettingsTimeoutOrDefault) {
896930 await self . canonicalTarget ( for: mainFile)
897931 } resultReceivedAfterTimeout: {
898- await self . delegate ? . fileBuildSettingsChanged ( [ document] )
932+ await self . filesBuildSettingsChangedDebouncer . scheduleCall ( [ document] )
899933 }
900934 }
901935 var languageForFile : Language
@@ -955,6 +989,10 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
955989 }
956990 // Handle any messages the build system might have sent us while updating.
957991 await messageHandlingQueue. async ( metadata: . stateChange) { } . valuePropagatingCancellation
992+
993+ // Ensure that we send out all delegate calls so that everybody is informed about the changes.
994+ await filesBuildSettingsChangedDebouncer. flush ( )
995+ await filesDependenciesUpdatedDebouncer. flush ( )
958996 }
959997
960998 /// The root targets of the project have depth of 0 and all target dependencies have a greater depth than the target
0 commit comments