@@ -115,6 +115,10 @@ final actor WorkDoneProgressState {
115
115
case progressCreationFailed
116
116
}
117
117
118
+ /// A queue so we can have synchronous `startProgress` and `endProgress` functions that don't need to wait for the
119
+ /// work done progress to be started or ended.
120
+ private let queue = AsyncQueue < Serial > ( )
121
+
118
122
/// How many active tasks are running.
119
123
///
120
124
/// A work done progress should be displayed if activeTasks > 0
@@ -135,13 +139,16 @@ final actor WorkDoneProgressState {
135
139
/// Start a new task, creating a new `WorkDoneProgress` if none is running right now.
136
140
///
137
141
/// - Parameter server: The server that is used to create the `WorkDoneProgress` on the client
138
- func startProgress( server: SourceKitLSPServer ) async {
142
+ nonisolated func startProgress( server: SourceKitLSPServer ) {
143
+ queue. async {
144
+ await self . startProgressImpl ( server: server)
145
+ }
146
+ }
147
+
148
+ func startProgressImpl( server: SourceKitLSPServer ) async {
149
+ await server. waitUntilInitialized ( )
139
150
activeTasks += 1
140
151
guard await server. capabilityRegistry? . clientCapabilities. window? . workDoneProgress ?? false else {
141
- // If the client doesn't support workDoneProgress, keep track of the active task count but don't update the state.
142
- // That way, if we call `startProgress` before initialization finishes, we won't send the
143
- // `CreateWorkDoneProgressRequest` but if we call `startProgress` again after initialization finished (and thus
144
- // the capability is set), we will create the work done progress.
145
152
return
146
153
}
147
154
if state == . noProgress {
@@ -180,7 +187,13 @@ final actor WorkDoneProgressState {
180
187
/// If this drops the active task count to 0, the work done progress is ended on the client.
181
188
///
182
189
/// - Parameter server: The server that is used to send and update of the `WorkDoneProgress` to the client
183
- func endProgress( server: SourceKitLSPServer ) async {
190
+ nonisolated func endProgress( server: SourceKitLSPServer ) {
191
+ queue. async {
192
+ await self . endProgressImpl ( server: server)
193
+ }
194
+ }
195
+
196
+ func endProgressImpl( server: SourceKitLSPServer ) async {
184
197
assert ( activeTasks > 0 , " Unbalanced startProgress/endProgress calls " )
185
198
activeTasks -= 1
186
199
guard await server. capabilityRegistry? . clientCapabilities. window? . workDoneProgress ?? false else {
@@ -440,11 +453,16 @@ public actor SourceKitLSPServer {
440
453
/// The connection to the editor.
441
454
public let client : Connection
442
455
456
+ /// Set to `true` after the `SourceKitLSPServer` has send the reply to the `InitializeRequest`.
457
+ ///
458
+ /// Initialization can be awaited using `waitUntilInitialized`.
459
+ private var initialized : Bool = false
460
+
443
461
var options : Options
444
462
445
463
let toolchainRegistry : ToolchainRegistry
446
464
447
- var capabilityRegistry : CapabilityRegistry ?
465
+ public var capabilityRegistry : CapabilityRegistry ?
448
466
449
467
var languageServices : [ LanguageServerType : [ LanguageService ] ] = [ : ]
450
468
@@ -508,19 +526,15 @@ public actor SourceKitLSPServer {
508
526
self . inProgressRequests [ id] = task
509
527
}
510
528
511
- let fs : FileSystem
512
-
513
529
var onExit : ( ) -> Void
514
530
515
531
/// Creates a language server for the given client.
516
532
public init (
517
533
client: Connection ,
518
- fileSystem: FileSystem = localFileSystem,
519
534
toolchainRegistry: ToolchainRegistry ,
520
535
options: Options ,
521
536
onExit: @escaping ( ) -> Void = { }
522
537
) {
523
- self . fs = fileSystem
524
538
self . toolchainRegistry = toolchainRegistry
525
539
self . options = options
526
540
self . onExit = onExit
@@ -534,6 +548,22 @@ public actor SourceKitLSPServer {
534
548
] )
535
549
}
536
550
551
+ /// Await until the server has send the reply to the initialize request.
552
+ func waitUntilInitialized( ) async {
553
+ // The polling of `initialized` is not perfect but it should be OK, because
554
+ // - In almost all cases the server should already be initialized.
555
+ // - If it's not initialized, we expect initialization to finish fairly quickly. Even if initialization takes 5s
556
+ // this only results in 50 polls, which is acceptable.
557
+ // Alternative solutions that signal via an async sequence seem overkill here.
558
+ while !initialized {
559
+ do {
560
+ try await Task . sleep ( for: . seconds( 0.1 ) )
561
+ } catch {
562
+ break
563
+ }
564
+ }
565
+ }
566
+
537
567
/// Search through all the parent directories of `uri` and check if any of these directories contain a workspace
538
568
/// capable of handling `uri`.
539
569
///
@@ -958,6 +988,8 @@ extension SourceKitLSPServer: MessageHandler {
958
988
switch request {
959
989
case let request as RequestAndReply < InitializeRequest > :
960
990
await request. reply { try await initialize ( request. params) }
991
+ // Only set `initialized` to `true` after we have sent the response to the initialize request to the client.
992
+ initialized = true
961
993
case let request as RequestAndReply < ShutdownRequest > :
962
994
await request. reply { try await shutdown ( request. params) }
963
995
case let request as RequestAndReply < WorkspaceSymbolsRequest > :
0 commit comments