Skip to content

Commit 01e8858

Browse files
committed
Don’t show a “Preparing current file” progress while determining target of file
Every time a request regarding a text document is sent, we call `schedulePreparationForEditorFunctionality` on the `SemanticIndexManager` with the assumption that it’s a no-op if the target is already prepared. We did, however, show the “Preparing current file” status while we were determining the file‘s target to determine that the file’s target is up-to-date.
1 parent d439d12 commit 01e8858

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,26 @@ public enum IndexProgressStatus {
9696
}
9797
}
9898

99+
/// See `SemanticIndexManager.inProgressPrepareForEditorTask`.
100+
fileprivate struct InProgressPrepareForEditorTask {
101+
fileprivate enum State {
102+
case determiningCanonicalConfiguredTarget
103+
case preparingTarget
104+
}
105+
/// A unique ID that identifies the preparation task and is used to set
106+
/// `SemanticIndexManager.inProgressPrepareForEditorTask` to `nil` when the current in progress task finishes.
107+
let id: UUID
108+
109+
/// The document that is being prepared.
110+
let document: DocumentURI
111+
112+
/// The task that prepares the document. Should never be awaited and only be used to cancel the task.
113+
let task: Task<Void, Never>
114+
115+
/// Whether the task is currently determining the file's target or actually preparing the document.
116+
var state: State
117+
}
118+
99119
/// Schedules index tasks and keeps track of the index status of files.
100120
public final actor SemanticIndexManager {
101121
/// The underlying index. This is used to check if the index of a file is already up-to-date, in which case it doesn't
@@ -133,10 +153,7 @@ public final actor SemanticIndexManager {
133153
/// avoid the following scenario: The user browses through documents from targets A, B, and C in quick succession. We
134154
/// don't want stack preparation of A, B, and C. Instead we want to only prepare target C - and also finish
135155
/// preparation of A if it has already started when the user opens C.
136-
///
137-
/// `id` is a unique ID that identifies the preparation task and is used to set `inProgressPrepareForEditorTask` to
138-
/// `nil` when the current in progress task finishes.
139-
private var inProgressPrepareForEditorTask: (id: UUID, document: DocumentURI, task: Task<Void, Never>)? = nil
156+
private var inProgressPrepareForEditorTask: InProgressPrepareForEditorTask? = nil
140157

141158
/// The `TaskScheduler` that manages the scheduling of index tasks. This is shared among all `SemanticIndexManager`s
142159
/// in the process, to ensure that we don't schedule more index operations than processor cores from multiple
@@ -161,7 +178,7 @@ public final actor SemanticIndexManager {
161178

162179
/// A summary of the tasks that this `SemanticIndexManager` has currently scheduled or is currently indexing.
163180
public var progressStatus: IndexProgressStatus {
164-
if inProgressPrepareForEditorTask != nil {
181+
if let inProgressPrepareForEditorTask, inProgressPrepareForEditorTask.state == .preparingTarget {
165182
return .preparingFileForEditorFunctionality
166183
}
167184
if generateBuildGraphTask != nil {
@@ -370,15 +387,33 @@ public final actor SemanticIndexManager {
370387
let id = UUID()
371388
let task = Task(priority: priority) {
372389
await withLoggingScope("preparation") {
373-
await self.prepareFileForEditorFunctionality(uri)
390+
// Should be kept in sync with `prepareFileForEditorFunctionality`
391+
guard let target = await buildSystemManager.canonicalConfiguredTarget(for: uri) else {
392+
return
393+
}
394+
if Task.isCancelled {
395+
return
396+
}
397+
if inProgressPrepareForEditorTask?.id == id {
398+
if inProgressPrepareForEditorTask?.state != .determiningCanonicalConfiguredTarget {
399+
logger.fault("inProgressPrepareForEditorTask is in unexpected state")
400+
}
401+
inProgressPrepareForEditorTask?.state = .preparingTarget
402+
}
403+
await self.prepare(targets: [target], priority: nil)
374404
if inProgressPrepareForEditorTask?.id == id {
375405
inProgressPrepareForEditorTask = nil
376406
self.indexProgressStatusDidChange()
377407
}
378408
}
379409
}
380410
inProgressPrepareForEditorTask?.task.cancel()
381-
inProgressPrepareForEditorTask = (id, uri, task)
411+
inProgressPrepareForEditorTask = InProgressPrepareForEditorTask(
412+
id: id,
413+
document: uri,
414+
task: task,
415+
state: .determiningCanonicalConfiguredTarget
416+
)
382417
self.indexProgressStatusDidChange()
383418
}
384419

@@ -387,6 +422,7 @@ public final actor SemanticIndexManager {
387422
///
388423
/// If file's target is known to be up-to-date, this returns almost immediately.
389424
public func prepareFileForEditorFunctionality(_ uri: DocumentURI) async {
425+
// Should be kept in sync with `schedulePreparationForEditorFunctionality`.
390426
guard let target = await buildSystemManager.canonicalConfiguredTarget(for: uri) else {
391427
return
392428
}

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,4 +868,23 @@ final class BackgroundIndexingTests: XCTestCase {
868868
let message = try await project.testClient.nextNotification(ofType: ShowMessageNotification.self)
869869
XCTAssert(message.message.contains("Background indexing"), "Received unexpected message: \(message.message)")
870870
}
871+
872+
func testNoPreparationStatusIfTargetIsUpToDate() async throws {
873+
let project = try await SwiftPMTestProject(
874+
files: [
875+
"Lib.swift": ""
876+
],
877+
capabilities: ClientCapabilities(window: WindowClientCapabilities(workDoneProgress: true)),
878+
enableBackgroundIndexing: true
879+
)
880+
881+
// Opening the document prepares it for editor functionality. Its target is already prepared, so we shouldn't show
882+
// a work done progress for it.
883+
project.testClient.handleSingleRequest { (request: CreateWorkDoneProgressRequest) in
884+
XCTFail("Received unexpected create work done progress: \(request)")
885+
return VoidResponse()
886+
}
887+
_ = try project.openDocument("Lib.swift")
888+
_ = try await project.testClient.send(BarrierRequest())
889+
}
871890
}

0 commit comments

Comments
 (0)