@@ -20,7 +20,7 @@ import SwiftExtensions
20
20
///
21
21
/// The work done progress is started when the object is created and ended when the object is destroyed.
22
22
/// In between, updates can be sent to the client.
23
- final actor WorkDoneProgressManager {
23
+ actor WorkDoneProgressManager {
24
24
private enum Status : Equatable {
25
25
case inProgress( message: String ? , percentage: Int ? )
26
26
case done
@@ -36,6 +36,15 @@ final actor WorkDoneProgressManager {
36
36
37
37
private weak var server : SourceKitLSPServer ?
38
38
39
+ /// A string with which the `token` of the generated `WorkDoneProgress` sent to the client starts.
40
+ ///
41
+ /// A UUID will be appended to this prefix to make the token unique. The token prefix can be used to classify the work
42
+ /// done progress into a category, which makes debugging easier because the tokens have semantic meaning and also
43
+ /// allows clients to interpret what the `WorkDoneProgress` represents (for example Swift for VS Code explicitly
44
+ /// recognizes work done progress that indicates that sourcekitd has crashed to offer a diagnostic bundle to be
45
+ /// generated).
46
+ private let tokenPrefix : String
47
+
39
48
private let title : String
40
49
41
50
/// The next status that should be sent to the client by `sendProgressUpdateImpl`.
@@ -58,6 +67,7 @@ final actor WorkDoneProgressManager {
58
67
59
68
init ? (
60
69
server: SourceKitLSPServer ,
70
+ tokenPrefix: String ,
61
71
initialDebounce: Duration ? = nil ,
62
72
title: String ,
63
73
message: String ? = nil ,
@@ -69,6 +79,7 @@ final actor WorkDoneProgressManager {
69
79
self . init (
70
80
server: server,
71
81
capabilityRegistry: capabilityRegistry,
82
+ tokenPrefix: tokenPrefix,
72
83
initialDebounce: initialDebounce,
73
84
title: title,
74
85
message: message,
@@ -79,6 +90,7 @@ final actor WorkDoneProgressManager {
79
90
init ? (
80
91
server: SourceKitLSPServer ,
81
92
capabilityRegistry: CapabilityRegistry ,
93
+ tokenPrefix: String ,
82
94
initialDebounce: Duration ? = nil ,
83
95
title: String ,
84
96
message: String ? = nil ,
@@ -87,6 +99,7 @@ final actor WorkDoneProgressManager {
87
99
guard capabilityRegistry. clientCapabilities. window? . workDoneProgress ?? false else {
88
100
return nil
89
101
}
102
+ self . tokenPrefix = tokenPrefix
90
103
self . server = server
91
104
self . title = title
92
105
self . pendingStatus = . inProgress( message: message, percentage: percentage)
@@ -121,7 +134,7 @@ final actor WorkDoneProgressManager {
121
134
)
122
135
)
123
136
} else {
124
- let token = ProgressToken . string ( UUID ( ) . uuidString)
137
+ let token = ProgressToken . string ( " \( tokenPrefix ) . \( UUID ( ) . uuidString) " )
125
138
do {
126
139
_ = try await server. client. send ( CreateWorkDoneProgressRequest ( token: token) )
127
140
} catch {
@@ -177,3 +190,68 @@ final actor WorkDoneProgressManager {
177
190
}
178
191
}
179
192
}
193
+
194
+ /// A `WorkDoneProgressManager` that essentially has two states. If any operation tracked by this type is currently
195
+ /// running, it displays a work done progress in the client. If multiple operations are running at the same time, it
196
+ /// doesn't show multiple work done progress in the client. For example, we only want to show one progress indicator
197
+ /// when sourcekitd has crashed, not one per `SwiftLanguageService`.
198
+ actor SharedWorkDoneProgressManager {
199
+ private weak var sourceKitLSPServer : SourceKitLSPServer ?
200
+
201
+ /// The number of in-progress operations. When greater than 0 `workDoneProgress` non-nil and a work done progress is
202
+ /// displayed to the user.
203
+ private var inProgressOperations = 0
204
+ private var workDoneProgress : WorkDoneProgressManager ?
205
+
206
+ private let tokenPrefix : String
207
+ private let title : String
208
+ private let message : String ?
209
+
210
+ public init (
211
+ sourceKitLSPServer: SourceKitLSPServer ,
212
+ tokenPrefix: String ,
213
+ title: String ,
214
+ message: String ? = nil
215
+ ) {
216
+ self . sourceKitLSPServer = sourceKitLSPServer
217
+ self . tokenPrefix = tokenPrefix
218
+ self . title = title
219
+ self . message = message
220
+ }
221
+
222
+ func start( ) async {
223
+ guard let sourceKitLSPServer else {
224
+ return
225
+ }
226
+ // Do all asynchronous operations up-front so that incrementing `inProgressOperations` and setting `workDoneProgress`
227
+ // cannot be interrupted by an `await` call
228
+ let initialDebounce = await sourceKitLSPServer. options. workDoneProgressDebounceDuration
229
+ let capabilityRegistry = await sourceKitLSPServer. capabilityRegistry
230
+
231
+ inProgressOperations += 1
232
+ if let capabilityRegistry, workDoneProgress == nil {
233
+ workDoneProgress = WorkDoneProgressManager (
234
+ server: sourceKitLSPServer,
235
+ capabilityRegistry: capabilityRegistry,
236
+ tokenPrefix: tokenPrefix,
237
+ initialDebounce: initialDebounce,
238
+ title: title,
239
+ message: message
240
+ )
241
+ }
242
+ }
243
+
244
+ func end( ) async {
245
+ if inProgressOperations > 0 {
246
+ inProgressOperations -= 1
247
+ } else {
248
+ logger. fault (
249
+ " Unbalanced calls to SharedWorkDoneProgressManager.start and end for \( self . tokenPrefix, privacy: . public) "
250
+ )
251
+ }
252
+ if inProgressOperations == 0 , let workDoneProgress {
253
+ self . workDoneProgress = nil
254
+ await workDoneProgress. end ( )
255
+ }
256
+ }
257
+ }
0 commit comments