5
5
from concurrent .futures import CancelledError
6
6
from dataclasses import dataclass , field
7
7
from enum import Enum
8
- from threading import Event , Lock , RLock , Timer
8
+ from threading import Event , Timer
9
9
from typing import TYPE_CHECKING , Any , Dict , Final , List , Optional , cast
10
10
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
12
12
from robotcode .core .event import event
13
13
from robotcode .core .lsp .types import (
14
14
Diagnostic ,
@@ -77,6 +77,7 @@ class DiagnosticsData:
77
77
future : Optional [Task [Any ]] = None
78
78
force : bool = False
79
79
single : bool = False
80
+ skipped_entries : bool = False
80
81
81
82
82
83
class DiagnosticsProtocolPart (LanguageServerProtocolPart ):
@@ -111,15 +112,16 @@ def __init__(self, protocol: "LanguageServerProtocol") -> None:
111
112
112
113
self ._current_diagnostics_task_lock = RLock ()
113
114
self ._current_diagnostics_task : Optional [Task [Any ]] = None
115
+ self ._diagnostics_task_timeout = 300
114
116
115
117
def server_initialized (self , sender : Any ) -> None :
116
- self ._workspace_diagnostics_task = run_as_task (self .run_workspace_diagnostics )
117
-
118
118
if not self .client_supports_pull :
119
119
self .parent .documents .did_open .add (self .update_document_diagnostics )
120
120
self .parent .documents .did_change .add (self .update_document_diagnostics )
121
121
self .parent .documents .did_save .add (self .update_document_diagnostics )
122
122
123
+ self ._workspace_diagnostics_task = run_as_task (self .run_workspace_diagnostics )
124
+
123
125
def extend_capabilities (self , capabilities : ServerCapabilities ) -> None :
124
126
if (
125
127
self .parent .client_capabilities is not None
@@ -230,7 +232,7 @@ def cancel_workspace_diagnostics_task(self, sender: Any) -> None:
230
232
231
233
def break_workspace_diagnostics_loop (self ) -> None :
232
234
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 ) :
234
236
if self ._current_diagnostics_task is not None and not self ._current_diagnostics_task .done ():
235
237
self ._current_diagnostics_task .cancel ()
236
238
@@ -256,6 +258,7 @@ def run_workspace_diagnostics(self) -> None:
256
258
(data := self .get_diagnostics_data (doc )).force
257
259
or doc .version != data .version
258
260
or data .future is None
261
+ or data .skipped_entries
259
262
)
260
263
and not data .single
261
264
)
@@ -267,12 +270,17 @@ def run_workspace_diagnostics(self) -> None:
267
270
check_current_task_canceled (1 )
268
271
continue
269
272
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" )
271
274
272
275
done_something = False
273
276
274
277
self .on_workspace_diagnostics_analyze (self )
275
278
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
+
276
284
start = time .monotonic ()
277
285
with self .parent .window .progress (
278
286
"Analyze Workspace" ,
@@ -282,9 +290,11 @@ def run_workspace_diagnostics(self) -> None:
282
290
start = False ,
283
291
) as progress :
284
292
for i , document in enumerate (documents ):
293
+ self ._logger .debug (lambda : f"Analyze { document } " )
285
294
check_current_task_canceled ()
286
295
287
296
if self ._break_diagnostics_loop_event .is_set ():
297
+ self ._logger .debug ("break workspace diagnostics loop 2" )
288
298
self .on_workspace_diagnostics_break (self )
289
299
break
290
300
@@ -312,7 +322,7 @@ def run_workspace_diagnostics(self) -> None:
312
322
callback_filter = language_id_filter (document ),
313
323
return_exceptions = True ,
314
324
)
315
- self ._current_diagnostics_task .result (300 )
325
+ self ._current_diagnostics_task .result (self . _diagnostics_task_timeout )
316
326
except CancelledError :
317
327
self ._logger .debug (lambda : f"Analyzing { document } cancelled" )
318
328
except BaseException as e :
@@ -325,31 +335,53 @@ def run_workspace_diagnostics(self) -> None:
325
335
with self ._current_diagnostics_task_lock :
326
336
self ._current_diagnostics_task = None
327
337
328
- self ._logger .info (
338
+ self ._logger .debug (
329
339
lambda : f"Analyzing workspace for { len (documents )} " f"documents takes { time .monotonic () - start } s"
330
340
)
331
341
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
+
332
347
self .on_workspace_diagnostics_collect (self )
333
348
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
+
334
362
start = time .monotonic ()
335
363
with self .parent .window .progress (
336
364
"Collect Diagnostics" ,
337
365
cancellable = False ,
338
366
current = 0 ,
339
- max = len (documents ),
367
+ max = len (documents_to_collect ),
340
368
start = False ,
341
369
) 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 } " )
343
372
check_current_task_canceled ()
344
373
345
374
if self ._break_diagnostics_loop_event .is_set ():
375
+ self ._logger .debug ("break workspace diagnostics loop 4" )
346
376
self .on_workspace_diagnostics_break (self )
347
377
break
348
378
349
379
mode = self .get_diagnostics_mode (document .uri )
350
380
if mode == DiagnosticsMode .OFF :
351
381
self .get_diagnostics_data (document ).force = False
352
382
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
353
385
self .get_diagnostics_data (document ).future = Task ()
354
386
continue
355
387
@@ -366,15 +398,15 @@ def run_workspace_diagnostics(self) -> None:
366
398
progress .report (f"Collect { name } " , current = i + 1 )
367
399
elif analysis_mode == AnalysisProgressMode .SIMPLE :
368
400
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 )
370
402
371
403
try :
372
404
with self ._current_diagnostics_task_lock :
373
405
self ._current_diagnostics_task = self .create_document_diagnostics_task (
374
406
document ,
375
407
False ,
376
408
False ,
377
- mode == DiagnosticsMode .WORKSPACE ,
409
+ mode == DiagnosticsMode .WORKSPACE or document . opened_in_editor ,
378
410
)
379
411
self ._current_diagnostics_task .result (300 )
380
412
except CancelledError :
@@ -392,8 +424,8 @@ def run_workspace_diagnostics(self) -> None:
392
424
if not done_something :
393
425
check_current_task_canceled (1 )
394
426
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 )} "
397
429
f"documents takes { time .monotonic () - start } s"
398
430
)
399
431
@@ -404,13 +436,9 @@ def run_workspace_diagnostics(self) -> None:
404
436
finally :
405
437
self .on_workspace_diagnostics_end (self )
406
438
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
414
442
415
443
def create_document_diagnostics_task (
416
444
self ,
@@ -421,11 +449,12 @@ def create_document_diagnostics_task(
421
449
) -> Task [Any ]:
422
450
data = self .get_diagnostics_data (document )
423
451
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
+
425
456
future = data .future
426
457
427
- data .force = False
428
- data .single = single
429
458
if future is not None and not future .done ():
430
459
self ._logger .debug (lambda : f"try to cancel diagnostics for { document } " )
431
460
@@ -441,6 +470,8 @@ def create_document_diagnostics_task(
441
470
)
442
471
443
472
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 } " )
444
475
445
476
return data .future
446
477
@@ -457,7 +488,7 @@ def _get_diagnostics_for_document(
457
488
if debounce :
458
489
check_current_task_canceled (0.75 )
459
490
460
- skipped_collectors = False
491
+ data . skipped_entries = False
461
492
collected_keys : List [Any ] = []
462
493
try :
463
494
for result in self .collect (
@@ -477,7 +508,7 @@ def _get_diagnostics_for_document(
477
508
478
509
data .id = str (uuid .uuid4 ())
479
510
if result .skipped :
480
- skipped_collectors = True
511
+ data . skipped_entries = True
481
512
482
513
if result .diagnostics is not None :
483
514
for d in result .diagnostics :
@@ -488,7 +519,9 @@ def _get_diagnostics_for_document(
488
519
if doc is not None :
489
520
r .location .range = doc .range_to_utf16 (r .location .range )
490
521
491
- data .entries [result .key ] = result .diagnostics
522
+ if not result .skipped :
523
+ data .entries [result .key ] = result .diagnostics
524
+
492
525
if result .diagnostics is not None :
493
526
collected_keys .append (result .key )
494
527
@@ -503,7 +536,6 @@ def _get_diagnostics_for_document(
503
536
finally :
504
537
for k in set (data .entries .keys ()) - set (collected_keys ):
505
538
data .entries .pop (k )
506
- data .force = skipped_collectors
507
539
508
540
def publish_diagnostics (self , document : TextDocument , diagnostics : List [Diagnostic ]) -> None :
509
541
self .parent .send_notification (
@@ -515,8 +547,11 @@ def publish_diagnostics(self, document: TextDocument, diagnostics: List[Diagnost
515
547
),
516
548
)
517
549
550
+ def _update_document_diagnostics (self , document : TextDocument ) -> None :
551
+ self .create_document_diagnostics_task (document , True ).result (self ._diagnostics_task_timeout )
552
+
518
553
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 )
520
555
521
556
@rpc_method (name = "textDocument/diagnostic" , param_type = DocumentDiagnosticParams , threaded = True )
522
557
def _text_document_diagnostic (
@@ -543,19 +578,19 @@ def _text_document_diagnostic(
543
578
f"Document { text_document !r} not found." ,
544
579
)
545
580
546
- self .create_document_diagnostics_task (document , True )
581
+ self .create_document_diagnostics_task (document , True ). result ( 300 )
547
582
548
583
return RelatedFullDocumentDiagnosticReport ([])
549
584
except CancelledError :
550
585
self ._logger .debug ("canceled _text_document_diagnostic" )
551
586
raise
552
587
553
588
def get_diagnostics_data (self , document : TextDocument ) -> DiagnosticsData :
554
- data : DiagnosticsData = document .get_data (self , None )
589
+ data : DiagnosticsData = document .get_data (DiagnosticsProtocolPart , None )
555
590
556
591
if data is None :
557
592
data = DiagnosticsData (str (uuid .uuid4 ())) # type: ignore
558
- document .set_data (self , data )
593
+ document .set_data (DiagnosticsProtocolPart , data )
559
594
560
595
return data
561
596
0 commit comments