@@ -121,8 +121,14 @@ def __init__(self, protocol: LanguageServerProtocol) -> None:
121121 self ._workspace_diagnostics_task : Optional [asyncio .Task [Any ]] = None
122122
123123 self ._diagnostics_loop : Optional [asyncio .AbstractEventLoop ] = None
124+ self ._single_diagnostics_loop : Optional [asyncio .AbstractEventLoop ] = None
125+
124126 self ._diagnostics_loop_lock = threading .RLock ()
125127 self ._diagnostics_started = threading .Event ()
128+ self ._single_diagnostics_started = threading .Event ()
129+
130+ self ._diagnostics_server_thread : Optional [threading .Thread ] = None
131+ self ._single_diagnostics_server_thread : Optional [threading .Thread ] = None
126132
127133 self .parent .on_initialized .add (self .initialized )
128134
@@ -148,6 +154,15 @@ def diagnostics_loop(self) -> asyncio.AbstractEventLoop:
148154
149155 return self ._diagnostics_loop
150156
157+ @property
158+ def single_diagnostics_loop (self ) -> asyncio .AbstractEventLoop :
159+ if self ._single_diagnostics_loop is None :
160+ self ._ensure_diagnostics_thread_started ()
161+
162+ assert self ._single_diagnostics_loop is not None
163+
164+ return self ._single_diagnostics_loop
165+
151166 def _run_diagnostics (self ) -> None :
152167 loop = asyncio .new_event_loop ()
153168 asyncio .set_event_loop (loop )
@@ -164,17 +179,39 @@ def _run_diagnostics(self) -> None:
164179 asyncio .set_event_loop (None )
165180 loop .close ()
166181
182+ def _single_run_diagnostics (self ) -> None :
183+ loop = asyncio .new_event_loop ()
184+ asyncio .set_event_loop (loop )
185+ try :
186+ self ._single_diagnostics_loop = loop
187+ self ._single_diagnostics_started .set ()
188+
189+ loop .slow_callback_duration = 10
190+
191+ loop .run_forever ()
192+ _cancel_all_tasks (loop )
193+ loop .run_until_complete (loop .shutdown_asyncgens ())
194+ finally :
195+ asyncio .set_event_loop (None )
196+ loop .close ()
197+
167198 def _ensure_diagnostics_thread_started (self ) -> None :
168199 with self ._diagnostics_loop_lock :
169200 if self ._diagnostics_loop is None :
170- self ._server_thread = threading .Thread (
201+ self ._diagnostics_server_thread = threading .Thread (
171202 name = "diagnostics_worker" , target = self ._run_diagnostics , daemon = True
172203 )
173204
174- self ._server_thread .start ()
205+ self ._diagnostics_server_thread .start ()
206+
207+ self ._single_diagnostics_server_thread = threading .Thread (
208+ name = "single_diagnostics_worker" , target = self ._single_run_diagnostics , daemon = True
209+ )
210+
211+ self ._single_diagnostics_server_thread .start ()
175212
176- if not self ._diagnostics_started .wait (10 ):
177- raise RuntimeError ("Can't start diagnostics worker thread ." )
213+ if not self ._diagnostics_started .wait (10 ) or not self . _single_diagnostics_started . wait ( 10 ) :
214+ raise RuntimeError ("Can't start diagnostics worker threads ." )
178215
179216 def extend_capabilities (self , capabilities : ServerCapabilities ) -> None :
180217 if (
@@ -280,7 +317,6 @@ async def run_workspace_diagnostics(self) -> None:
280317
281318 while True :
282319 try :
283-
284320 documents = [
285321 doc
286322 for doc in self .parent .documents .documents
@@ -296,8 +332,11 @@ async def run_workspace_diagnostics(self) -> None:
296332 await asyncio .sleep (1 )
297333 continue
298334
335+ self ._logger .info (f"start collecting workspace diagnostics for { len (documents )} documents" )
336+
299337 done_something = False
300338
339+ start = time .monotonic ()
301340 async with self .parent .window .progress (
302341 "Analyse workspace" , cancellable = False , current = 0 , max = len (documents ) + 1 , start = False
303342 ) as progress :
@@ -324,21 +363,31 @@ async def run_workspace_diagnostics(self) -> None:
324363 progress .report (current = i + 1 )
325364
326365 await self .create_document_diagnostics_task (
327- document , False , await self .get_diagnostics_mode (document .uri ) == DiagnosticsMode .WORKSPACE
366+ document ,
367+ False ,
368+ False ,
369+ await self .get_diagnostics_mode (document .uri ) == DiagnosticsMode .WORKSPACE ,
328370 )
329371
330372 if not done_something :
331373 await asyncio .sleep (1 )
374+
375+ self ._logger .info (
376+ f"collecting workspace diagnostics for for { len (documents )} "
377+ f"documents takes { time .monotonic () - start } s"
378+ )
379+
332380 except (SystemExit , KeyboardInterrupt , asyncio .CancelledError ):
333381 raise
334382 except BaseException as e :
335383 self ._logger .exception (f"Error in workspace diagnostics loop: { e } " , exc_info = e )
336384
337385 def create_document_diagnostics_task (
338- self , document : TextDocument , debounce : bool = True , send_diagnostics : bool = True
386+ self , document : TextDocument , single : bool , debounce : bool = True , send_diagnostics : bool = True
339387 ) -> asyncio .Task [Any ]:
340388 def done (t : asyncio .Task [Any ]) -> None :
341389 self ._logger .debug (lambda : f"diagnostics for { document } { 'canceled' if t .cancelled () else 'ended' } " )
390+
342391 if t .done () and not t .cancelled ():
343392 ex = t .exception ()
344393
@@ -372,7 +421,7 @@ async def cancel(t: asyncio.Task[Any]) -> None:
372421 data .version = document .version
373422 data .task = create_sub_task (
374423 self ._get_diagnostics_for_document (document , data , debounce , send_diagnostics ),
375- loop = self .diagnostics_loop ,
424+ loop = self .single_diagnostics_loop if single else self . diagnostics_loop ,
376425 name = f"diagnostics ${ document .uri } " ,
377426 )
378427
@@ -444,7 +493,7 @@ async def _text_document_diagnostic(
444493 if document is None :
445494 raise JsonRPCErrorException (ErrorCodes .SERVER_CANCELLED , f"Document { text_document !r} not found." )
446495
447- self .create_document_diagnostics_task (document )
496+ self .create_document_diagnostics_task (document , True )
448497
449498 return RelatedFullDocumentDiagnosticReport ([])
450499 except asyncio .CancelledError :
0 commit comments