@@ -69,6 +69,9 @@ class PluginServer {
69
69
/// The recent state of analysis reults, to be cleared on file changes.
70
70
final _recentState = < String , _PluginState > {};
71
71
72
+ /// The next modification stamp for a changed file in the [resourceProvider] .
73
+ int _overlayModificationStamp = 0 ;
74
+
72
75
PluginServer ({
73
76
required ResourceProvider resourceProvider,
74
77
required List <Plugin > plugins,
@@ -79,29 +82,6 @@ class PluginServer {
79
82
}
80
83
}
81
84
82
- /// Handles an 'analysis.setContextRoots' request.
83
- Future <protocol.AnalysisSetContextRootsResult > handleAnalysisSetContextRoots (
84
- protocol.AnalysisSetContextRootsParams parameters) async {
85
- var currentContextCollection = _contextCollection;
86
- if (currentContextCollection != null ) {
87
- _contextCollection = null ;
88
- await currentContextCollection.dispose ();
89
- }
90
-
91
- var includedPaths = parameters.roots.map ((e) => e.root).toList ();
92
- var contextCollection = AnalysisContextCollectionImpl (
93
- resourceProvider: _resourceProvider,
94
- includedPaths: includedPaths,
95
- byteStore: _byteStore,
96
- sdkPath: _sdkPath,
97
- fileContentCache: FileContentCache (_resourceProvider),
98
- );
99
- _contextCollection = contextCollection;
100
- await _analyzeAllFilesInContextCollection (
101
- contextCollection: contextCollection);
102
- return protocol.AnalysisSetContextRootsResult ();
103
- }
104
-
105
85
/// Handles an 'edit.getFixes' request.
106
86
///
107
87
/// Throws a [RequestFailure] if the request could not be handled.
@@ -193,8 +173,6 @@ class PluginServer {
193
173
194
174
/// This method is invoked when a new instance of [AnalysisContextCollection]
195
175
/// is created, so the plugin can perform initial analysis of analyzed files.
196
- ///
197
- /// By default analyzes every [AnalysisContext] with [_analyzeFiles] .
198
176
Future <void > _analyzeAllFilesInContextCollection ({
199
177
required AnalysisContextCollection contextCollection,
200
178
}) async {
@@ -326,27 +304,36 @@ class PluginServer {
326
304
case protocol.ANALYSIS_REQUEST_GET_NAVIGATION :
327
305
case protocol.ANALYSIS_REQUEST_HANDLE_WATCH_EVENTS :
328
306
result = null ;
307
+
329
308
case protocol.ANALYSIS_REQUEST_SET_CONTEXT_ROOTS :
330
309
var params =
331
310
protocol.AnalysisSetContextRootsParams .fromRequest (request);
332
- result = await handleAnalysisSetContextRoots (params);
311
+ result = await _handleAnalysisSetContextRoots (params);
312
+
333
313
case protocol.ANALYSIS_REQUEST_SET_PRIORITY_FILES :
334
314
case protocol.ANALYSIS_REQUEST_SET_SUBSCRIPTIONS :
335
315
case protocol.ANALYSIS_REQUEST_UPDATE_CONTENT :
316
+ var params = protocol.AnalysisUpdateContentParams .fromRequest (request);
317
+ result = await _handleAnalysisUpdateContent (params);
318
+
336
319
case protocol.COMPLETION_REQUEST_GET_SUGGESTIONS :
337
320
case protocol.EDIT_REQUEST_GET_ASSISTS :
338
321
case protocol.EDIT_REQUEST_GET_AVAILABLE_REFACTORINGS :
339
322
result = null ;
323
+
340
324
case protocol.EDIT_REQUEST_GET_FIXES :
341
325
var params = protocol.EditGetFixesParams .fromRequest (request);
342
326
result = await handleEditGetFixes (params);
327
+
343
328
case protocol.EDIT_REQUEST_GET_REFACTORING :
344
329
result = null ;
330
+
345
331
case protocol.PLUGIN_REQUEST_SHUTDOWN :
346
332
_channel.sendResponse (protocol.PluginShutdownResult ()
347
333
.toResponse (request.id, requestTime));
348
334
_channel.close ();
349
335
return null ;
336
+
350
337
case protocol.PLUGIN_REQUEST_VERSION_CHECK :
351
338
var params = protocol.PluginVersionCheckParams .fromRequest (request);
352
339
result = await handlePluginVersionCheck (params);
@@ -358,6 +345,119 @@ class PluginServer {
358
345
return result.toResponse (request.id, requestTime);
359
346
}
360
347
348
+ /// Handles files that might have been affected by a content change of
349
+ /// one or more files. The implementation may check if these files should
350
+ /// be analyzed, do such analysis, and send diagnostics.
351
+ ///
352
+ /// By default invokes [_analyzeFiles] only for files that are analyzed in
353
+ /// this [analysisContext] .
354
+ Future <void > _handleAffectedFiles ({
355
+ required AnalysisContext analysisContext,
356
+ required List <String > paths,
357
+ }) async {
358
+ var analyzedPaths = paths
359
+ .where (analysisContext.contextRoot.isAnalyzed)
360
+ .toList (growable: false );
361
+
362
+ await _analyzeFiles (
363
+ analysisContext: analysisContext,
364
+ paths: analyzedPaths,
365
+ );
366
+ }
367
+
368
+ /// Handles an 'analysis.setContextRoots' request.
369
+ Future <protocol.AnalysisSetContextRootsResult > _handleAnalysisSetContextRoots (
370
+ protocol.AnalysisSetContextRootsParams parameters) async {
371
+ var currentContextCollection = _contextCollection;
372
+ if (currentContextCollection != null ) {
373
+ _contextCollection = null ;
374
+ await currentContextCollection.dispose ();
375
+ }
376
+
377
+ var includedPaths = parameters.roots.map ((e) => e.root).toList ();
378
+ var contextCollection = AnalysisContextCollectionImpl (
379
+ resourceProvider: _resourceProvider,
380
+ includedPaths: includedPaths,
381
+ byteStore: _byteStore,
382
+ sdkPath: _sdkPath,
383
+ fileContentCache: FileContentCache (_resourceProvider),
384
+ );
385
+ _contextCollection = contextCollection;
386
+ await _analyzeAllFilesInContextCollection (
387
+ contextCollection: contextCollection);
388
+ return protocol.AnalysisSetContextRootsResult ();
389
+ }
390
+
391
+ /// Handles an 'analysis.updateContent' request.
392
+ ///
393
+ /// Throws a [RequestFailure] if the request could not be handled.
394
+ Future <protocol.AnalysisUpdateContentResult > _handleAnalysisUpdateContent (
395
+ protocol.AnalysisUpdateContentParams parameters) async {
396
+ var changedPaths = < String > {};
397
+ var paths = parameters.files;
398
+ paths.forEach ((String path, Object overlay) {
399
+ // Prepare the old overlay contents.
400
+ String ? oldContent;
401
+ try {
402
+ if (_resourceProvider.hasOverlay (path)) {
403
+ oldContent = _resourceProvider.getFile (path).readAsStringSync ();
404
+ }
405
+ } catch (_) {
406
+ // Leave `oldContent` empty.
407
+ }
408
+
409
+ // Prepare the new contents.
410
+ String ? newContent;
411
+ if (overlay is protocol.AddContentOverlay ) {
412
+ newContent = overlay.content;
413
+ } else if (overlay is protocol.ChangeContentOverlay ) {
414
+ if (oldContent == null ) {
415
+ // The server should only send a ChangeContentOverlay if there is
416
+ // already an existing overlay for the source.
417
+ throw RequestFailure (
418
+ RequestErrorFactory .invalidOverlayChangeNoContent ());
419
+ }
420
+ try {
421
+ newContent =
422
+ protocol.SourceEdit .applySequence (oldContent, overlay.edits);
423
+ } on RangeError {
424
+ throw RequestFailure (
425
+ RequestErrorFactory .invalidOverlayChangeInvalidEdit ());
426
+ }
427
+ } else if (overlay is protocol.RemoveContentOverlay ) {
428
+ newContent = null ;
429
+ }
430
+
431
+ if (newContent != null ) {
432
+ _resourceProvider.setOverlay (
433
+ path,
434
+ content: newContent,
435
+ modificationStamp: _overlayModificationStamp++ ,
436
+ );
437
+ } else {
438
+ _resourceProvider.removeOverlay (path);
439
+ }
440
+
441
+ changedPaths.add (path);
442
+ });
443
+ await _handleContentChanged (changedPaths.toList ());
444
+ return protocol.AnalysisUpdateContentResult ();
445
+ }
446
+
447
+ /// Handles the fact that files with [paths] were changed.
448
+ Future <void > _handleContentChanged (List <String > paths) async {
449
+ if (_contextCollection case var contextCollection? ) {
450
+ await _forAnalysisContexts (contextCollection, (analysisContext) async {
451
+ for (var path in paths) {
452
+ analysisContext.changeFile (path);
453
+ }
454
+ var affected = await analysisContext.applyPendingFileChanges ();
455
+ await _handleAffectedFiles (
456
+ analysisContext: analysisContext, paths: affected);
457
+ });
458
+ }
459
+ }
460
+
361
461
Future <void > _handleRequest (Request request) async {
362
462
var requestTime = DateTime .now ().millisecondsSinceEpoch;
363
463
var id = request.id;
0 commit comments