@@ -47,6 +47,7 @@ import 'package:analyzer/dart/analysis/results.dart';
47
47
import 'package:analyzer/dart/ast/ast.dart' ;
48
48
import 'package:analyzer/exception/exception.dart' ;
49
49
import 'package:analyzer/file_system/file_system.dart' ;
50
+ import 'package:analyzer/file_system/overlay_file_system.dart' ;
50
51
import 'package:analyzer/instrumentation/instrumentation.dart' ;
51
52
import 'package:analyzer/src/dart/analysis/driver.dart' as analysis;
52
53
import 'package:analyzer/src/dart/analysis/status.dart' as analysis;
@@ -123,6 +124,34 @@ class AnalysisServer extends AbstractAnalysisServer {
123
124
final StreamController _onAnalysisSetChangedController =
124
125
StreamController .broadcast (sync : true );
125
126
127
+ /// Key: a file path for which removing of the overlay was requested.
128
+ /// Value: a timer that will remove the overlay, or cancelled.
129
+ ///
130
+ /// This helps for analysis server running remotely, with slow remote file
131
+ /// systems, in the following scenario:
132
+ /// 1. User edits file, IDE sends "add overlay".
133
+ /// 2. User saves file, IDE saves file locally, sends "remove overlay".
134
+ /// 3. The remove server reads the file on "remove overlay". But the content
135
+ /// of the file on the remove machine is not the same as it is locally,
136
+ /// and not what the user looks in the IDE. So, analysis results, such
137
+ /// as semantic highlighting, are inconsistent with the local file content.
138
+ /// 4. (after a few seconds) The file is synced to the remove machine,
139
+ /// the watch event happens, server reads the file, sends analysis
140
+ /// results that are consistent with the local file content.
141
+ ///
142
+ /// We try to prevent the inconsistency between moments (3) and (4).
143
+ /// It is not wrong, we are still in the eventual consistency, but we
144
+ /// want to keep the inconsistency time shorter.
145
+ ///
146
+ /// To do this we keep the last overlay content on "remove overlay",
147
+ /// and wait for the next watch event in (4). But there might be race
148
+ /// condition, and when it happens, we still want to get to the eventual
149
+ /// consistency, so on timer we remove the overlay anyway.
150
+ final Map <String , Timer > _pendingFilesToRemoveOverlay = {};
151
+
152
+ @visibleForTesting
153
+ Duration pendingFilesRemoveOverlayDelay = const Duration (seconds: 10 );
154
+
126
155
final DetachableFileSystemManager ? detachableFileSystemManager;
127
156
128
157
/// The broadcast stream of requests that were discarded because there
@@ -231,6 +260,12 @@ class AnalysisServer extends AbstractAnalysisServer {
231
260
cancellationTokens[id]? .cancel ();
232
261
}
233
262
263
+ Future <void > dispose () async {
264
+ for (var timer in _pendingFilesToRemoveOverlay.values) {
265
+ timer.cancel ();
266
+ }
267
+ }
268
+
234
269
/// The socket from which requests are being read has been closed.
235
270
void done () {}
236
271
@@ -511,7 +546,7 @@ class AnalysisServer extends AbstractAnalysisServer {
511
546
} catch (_) {}
512
547
513
548
// Prepare the new contents.
514
- String ? newContents;
549
+ String newContents;
515
550
if (change is AddContentOverlay ) {
516
551
newContents = change.content;
517
552
} else if (change is ChangeContentOverlay ) {
@@ -530,25 +565,28 @@ class AnalysisServer extends AbstractAnalysisServer {
530
565
'Invalid overlay change' )));
531
566
}
532
567
} else if (change is RemoveContentOverlay ) {
533
- newContents = null ;
568
+ _pendingFilesToRemoveOverlay[file]? .cancel ();
569
+ _pendingFilesToRemoveOverlay[file] = Timer (
570
+ pendingFilesRemoveOverlayDelay,
571
+ () {
572
+ resourceProvider.removeOverlay (file);
573
+ _changeFileInDrivers (file);
574
+ },
575
+ );
576
+ return ;
534
577
} else {
535
578
// Protocol parsing should have ensured that we never get here.
536
579
throw AnalysisException ('Illegal change type' );
537
580
}
538
581
539
- if (newContents != null ) {
540
- resourceProvider.setOverlay (
541
- file,
542
- content: newContents,
543
- modificationStamp: overlayModificationStamp++ ,
544
- );
545
- } else {
546
- resourceProvider.removeOverlay (file);
547
- }
582
+ _pendingFilesToRemoveOverlay[file]? .cancel ();
583
+ resourceProvider.setOverlay (
584
+ file,
585
+ content: newContents,
586
+ modificationStamp: overlayModificationStamp++ ,
587
+ );
548
588
549
- driverMap.values.forEach ((driver) {
550
- driver.changeFile (file);
551
- });
589
+ _changeFileInDrivers (file);
552
590
553
591
// If the file did not exist, and is "overlay only", it still should be
554
592
// analyzed. Add it to driver to which it should have been added.
@@ -586,6 +624,12 @@ class AnalysisServer extends AbstractAnalysisServer {
586
624
// });
587
625
}
588
626
627
+ void _changeFileInDrivers (String path) {
628
+ for (var driver in driverMap.values) {
629
+ driver.changeFile (path);
630
+ }
631
+ }
632
+
589
633
/// Returns `true` if there is a subscription for the given [service] and
590
634
/// [file] .
591
635
bool _hasAnalysisServiceSubscription (AnalysisService service, String file) {
@@ -671,7 +715,7 @@ class ServerContextManagerCallbacks extends ContextManagerCallbacks {
671
715
final AnalysisServer analysisServer;
672
716
673
717
/// The [ResourceProvider] by which paths are converted into [Resource] s.
674
- final ResourceProvider resourceProvider;
718
+ final OverlayResourceProvider resourceProvider;
675
719
676
720
/// The set of files for which notifications were sent.
677
721
final Set <String > filesToFlush = {};
@@ -698,6 +742,15 @@ class ServerContextManagerCallbacks extends ContextManagerCallbacks {
698
742
699
743
@override
700
744
void afterWatchEvent (WatchEvent event) {
745
+ var path = event.path;
746
+
747
+ var pendingTimer = analysisServer._pendingFilesToRemoveOverlay.remove (path);
748
+ if (pendingTimer != null ) {
749
+ pendingTimer.cancel ();
750
+ resourceProvider.removeOverlay (path);
751
+ analysisServer._changeFileInDrivers (path);
752
+ }
753
+
701
754
analysisServer._onAnalysisSetChangedController.add (null );
702
755
}
703
756
0 commit comments