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