Skip to content

Commit a887981

Browse files
committed
Add an option to show the files that are currently being index / targets being prepared in the work done progress
1 parent 44acd0d commit a887981

File tree

8 files changed

+101
-63
lines changed

8 files changed

+101
-63
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
default.profraw
33
Package.resolved
44
/.build
5-
/.index-build
6-
/.linux-build
5+
/.*-build
76
/Packages
87
/*.xcodeproj
98
/*.sublime-project

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ private enum InProgressIndexStore {
5555
case updatingIndexStore(updateIndexStoreTask: OpaqueQueuedIndexTask, indexTask: Task<Void, Never>)
5656
}
5757

58+
/// Status of document indexing / target preparation in `inProgressIndexAndPreparationTasks`.
59+
public enum IndexTaskStatus: Comparable {
60+
case scheduled
61+
case executing
62+
}
63+
5864
/// Schedules index tasks and keeps track of the index status of files.
5965
public final actor SemanticIndexManager {
6066
/// 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
@@ -107,42 +113,36 @@ public final actor SemanticIndexManager {
107113
/// The parameter is the number of files that were scheduled to be indexed.
108114
private let indexTasksWereScheduled: @Sendable (_ numberOfFileScheduled: Int) -> Void
109115

110-
/// Callback that is called when an index task has finished.
116+
/// Callback that is called when the progress status of an update indexstore or preparation task finishes.
111117
///
112118
/// An object observing this property probably wants to check `inProgressIndexTasks` when the callback is called to
113119
/// get the current list of in-progress index tasks.
114120
///
115-
/// The number of `indexTaskDidFinish` calls does not have to relate to the number of `indexTasksWereScheduled` calls.
116-
private let indexTaskDidFinish: @Sendable () -> Void
121+
/// The number of `indexStatusDidChange` calls does not have to relate to the number of `indexTasksWereScheduled` calls.
122+
private let indexStatusDidChange: @Sendable () -> Void
117123

118124
// MARK: - Public API
119125

120-
/// The files that still need to be indexed.
121-
///
122-
/// Scheduled tasks are files that are waiting for their target to be prepared or whose index store update task is
123-
/// waiting to be scheduled by the task scheduler.
124-
///
125-
/// `executing` are the files that currently have an active index store update task running.
126-
public var inProgressIndexFiles: (scheduled: [DocumentURI], executing: [DocumentURI]) {
127-
var scheduled: [DocumentURI] = []
128-
var executing: [DocumentURI] = []
129-
for (uri, status) in inProgressIndexTasks {
130-
let isExecuting: Bool
126+
/// A summary of the tasks that this `SemanticIndexManager` has currently scheduled or is currently indexing.
127+
public var inProgressTasks:
128+
(
129+
isGeneratingBuildGraph: Bool,
130+
indexTasks: [DocumentURI: IndexTaskStatus],
131+
preparationTasks: [ConfiguredTarget: IndexTaskStatus]
132+
)
133+
{
134+
let indexTasks = inProgressIndexTasks.mapValues { status in
131135
switch status {
132136
case .waitingForPreparation:
133-
isExecuting = false
137+
return IndexTaskStatus.scheduled
134138
case .updatingIndexStore(updateIndexStoreTask: let updateIndexStoreTask, indexTask: _):
135-
isExecuting = updateIndexStoreTask.isExecuting
136-
}
137-
138-
if isExecuting {
139-
executing.append(uri)
140-
} else {
141-
scheduled.append(uri)
139+
return updateIndexStoreTask.isExecuting ? IndexTaskStatus.executing : IndexTaskStatus.scheduled
142140
}
143141
}
144-
145-
return (scheduled, executing)
142+
let preparationTasks = inProgressPreparationTasks.mapValues { queuedTask in
143+
return queuedTask.isExecuting ? IndexTaskStatus.executing : IndexTaskStatus.scheduled
144+
}
145+
return (generateBuildGraphTask != nil, indexTasks, preparationTasks)
146146
}
147147

148148
public init(
@@ -151,14 +151,14 @@ public final actor SemanticIndexManager {
151151
testHooks: IndexTestHooks,
152152
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
153153
indexTasksWereScheduled: @escaping @Sendable (Int) -> Void,
154-
indexTaskDidFinish: @escaping @Sendable () -> Void
154+
indexStatusDidChange: @escaping @Sendable () -> Void
155155
) {
156156
self.index = index
157157
self.buildSystemManager = buildSystemManager
158158
self.testHooks = testHooks
159159
self.indexTaskScheduler = indexTaskScheduler
160160
self.indexTasksWereScheduled = indexTasksWereScheduled
161-
self.indexTaskDidFinish = indexTaskDidFinish
161+
self.indexStatusDidChange = indexStatusDidChange
162162
}
163163

164164
/// Schedules a task to index `files`. Files that are known to be up-to-date based on `indexStatus` will
@@ -358,14 +358,15 @@ public final actor SemanticIndexManager {
358358
}
359359
let preparationTask = await indexTaskScheduler.schedule(priority: priority, taskDescription) { task, newState in
360360
guard case .finished = newState else {
361+
self.indexStatusDidChange()
361362
return
362363
}
363364
for target in targetsToPrepare {
364365
if self.inProgressPreparationTasks[target] == OpaqueQueuedIndexTask(task) {
365366
self.inProgressPreparationTasks[target] = nil
366367
}
367368
}
368-
self.indexTaskDidFinish()
369+
self.indexStatusDidChange()
369370
}
370371
for target in targetsToPrepare {
371372
inProgressPreparationTasks[target] = OpaqueQueuedIndexTask(preparationTask)
@@ -400,6 +401,7 @@ public final actor SemanticIndexManager {
400401
)
401402
let updateIndexTask = await indexTaskScheduler.schedule(priority: priority, taskDescription) { task, newState in
402403
guard case .finished = newState else {
404+
self.indexStatusDidChange()
403405
return
404406
}
405407
for fileAndTarget in filesAndTargets {
@@ -409,7 +411,7 @@ public final actor SemanticIndexManager {
409411
self.inProgressIndexTasks[fileAndTarget.file.sourceFile] = nil
410412
}
411413
}
412-
self.indexTaskDidFinish()
414+
self.indexStatusDidChange()
413415
}
414416
for fileAndTarget in filesAndTargets {
415417
if case .waitingForPreparation(preparationTaskID, let indexTask) = inProgressIndexTasks[

Sources/SourceKitLSP/IndexProgressManager.swift

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,13 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import LanguageServerProtocol
14+
import SKCore
1415
import SKSupport
1516
import SemanticIndex
1617

1718
/// Listens for index status updates from `SemanticIndexManagers`. From that information, it manages a
1819
/// `WorkDoneProgress` that communicates the index progress to the editor.
1920
actor IndexProgressManager {
20-
/// A queue on which `indexTaskWasQueued` and `indexStatusDidChange` are handled.
21-
///
22-
/// This allows the two functions two be `nonisolated` (and eg. the caller of `indexStatusDidChange` doesn't have to
23-
/// wait for the work done progress to be updated) while still guaranteeing that we handle them in the order they
24-
/// were called.
25-
private let queue = AsyncQueue<Serial>()
26-
2721
/// The `SourceKitLSPServer` for which this manages the index progress. It gathers all `SemanticIndexManagers` from
2822
/// the workspaces in the `SourceKitLSPServer`.
2923
private weak var sourceKitLSPServer: SourceKitLSPServer?
@@ -47,20 +41,20 @@ actor IndexProgressManager {
4741
}
4842

4943
/// Called when a new file is scheduled to be indexed. Increments the target index count, eg. the 3 in `1/3`.
50-
nonisolated func indexTaskWasQueued(count: Int) {
51-
queue.async {
52-
await self.indexTaskWasQueuedImpl(count: count)
44+
nonisolated func indexTasksWereScheduled(count: Int) {
45+
Task {
46+
await self.indexTasksWereScheduledImpl(count: count)
5347
}
5448
}
5549

56-
private func indexTaskWasQueuedImpl(count: Int) async {
50+
private func indexTasksWereScheduledImpl(count: Int) async {
5751
queuedIndexTasks += count
5852
await indexStatusDidChangeImpl()
5953
}
6054

6155
/// Called when a `SemanticIndexManager` finishes indexing a file. Adjusts the done index count, eg. the 1 in `1/3`.
6256
nonisolated func indexStatusDidChange() {
63-
queue.async {
57+
Task {
6458
await self.indexStatusDidChangeImpl()
6559
}
6660
}
@@ -70,23 +64,47 @@ actor IndexProgressManager {
7064
workDoneProgress = nil
7165
return
7266
}
73-
var scheduled: [DocumentURI] = []
74-
var executing: [DocumentURI] = []
67+
var isGeneratingBuildGraph = false
68+
var indexTasks: [DocumentURI: IndexTaskStatus] = [:]
69+
var preparationTasks: [ConfiguredTarget: IndexTaskStatus] = [:]
7570
for indexManager in await sourceKitLSPServer.workspaces.compactMap({ $0.semanticIndexManager }) {
76-
let inProgress = await indexManager.inProgressIndexFiles
77-
scheduled += inProgress.scheduled
78-
executing += inProgress.executing
71+
let inProgress = await indexManager.inProgressTasks
72+
isGeneratingBuildGraph = isGeneratingBuildGraph || inProgress.isGeneratingBuildGraph
73+
indexTasks.merge(inProgress.indexTasks) { lhs, rhs in
74+
return max(lhs, rhs)
75+
}
76+
preparationTasks.merge(inProgress.preparationTasks) { lhs, rhs in
77+
return max(lhs, rhs)
78+
}
7979
}
8080

81-
if scheduled.isEmpty && executing.isEmpty {
81+
if indexTasks.isEmpty {
8282
// Nothing left to index. Reset the target count and dismiss the work done progress.
8383
queuedIndexTasks = 0
8484
workDoneProgress = nil
8585
return
8686
}
8787

88-
let finishedTasks = queuedIndexTasks - scheduled.count - executing.count
89-
let message = "\(finishedTasks) / \(queuedIndexTasks)"
88+
// We can get into a situation where queuedIndexTasks < indexTasks.count if we haven't processed all
89+
// `indexTasksWereScheduled` calls yet but the semantic index managers already track them in their in-progress tasks.
90+
// Clip the finished tasks to 0 because showing a negative number there looks stupid.
91+
let finishedTasks = max(queuedIndexTasks - indexTasks.count, 0)
92+
var message = "\(finishedTasks) / \(queuedIndexTasks)"
93+
94+
if await sourceKitLSPServer.options.indexOptions.showActivePreparationTasksInProgress {
95+
var inProgressTasks: [String] = []
96+
if isGeneratingBuildGraph {
97+
inProgressTasks.append("- Generating build graph")
98+
}
99+
inProgressTasks += preparationTasks.filter { $0.value == .executing }
100+
.map { "- Preparing \($0.key.targetID)" }
101+
.sorted()
102+
inProgressTasks += indexTasks.filter { $0.value == .executing }
103+
.map { "- Indexing \($0.key.fileURL?.lastPathComponent ?? $0.key.pseudoPath)" }
104+
.sorted()
105+
106+
message += "\n\n" + inProgressTasks.joined(separator: "\n")
107+
}
90108

91109
let percentage = Int(Double(finishedTasks) / Double(queuedIndexTasks) * 100)
92110
if let workDoneProgress {

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,9 +1233,9 @@ extension SourceKitLSPServer {
12331233
}
12341234
},
12351235
indexTasksWereScheduled: { [weak self] count in
1236-
self?.indexProgressManager.indexTaskWasQueued(count: count)
1236+
self?.indexProgressManager.indexTasksWereScheduled(count: count)
12371237
},
1238-
indexTaskDidFinish: { [weak self] in
1238+
indexStatusDidChange: { [weak self] in
12391239
self?.indexProgressManager.indexStatusDidChange()
12401240
}
12411241
)
@@ -1296,9 +1296,9 @@ extension SourceKitLSPServer {
12961296
indexDelegate: nil,
12971297
indexTaskScheduler: self.indexTaskScheduler,
12981298
indexTasksWereScheduled: { [weak self] count in
1299-
self?.indexProgressManager.indexTaskWasQueued(count: count)
1299+
self?.indexProgressManager.indexTasksWereScheduled(count: count)
13001300
},
1301-
indexTaskDidFinish: { [weak self] in
1301+
indexStatusDidChange: { [weak self] in
13021302
self?.indexProgressManager.indexStatusDidChange()
13031303
}
13041304
)

Sources/SourceKitLSP/Workspace.swift

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public final class Workspace: Sendable {
9595
indexDelegate: SourceKitIndexDelegate?,
9696
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
9797
indexTasksWereScheduled: @escaping @Sendable (Int) -> Void,
98-
indexTaskDidFinish: @escaping @Sendable () -> Void
98+
indexStatusDidChange: @escaping @Sendable () -> Void
9999
) async {
100100
self.documentManager = documentManager
101101
self.buildSetup = options.buildSetup
@@ -115,7 +115,7 @@ public final class Workspace: Sendable {
115115
testHooks: options.indexTestHooks,
116116
indexTaskScheduler: indexTaskScheduler,
117117
indexTasksWereScheduled: indexTasksWereScheduled,
118-
indexTaskDidFinish: indexTaskDidFinish
118+
indexStatusDidChange: indexStatusDidChange
119119
)
120120
} else {
121121
self.semanticIndexManager = nil
@@ -153,7 +153,7 @@ public final class Workspace: Sendable {
153153
indexTaskScheduler: TaskScheduler<AnyIndexTaskDescription>,
154154
reloadPackageStatusCallback: @Sendable @escaping (ReloadPackageStatus) async -> Void,
155155
indexTasksWereScheduled: @Sendable @escaping (Int) -> Void,
156-
indexTaskDidFinish: @Sendable @escaping () -> Void
156+
indexStatusDidChange: @Sendable @escaping () -> Void
157157
) async throws {
158158
var buildSystem: BuildSystem? = nil
159159

@@ -259,7 +259,7 @@ public final class Workspace: Sendable {
259259
indexDelegate: indexDelegate,
260260
indexTaskScheduler: indexTaskScheduler,
261261
indexTasksWereScheduled: indexTasksWereScheduled,
262-
indexTaskDidFinish: indexTaskDidFinish
262+
indexStatusDidChange: indexStatusDidChange
263263
)
264264
}
265265

@@ -316,19 +316,29 @@ public struct IndexOptions: Sendable {
316316
/// Setting this to a value < 1 ensures that background indexing doesn't use all CPU resources.
317317
public var maxCoresPercentageToUseForBackgroundIndexing: Double
318318

319+
/// Whether to show the files that are currently being indexed / the targets that are currently being prepared in the
320+
/// work done progress.
321+
///
322+
/// This is an option because VS Code tries to render a multi-line work done progress into a single line text field in
323+
/// the status bar, which looks broken. But at the same time, it is very useful to get a feeling about what's
324+
/// currently happening indexing-wise.
325+
public var showActivePreparationTasksInProgress: Bool
326+
319327
public init(
320328
indexStorePath: AbsolutePath? = nil,
321329
indexDatabasePath: AbsolutePath? = nil,
322330
indexPrefixMappings: [PathPrefixMapping]? = nil,
323331
listenToUnitEvents: Bool = true,
324332
enableBackgroundIndexing: Bool = false,
325-
maxCoresPercentageToUseForBackgroundIndexing: Double = 1
333+
maxCoresPercentageToUseForBackgroundIndexing: Double = 1,
334+
showActivePreparationTasksInProgress: Bool = false
326335
) {
327336
self.indexStorePath = indexStorePath
328337
self.indexDatabasePath = indexDatabasePath
329338
self.indexPrefixMappings = indexPrefixMappings
330339
self.listenToUnitEvents = listenToUnitEvents
331340
self.enableBackgroundIndexing = enableBackgroundIndexing
332341
self.maxCoresPercentageToUseForBackgroundIndexing = maxCoresPercentageToUseForBackgroundIndexing
342+
self.showActivePreparationTasksInProgress = showActivePreparationTasksInProgress
333343
}
334344
}

Sources/sourcekit-lsp/SourceKitLSP.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,15 @@ struct SourceKitLSP: AsyncParsableCommand {
203203
@Flag(
204204
help: "Enable background indexing. This feature is still under active development and may be incomplete."
205205
)
206-
var enableExperimentalBackgroundIndexing = false
206+
var experimentalEnableBackgroundIndexing = false
207+
208+
@Flag(
209+
help: """
210+
Show which index tasks are currently running in the indexing work done progress. \
211+
This produces a multi-line work done progress, which might render incorrectly depending in the editor.
212+
"""
213+
)
214+
var experimentalShowActivePreparationTasksInProgress = false
207215

208216
func mapOptions() -> SourceKitLSPServer.Options {
209217
var serverOptions = SourceKitLSPServer.Options()
@@ -220,7 +228,8 @@ struct SourceKitLSP: AsyncParsableCommand {
220228
serverOptions.indexOptions.indexStorePath = indexStorePath
221229
serverOptions.indexOptions.indexDatabasePath = indexDatabasePath
222230
serverOptions.indexOptions.indexPrefixMappings = indexPrefixMappings
223-
serverOptions.indexOptions.enableBackgroundIndexing = enableExperimentalBackgroundIndexing
231+
serverOptions.indexOptions.enableBackgroundIndexing = experimentalEnableBackgroundIndexing
232+
serverOptions.indexOptions.showActivePreparationTasksInProgress = experimentalShowActivePreparationTasksInProgress
224233
serverOptions.completionOptions.maxResults = completionMaxResults
225234
serverOptions.generatedInterfacesPath = generatedInterfacesPath
226235

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ final class BackgroundIndexingTests: XCTestCase {
462462
return
463463
}
464464
var didGetEndWorkDoneProgress = false
465-
for _ in 0..<3 {
465+
for _ in 0..<5 {
466466
let workEndProgress = try await project.testClient.nextNotification(ofType: WorkDoneProgress.self)
467467
switch workEndProgress.value {
468468
case .begin:

Tests/SourceKitLSPTests/BuildSystemTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ final class BuildSystemTests: XCTestCase {
137137
indexDelegate: nil,
138138
indexTaskScheduler: .forTesting,
139139
indexTasksWereScheduled: { _ in },
140-
indexTaskDidFinish: {}
140+
indexStatusDidChange: {}
141141
)
142142

143143
await server.setWorkspaces([(workspace: workspace, isImplicit: false)])

0 commit comments

Comments
 (0)