Skip to content

Commit fec5dd1

Browse files
authored
Merge pull request #1507 from ahoppen/re-index
Add a request to re-index all files in SourceKit-LSP
2 parents 4645610 + e23e300 commit fec5dd1

File tree

8 files changed

+185
-12
lines changed

8 files changed

+185
-12
lines changed

Documentation/LSP Extensions.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,17 @@ New request that returns symbols for all the test classes and test methods withi
431431
```ts
432432
export interface WorkspaceTestsParams {}
433433
```
434+
435+
## `workspace/triggerReindex`
436+
437+
New request to re-index all files open in the SourceKit-LSP server.
438+
439+
Users should not need to rely on this request. The index should always be updated automatically in the background. Having to invoke this request means there is a bug in SourceKit-LSP's automatic re-indexing. It does, however, offer a workaround to re-index files when such a bug occurs where otherwise there would be no workaround.
440+
441+
442+
- params: `TriggerReindexParams`
443+
- result: `void`
444+
445+
```ts
446+
export interface TriggerReindexParams {}
447+
```

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ add_library(LanguageServerProtocol STATIC
7878
Requests/ShutdownRequest.swift
7979
Requests/SignatureHelpRequest.swift
8080
Requests/SymbolInfoRequest.swift
81+
Requests/TriggerReindexRequest.swift
8182
Requests/TypeDefinitionRequest.swift
8283
Requests/TypeHierarchyPrepareRequest.swift
8384
Requests/TypeHierarchySubtypesRequest.swift
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// Re-index all files open in the SourceKit-LSP server.
14+
///
15+
/// Users should not need to rely on this request. The index should always be updated automatically in the background.
16+
/// Having to invoke this request means there is a bug in SourceKit-LSP's automatic re-indexing. It does, however, offer
17+
/// a workaround to re-index files when such a bug occurs where otherwise there would be no workaround.
18+
///
19+
/// **LSP Extension**
20+
public struct TriggerReindexRequest: RequestType {
21+
public static let method: String = "workspace/triggerReindex"
22+
public typealias Response = VoidResponse
23+
24+
public init() {}
25+
}

Sources/SemanticIndex/SemanticIndexManager.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,15 +231,15 @@ public final actor SemanticIndexManager {
231231
/// Returns immediately after scheduling that task.
232232
///
233233
/// Indexing is being performed with a low priority.
234-
private func scheduleBackgroundIndex(files: some Collection<DocumentURI>) async {
235-
_ = await self.scheduleIndexing(of: files, priority: .low)
234+
private func scheduleBackgroundIndex(files: some Collection<DocumentURI>, indexFilesWithUpToDateUnit: Bool) async {
235+
_ = await self.scheduleIndexing(of: files, indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit, priority: .low)
236236
}
237237

238238
/// Regenerate the build graph (also resolving package dependencies) and then index all the source files known to the
239239
/// build system that don't currently have a unit with a timestamp that matches the mtime of the file.
240240
///
241241
/// This method is intended to initially update the index of a project after it is opened.
242-
public func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles() async {
242+
public func scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: Bool = false) async {
243243
generateBuildGraphTask = Task(priority: .low) {
244244
await withLoggingSubsystemAndScope(subsystem: indexLoggingSubsystem, scope: "build-graph-generation") {
245245
logger.log(
@@ -263,16 +263,26 @@ public final actor SemanticIndexManager {
263263
// potentially not knowing about unit files, which causes the corresponding source files to be re-indexed.
264264
index.pollForUnitChangesAndWait()
265265
await testHooks.buildGraphGenerationDidFinish?()
266-
let index = index.checked(for: .modifiedFiles)
267-
let filesToIndex = await self.buildSystemManager.sourceFiles().lazy.map(\.uri)
268-
.filter { !index.hasUpToDateUnit(for: $0) }
269-
await scheduleBackgroundIndex(files: filesToIndex)
266+
var filesToIndex: any Collection<DocumentURI> = await self.buildSystemManager.sourceFiles().lazy.map(\.uri)
267+
if !indexFilesWithUpToDateUnit {
268+
let index = index.checked(for: .modifiedFiles)
269+
filesToIndex = filesToIndex.filter { !index.hasUpToDateUnit(for: $0) }
270+
}
271+
await scheduleBackgroundIndex(files: filesToIndex, indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit)
270272
generateBuildGraphTask = nil
271273
}
272274
}
273275
indexProgressStatusDidChange()
274276
}
275277

278+
/// Causes all files to be re-indexed even if the unit file for the source file is up to date.
279+
/// See `TriggerReindexRequest`.
280+
public func scheduleReindex() async {
281+
await indexStoreUpToDateTracker.markAllKnownOutOfDate()
282+
await preparationUpToDateTracker.markAllKnownOutOfDate()
283+
await scheduleBuildGraphGenerationAndBackgroundIndexAllFiles(indexFilesWithUpToDateUnit: true)
284+
}
285+
276286
/// Wait for all in-progress index tasks to finish.
277287
public func waitForUpToDateIndex() async {
278288
logger.info("Waiting for up-to-date index")
@@ -312,7 +322,7 @@ public final actor SemanticIndexManager {
312322
// Create a new index task for the files that aren't up-to-date. The newly scheduled index tasks will
313323
// - Wait for the existing index operations to finish if they have the same number of files.
314324
// - Reschedule the background index task in favor of an index task with fewer source files.
315-
await self.scheduleIndexing(of: uris, priority: nil).value
325+
await self.scheduleIndexing(of: uris, indexFilesWithUpToDateUnit: false, priority: nil).value
316326
index.pollForUnitChangesAndWait()
317327
logger.debug("Done waiting for up-to-date index")
318328
}
@@ -347,7 +357,7 @@ public final actor SemanticIndexManager {
347357
await preparationUpToDateTracker.markOutOfDate(inProgressPreparationTasks.keys)
348358
}
349359

350-
await scheduleBackgroundIndex(files: changedFiles)
360+
await scheduleBackgroundIndex(files: changedFiles, indexFilesWithUpToDateUnit: false)
351361
}
352362

353363
/// Returns the files that should be indexed to get up-to-date index information for the given files.
@@ -500,6 +510,7 @@ public final actor SemanticIndexManager {
500510
/// Update the index store for the given files, assuming that their targets have already been prepared.
501511
private func updateIndexStore(
502512
for filesAndTargets: [FileAndTarget],
513+
indexFilesWithUpToDateUnit: Bool,
503514
preparationTaskID: UUID,
504515
priority: TaskPriority?
505516
) async {
@@ -509,6 +520,7 @@ public final actor SemanticIndexManager {
509520
buildSystemManager: self.buildSystemManager,
510521
index: index,
511522
indexStoreUpToDateTracker: indexStoreUpToDateTracker,
523+
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
512524
logMessageToIndexLog: logMessageToIndexLog,
513525
testHooks: testHooks
514526
)
@@ -545,6 +557,7 @@ public final actor SemanticIndexManager {
545557
/// The returned task finishes when all files are indexed.
546558
private func scheduleIndexing(
547559
of files: some Collection<DocumentURI>,
560+
indexFilesWithUpToDateUnit: Bool,
548561
priority: TaskPriority?
549562
) async -> Task<Void, Never> {
550563
// Perform a quick initial check to whether the files is up-to-date, in which case we don't need to schedule a
@@ -619,6 +632,7 @@ public final actor SemanticIndexManager {
619632
taskGroup.addTask {
620633
await self.updateIndexStore(
621634
for: fileBatch.map { FileAndTarget(file: $0, target: target) },
635+
indexFilesWithUpToDateUnit: indexFilesWithUpToDateUnit,
622636
preparationTaskID: preparationTaskID,
623637
priority: priority
624638
)

Sources/SemanticIndex/UpdateIndexStoreTaskDescription.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,18 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
105105
/// The build system manager that is used to get the toolchain and build settings for the files to index.
106106
private let buildSystemManager: BuildSystemManager
107107

108-
private let indexStoreUpToDateTracker: UpToDateTracker<DocumentURI>
109-
110108
/// A reference to the underlying index store. Used to check if the index is already up-to-date for a file, in which
111109
/// case we don't need to index it again.
112110
private let index: UncheckedIndex
113111

112+
private let indexStoreUpToDateTracker: UpToDateTracker<DocumentURI>
113+
114+
/// Whether files that have an up-to-date unit file should be indexed.
115+
///
116+
/// In general, this should be `false`. The only situation when this should be set to `true` is when the user
117+
/// explicitly requested a re-index of all files.
118+
private let indexFilesWithUpToDateUnit: Bool
119+
114120
/// See `SemanticIndexManager.logMessageToIndexLog`.
115121
private let logMessageToIndexLog: @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void
116122

@@ -139,13 +145,15 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
139145
buildSystemManager: BuildSystemManager,
140146
index: UncheckedIndex,
141147
indexStoreUpToDateTracker: UpToDateTracker<DocumentURI>,
148+
indexFilesWithUpToDateUnit: Bool,
142149
logMessageToIndexLog: @escaping @Sendable (_ taskID: IndexTaskID, _ message: String) -> Void,
143150
testHooks: IndexTestHooks
144151
) {
145152
self.filesToIndex = filesToIndex
146153
self.buildSystemManager = buildSystemManager
147154
self.index = index
148155
self.indexStoreUpToDateTracker = indexStoreUpToDateTracker
156+
self.indexFilesWithUpToDateUnit = indexFilesWithUpToDateUnit
149157
self.logMessageToIndexLog = logMessageToIndexLog
150158
self.testHooks = testHooks
151159
}
@@ -206,7 +214,9 @@ public struct UpdateIndexStoreTaskDescription: IndexTaskDescription {
206214
// If we know that the file is up-to-date without having ot hit the index, do that because it's fastest.
207215
return
208216
}
209-
guard !index.checked(for: .modifiedFiles).hasUpToDateUnit(for: file.sourceFile, mainFile: file.mainFile)
217+
guard
218+
indexFilesWithUpToDateUnit
219+
|| !index.checked(for: .modifiedFiles).hasUpToDateUnit(for: file.sourceFile, mainFile: file.mainFile)
210220
else {
211221
logger.debug("Not indexing \(file.forLogging) because index has an up-to-date unit")
212222
// We consider a file's index up-to-date if we have any up-to-date unit. Changing build settings does not

Sources/SourceKitLSP/MessageHandlingDependencyTracker.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ enum MessageHandlingDependencyTracker: DependencyTracker {
199199
self = .freestanding
200200
case is ShutdownRequest:
201201
self = .globalConfigurationChange
202+
case is TriggerReindexRequest:
203+
self = .globalConfigurationChange
202204
case is TypeHierarchySubtypesRequest:
203205
self = .freestanding
204206
case is TypeHierarchySupertypesRequest:

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,8 @@ extension SourceKitLSPServer: MessageHandler {
752752
await request.reply { try await shutdown(request.params) }
753753
case let request as RequestAndReply<SymbolInfoRequest>:
754754
await self.handleRequest(for: request, requestHandler: self.symbolInfo)
755+
case let request as RequestAndReply<TriggerReindexRequest>:
756+
await request.reply { try await triggerReindex(request.params) }
755757
case let request as RequestAndReply<TypeHierarchyPrepareRequest>:
756758
await self.handleRequest(for: request, requestHandler: self.prepareTypeHierarchy)
757759
case let request as RequestAndReply<TypeHierarchySubtypesRequest>:
@@ -2343,6 +2345,13 @@ extension SourceKitLSPServer {
23432345
}
23442346
return VoidResponse()
23452347
}
2348+
2349+
func triggerReindex(_ req: TriggerReindexRequest) async throws -> VoidResponse {
2350+
for workspace in workspaces {
2351+
await workspace.semanticIndexManager?.scheduleReindex()
2352+
}
2353+
return VoidResponse()
2354+
}
23462355
}
23472356

23482357
private func languageClass(for language: Language) -> [Language] {

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,104 @@ final class BackgroundIndexingTests: XCTestCase {
12461246
)
12471247
_ = try await project.testClient.send(PollIndexRequest())
12481248
}
1249+
1250+
func testManualReindex() async throws {
1251+
// This test relies on the issue described in https://github.com/apple/sourcekit-lsp/issues/1264 that we don't
1252+
// re-index dependent files if a function of a low-level module gains a new default parameter, which changes the
1253+
// function's USR but is API compatible with all dependencies.
1254+
// Check that after running the re-index request, the index gets updated.
1255+
1256+
let project = try await SwiftPMTestProject(
1257+
files: [
1258+
"LibA/LibA.swift": """
1259+
public func 1️⃣getInt() -> Int {
1260+
return 1
1261+
}
1262+
""",
1263+
"LibB/LibB.swift": """
1264+
import LibA
1265+
1266+
public func 2️⃣test() -> Int {
1267+
return 3️⃣getInt()
1268+
}
1269+
""",
1270+
],
1271+
manifest: """
1272+
let package = Package(
1273+
name: "MyLibrary",
1274+
targets: [
1275+
.target(name: "LibA"),
1276+
.target(name: "LibB", dependencies: ["LibA"]),
1277+
]
1278+
)
1279+
""",
1280+
enableBackgroundIndexing: true
1281+
)
1282+
1283+
let expectedCallHierarchyItem = CallHierarchyIncomingCall(
1284+
from: CallHierarchyItem(
1285+
name: "test()",
1286+
kind: .function,
1287+
tags: nil,
1288+
uri: try project.uri(for: "LibB.swift"),
1289+
range: try project.range(from: "2️⃣", to: "2️⃣", in: "LibB.swift"),
1290+
selectionRange: try project.range(from: "2️⃣", to: "2️⃣", in: "LibB.swift"),
1291+
data: .dictionary([
1292+
"usr": .string("s:4LibB4testSiyF"),
1293+
"uri": .string(try project.uri(for: "LibB.swift").stringValue),
1294+
])
1295+
),
1296+
fromRanges: [try project.range(from: "3️⃣", to: "3️⃣", in: "LibB.swift")]
1297+
)
1298+
1299+
/// Start by making a call hierarchy request to check that we get the expected results without any edits.
1300+
let (uri, positions) = try project.openDocument("LibA.swift")
1301+
let prepareBeforeUpdate = try await project.testClient.send(
1302+
CallHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1303+
)
1304+
let callHierarchyBeforeUpdate = try await project.testClient.send(
1305+
CallHierarchyIncomingCallsRequest(item: XCTUnwrap(prepareBeforeUpdate?.only))
1306+
)
1307+
XCTAssertEqual(callHierarchyBeforeUpdate, [expectedCallHierarchyItem])
1308+
1309+
// Now add a new default parameter to `getInt`.
1310+
project.testClient.send(DidCloseTextDocumentNotification(textDocument: TextDocumentIdentifier(uri)))
1311+
let newLibAContents = """
1312+
public func getInt(value: Int = 1) -> Int {
1313+
return value
1314+
}
1315+
"""
1316+
try newLibAContents.write(to: XCTUnwrap(uri.fileURL), atomically: true, encoding: .utf8)
1317+
project.testClient.send(
1318+
DidOpenTextDocumentNotification(
1319+
textDocument: TextDocumentItem(uri: uri, language: .swift, version: 0, text: newLibAContents)
1320+
)
1321+
)
1322+
project.testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: uri, type: .changed)]))
1323+
_ = try await project.testClient.send(PollIndexRequest())
1324+
1325+
// The USR of `getInt` has changed but LibB.swift has not been re-indexed due to
1326+
// https://github.com/apple/sourcekit-lsp/issues/1264. We expect to get an empty call hierarchy.
1327+
let prepareAfterUpdate = try await project.testClient.send(
1328+
CallHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1329+
)
1330+
let callHierarchyAfterUpdate = try await project.testClient.send(
1331+
CallHierarchyIncomingCallsRequest(item: XCTUnwrap(prepareAfterUpdate?.only))
1332+
)
1333+
XCTAssertEqual(callHierarchyAfterUpdate, [])
1334+
1335+
// After re-indexing, we expect to get a full call hierarchy again.
1336+
_ = try await project.testClient.send(TriggerReindexRequest())
1337+
_ = try await project.testClient.send(PollIndexRequest())
1338+
1339+
let prepareAfterReindex = try await project.testClient.send(
1340+
CallHierarchyPrepareRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
1341+
)
1342+
let callHierarchyAfterReindex = try await project.testClient.send(
1343+
CallHierarchyIncomingCallsRequest(item: XCTUnwrap(prepareAfterReindex?.only))
1344+
)
1345+
XCTAssertEqual(callHierarchyAfterReindex, [expectedCallHierarchyItem])
1346+
}
12491347
}
12501348

12511349
extension HoverResponseContents {

0 commit comments

Comments
 (0)