55from concurrent .futures import CancelledError
66from dataclasses import dataclass , field
77from enum import Enum
8- from threading import Event , Lock , RLock , Timer
8+ from threading import Event , Timer
99from typing import TYPE_CHECKING , Any , Dict , Final , List , Optional , cast
1010
11- from robotcode .core .concurrent import Task , check_current_task_canceled , run_as_task
11+ from robotcode .core .concurrent import Lock , RLock , Task , check_current_task_canceled , run_as_task
1212from robotcode .core .event import event
1313from robotcode .core .lsp .types import (
1414 Diagnostic ,
@@ -77,6 +77,7 @@ class DiagnosticsData:
7777 future : Optional [Task [Any ]] = None
7878 force : bool = False
7979 single : bool = False
80+ skipped_entries : bool = False
8081
8182
8283class DiagnosticsProtocolPart (LanguageServerProtocolPart ):
@@ -111,15 +112,16 @@ def __init__(self, protocol: "LanguageServerProtocol") -> None:
111112
112113 self ._current_diagnostics_task_lock = RLock ()
113114 self ._current_diagnostics_task : Optional [Task [Any ]] = None
115+ self ._diagnostics_task_timeout = 300
114116
115117 def server_initialized (self , sender : Any ) -> None :
116- self ._workspace_diagnostics_task = run_as_task (self .run_workspace_diagnostics )
117-
118118 if not self .client_supports_pull :
119119 self .parent .documents .did_open .add (self .update_document_diagnostics )
120120 self .parent .documents .did_change .add (self .update_document_diagnostics )
121121 self .parent .documents .did_save .add (self .update_document_diagnostics )
122122
123+ self ._workspace_diagnostics_task = run_as_task (self .run_workspace_diagnostics )
124+
123125 def extend_capabilities (self , capabilities : ServerCapabilities ) -> None :
124126 if (
125127 self .parent .client_capabilities is not None
@@ -230,7 +232,7 @@ def cancel_workspace_diagnostics_task(self, sender: Any) -> None:
230232
231233 def break_workspace_diagnostics_loop (self ) -> None :
232234 self ._break_diagnostics_loop_event .set ()
233- with self ._current_diagnostics_task_lock :
235+ with self ._current_diagnostics_task_lock ( timeout = self . _diagnostics_task_timeout * 2 ) :
234236 if self ._current_diagnostics_task is not None and not self ._current_diagnostics_task .done ():
235237 self ._current_diagnostics_task .cancel ()
236238
@@ -256,6 +258,7 @@ def run_workspace_diagnostics(self) -> None:
256258 (data := self .get_diagnostics_data (doc )).force
257259 or doc .version != data .version
258260 or data .future is None
261+ or data .skipped_entries
259262 )
260263 and not data .single
261264 )
@@ -267,12 +270,17 @@ def run_workspace_diagnostics(self) -> None:
267270 check_current_task_canceled (1 )
268271 continue
269272
270- self ._logger .info (lambda : f"start collecting workspace diagnostics for { len (documents )} documents" )
273+ self ._logger .debug (lambda : f"start collecting workspace diagnostics for { len (documents )} documents" )
271274
272275 done_something = False
273276
274277 self .on_workspace_diagnostics_analyze (self )
275278
279+ if self ._break_diagnostics_loop_event .is_set ():
280+ self ._logger .debug ("break workspace diagnostics loop 1" )
281+ self .on_workspace_diagnostics_break (self )
282+ continue
283+
276284 start = time .monotonic ()
277285 with self .parent .window .progress (
278286 "Analyze Workspace" ,
@@ -282,9 +290,11 @@ def run_workspace_diagnostics(self) -> None:
282290 start = False ,
283291 ) as progress :
284292 for i , document in enumerate (documents ):
293+ self ._logger .debug (lambda : f"Analyze { document } " )
285294 check_current_task_canceled ()
286295
287296 if self ._break_diagnostics_loop_event .is_set ():
297+ self ._logger .debug ("break workspace diagnostics loop 2" )
288298 self .on_workspace_diagnostics_break (self )
289299 break
290300
@@ -312,7 +322,7 @@ def run_workspace_diagnostics(self) -> None:
312322 callback_filter = language_id_filter (document ),
313323 return_exceptions = True ,
314324 )
315- self ._current_diagnostics_task .result (300 )
325+ self ._current_diagnostics_task .result (self . _diagnostics_task_timeout )
316326 except CancelledError :
317327 self ._logger .debug (lambda : f"Analyzing { document } cancelled" )
318328 except BaseException as e :
@@ -325,31 +335,53 @@ def run_workspace_diagnostics(self) -> None:
325335 with self ._current_diagnostics_task_lock :
326336 self ._current_diagnostics_task = None
327337
328- self ._logger .info (
338+ self ._logger .debug (
329339 lambda : f"Analyzing workspace for { len (documents )} " f"documents takes { time .monotonic () - start } s"
330340 )
331341
342+ if self ._break_diagnostics_loop_event .is_set ():
343+ self ._logger .debug ("break workspace diagnostics loop 3" )
344+ self .on_workspace_diagnostics_break (self )
345+ continue
346+
332347 self .on_workspace_diagnostics_collect (self )
333348
349+ documents_to_collect = [
350+ doc
351+ for doc in documents
352+ if doc .opened_in_editor or self .get_diagnostics_mode (document .uri ) == DiagnosticsMode .WORKSPACE
353+ ]
354+
355+ for document in set (documents ) - set (documents_to_collect ):
356+ self .get_diagnostics_data (document ).force = False
357+ self .get_diagnostics_data (document ).version = document .version
358+ self .get_diagnostics_data (document ).skipped_entries = False
359+ self .get_diagnostics_data (document ).single = False
360+ self .get_diagnostics_data (document ).future = Task ()
361+
334362 start = time .monotonic ()
335363 with self .parent .window .progress (
336364 "Collect Diagnostics" ,
337365 cancellable = False ,
338366 current = 0 ,
339- max = len (documents ),
367+ max = len (documents_to_collect ),
340368 start = False ,
341369 ) as progress :
342- for i , document in enumerate (documents ):
370+ for i , document in enumerate (documents_to_collect ):
371+ self ._logger .debug (lambda : f"Collect diagnostics for { document } " )
343372 check_current_task_canceled ()
344373
345374 if self ._break_diagnostics_loop_event .is_set ():
375+ self ._logger .debug ("break workspace diagnostics loop 4" )
346376 self .on_workspace_diagnostics_break (self )
347377 break
348378
349379 mode = self .get_diagnostics_mode (document .uri )
350380 if mode == DiagnosticsMode .OFF :
351381 self .get_diagnostics_data (document ).force = False
352382 self .get_diagnostics_data (document ).version = document .version
383+ self .get_diagnostics_data (document ).skipped_entries = False
384+ self .get_diagnostics_data (document ).single = False
353385 self .get_diagnostics_data (document ).future = Task ()
354386 continue
355387
@@ -366,15 +398,15 @@ def run_workspace_diagnostics(self) -> None:
366398 progress .report (f"Collect { name } " , current = i + 1 )
367399 elif analysis_mode == AnalysisProgressMode .SIMPLE :
368400 progress .begin ()
369- progress .report (f"Collect { i + 1 } /{ len (documents )} " , current = i + 1 )
401+ progress .report (f"Collect { i + 1 } /{ len (documents_to_collect )} " , current = i + 1 )
370402
371403 try :
372404 with self ._current_diagnostics_task_lock :
373405 self ._current_diagnostics_task = self .create_document_diagnostics_task (
374406 document ,
375407 False ,
376408 False ,
377- mode == DiagnosticsMode .WORKSPACE ,
409+ mode == DiagnosticsMode .WORKSPACE or document . opened_in_editor ,
378410 )
379411 self ._current_diagnostics_task .result (300 )
380412 except CancelledError :
@@ -392,8 +424,8 @@ def run_workspace_diagnostics(self) -> None:
392424 if not done_something :
393425 check_current_task_canceled (1 )
394426
395- self ._logger .info (
396- lambda : f"collecting workspace diagnostics for { len (documents )} "
427+ self ._logger .debug (
428+ lambda : f"collecting workspace diagnostics for { len (documents_to_collect )} "
397429 f"documents takes { time .monotonic () - start } s"
398430 )
399431
@@ -404,13 +436,9 @@ def run_workspace_diagnostics(self) -> None:
404436 finally :
405437 self .on_workspace_diagnostics_end (self )
406438
407- def _diagnostics_task_done (self , document : TextDocument , data : DiagnosticsData , t : Task [Any ]) -> None :
408- self ._logger .debug (lambda : f"diagnostics for { document } task { 'canceled' if t .cancelled () else 'ended' } " )
409-
410- data .force = data .single
411- data .single = False
412- if data .force :
413- self .break_workspace_diagnostics_loop ()
439+ def _diagnostics_task_done (self , document : TextDocument , data : DiagnosticsData , task : Task [Any ]) -> None :
440+ if task .done () and not task .cancelled ():
441+ data .single = False
414442
415443 def create_document_diagnostics_task (
416444 self ,
@@ -421,11 +449,12 @@ def create_document_diagnostics_task(
421449 ) -> Task [Any ]:
422450 data = self .get_diagnostics_data (document )
423451
424- if data .force or document .version != data .version or data .future is None :
452+ if data .force or document .version != data .version or data .future is None or data .skipped_entries :
453+ data .single = single
454+ data .force = False
455+
425456 future = data .future
426457
427- data .force = False
428- data .single = single
429458 if future is not None and not future .done ():
430459 self ._logger .debug (lambda : f"try to cancel diagnostics for { document } " )
431460
@@ -441,6 +470,8 @@ def create_document_diagnostics_task(
441470 )
442471
443472 data .future .add_done_callback (functools .partial (self ._diagnostics_task_done , document , data ))
473+ else :
474+ self ._logger .debug (lambda : f"skip diagnostics for { document } " )
444475
445476 return data .future
446477
@@ -457,7 +488,7 @@ def _get_diagnostics_for_document(
457488 if debounce :
458489 check_current_task_canceled (0.75 )
459490
460- skipped_collectors = False
491+ data . skipped_entries = False
461492 collected_keys : List [Any ] = []
462493 try :
463494 for result in self .collect (
@@ -477,7 +508,7 @@ def _get_diagnostics_for_document(
477508
478509 data .id = str (uuid .uuid4 ())
479510 if result .skipped :
480- skipped_collectors = True
511+ data . skipped_entries = True
481512
482513 if result .diagnostics is not None :
483514 for d in result .diagnostics :
@@ -488,7 +519,9 @@ def _get_diagnostics_for_document(
488519 if doc is not None :
489520 r .location .range = doc .range_to_utf16 (r .location .range )
490521
491- data .entries [result .key ] = result .diagnostics
522+ if not result .skipped :
523+ data .entries [result .key ] = result .diagnostics
524+
492525 if result .diagnostics is not None :
493526 collected_keys .append (result .key )
494527
@@ -503,7 +536,6 @@ def _get_diagnostics_for_document(
503536 finally :
504537 for k in set (data .entries .keys ()) - set (collected_keys ):
505538 data .entries .pop (k )
506- data .force = skipped_collectors
507539
508540 def publish_diagnostics (self , document : TextDocument , diagnostics : List [Diagnostic ]) -> None :
509541 self .parent .send_notification (
@@ -515,8 +547,11 @@ def publish_diagnostics(self, document: TextDocument, diagnostics: List[Diagnost
515547 ),
516548 )
517549
550+ def _update_document_diagnostics (self , document : TextDocument ) -> None :
551+ self .create_document_diagnostics_task (document , True ).result (self ._diagnostics_task_timeout )
552+
518553 def update_document_diagnostics (self , sender : Any , document : TextDocument ) -> None :
519- self .create_document_diagnostics_task ( document , True )
554+ run_as_task ( self ._update_document_diagnostics , document )
520555
521556 @rpc_method (name = "textDocument/diagnostic" , param_type = DocumentDiagnosticParams , threaded = True )
522557 def _text_document_diagnostic (
@@ -543,19 +578,19 @@ def _text_document_diagnostic(
543578 f"Document { text_document !r} not found." ,
544579 )
545580
546- self .create_document_diagnostics_task (document , True )
581+ self .create_document_diagnostics_task (document , True ). result ( 300 )
547582
548583 return RelatedFullDocumentDiagnosticReport ([])
549584 except CancelledError :
550585 self ._logger .debug ("canceled _text_document_diagnostic" )
551586 raise
552587
553588 def get_diagnostics_data (self , document : TextDocument ) -> DiagnosticsData :
554- data : DiagnosticsData = document .get_data (self , None )
589+ data : DiagnosticsData = document .get_data (DiagnosticsProtocolPart , None )
555590
556591 if data is None :
557592 data = DiagnosticsData (str (uuid .uuid4 ())) # type: ignore
558- document .set_data (self , data )
593+ document .set_data (DiagnosticsProtocolPart , data )
559594
560595 return data
561596
0 commit comments