Skip to content

Commit 8bfd02d

Browse files
committed
Show a work done progress while the semantic index is rebuilding a build graph
Rebuilding the build graph can take a while (initial loading of the build graph takes ~7s for sourcekit-lsp) and it’s good to show some progress during this time.
1 parent 3300c77 commit 8bfd02d

File tree

5 files changed

+84
-26
lines changed

5 files changed

+84
-26
lines changed

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,16 @@ public final class TestSourceKitLSPClient: MessageHandler {
248248
return try await nextNotification(ofType: PublishDiagnosticsNotification.self, timeout: timeout)
249249
}
250250

251-
/// Waits for the next notification of the given type to be sent to the client. Ignores any notifications that are of
252-
/// a different type.
251+
/// Waits for the next notification of the given type to be sent to the client that satisfies the given predicate.
252+
/// Ignores any notifications that are of a different type or that don't satisfy the predicate.
253253
public func nextNotification<ExpectedNotificationType: NotificationType>(
254254
ofType: ExpectedNotificationType.Type,
255+
satisfying predicate: (ExpectedNotificationType) -> Bool = { _ in true },
255256
timeout: TimeInterval = defaultTimeout
256257
) async throws -> ExpectedNotificationType {
257258
while true {
258259
let nextNotification = try await nextNotification(timeout: timeout)
259-
if let notification = nextNotification as? ExpectedNotificationType {
260+
if let notification = nextNotification as? ExpectedNotificationType, predicate(notification) {
260261
return notification
261262
}
262263
}

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ public final actor SemanticIndexManager {
192192
defer {
193193
signposter.endInterval("Preparing", state)
194194
}
195+
await testHooks.buildGraphGenerationDidStart?()
195196
await orLog("Generating build graph") { try await self.buildSystemManager.generateBuildGraph() }
197+
await testHooks.buildGraphGenerationDidFinish?()
196198
let index = index.checked(for: .modifiedFiles)
197199
let filesToIndex = await self.buildSystemManager.sourceFiles().lazy.map(\.uri)
198200
.filter { uri in
@@ -205,6 +207,7 @@ public final actor SemanticIndexManager {
205207
await scheduleBackgroundIndex(files: filesToIndex)
206208
generateBuildGraphTask = nil
207209
}
210+
indexStatusDidChange()
208211
}
209212

210213
/// Wait for all in-progress index tasks to finish.

Sources/SemanticIndex/TestHooks.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
/// Callbacks that allow inspection of internal state modifications during testing.
1414
public struct IndexTestHooks: Sendable {
15+
public var buildGraphGenerationDidStart: (@Sendable () async -> Void)?
16+
17+
public var buildGraphGenerationDidFinish: (@Sendable () async -> Void)?
18+
1519
public var preparationTaskDidStart: (@Sendable (PreparationTaskDescription) async -> Void)?
1620

1721
public var preparationTaskDidFinish: (@Sendable (PreparationTaskDescription) async -> Void)?
@@ -22,11 +26,15 @@ public struct IndexTestHooks: Sendable {
2226
public var updateIndexStoreTaskDidFinish: (@Sendable (UpdateIndexStoreTaskDescription) async -> Void)?
2327

2428
public init(
29+
buildGraphGenerationDidStart: (@Sendable () async -> Void)? = nil,
30+
buildGraphGenerationDidFinish: (@Sendable () async -> Void)? = nil,
2531
preparationTaskDidStart: (@Sendable (PreparationTaskDescription) async -> Void)? = nil,
2632
preparationTaskDidFinish: (@Sendable (PreparationTaskDescription) async -> Void)? = nil,
2733
updateIndexStoreTaskDidStart: (@Sendable (UpdateIndexStoreTaskDescription) async -> Void)? = nil,
2834
updateIndexStoreTaskDidFinish: (@Sendable (UpdateIndexStoreTaskDescription) async -> Void)? = nil
2935
) {
36+
self.buildGraphGenerationDidStart = buildGraphGenerationDidStart
37+
self.buildGraphGenerationDidFinish = buildGraphGenerationDidFinish
3038
self.preparationTaskDidStart = preparationTaskDidStart
3139
self.preparationTaskDidFinish = preparationTaskDidFinish
3240
self.updateIndexStoreTaskDidStart = updateIndexStoreTaskDidStart

Sources/SourceKitLSP/IndexProgressManager.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ actor IndexProgressManager {
9393
}
9494
}
9595

96-
if indexTasks.isEmpty {
96+
if indexTasks.isEmpty && !isGeneratingBuildGraph {
9797
// Nothing left to index. Reset the target count and dismiss the work done progress.
9898
queuedIndexTasks = 0
9999
workDoneProgress = nil
@@ -104,7 +104,12 @@ actor IndexProgressManager {
104104
// `indexTasksWereScheduled` calls yet but the semantic index managers already track them in their in-progress tasks.
105105
// Clip the finished tasks to 0 because showing a negative number there looks stupid.
106106
let finishedTasks = max(queuedIndexTasks - indexTasks.count, 0)
107-
var message = "\(finishedTasks) / \(queuedIndexTasks)"
107+
var message: String
108+
if isGeneratingBuildGraph {
109+
message = "Generating build graph"
110+
} else {
111+
message = "\(finishedTasks) / \(queuedIndexTasks)"
112+
}
108113

109114
if await sourceKitLSPServer.options.indexOptions.showActivePreparationTasksInProgress {
110115
var inProgressTasks: [String] = []
@@ -121,7 +126,12 @@ actor IndexProgressManager {
121126
message += "\n\n" + inProgressTasks.joined(separator: "\n")
122127
}
123128

124-
let percentage = Int(Double(finishedTasks) / Double(queuedIndexTasks) * 100)
129+
let percentage: Int
130+
if queuedIndexTasks != 0 {
131+
percentage = Int(Double(finishedTasks) / Double(queuedIndexTasks) * 100)
132+
} else {
133+
percentage = 0
134+
}
125135
if let workDoneProgress {
126136
workDoneProgress.update(message: message, percentage: percentage)
127137
} else {

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,21 @@ final class BackgroundIndexingTests: XCTestCase {
333333
}
334334

335335
func testBackgroundIndexingStatusWorkDoneProgress() async throws {
336+
let receivedBeginProgressNotification = self.expectation(
337+
description: "Received work done progress saying build graph generation"
338+
)
339+
let receivedReportProgressNotification = self.expectation(
340+
description: "Received work done progress saying indexing"
341+
)
342+
var serverOptions = backgroundIndexingOptions
343+
serverOptions.indexTestHooks = IndexTestHooks(
344+
buildGraphGenerationDidFinish: {
345+
await self.fulfillment(of: [receivedBeginProgressNotification], timeout: defaultTimeout)
346+
},
347+
updateIndexStoreTaskDidFinish: { _ in
348+
await self.fulfillment(of: [receivedReportProgressNotification], timeout: defaultTimeout)
349+
}
350+
)
336351
let project = try await SwiftPMTestProject(
337352
files: [
338353
"MyFile.swift": """
@@ -343,36 +358,57 @@ final class BackgroundIndexingTests: XCTestCase {
343358
"""
344359
],
345360
capabilities: ClientCapabilities(window: WindowClientCapabilities(workDoneProgress: true)),
346-
serverOptions: backgroundIndexingOptions,
361+
serverOptions: serverOptions,
362+
pollIndex: false,
347363
preInitialization: { testClient in
348364
testClient.handleMultipleRequests { (request: CreateWorkDoneProgressRequest) in
349365
return VoidResponse()
350366
}
351367
}
352368
)
353-
var indexingWorkDoneProgressToken: ProgressToken? = nil
354-
var didGetEndWorkDoneProgress = false
355-
// Loop terminates when we see the work done end progress or if waiting for the next notification times out
356-
LOOP: while true {
357-
let workDoneProgress = try await project.testClient.nextNotification(ofType: WorkDoneProgress.self)
358-
switch workDoneProgress.value {
359-
case .begin(let data):
360-
if data.title == "Indexing" {
361-
XCTAssertNil(indexingWorkDoneProgressToken, "Received multiple work done progress notifications for indexing")
362-
indexingWorkDoneProgressToken = workDoneProgress.token
369+
370+
let beginNotification = try await project.testClient.nextNotification(
371+
ofType: WorkDoneProgress.self,
372+
satisfying: { notification in
373+
guard case .begin(let data) = notification.value else {
374+
return false
363375
}
364-
case .report:
365-
// We ignore progress reports in the test because it's non-deterministic how many we get
366-
break
367-
case .end:
368-
if workDoneProgress.token == indexingWorkDoneProgressToken {
369-
didGetEndWorkDoneProgress = true
370-
break LOOP
376+
return data.title == "Indexing"
377+
}
378+
)
379+
receivedBeginProgressNotification.fulfill()
380+
guard case .begin(let beginData) = beginNotification.value else {
381+
XCTFail("Expected begin notification")
382+
return
383+
}
384+
XCTAssertEqual(beginData.message, "Generating build graph")
385+
let indexingWorkDoneProgressToken = beginNotification.token
386+
387+
let reportNotification = try await project.testClient.nextNotification(
388+
ofType: WorkDoneProgress.self,
389+
satisfying: { notification in
390+
guard notification.token == indexingWorkDoneProgressToken, case .report = notification.value else {
391+
return false
371392
}
393+
return true
372394
}
395+
)
396+
receivedReportProgressNotification.fulfill()
397+
guard case .report(let reportData) = reportNotification.value else {
398+
XCTFail("Expected report notification")
399+
return
373400
}
374-
XCTAssertNotNil(indexingWorkDoneProgressToken, "Expected to receive a work done progress start")
375-
XCTAssert(didGetEndWorkDoneProgress, "Expected end work done progress")
401+
XCTAssertEqual(reportData.message, "0 / 1")
402+
403+
_ = try await project.testClient.nextNotification(
404+
ofType: WorkDoneProgress.self,
405+
satisfying: { notification in
406+
guard notification.token == indexingWorkDoneProgressToken, case .end = notification.value else {
407+
return false
408+
}
409+
return true
410+
}
411+
)
376412

377413
withExtendedLifetime(project) {}
378414
}

0 commit comments

Comments
 (0)