Skip to content

Generalize PollIndexRequest and BarrierRequest into a single SynchronizeRequest #2060

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions Contributor Documentation/LSP Extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,20 +546,6 @@ export interface StructuredLogEnd {
}
```

## `workspace/_pollIndex`

New request to wait until the index is up-to-date.

> [!IMPORTANT]
> This request is experimental and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.

- params: `PollIndexParams`
- result: `void`

```ts
export interface PollIndexParams {}
```

## `workspace/_setOptions`

New request to modify runtime options of SourceKit-LSP.
Expand Down Expand Up @@ -678,6 +664,33 @@ export interface SourceKitOptionsResult {
}
```

## `workspace/_synchronize`

New request from the client to the server to wait for SourceKit-LSP to handle all ongoing requests and, optionally, wait for background activity to finish.

> [!IMPORTANT]
> This request is experimental, guarded behind the `synchronize-request` experimental feature and may be modified or removed in future versions of SourceKit-LSP without notice. Do not rely on it.

- params: `SynchronizeParams`
- result: `void`

```ts
export interface SynchronizeParams {
/**
* Wait for the build server to have an up-to-date build graph by sending a `workspace/waitForBuildSystemUpdates` to
* it.
*/
buildServerUpdates?: bool

/**
* Wait for background indexing to finish and all index unit files to be loaded into indexstore-db.
*
* Implies `buildServerUpdates = true`.
*/
index?: bool
}
```

## `workspace/_outputPaths`

New request from the client to the server to retrieve the output paths of a target (see the `buildTarget/outputPaths` BSP request).
Expand Down
2 changes: 1 addition & 1 deletion Documentation/Configuration File.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ The structure of the file is currently not guaranteed to be stable. Options may
- `noLazy`: Prepare a target without generating object files but do not do lazy type checking and function body skipping. This uses SwiftPM's `--experimental-prepare-for-indexing-no-lazy` flag.
- `enabled`: Prepare a target without generating object files.
- `cancelTextDocumentRequestsOnEditAndClose: boolean`: Whether sending a `textDocument/didChange` or `textDocument/didClose` notification for a document should cancel all pending requests for that document.
- `experimentalFeatures: ("on-type-formatting"|"set-options-request"|"sourcekit-options-request"|"is-indexing-request"|"structured-logs"|"output-paths-request")[]`: Experimental features that are enabled.
- `experimentalFeatures: ("on-type-formatting"|"structured-logs")[]`: Experimental features that are enabled.
- `swiftPublishDiagnosticsDebounceDuration: number`: The time that `SwiftLanguageService` should wait after an edit before starting to compute diagnostics and sending a `PublishDiagnosticsNotification`.
- `workDoneProgressDebounceDuration: number`: When a task is started that should be displayed to the client as a work done progress, how many milliseconds to wait before actually starting the work done progress. This prevents flickering of the work done progress in the client for short-lived index tasks which end within this duration.
- `sourcekitdRequestTimeout: number`: The maximum duration that a sourcekitd request should be allowed to execute before being declared as timed out. In general, editors should cancel requests that they are no longer interested in, but in case editors don't cancel requests, this ensures that a long-running non-cancelled request is not blocking sourcekitd and thus most semantic functionality. In particular, VS Code does not cancel the semantic tokens request, which can cause a long-running AST build that blocks sourcekitd.
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ struct OptionSchemaContext {
guard let caseDecl = member.decl.as(EnumCaseDeclSyntax.self) else {
return []
}
return try caseDecl.elements.map {
return try caseDecl.elements.compactMap {
guard $0.parameterClause == nil else {
throw ConfigSchemaGenError("Associated values in enum cases are not supported: \(caseDecl)")
}
Expand All @@ -168,7 +168,11 @@ struct OptionSchemaContext {
} else {
name = $0.name.text
}
return OptionTypeSchama.Case(name: name, description: Self.extractDocComment(caseDecl.leadingTrivia))
let description = Self.extractDocComment(caseDecl.leadingTrivia)
if description?.contains("- Note: Internal option") ?? false {
return nil
}
return OptionTypeSchama.Case(name: name, description: description)
}
}
let typeName = node.name.text
Expand Down
2 changes: 1 addition & 1 deletion Sources/Diagnose/IndexCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ package struct IndexCommand: AsyncParsableCommand {
messageHandler: messageHandler
)
let start = ContinuousClock.now
_ = try await inProcessClient.send(PollIndexRequest())
_ = try await inProcessClient.send(SynchronizeRequest(index: true))
print("Indexing finished in \(start.duration(to: .now))")
if await messageHandler.hasSeenError {
throw ExitCode(1)
Expand Down
3 changes: 1 addition & 2 deletions Sources/LanguageServerProtocol/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ add_library(LanguageServerProtocol STATIC
Notifications/WorkDoneProgress.swift

Requests/ApplyEditRequest.swift
Requests/BarrierRequest.swift
Requests/CallHierarchyIncomingCallsRequest.swift
Requests/CallHierarchyOutgoingCallsRequest.swift
Requests/CallHierarchyPrepareRequest.swift
Expand Down Expand Up @@ -72,7 +71,6 @@ add_library(LanguageServerProtocol STATIC
Requests/MonikersRequest.swift
Requests/OutputPathsRequest.swift
Requests/PeekDocumentsRequest.swift
Requests/PollIndexRequest.swift
Requests/PrepareRenameRequest.swift
Requests/ReferencesRequest.swift
Requests/RegisterCapabilityRequest.swift
Expand All @@ -85,6 +83,7 @@ add_library(LanguageServerProtocol STATIC
Requests/SignatureHelpRequest.swift
Requests/SourceKitOptionsRequest.swift
Requests/SymbolInfoRequest.swift
Requests/SynchronizeRequest.swift
Requests/TriggerReindexRequest.swift
Requests/TypeDefinitionRequest.swift
Requests/TypeHierarchyPrepareRequest.swift
Expand Down
3 changes: 1 addition & 2 deletions Sources/LanguageServerProtocol/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
/// `MessageRegistry._register()` which allows you to avoid bloating the real server implementation.
public let builtinRequests: [_RequestType.Type] = [
ApplyEditRequest.self,
BarrierRequest.self,
CallHierarchyIncomingCallsRequest.self,
CallHierarchyOutgoingCallsRequest.self,
CallHierarchyPrepareRequest.self,
Expand Down Expand Up @@ -63,7 +62,7 @@ public let builtinRequests: [_RequestType.Type] = [
MonikersRequest.self,
OutputPathsRequest.self,
PeekDocumentsRequest.self,
PollIndexRequest.self,
SynchronizeRequest.self,
PrepareRenameRequest.self,
ReferencesRequest.self,
RegisterCapabilityRequest.self,
Expand Down
20 changes: 0 additions & 20 deletions Sources/LanguageServerProtocol/Requests/BarrierRequest.swift

This file was deleted.

20 changes: 0 additions & 20 deletions Sources/LanguageServerProtocol/Requests/PollIndexRequest.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

/// Wait for SourceKit-LSP to handle all ongoing requests and, optionally, wait for background activity to finish.
///
/// **LSP Extension, For Testing**.
public struct SynchronizeRequest: RequestType {
public static let method: String = "workspace/_synchronize"
public typealias Response = VoidResponse

/// Wait for the build server to have an up-to-date build graph by sending a `workspace/waitForBuildSystemUpdates` to
/// it.
public var buildServerUpdates: Bool?

/// Wait for background indexing to finish and all index unit files to be loaded into indexstore-db.
///
/// Implies `buildServerUpdates = true`.
public var index: Bool?

public init(buildServerUpdates: Bool? = nil, index: Bool? = nil) {
self.buildServerUpdates = buildServerUpdates
self.index = index
}
}
13 changes: 13 additions & 0 deletions Sources/SKOptions/ExperimentalFeatures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,30 @@ public enum ExperimentalFeature: String, Codable, Sendable, CaseIterable {
case onTypeFormatting = "on-type-formatting"

/// Enable support for the `workspace/_setOptions` request.
///
/// - Note: Internal option
case setOptionsRequest = "set-options-request"

/// Enable the `workspace/_sourceKitOptions` request.
///
/// - Note: Internal option
case sourceKitOptionsRequest = "sourcekit-options-request"

/// Enable the `sourceKit/_isIndexing` request.
///
/// - Note: Internal option
case isIndexingRequest = "is-indexing-request"

/// Indicate that the client can handle the experimental `structure` field in the `window/logMessage` notification.
case structuredLogs = "structured-logs"

/// Enable the `workspace/_outputPaths` request.
///
/// - Note: Internal option
case outputPathsRequest = "output-paths-request"

/// Enable the `workspace/_synchronize` request.
///
/// - Note: Internal option, for testing only
case synchronizeRequest = "synchronize-request"
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ package struct IndexedSingleSwiftFileTestProject {
)

// Wait for the indexstore-db to finish indexing
try await testClient.send(PollIndexRequest())
try await testClient.send(SynchronizeRequest(index: true))

// Open the document
self.fileURI = DocumentURI(testFileURL)
Expand Down
2 changes: 1 addition & 1 deletion Sources/SKTestSupport/MultiFileTestProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ package class MultiFileTestProject {
}
testClient.send(DidChangeWatchedFilesNotification(changes: [FileEvent(uri: uri, type: changeType)]))
// Ensure that we handle the `DidChangeWatchedFilesNotification`.
try await testClient.send(BarrierRequest())
try await testClient.send(SynchronizeRequest())

return (uri, positions)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/SKTestSupport/SwiftPMTestProject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ package class SwiftPMTestProject: MultiFileTestProject {

if pollIndex {
// Wait for the indexstore-db to finish indexing
try await testClient.send(PollIndexRequest())
try await testClient.send(SynchronizeRequest(index: true))
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/SKTestSupport/TestSourceKitLSPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import XCTest
extension SourceKitLSPOptions {
package static func testDefault(
backgroundIndexing: Bool = true,
experimentalFeatures: Set<ExperimentalFeature>? = nil
experimentalFeatures: Set<ExperimentalFeature> = [],
) async throws -> SourceKitLSPOptions {
let pluginPaths = try await sourceKitPluginPaths
return SourceKitLSPOptions(
Expand All @@ -36,7 +36,7 @@ extension SourceKitLSPOptions {
servicePlugin: try pluginPaths.servicePlugin.filePath
),
backgroundIndexing: backgroundIndexing,
experimentalFeatures: experimentalFeatures,
experimentalFeatures: experimentalFeatures.union([.synchronizeRequest]),
swiftPublishDiagnosticsDebounceDuration: 0,
workDoneProgressDebounceDuration: 0
)
Expand Down
6 changes: 2 additions & 4 deletions Sources/SourceKitLSP/MessageHandlingDependencyTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,6 @@ package enum MessageHandlingDependencyTracker: QueueBasedMessageHandlerDependenc
switch request {
case is ApplyEditRequest:
self = .freestanding
case is BarrierRequest:
self = .globalConfigurationChange
case is CallHierarchyIncomingCallsRequest:
self = .freestanding
case is CallHierarchyOutgoingCallsRequest:
Expand Down Expand Up @@ -208,8 +206,6 @@ package enum MessageHandlingDependencyTracker: QueueBasedMessageHandlerDependenc
self = .freestanding
case is OutputPathsRequest:
self = .freestanding
case is PollIndexRequest:
self = .globalConfigurationChange
case is RenameRequest:
// Rename might touch multiple files. Make it a global configuration change so that edits to all files that might
// be affected have been processed.
Expand All @@ -226,6 +222,8 @@ package enum MessageHandlingDependencyTracker: QueueBasedMessageHandlerDependenc
self = .globalConfigurationChange
case is SourceKitOptionsRequest:
self = .freestanding
case is SynchronizeRequest:
self = .globalConfigurationChange
case is TriggerReindexRequest:
self = .globalConfigurationChange
case is TypeHierarchySubtypesRequest:
Expand Down
21 changes: 13 additions & 8 deletions Sources/SourceKitLSP/SourceKitLSPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -742,8 +742,6 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
}

switch request {
case let request as RequestAndReply<BarrierRequest>:
await request.reply { VoidResponse() }
case let request as RequestAndReply<CallHierarchyIncomingCallsRequest>:
await request.reply { try await incomingCalls(request.params) }
case let request as RequestAndReply<CallHierarchyOutgoingCallsRequest>:
Expand Down Expand Up @@ -812,8 +810,6 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
await request.reply { try await self.isIndexing(request.params) }
case let request as RequestAndReply<OutputPathsRequest>:
await request.reply { try await outputPaths(request.params) }
case let request as RequestAndReply<PollIndexRequest>:
await request.reply { try await pollIndex(request.params) }
case let request as RequestAndReply<PrepareRenameRequest>:
await self.handleRequest(for: request, requestHandler: self.prepareRename)
case let request as RequestAndReply<ReferencesRequest>:
Expand All @@ -828,6 +824,8 @@ extension SourceKitLSPServer: QueueBasedMessageHandler {
await request.reply { try await shutdown(request.params) }
case let request as RequestAndReply<SymbolInfoRequest>:
await self.handleRequest(for: request, requestHandler: self.symbolInfo)
case let request as RequestAndReply<SynchronizeRequest>:
await request.reply { try await synchronize(request.params) }
case let request as RequestAndReply<TriggerReindexRequest>:
await request.reply { try await triggerReindex(request.params) }
case let request as RequestAndReply<TypeHierarchyPrepareRequest>:
Expand Down Expand Up @@ -2540,11 +2538,18 @@ extension SourceKitLSPServer {
return types.sorted { $0.name < $1.name }
}

func pollIndex(_ req: PollIndexRequest) async throws -> VoidResponse {
func synchronize(_ req: SynchronizeRequest) async throws -> VoidResponse {
guard self.options.hasExperimentalFeature(.synchronizeRequest) else {
throw ResponseError.unknown("\(SynchronizeRequest.method) indexing is an experimental request")
}
for workspace in workspaces {
await workspace.buildSystemManager.waitForUpToDateBuildGraph()
await workspace.semanticIndexManager?.waitForUpToDateIndex()
workspace.uncheckedIndex?.pollForUnitChangesAndWait()
if req.buildServerUpdates ?? false || req.index ?? false {
await workspace.buildSystemManager.waitForUpToDateBuildGraph()
}
if req.index ?? false {
await workspace.semanticIndexManager?.waitForUpToDateIndex()
workspace.uncheckedIndex?.pollForUnitChangesAndWait()
}
}
return VoidResponse()
}
Expand Down
Loading