| Index: pkg/analysis_server/lib/src/plugin/notification_manager.dart
|
| diff --git a/pkg/analysis_server/lib/src/plugin/notification_manager.dart b/pkg/analysis_server/lib/src/plugin/notification_manager.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7f150245b368fbef1d20c7d994b196278c541d6b
|
| --- /dev/null
|
| +++ b/pkg/analysis_server/lib/src/plugin/notification_manager.dart
|
| @@ -0,0 +1,387 @@
|
| +// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +import 'dart:collection';
|
| +
|
| +import 'package:analysis_server/plugin/protocol/protocol.dart' as server;
|
| +import 'package:analysis_server/src/channel/channel.dart';
|
| +import 'package:analysis_server/src/plugin/result_collector.dart';
|
| +import 'package:analysis_server/src/plugin/result_converter.dart';
|
| +import 'package:analysis_server/src/plugin/result_merger.dart';
|
| +import 'package:analyzer/file_system/file_system.dart';
|
| +import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
|
| +import 'package:analyzer_plugin/protocol/protocol_constants.dart' as plugin;
|
| +import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
|
| +
|
| +/**
|
| + * The object used to coordinate the results of notifications from the analysis
|
| + * server and multiple plugins.
|
| + */
|
| +class NotificationManager {
|
| + /**
|
| + * The identifier used to identify results from the server.
|
| + */
|
| + static const String serverId = 'server';
|
| +
|
| + /**
|
| + * The channel used to send notifications to the client.
|
| + */
|
| + final ServerCommunicationChannel channel;
|
| +
|
| + /**
|
| + * The resource provider used to get the path context.
|
| + */
|
| + final ResourceProvider provider;
|
| +
|
| + /**
|
| + * A list of the paths of files and directories that are included for analysis.
|
| + */
|
| + List<String> includedPaths = <String>[];
|
| +
|
| + /**
|
| + * A list of the paths of files and directories that are excluded from
|
| + * analysis.
|
| + */
|
| + List<String> excludedPaths = <String>[];
|
| +
|
| + /**
|
| + * The current set of subscriptions to which the client has subscribed.
|
| + */
|
| + Map<server.AnalysisService, List<String>> currentSubscriptions =
|
| + <server.AnalysisService, List<String>>{};
|
| +
|
| + /**
|
| + * The collector being used to collect the analysis errors from the plugins.
|
| + */
|
| + ResultCollector<List<server.AnalysisError>> errors;
|
| +
|
| + /**
|
| + * The collector being used to collect the folding regions from the plugins.
|
| + */
|
| + ResultCollector<List<server.FoldingRegion>> folding;
|
| +
|
| + /**
|
| + * The collector being used to collect the highlight regions from the plugins.
|
| + */
|
| + ResultCollector<List<server.HighlightRegion>> highlights;
|
| +
|
| + /**
|
| + * The collector being used to collect the navigation parameters from the
|
| + * plugins.
|
| + */
|
| + ResultCollector<server.AnalysisNavigationParams> navigation;
|
| +
|
| + /**
|
| + * The collector being used to collect the occurrences from the plugins.
|
| + */
|
| + ResultCollector<List<server.Occurrences>> occurrences;
|
| +
|
| + /**
|
| + * The collector being used to collect the outlines from the plugins.
|
| + */
|
| + ResultCollector<List<server.Outline>> outlines;
|
| +
|
| + /**
|
| + * The object used to convert results.
|
| + */
|
| + ResultConverter converter = new ResultConverter();
|
| +
|
| + /**
|
| + * The object used to merge results.
|
| + */
|
| + ResultMerger merger = new ResultMerger();
|
| +
|
| + /**
|
| + * Initialize a newly created notification manager.
|
| + */
|
| + NotificationManager(this.channel, this.provider) {
|
| + errors = new ResultCollector<List<server.AnalysisError>>(serverId,
|
| + predicate: _isIncluded);
|
| + folding = new ResultCollector<List<server.FoldingRegion>>(serverId);
|
| + highlights = new ResultCollector<List<server.HighlightRegion>>(serverId);
|
| + navigation = new ResultCollector<server.AnalysisNavigationParams>(serverId);
|
| + occurrences = new ResultCollector<List<server.Occurrences>>(serverId);
|
| + outlines = new ResultCollector<List<server.Outline>>(serverId);
|
| + }
|
| +
|
| + /**
|
| + * Handle the given [notification] from the plugin with the given [pluginId].
|
| + */
|
| + void handlePluginNotification(
|
| + String pluginId, plugin.Notification notification) {
|
| + String event = notification.event;
|
| + switch (event) {
|
| + case plugin.ANALYSIS_NOTIFICATION_ERRORS:
|
| + plugin.AnalysisErrorsParams params =
|
| + new plugin.AnalysisErrorsParams.fromNotification(notification);
|
| + recordAnalysisErrors(
|
| + pluginId,
|
| + params.file,
|
| + params.errors
|
| + .map((plugin.AnalysisError error) =>
|
| + converter.convertAnalysisError(error))
|
| + .toList());
|
| + break;
|
| + case plugin.ANALYSIS_NOTIFICATION_FOLDING:
|
| + plugin.AnalysisFoldingParams params =
|
| + new plugin.AnalysisFoldingParams.fromNotification(notification);
|
| + recordFoldingRegions(
|
| + pluginId,
|
| + params.file,
|
| + params.regions
|
| + .map((plugin.FoldingRegion region) =>
|
| + converter.convertFoldingRegion(region))
|
| + .toList());
|
| + break;
|
| + case plugin.ANALYSIS_NOTIFICATION_HIGHLIGHTS:
|
| + plugin.AnalysisHighlightsParams params =
|
| + new plugin.AnalysisHighlightsParams.fromNotification(notification);
|
| + recordHighlightRegions(
|
| + pluginId,
|
| + params.file,
|
| + params.regions
|
| + .map((plugin.HighlightRegion region) =>
|
| + converter.convertHighlightRegion(region))
|
| + .toList());
|
| + break;
|
| + case plugin.ANALYSIS_NOTIFICATION_NAVIGATION:
|
| + plugin.AnalysisNavigationParams params =
|
| + new plugin.AnalysisNavigationParams.fromNotification(notification);
|
| + recordNavigationParams(pluginId, params.file,
|
| + converter.convertAnalysisNavigationParams(params));
|
| + break;
|
| + case plugin.ANALYSIS_NOTIFICATION_OCCURRENCES:
|
| + plugin.AnalysisOccurrencesParams params =
|
| + new plugin.AnalysisOccurrencesParams.fromNotification(notification);
|
| + recordOccurrences(
|
| + pluginId,
|
| + params.file,
|
| + params.occurrences
|
| + .map((plugin.Occurrences occurrences) =>
|
| + converter.convertOccurrences(occurrences))
|
| + .toList());
|
| + break;
|
| + case plugin.ANALYSIS_NOTIFICATION_OUTLINE:
|
| + plugin.AnalysisOutlineParams params =
|
| + new plugin.AnalysisOutlineParams.fromNotification(notification);
|
| + recordOutlines(
|
| + pluginId,
|
| + params.file,
|
| + params.outline
|
| + .map((plugin.Outline outline) =>
|
| + converter.convertOutline(outline))
|
| + .toList());
|
| + break;
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Record error information from the plugin with the given [pluginId] for the
|
| + * file with the given [filePath].
|
| + */
|
| + void recordAnalysisErrors(
|
| + String pluginId, String filePath, List<server.AnalysisError> errorData) {
|
| + if (errors.isCollectingFor(filePath)) {
|
| + errors.putResults(filePath, pluginId, errorData);
|
| + List<List<server.AnalysisError>> unmergedErrors =
|
| + errors.getResults(filePath);
|
| + List<server.AnalysisError> mergedErrors =
|
| + merger.mergeAnalysisErrors(unmergedErrors);
|
| + channel.sendNotification(
|
| + new server.AnalysisErrorsParams(filePath, mergedErrors)
|
| + .toNotification());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Record folding information from the plugin with the given [pluginId] for
|
| + * the file with the given [filePath].
|
| + */
|
| + void recordFoldingRegions(String pluginId, String filePath,
|
| + List<server.FoldingRegion> foldingData) {
|
| + if (folding.isCollectingFor(filePath)) {
|
| + folding.putResults(filePath, pluginId, foldingData);
|
| + List<List<server.FoldingRegion>> unmergedFolding =
|
| + folding.getResults(filePath);
|
| + List<server.FoldingRegion> mergedFolding =
|
| + merger.mergeFoldingRegions(unmergedFolding);
|
| + channel.sendNotification(
|
| + new server.AnalysisFoldingParams(filePath, mergedFolding)
|
| + .toNotification());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Record highlight information from the plugin with the given [pluginId] for
|
| + * the file with the given [filePath].
|
| + */
|
| + void recordHighlightRegions(String pluginId, String filePath,
|
| + List<server.HighlightRegion> highlightData) {
|
| + if (highlights.isCollectingFor(filePath)) {
|
| + highlights.putResults(filePath, pluginId, highlightData);
|
| + List<List<server.HighlightRegion>> unmergedHighlights =
|
| + highlights.getResults(filePath);
|
| + List<server.HighlightRegion> mergedHighlights =
|
| + merger.mergeHighlightRegions(unmergedHighlights);
|
| + channel.sendNotification(
|
| + new server.AnalysisHighlightsParams(filePath, mergedHighlights)
|
| + .toNotification());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Record navigation information from the plugin with the given [pluginId] for
|
| + * the file with the given [filePath].
|
| + */
|
| + void recordNavigationParams(String pluginId, String filePath,
|
| + server.AnalysisNavigationParams navigationData) {
|
| + if (navigation.isCollectingFor(filePath)) {
|
| + navigation.putResults(filePath, pluginId, navigationData);
|
| + List<server.AnalysisNavigationParams> unmergedNavigations =
|
| + navigation.getResults(filePath);
|
| + server.AnalysisNavigationParams mergedNavigations =
|
| + merger.mergeNavigation(unmergedNavigations);
|
| + channel.sendNotification(mergedNavigations.toNotification());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Record occurrences information from the plugin with the given [pluginId]
|
| + * for the file with the given [filePath].
|
| + */
|
| + void recordOccurrences(String pluginId, String filePath,
|
| + List<server.Occurrences> occurrencesData) {
|
| + if (occurrences.isCollectingFor(filePath)) {
|
| + occurrences.putResults(filePath, pluginId, occurrencesData);
|
| + List<List<server.Occurrences>> unmergedOccurrences =
|
| + occurrences.getResults(filePath);
|
| + List<server.Occurrences> mergedOccurrences =
|
| + merger.mergeOccurrences(unmergedOccurrences);
|
| + channel.sendNotification(
|
| + new server.AnalysisOccurrencesParams(filePath, mergedOccurrences)
|
| + .toNotification());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Record outline information from the plugin with the given [pluginId] for
|
| + * the file with the given [filePath].
|
| + */
|
| + void recordOutlines(
|
| + String pluginId, String filePath, List<server.Outline> outlineData) {
|
| + if (outlines.isCollectingFor(filePath)) {
|
| + outlines.putResults(filePath, pluginId, outlineData);
|
| + List<List<server.Outline>> unmergedOutlines =
|
| + outlines.getResults(filePath);
|
| + List<server.Outline> mergedOutlines =
|
| + merger.mergeOutline(unmergedOutlines);
|
| + channel.sendNotification(new server.AnalysisOutlineParams(
|
| + filePath, server.FileKind.LIBRARY, mergedOutlines[0])
|
| + .toNotification());
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Set the lists of [included] and [excluded] files.
|
| + */
|
| + void setAnalysisRoots(List<String> included, List<String> excluded) {
|
| + includedPaths = included;
|
| + excludedPaths = excluded;
|
| + }
|
| +
|
| + /**
|
| + * Set the current subscriptions to the given set of [newSubscriptions].
|
| + */
|
| + void setSubscriptions(
|
| + Map<server.AnalysisService, List<String>> newSubscriptions) {
|
| + /**
|
| + * Return the collector associated with the given service, or `null` if the
|
| + * service is not handled by this manager.
|
| + */
|
| + ResultCollector collectorFor(server.AnalysisService service) {
|
| + switch (service) {
|
| + case server.AnalysisService.FOLDING:
|
| + return folding;
|
| + case server.AnalysisService.HIGHLIGHTS:
|
| + return highlights;
|
| + case server.AnalysisService.NAVIGATION:
|
| + return navigation;
|
| + case server.AnalysisService.OCCURRENCES:
|
| + return occurrences;
|
| + case server.AnalysisService.OUTLINE:
|
| + return outlines;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + Set<server.AnalysisService> services =
|
| + new HashSet<server.AnalysisService>();
|
| + services.addAll(currentSubscriptions.keys);
|
| + services.addAll(newSubscriptions.keys);
|
| + services.forEach((server.AnalysisService service) {
|
| + ResultCollector collector = collectorFor(service);
|
| + if (collector != null) {
|
| + List<String> currentPaths = currentSubscriptions[service];
|
| + List<String> newPaths = newSubscriptions[service];
|
| + if (currentPaths == null) {
|
| + if (newPaths == null) {
|
| + // This should not happen.
|
| + return;
|
| + }
|
| + // All of the [newPaths] need to be added.
|
| + newPaths.forEach((String filePath) {
|
| + collector.startCollectingFor(filePath);
|
| + });
|
| + } else if (newPaths == null) {
|
| + // All of the [currentPaths] need to be removed.
|
| + currentPaths.forEach((String filePath) {
|
| + collector.stopCollectingFor(filePath);
|
| + });
|
| + } else {
|
| + // Compute the difference of the two sets.
|
| + newPaths.forEach((String filePath) {
|
| + if (!currentPaths.contains(filePath)) {
|
| + collector.startCollectingFor(filePath);
|
| + }
|
| + });
|
| + currentPaths.forEach((String filePath) {
|
| + if (!newPaths.contains(filePath)) {
|
| + collector.stopCollectingFor(filePath);
|
| + }
|
| + });
|
| + }
|
| + }
|
| + });
|
| + currentSubscriptions = newSubscriptions;
|
| + }
|
| +
|
| + /**
|
| + * Return `true` if errors should be collected for the file with the given
|
| + * [path] (because it is being analyzed).
|
| + */
|
| + bool _isIncluded(String path) {
|
| + bool isIncluded() {
|
| + for (String includedPath in includedPaths) {
|
| + if (provider.pathContext.isWithin(includedPath, path) ||
|
| + provider.pathContext.equals(includedPath, path)) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + bool isExcluded() {
|
| + for (String excludedPath in excludedPaths) {
|
| + if (provider.pathContext.isWithin(excludedPath, path)) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + // TODO(brianwilkerson) Return false if error notifications are globally
|
| + // disabled.
|
| + return isIncluded() && !isExcluded();
|
| + }
|
| +}
|
|
|