mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
Move syntactic index back to Workspace
This commit is contained in:
@@ -30,16 +30,6 @@ package protocol BuildServerManagerDelegate: AnyObject, Sendable {
|
||||
/// Notify the delegate that some information about the given build targets has changed and that it should recompute
|
||||
/// any information based on top of it.
|
||||
func buildTargetsChanged(_ changedTargets: Set<BuildTargetIdentifier>?) async
|
||||
|
||||
func addBuiltTargetListener(_ listener: any BuildTargetListener)
|
||||
|
||||
func removeBuiltTargetListener(_ listener: any BuildTargetListener)
|
||||
}
|
||||
|
||||
package protocol BuildTargetListener: AnyObject, Sendable {
|
||||
/// Notify the listener that some information about the given build targets has changed and that it should recompute
|
||||
/// any information based on top of it.
|
||||
func buildTargetsChanged(_ changedTargets: Set<BuildTargetIdentifier>?) async
|
||||
}
|
||||
|
||||
/// Methods with which the `BuildServerManager` can send messages to the client (aka. editor).
|
||||
|
||||
@@ -644,11 +644,14 @@ extension ClangLanguageService {
|
||||
return nil
|
||||
}
|
||||
|
||||
package func syntacticTests(in workspace: Workspace) async -> [AnnotatedTestItem] {
|
||||
package func syntacticTestItems(for snapshot: DocumentSnapshot) async -> [AnnotatedTestItem] {
|
||||
return []
|
||||
}
|
||||
|
||||
package func syntacticPlaygrounds(in workspace: Workspace) async -> [Playground] {
|
||||
package func syntacticPlaygrounds(
|
||||
for snapshot: DocumentSnapshot,
|
||||
in workspace: Workspace
|
||||
) async -> [TextDocumentPlayground] {
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@@ -88,11 +88,14 @@ package actor DocumentationLanguageService: LanguageService, Sendable {
|
||||
// The DocumentationLanguageService does not do anything with document events
|
||||
}
|
||||
|
||||
package func syntacticTests(in workspace: Workspace) async -> [AnnotatedTestItem] {
|
||||
package func syntacticTestItems(for snapshot: DocumentSnapshot) async -> [AnnotatedTestItem] {
|
||||
return []
|
||||
}
|
||||
|
||||
package func syntacticPlaygrounds(in workspace: Workspace) async -> [Playground] {
|
||||
package func syntacticPlaygrounds(
|
||||
for snapshot: DocumentSnapshot,
|
||||
in workspace: Workspace
|
||||
) async -> [TextDocumentPlayground] {
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@@ -313,15 +313,20 @@ package protocol LanguageService: AnyObject, Sendable {
|
||||
/// A return value of `nil` indicates that this language service does not support syntactic test discovery.
|
||||
func syntacticDocumentTests(for uri: DocumentURI, in workspace: Workspace) async throws -> [AnnotatedTestItem]?
|
||||
|
||||
/// Returns the syntactically scanned tests declared within the workspace.
|
||||
/// Syntactically scans the file at the given URL for tests declared within it.
|
||||
///
|
||||
/// Does not write the results to the index.
|
||||
///
|
||||
/// The order of the returned tests is not defined. The results should be sorted before being returned to the editor.
|
||||
func syntacticTests(in workspace: Workspace) async -> [AnnotatedTestItem]
|
||||
func syntacticTestItems(for snapshot: DocumentSnapshot) async -> [AnnotatedTestItem]
|
||||
|
||||
/// Returns the syntactically scanned playgrounds declared within the workspace.
|
||||
///
|
||||
/// The order of the returned playgrounds is not defined. The results should be sorted before being returned to the editor.
|
||||
func syntacticPlaygrounds(in workspace: Workspace) async -> [Playground]
|
||||
func syntacticPlaygrounds(
|
||||
for snapshot: DocumentSnapshot,
|
||||
in workspace: Workspace
|
||||
) async -> [TextDocumentPlayground]
|
||||
|
||||
/// A position that is canonical for all positions within a declaration. For example, if we have the following
|
||||
/// declaration, then all `|` markers should return the same canonical position.
|
||||
|
||||
@@ -28,9 +28,7 @@ extension SourceKitLSPServer {
|
||||
// playgrounds.
|
||||
await workspace.buildServerManager.waitForUpToDateBuildGraph()
|
||||
|
||||
let playgroundsFromSyntacticIndex = await languageServices.values.asyncFlatMap {
|
||||
await $0.asyncFlatMap { await $0.syntacticPlaygrounds(in: workspace) }
|
||||
}
|
||||
let playgroundsFromSyntacticIndex = await workspace.syntacticIndex.playgrounds()
|
||||
|
||||
// We don't need to sort the playgrounds here because they will get sorted by `workspacePlaygrounds` request handler
|
||||
return playgroundsFromSyntacticIndex
|
||||
|
||||
@@ -83,7 +83,7 @@ private struct IndexedSourceFile {
|
||||
///
|
||||
/// The index does not get persisted to disk but instead gets rebuilt every time a workspace is opened (ie. usually when
|
||||
/// sourcekit-lsp is launched). Building it takes only a few seconds, even for large projects.
|
||||
package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
package actor SwiftSyntacticIndex: Sendable {
|
||||
/// The tests discovered by the index.
|
||||
private var indexedSources: [DocumentURI: IndexedSourceFile] = [:]
|
||||
|
||||
@@ -104,20 +104,23 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
/// Fetch the list of source files to scan for a given set of build targets
|
||||
private let determineFilesToScan: @Sendable (Set<BuildTargetIdentifier>?) async -> [DocumentURI]
|
||||
|
||||
// Syntactically parse tests from the given snapshot
|
||||
private let syntacticTests: @Sendable (DocumentSnapshot) async -> [AnnotatedTestItem]
|
||||
/// Syntactically parse tests from the given snapshot
|
||||
private let syntacticTests: @Sendable (DocumentSnapshot, Workspace) async -> [AnnotatedTestItem]
|
||||
|
||||
// Syntactically parse playgrounds from the given snapshot
|
||||
private let syntacticPlaygrounds: @Sendable (DocumentSnapshot) async -> [TextDocumentPlayground]
|
||||
/// Syntactically parse playgrounds from the given snapshot
|
||||
private let syntacticPlaygrounds: @Sendable (DocumentSnapshot, Workspace) async -> [TextDocumentPlayground]
|
||||
|
||||
package init(
|
||||
determineFilesToScan: @Sendable @escaping (Set<BuildTargetIdentifier>?) async -> [DocumentURI],
|
||||
syntacticTests: @Sendable @escaping (DocumentSnapshot) async -> [AnnotatedTestItem],
|
||||
syntacticPlaygrounds: @Sendable @escaping (DocumentSnapshot) async -> [TextDocumentPlayground]
|
||||
syntacticTests: @Sendable @escaping (DocumentSnapshot, Workspace) async -> [AnnotatedTestItem],
|
||||
syntacticPlaygrounds: @Sendable @escaping (DocumentSnapshot, Workspace) async -> [TextDocumentPlayground]
|
||||
) {
|
||||
self.determineFilesToScan = determineFilesToScan
|
||||
self.syntacticTests = syntacticTests
|
||||
self.syntacticPlaygrounds = syntacticPlaygrounds
|
||||
}
|
||||
|
||||
func scan(workspace: Workspace) {
|
||||
indexingQueue.async(priority: .low, metadata: .initialPopulation) {
|
||||
let filesToScan = await self.determineFilesToScan(nil)
|
||||
// Divide the files into multiple batches. This is more efficient than spawning a new task for every file, mostly
|
||||
@@ -128,7 +131,7 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
let batches = filesToScan.partition(intoNumberOfBatches: ProcessInfo.processInfo.activeProcessorCount * 4)
|
||||
await batches.concurrentForEach { filesInBatch in
|
||||
for uri in filesInBatch {
|
||||
await self.rescanFileAssumingOnQueue(uri)
|
||||
await self.rescanFileAssumingOnQueue(uri, workspace)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,15 +147,15 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
/// Called when the list of targets is updated.
|
||||
///
|
||||
/// All files that are not in the new list of buildable files will be removed from the index.
|
||||
package func buildTargetsChanged(_ changedTargets: Set<BuildTargetIdentifier>?) async {
|
||||
package func buildTargetsChanged(_ changedTargets: Set<BuildTargetIdentifier>?, _ workspace: Workspace) async {
|
||||
let changedFiles = await determineFilesToScan(changedTargets)
|
||||
let removedFiles = Set(self.indexedSources.keys).subtracting(changedFiles)
|
||||
removeFilesFromIndex(removedFiles)
|
||||
|
||||
rescanFiles(changedFiles)
|
||||
rescanFiles(changedFiles, workspace)
|
||||
}
|
||||
|
||||
package func filesDidChange(_ events: [FileEvent]) {
|
||||
package func filesDidChange(_ events: [FileEvent], _ workspace: Workspace) {
|
||||
var removedFiles: Set<DocumentURI> = []
|
||||
var filesToRescan: [DocumentURI] = []
|
||||
for fileEvent in events {
|
||||
@@ -168,11 +171,11 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
}
|
||||
}
|
||||
removeFilesFromIndex(removedFiles)
|
||||
rescanFiles(filesToRescan)
|
||||
rescanFiles(filesToRescan, workspace)
|
||||
}
|
||||
|
||||
/// Called when a list of files was updated. Re-scans those files
|
||||
private func rescanFiles(_ uris: [DocumentURI]) {
|
||||
private func rescanFiles(_ uris: [DocumentURI], _ workspace: Workspace) {
|
||||
// If we scan a file again, it might have been added after being removed before. Remove it from the list of removed
|
||||
// files.
|
||||
removedFiles.subtract(uris)
|
||||
@@ -212,7 +215,7 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
for batch in batches {
|
||||
self.indexingQueue.async(priority: .low, metadata: .index(Set(batch))) {
|
||||
for uri in batch {
|
||||
await self.rescanFileAssumingOnQueue(uri)
|
||||
await self.rescanFileAssumingOnQueue(uri, workspace)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +224,7 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
/// Re-scans a single file.
|
||||
///
|
||||
/// - Important: This method must be called in a task that is executing on `indexingQueue`.
|
||||
private func rescanFileAssumingOnQueue(_ uri: DocumentURI) async {
|
||||
private func rescanFileAssumingOnQueue(_ uri: DocumentURI, _ workspace: Workspace) async {
|
||||
guard let url = uri.fileURL else {
|
||||
logger.log("Not indexing \(uri.forLogging) because it is not a file URL")
|
||||
return
|
||||
@@ -254,10 +257,6 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = uri.fileURL else {
|
||||
logger.log("Not indexing \(uri.forLogging) because it is not a file URL")
|
||||
return
|
||||
}
|
||||
let snapshot: DocumentSnapshot? = orLog("Getting document snapshot for syntactic Swift scanning") {
|
||||
try DocumentSnapshot(withContentsFromDisk: url, language: .swift)
|
||||
}
|
||||
@@ -265,7 +264,9 @@ package actor SwiftSyntacticIndex: BuildTargetListener, Sendable {
|
||||
return
|
||||
}
|
||||
|
||||
let (testItems, playgrounds) = await (syntacticTests(snapshot), syntacticPlaygrounds(snapshot))
|
||||
let (testItems, playgrounds) = await (
|
||||
syntacticTests(snapshot, workspace), syntacticPlaygrounds(snapshot, workspace)
|
||||
)
|
||||
|
||||
guard !removedFiles.contains(uri) else {
|
||||
// Check whether the file got removed while we were scanning it for tests. If so, don't add it back to
|
||||
|
||||
@@ -245,9 +245,7 @@ extension SourceKitLSPServer {
|
||||
|
||||
let semanticTestSymbolOccurrences = index?.unitTests().filter { return $0.canBeTestDefinition } ?? []
|
||||
|
||||
let testsFromSyntacticIndex = await languageServices.values.asyncFlatMap {
|
||||
await $0.asyncFlatMap { await $0.syntacticTests(in: workspace) }
|
||||
}
|
||||
let testsFromSyntacticIndex = await workspace.syntacticIndex.tests()
|
||||
let testsFromSemanticIndex = testItems(
|
||||
for: semanticTestSymbolOccurrences,
|
||||
index: index,
|
||||
|
||||
@@ -184,13 +184,12 @@ package final class Workspace: Sendable, BuildServerManagerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
/// The index that syntactically scans the workspace for Swift symbols.
|
||||
let syntacticIndex: SwiftSyntacticIndex
|
||||
|
||||
/// Language service for an open document, if available.
|
||||
private let languageServices: ThreadSafeBox<[DocumentURI: [any LanguageService]]> = ThreadSafeBox(initialValue: [:])
|
||||
|
||||
/// Build target listeners
|
||||
private let buildTargetListeners: ThreadSafeBox<[ObjectIdentifier: BuildTargetListener]> = ThreadSafeBox(
|
||||
initialValue: [:])
|
||||
|
||||
/// The task that constructs the `SemanticIndexManager`, which keeps track of whose file's index is up-to-date in the
|
||||
/// workspace and schedules indexing and preparation tasks for files with out-of-date index.
|
||||
///
|
||||
@@ -261,6 +260,25 @@ package final class Workspace: Sendable, BuildServerManagerDelegate {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Trigger an initial population of `syntacticIndex`.
|
||||
self.syntacticIndex = SwiftSyntacticIndex(
|
||||
determineFilesToScan: { targets in
|
||||
await orLog("Getting list of files for syntactic index population") {
|
||||
try await buildServerManager.projectSourceFiles(in: targets)
|
||||
} ?? []
|
||||
},
|
||||
syntacticTests: { (snapshot, workspace) in
|
||||
await sourceKitLSPServer.languageServices(for: snapshot.uri, snapshot.language, in: workspace).asyncFlatMap {
|
||||
await $0.syntacticTestItems(for: snapshot)
|
||||
}
|
||||
},
|
||||
syntacticPlaygrounds: { (snapshot, workspace) in
|
||||
await sourceKitLSPServer.languageServices(for: snapshot.uri, snapshot.language, in: workspace).asyncFlatMap {
|
||||
await $0.syntacticPlaygrounds(for: snapshot, in: workspace)
|
||||
}
|
||||
}
|
||||
)
|
||||
await syntacticIndex.scan(workspace: self)
|
||||
}
|
||||
|
||||
/// Creates a workspace for a given root `DocumentURI`, inferring the `ExternalWorkspace` if possible.
|
||||
@@ -399,9 +417,9 @@ package final class Workspace: Sendable, BuildServerManagerDelegate {
|
||||
// Notify all clients about the reported and inferred edits.
|
||||
await buildServerManager.filesDidChange(events)
|
||||
|
||||
async let updateServer: Void? = await sourceKitLSPServer?.filesDidChange(events)
|
||||
async let updateSyntacticIndex: Void = await syntacticIndex.filesDidChange(events, self)
|
||||
async let updateSemanticIndex: Void? = await semanticIndexManager?.filesDidChange(events)
|
||||
_ = await (updateServer, updateSemanticIndex)
|
||||
_ = await (updateSyntacticIndex, updateSemanticIndex)
|
||||
}
|
||||
|
||||
/// The language services that can handle the given document. Callers should try to merge the results from the
|
||||
@@ -464,26 +482,12 @@ package final class Workspace: Sendable, BuildServerManagerDelegate {
|
||||
await sourceKitLSPServer?.fileHandlingCapabilityChanged()
|
||||
await semanticIndexManager?.buildTargetsChanged(changedTargets)
|
||||
await orLog("Scheduling syntactic file re-indexing") {
|
||||
_ = await buildTargetListeners.value.values.asyncMap {
|
||||
await $0.buildTargetsChanged(changedTargets)
|
||||
}
|
||||
await syntacticIndex.buildTargetsChanged(changedTargets, self)
|
||||
}
|
||||
|
||||
await scheduleUpdateOfUnitOutputPathsInIndexIfNecessary()
|
||||
}
|
||||
|
||||
package func addBuiltTargetListener(_ listener: any BuildTargetListener) {
|
||||
buildTargetListeners.withLock {
|
||||
$0[ObjectIdentifier(listener)] = listener
|
||||
}
|
||||
}
|
||||
|
||||
package func removeBuiltTargetListener(_ listener: any BuildTargetListener) {
|
||||
buildTargetListeners.withLock {
|
||||
$0[ObjectIdentifier(listener)] = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func scheduleUpdateOfUnitOutputPathsInIndexIfNecessary() async {
|
||||
indexUnitOutputPathsUpdateQueue.async {
|
||||
guard await self.uncheckedIndex?.usesExplicitOutputPaths ?? false else {
|
||||
|
||||
@@ -13,24 +13,18 @@
|
||||
import BuildServerIntegration
|
||||
@_spi(SourceKitLSP) import BuildServerProtocol
|
||||
import Foundation
|
||||
@_spi(SourceKitLSP) import LanguageServerProtocol
|
||||
@_spi(SourceKitLSP) package import LanguageServerProtocol
|
||||
@_spi(SourceKitLSP) import SKLogging
|
||||
import SemanticIndex
|
||||
import SourceKitLSP
|
||||
package import SourceKitLSP
|
||||
import SwiftExtensions
|
||||
import ToolchainRegistry
|
||||
|
||||
extension SwiftLanguageService {
|
||||
static func syntacticPlaygrounds(
|
||||
package func syntacticPlaygrounds(
|
||||
for snapshot: DocumentSnapshot,
|
||||
in workspace: Workspace,
|
||||
using syntaxTreeManager: SyntaxTreeManager,
|
||||
toolchain: Toolchain
|
||||
in workspace: Workspace
|
||||
) async -> [TextDocumentPlayground] {
|
||||
guard toolchain.swiftPlay != nil else {
|
||||
return []
|
||||
}
|
||||
return await SwiftPlaygroundsScanner.findDocumentPlaygrounds(
|
||||
await SwiftPlaygroundsScanner.findDocumentPlaygrounds(
|
||||
for: snapshot,
|
||||
workspace: workspace,
|
||||
syntaxTreeManager: syntaxTreeManager
|
||||
|
||||
@@ -139,9 +139,6 @@ package actor SwiftLanguageService: LanguageService, Sendable {
|
||||
/// Shared syntax tree manager to share syntax trees when syntactically parsing different types
|
||||
let syntaxTreeManager: SyntaxTreeManager
|
||||
|
||||
/// The index that syntactically scans the workspace.
|
||||
let syntacticIndex: SwiftSyntacticIndex
|
||||
|
||||
/// Workspace this language service was created for
|
||||
let workspace: Workspace
|
||||
|
||||
@@ -279,27 +276,6 @@ package actor SwiftLanguageService: LanguageService, Sendable {
|
||||
clientHasDiagnosticsCodeDescriptionSupport: await capabilityRegistry.clientHasDiagnosticsCodeDescriptionSupport
|
||||
)
|
||||
|
||||
// Trigger an initial population of `syntacticIndex`.
|
||||
self.syntacticIndex = SwiftSyntacticIndex(
|
||||
determineFilesToScan: { targets in
|
||||
await orLog("Getting list of files for syntactic index population") {
|
||||
try await workspace.buildServerManager.projectSourceFiles(in: targets)
|
||||
} ?? []
|
||||
},
|
||||
syntacticTests: {
|
||||
await SwiftLanguageService.syntacticTestItems(for: $0, using: syntaxTreeManager)
|
||||
},
|
||||
syntacticPlaygrounds: {
|
||||
await SwiftLanguageService.syntacticPlaygrounds(
|
||||
for: $0,
|
||||
in: workspace,
|
||||
using: syntaxTreeManager,
|
||||
toolchain: toolchain
|
||||
)
|
||||
}
|
||||
)
|
||||
workspace.addBuiltTargetListener(syntacticIndex)
|
||||
|
||||
self.macroExpansionManager = MacroExpansionManager(swiftLanguageService: self)
|
||||
self.generatedInterfaceManager = GeneratedInterfaceManager(swiftLanguageService: self)
|
||||
|
||||
@@ -397,10 +373,6 @@ package actor SwiftLanguageService: LanguageService, Sendable {
|
||||
) {
|
||||
self.stateChangeHandlers.append(handler)
|
||||
}
|
||||
|
||||
package func filesDidChange(_ events: [FileEvent]) async {
|
||||
await syntacticIndex.filesDidChange(events)
|
||||
}
|
||||
}
|
||||
|
||||
extension SwiftLanguageService {
|
||||
@@ -458,7 +430,6 @@ extension SwiftLanguageService {
|
||||
}
|
||||
|
||||
package func shutdown() async {
|
||||
self.workspace.removeBuiltTargetListener(syntacticIndex)
|
||||
await self.sourcekitd.removeNotificationHandler(self)
|
||||
}
|
||||
|
||||
@@ -1154,14 +1125,6 @@ extension SwiftLanguageService {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
package func syntacticTests(in workspace: Workspace) async -> [AnnotatedTestItem] {
|
||||
await syntacticIndex.tests()
|
||||
}
|
||||
|
||||
package func syntacticPlaygrounds(in workspace: Workspace) async -> [Playground] {
|
||||
await syntacticIndex.playgrounds()
|
||||
}
|
||||
}
|
||||
|
||||
extension SwiftLanguageService: SKDNotificationHandler {
|
||||
|
||||
@@ -52,9 +52,8 @@ extension SwiftLanguageService {
|
||||
/// Does not write the results to the index.
|
||||
///
|
||||
/// The order of the returned tests is not defined. The results should be sorted before being returned to the editor.
|
||||
static package func syntacticTestItems(
|
||||
package func syntacticTestItems(
|
||||
for snapshot: DocumentSnapshot,
|
||||
using syntaxTreeManager: SyntaxTreeManager
|
||||
) async -> [AnnotatedTestItem] {
|
||||
async let swiftTestingTests = SyntacticSwiftTestingTestScanner.findTestSymbols(
|
||||
in: snapshot,
|
||||
|
||||
Reference in New Issue
Block a user