@@ -177,7 +177,10 @@ package actor SourceKitLSPServer {
177177 /// The requests that we are currently handling.
178178 ///
179179 /// 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 > ] = [ : ]
181184
182185 /// Up to 10 request IDs that have recently finished.
183186 ///
@@ -187,14 +190,22 @@ package actor SourceKitLSPServer {
187190
188191 /// - Note: Needed so we can set an in-progress request from a different
189192 /// 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
192195 if task == nil {
193196 recentlyFinishedRequests. append ( id)
194197 while recentlyFinishedRequests. count > 10 {
195198 recentlyFinishedRequests. removeFirst ( )
196199 }
197200 }
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+ }
198209 }
199210
200211 var onExit : ( ) -> Void
@@ -547,12 +558,19 @@ extension SourceKitLSPServer: MessageHandler {
547558 package nonisolated func handle( _ params: some NotificationType ) {
548559 let notificationID = notificationIDForLogging. fetchAndIncrement ( )
549560 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 :
554566 self . cancelRequest ( params)
555567 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
556574 }
557575
558576 let signposter = Logger ( subsystem: LoggingScope . subsystem, category: " message-handling " )
@@ -617,6 +635,7 @@ extension SourceKitLSPServer: MessageHandler {
617635 // The last 2 digits should be sufficient to differentiate between multiple concurrently running requests.
618636 await withLoggingScope ( " request- \( id. numericValue % 100 ) " ) {
619637 await withTaskCancellationHandler {
638+ await self . testHooks. handleRequest ? ( params)
620639 await self . handleImpl ( params, id: id, reply: reply)
621640 signposter. endInterval ( " Request " , state, " Done " )
622641 } onCancel: {
@@ -626,14 +645,14 @@ extension SourceKitLSPServer: MessageHandler {
626645 // We have handled the request and can't cancel it anymore.
627646 // Stop keeping track of it to free the memory.
628647 self . cancellationMessageHandlingQueue. async ( priority: . background) {
629- await self . setInProgressRequest ( for: id, task: nil )
648+ await self . setInProgressRequest ( for: id, params , task: nil )
630649 }
631650 }
632651 // Keep track of the ID -> Task management with low priority. Once we cancel
633652 // a request, the cancellation task runs with a high priority and depends on
634653 // this task, which will elevate this task's priority.
635654 cancellationMessageHandlingQueue. async ( priority: . background) {
636- await self . setInProgressRequest ( for: id, task: task)
655+ await self . setInProgressRequest ( for: id, params , task: task)
637656 }
638657 }
639658
@@ -1222,11 +1241,11 @@ extension SourceKitLSPServer {
12221241 // Nothing to do.
12231242 }
12241243
1225- nonisolated func cancelRequest( _ notification: CancelRequestNotification ) {
1244+ private nonisolated func cancelRequest( _ notification: CancelRequestNotification ) {
12261245 // Since the request is very cheap to execute and stops other requests
12271246 // from performing more work, we execute it with a high priority.
12281247 cancellationMessageHandlingQueue. async ( priority: . high) {
1229- if let task = await self . inProgressRequests [ notification. id] {
1248+ if let task = await self . inProgressRequestsByID [ notification. id] {
12301249 task. cancel ( )
12311250 return
12321251 }
@@ -1238,6 +1257,38 @@ extension SourceKitLSPServer {
12381257 }
12391258 }
12401259
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+
12411292 /// The server is about to exit, and the server should flush any buffered state.
12421293 ///
12431294 /// The server shall not be used to handle more requests (other than possibly
0 commit comments