@@ -177,7 +177,10 @@ package actor SourceKitLSPServer {
177
177
/// The requests that we are currently handling.
178
178
///
179
179
/// Used to cancel the tasks if the client requests cancellation.
180
- private var inProgressRequests : [ RequestID : Task < ( ) , Never > ] = [ : ]
180
+ private var inProgressRequestsByID : [ RequestID : Task < ( ) , Never > ] = [ : ]
181
+
182
+ /// For all currently handled text document requests a mapping from the document to the corresponding request ID.
183
+ private var inProgressTextDocumentRequests : [ DocumentURI : Set < RequestID > ] = [ : ]
181
184
182
185
/// Up to 10 request IDs that have recently finished.
183
186
///
@@ -187,14 +190,22 @@ package actor SourceKitLSPServer {
187
190
188
191
/// - Note: Needed so we can set an in-progress request from a different
189
192
/// isolation context.
190
- private func setInProgressRequest( for id: RequestID , task: Task < ( ) , Never > ? ) {
191
- self . inProgressRequests [ id] = task
193
+ private func setInProgressRequest( for id: RequestID , _ request : some RequestType , task: Task < ( ) , Never > ? ) {
194
+ self . inProgressRequestsByID [ id] = task
192
195
if task == nil {
193
196
recentlyFinishedRequests. append ( id)
194
197
while recentlyFinishedRequests. count > 10 {
195
198
recentlyFinishedRequests. removeFirst ( )
196
199
}
197
200
}
201
+
202
+ if let request = request as? any TextDocumentRequest {
203
+ if task == nil {
204
+ inProgressTextDocumentRequests [ request. textDocument. uri, default: [ ] ] . remove ( id)
205
+ } else {
206
+ inProgressTextDocumentRequests [ request. textDocument. uri, default: [ ] ] . insert ( id)
207
+ }
208
+ }
198
209
}
199
210
200
211
var onExit : ( ) -> Void
@@ -547,12 +558,19 @@ extension SourceKitLSPServer: MessageHandler {
547
558
package nonisolated func handle( _ params: some NotificationType ) {
548
559
let notificationID = notificationIDForLogging. fetchAndIncrement ( )
549
560
withLoggingScope ( " notification- \( notificationID % 100 ) " ) {
550
- if let params = params as? CancelRequestNotification {
551
- // Request cancellation needs to be able to overtake any other message we
552
- // are currently handling. Ordering is not important here. We thus don't
553
- // need to execute it on `messageHandlingQueue`.
561
+ // Request cancellation needs to be able to overtake any other message we
562
+ // are currently handling. Ordering is not important here. We thus don't
563
+ // need to execute it on `messageHandlingQueue`.
564
+ switch params {
565
+ case let params as CancelRequestNotification :
554
566
self . cancelRequest ( params)
555
567
return
568
+ case let params as DidChangeTextDocumentNotification :
569
+ self . cancelTextDocumentRequests ( for: params. textDocument. uri)
570
+ case let params as DidCloseTextDocumentNotification :
571
+ self . cancelTextDocumentRequests ( for: params. textDocument. uri)
572
+ default :
573
+ break
556
574
}
557
575
558
576
let signposter = Logger ( subsystem: LoggingScope . subsystem, category: " message-handling " )
@@ -617,6 +635,7 @@ extension SourceKitLSPServer: MessageHandler {
617
635
// The last 2 digits should be sufficient to differentiate between multiple concurrently running requests.
618
636
await withLoggingScope ( " request- \( id. numericValue % 100 ) " ) {
619
637
await withTaskCancellationHandler {
638
+ await self . testHooks. handleRequest ? ( params)
620
639
await self . handleImpl ( params, id: id, reply: reply)
621
640
signposter. endInterval ( " Request " , state, " Done " )
622
641
} onCancel: {
@@ -626,14 +645,14 @@ extension SourceKitLSPServer: MessageHandler {
626
645
// We have handled the request and can't cancel it anymore.
627
646
// Stop keeping track of it to free the memory.
628
647
self . cancellationMessageHandlingQueue. async ( priority: . background) {
629
- await self . setInProgressRequest ( for: id, task: nil )
648
+ await self . setInProgressRequest ( for: id, params , task: nil )
630
649
}
631
650
}
632
651
// Keep track of the ID -> Task management with low priority. Once we cancel
633
652
// a request, the cancellation task runs with a high priority and depends on
634
653
// this task, which will elevate this task's priority.
635
654
cancellationMessageHandlingQueue. async ( priority: . background) {
636
- await self . setInProgressRequest ( for: id, task: task)
655
+ await self . setInProgressRequest ( for: id, params , task: task)
637
656
}
638
657
}
639
658
@@ -1222,11 +1241,11 @@ extension SourceKitLSPServer {
1222
1241
// Nothing to do.
1223
1242
}
1224
1243
1225
- nonisolated func cancelRequest( _ notification: CancelRequestNotification ) {
1244
+ private nonisolated func cancelRequest( _ notification: CancelRequestNotification ) {
1226
1245
// Since the request is very cheap to execute and stops other requests
1227
1246
// from performing more work, we execute it with a high priority.
1228
1247
cancellationMessageHandlingQueue. async ( priority: . high) {
1229
- if let task = await self . inProgressRequests [ notification. id] {
1248
+ if let task = await self . inProgressRequestsByID [ notification. id] {
1230
1249
task. cancel ( )
1231
1250
return
1232
1251
}
@@ -1238,6 +1257,38 @@ extension SourceKitLSPServer {
1238
1257
}
1239
1258
}
1240
1259
1260
+ /// Cancel all in-progress text document requests for the given document.
1261
+ ///
1262
+ /// As a user makes an edit to a file, these requests are most likely no longer relevant. It also makes sure that a
1263
+ /// long-running sourcekitd request can't block the entire language server if the client does not cancel all requests.
1264
+ /// For example, consider the following sequence of requests:
1265
+ /// - `textDocument/semanticTokens/full` for document A
1266
+ /// - `textDocument/didChange` for document A
1267
+ /// - `textDocument/formatting` for document A
1268
+ ///
1269
+ /// If the editor is not cancelling the semantic tokens request on edit (like VS Code does), then the `didChange`
1270
+ /// notification is blocked on the semantic tokens request finishing. Hence, we also can't run the
1271
+ /// `textDocument/formatting` request. Cancelling the semantic tokens on the edit fixes the issue.
1272
+ ///
1273
+ /// This method is a no-op if `cancelTextDocumentRequestsOnEditAndClose` is disabled.
1274
+ private nonisolated func cancelTextDocumentRequests( for uri: DocumentURI ) {
1275
+ // Since the request is very cheap to execute and stops other requests
1276
+ // from performing more work, we execute it with a high priority.
1277
+ cancellationMessageHandlingQueue. async ( priority: . high) {
1278
+ await self . cancelTextDocumentRequestsImpl ( for: uri)
1279
+ }
1280
+ }
1281
+
1282
+ private func cancelTextDocumentRequestsImpl( for uri: DocumentURI ) {
1283
+ guard self . options. cancelTextDocumentRequestsOnEditAndCloseOrDefault else {
1284
+ return
1285
+ }
1286
+ for requestID in self . inProgressTextDocumentRequests [ uri, default: [ ] ] {
1287
+ logger. info ( " Implicitly cancelling request \( requestID) " )
1288
+ self . inProgressRequestsByID [ requestID] ? . cancel ( )
1289
+ }
1290
+ }
1291
+
1241
1292
/// The server is about to exit, and the server should flush any buffered state.
1242
1293
///
1243
1294
/// The server shall not be used to handle more requests (other than possibly
0 commit comments