@@ -16,13 +16,14 @@ import LanguageServerProtocol
16
16
import SKCore
17
17
18
18
/// Describes the state of indexing for a single source file
19
- private enum FileIndexStatus {
19
+ private enum IndexStatus {
20
20
/// The index is up-to-date.
21
21
case upToDate
22
- /// The file is not up to date and we have scheduled a task to index it but that index operation hasn't been started
23
- /// yet.
22
+ /// The file or target is not up to date and we have scheduled a task to update the index store for the file / prepare
23
+ /// the target it but that index operation hasn't been started yet.
24
24
case scheduled( Task < Void , Never > )
25
- /// We are currently actively indexing this file, ie. we are running a subprocess that indexes the file.
25
+ /// We are currently actively updating the index store for the file / preparing the target, ie. we are running a
26
+ /// subprocess that updates the index store / prepares a target.
26
27
case executing( Task < Void , Never > )
27
28
28
29
var description : String {
@@ -46,15 +47,20 @@ public final actor SemanticIndexManager {
46
47
/// The build system manager that is used to get compiler arguments for a file.
47
48
private let buildSystemManager : BuildSystemManager
48
49
49
- /// The index status of the source files that the `SemanticIndexManager` knows about.
50
- ///
51
- /// Files that have never been indexed are not in this dictionary.
52
- private var indexStatus : [ DocumentURI : FileIndexStatus ] = [ : ]
53
-
54
50
/// The task to generate the build graph (resolving package dependencies, generating the build description,
55
51
/// ...). `nil` if no build graph is currently being generated.
56
52
private var generateBuildGraphTask : Task < Void , Never > ?
57
53
54
+ /// The preparation status of the targets that the `SemanticIndexManager` has started preparation for.
55
+ ///
56
+ /// Targets will be removed from this dictionary when they are no longer known to be up-to-date.
57
+ private var preparationStatus : [ ConfiguredTarget : IndexStatus ] = [ : ]
58
+
59
+ /// The index status of the source files that the `SemanticIndexManager` knows about.
60
+ ///
61
+ /// Files will be removed from this dictionary if their index is no longer up-to-date.
62
+ private var indexStatus : [ DocumentURI : IndexStatus ] = [ : ]
63
+
58
64
/// The `TaskScheduler` that manages the scheduling of index tasks. This is shared among all `SemanticIndexManager`s
59
65
/// in the process, to ensure that we don't schedule more index operations than processor cores from multiple
60
66
/// workspaces.
@@ -79,13 +85,13 @@ public final actor SemanticIndexManager {
79
85
///
80
86
/// See `FileIndexStatus` for the distinction between `scheduled` and `executing`.
81
87
public var inProgressIndexTasks : ( scheduled: [ DocumentURI ] , executing: [ DocumentURI ] ) {
82
- let scheduled = indexStatus. compactMap { ( uri: DocumentURI , status: FileIndexStatus ) in
88
+ let scheduled = indexStatus. compactMap { ( uri: DocumentURI , status: IndexStatus ) in
83
89
if case . scheduled = status {
84
90
return uri
85
91
}
86
92
return nil
87
93
}
88
- let inProgress = indexStatus. compactMap { ( uri: DocumentURI , status: FileIndexStatus ) in
94
+ let inProgress = indexStatus. compactMap { ( uri: DocumentURI , status: IndexStatus ) in
89
95
if case . executing = status {
90
96
return uri
91
97
}
@@ -192,6 +198,11 @@ public final actor SemanticIndexManager {
192
198
for uri in changedFiles {
193
199
indexStatus [ uri] = nil
194
200
}
201
+ // Clear the preparation status so that we re-prepare them. If the target hasn't been affected by the file changes,
202
+ // we rely on a null build during preparation to fast re-preparation.
203
+ // Ideally, we would have more fine-grained dependency management here and only mark those targets out-of-date that
204
+ // might be affected by the changed files.
205
+ preparationStatus. removeAll ( )
195
206
await scheduleBackgroundIndex ( files: changedFiles)
196
207
}
197
208
@@ -226,14 +237,59 @@ public final actor SemanticIndexManager {
226
237
227
238
/// Prepare the given targets for indexing
228
239
private func prepare( targets: [ ConfiguredTarget ] , priority: TaskPriority ? ) async {
240
+ let targetsToPrepare = targets. filter {
241
+ if case . upToDate = preparationStatus [ $0] {
242
+ return false
243
+ }
244
+ return true
245
+ }
229
246
let taskDescription = AnyIndexTaskDescription (
230
247
PreparationTaskDescription (
231
- targetsToPrepare: targets ,
248
+ targetsToPrepare: targetsToPrepare ,
232
249
buildSystemManager: self . buildSystemManager
233
250
)
234
251
)
235
- await self . indexTaskScheduler. schedule ( priority: priority, taskDescription) . value
236
- self . indexTaskDidFinish ( )
252
+ let preparationTask = await self . indexTaskScheduler. schedule ( priority: priority, taskDescription) { newState in
253
+ switch newState {
254
+ case . executing:
255
+ for target in targetsToPrepare {
256
+ if case . scheduled( let task) = self . preparationStatus [ target] {
257
+ self . preparationStatus [ target] = . executing( task)
258
+ } else {
259
+ logger. fault (
260
+ """
261
+ Preparation status of \( target. forLogging) is in an unexpected state \
262
+ ' \( self . preparationStatus [ target] ? . description ?? " <nil> " , privacy: . public) ' when preparation task \
263
+ started executing
264
+ """
265
+ )
266
+ }
267
+ }
268
+ case . cancelledToBeRescheduled:
269
+ for target in targetsToPrepare {
270
+ if case . executing( let task) = self . preparationStatus [ target] {
271
+ self . preparationStatus [ target] = . scheduled( task)
272
+ } else {
273
+ logger. fault (
274
+ """
275
+ Preparation status of \( target. forLogging) is in an unexpected state \
276
+ ' \( self . preparationStatus [ target] ? . description ?? " <nil> " , privacy: . public) ' when preparation task \
277
+ is cancelled to be rescheduled.
278
+ """
279
+ )
280
+ }
281
+ }
282
+ case . finished:
283
+ for target in targetsToPrepare {
284
+ self . preparationStatus [ target] = . upToDate
285
+ }
286
+ self . indexTaskDidFinish ( )
287
+ }
288
+ }
289
+ for target in targetsToPrepare {
290
+ preparationStatus [ target] = . scheduled( preparationTask)
291
+ }
292
+ await preparationTask. value
237
293
}
238
294
239
295
/// Update the index store for the given files, assuming that their targets have already been prepared.
0 commit comments