| // 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:async'; | |
| import 'package:analyzer/dart/analysis/results.dart'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/file_system/physical_file_system.dart'; | |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; | |
| import 'package:analyzer/src/dart/analysis/driver.dart' | |
| show AnalysisDriver, AnalysisDriverGeneric, AnalysisDriverScheduler; | |
| import 'package:analyzer/src/dart/analysis/file_byte_store.dart'; | |
| import 'package:analyzer/src/dart/analysis/file_state.dart'; | |
| import 'package:analyzer/src/dart/analysis/performance_logger.dart'; | |
| import 'package:analyzer/src/generated/sdk.dart'; | |
| import 'package:analyzer_plugin/channel/channel.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_common.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_constants.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_generated.dart'; | |
| import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'; | |
| import 'package:analyzer_plugin/src/utilities/null_string_sink.dart'; | |
| import 'package:analyzer_plugin/utilities/subscriptions/subscription_manager.dart'; | |
| import 'package:path/src/context.dart'; | |
| import 'package:pub_semver/pub_semver.dart'; | |
| /** | |
| * The abstract superclass of any class implementing a plugin for the analysis | |
| * server. | |
| * | |
| * Clients may not implement or mix-in this class, but are expected to extend | |
| * it. | |
| */ | |
| abstract class ServerPlugin { | |
| /** | |
| * A megabyte. | |
| */ | |
| static const int M = 1024 * 1024; | |
| /** | |
| * The communication channel being used to communicate with the analysis | |
| * server. | |
| */ | |
| PluginCommunicationChannel _channel; | |
| /** | |
| * The resource provider used to access the file system. | |
| */ | |
| final ResourceProvider resourceProvider; | |
| /** | |
| * The object used to manage analysis subscriptions. | |
| */ | |
| final SubscriptionManager subscriptionManager = new SubscriptionManager(); | |
| /** | |
| * The scheduler used by any analysis drivers that are created. | |
| */ | |
| AnalysisDriverScheduler analysisDriverScheduler; | |
| /** | |
| * A table mapping the current context roots to the analysis driver created | |
| * for that root. | |
| */ | |
| final Map<ContextRoot, AnalysisDriverGeneric> driverMap = | |
| <ContextRoot, AnalysisDriverGeneric>{}; | |
| /** | |
| * The performance log used by any analysis drivers that are created. | |
| */ | |
| final PerformanceLog performanceLog = | |
| new PerformanceLog(new NullStringSink()); | |
| /** | |
| * The byte store used by any analysis drivers that are created, or `null` if | |
| * the cache location isn't known because the 'plugin.version' request has not | |
| * yet been received. | |
| */ | |
| ByteStore _byteStore; | |
| /** | |
| * The SDK manager used to manage SDKs. | |
| */ | |
| DartSdkManager _sdkManager; | |
| /** | |
| * The file content overlay used by any analysis drivers that are created. | |
| */ | |
| final FileContentOverlay fileContentOverlay = new FileContentOverlay(); | |
| /** | |
| * Initialize a newly created analysis server plugin. If a resource [provider] | |
| * is given, then it will be used to access the file system. Otherwise a | |
| * resource provider that accesses the physical file system will be used. | |
| */ | |
| ServerPlugin(ResourceProvider provider) | |
| : resourceProvider = provider ?? PhysicalResourceProvider.INSTANCE { | |
| analysisDriverScheduler = new AnalysisDriverScheduler(performanceLog); | |
| analysisDriverScheduler.start(); | |
| } | |
| /** | |
| * Return the byte store used by any analysis drivers that are created, or | |
| * `null` if the cache location isn't known because the 'plugin.version' | |
| * request has not yet been received. | |
| */ | |
| ByteStore get byteStore => _byteStore; | |
| /** | |
| * Return the communication channel being used to communicate with the | |
| * analysis server, or `null` if the plugin has not been started. | |
| */ | |
| PluginCommunicationChannel get channel => _channel; | |
| /** | |
| * Return the user visible information about how to contact the plugin authors | |
| * with any problems that are found, or `null` if there is no contact info. | |
| */ | |
| String get contactInfo => null; | |
| /** | |
| * Return a list of glob patterns selecting the files that this plugin is | |
| * interested in analyzing. | |
| */ | |
| List<String> get fileGlobsToAnalyze; | |
| /** | |
| * Return the user visible name of this plugin. | |
| */ | |
| String get name; | |
| /** | |
| * Return the SDK manager used to manage SDKs. | |
| */ | |
| DartSdkManager get sdkManager => _sdkManager; | |
| /** | |
| * Return the version number of this plugin, encoded as a string. | |
| */ | |
| String get version; | |
| /** | |
| * Handle the fact that the file with the given [path] has been modified. | |
| */ | |
| void contentChanged(String path) { | |
| // Ignore changes to files. | |
| } | |
| /** | |
| * Return the context root containing the file at the given [filePath]. | |
| */ | |
| ContextRoot contextRootContaining(String filePath) { | |
| Context pathContext = resourceProvider.pathContext; | |
| /** | |
| * Return `true` if the given [child] is either the same as or within the | |
| * given [parent]. | |
| */ | |
| bool isOrWithin(String parent, String child) { | |
| return parent == child || pathContext.isWithin(parent, child); | |
| } | |
| /** | |
| * Return `true` if the given context [root] contains the target [file]. | |
| */ | |
| bool ownsFile(ContextRoot root) { | |
| if (isOrWithin(root.root, filePath)) { | |
| List<String> excludedPaths = root.exclude; | |
| for (String excludedPath in excludedPaths) { | |
| if (isOrWithin(excludedPath, filePath)) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| for (ContextRoot root in driverMap.keys) { | |
| if (ownsFile(root)) { | |
| return root; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * Create an analysis driver that can analyze the files within the given | |
| * [contextRoot]. | |
| */ | |
| AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot); | |
| /** | |
| * Return the driver being used to analyze the file with the given [path]. | |
| */ | |
| AnalysisDriverGeneric driverForPath(String path) { | |
| ContextRoot contextRoot = contextRootContaining(path); | |
| if (contextRoot == null) { | |
| return null; | |
| } | |
| return driverMap[contextRoot]; | |
| } | |
| /** | |
| * Return the result of analyzing the file with the given [path]. | |
| * | |
| * Throw a [RequestFailure] is the file cannot be analyzed or if the driver | |
| * associated with the file is not an [AnalysisDriver]. | |
| */ | |
| Future<ResolveResult> getResolveResult(String path) async { | |
| AnalysisDriverGeneric driver = driverForPath(path); | |
| if (driver is! AnalysisDriver) { | |
| // Return an error from the request. | |
| throw new RequestFailure( | |
| RequestErrorFactory.pluginError('Failed to analyze $path', null)); | |
| } | |
| ResolveResult result = await (driver as AnalysisDriver).getResult(path); | |
| ResultState state = result.state; | |
| if (state != ResultState.VALID) { | |
| // Return an error from the request. | |
| throw new RequestFailure( | |
| RequestErrorFactory.pluginError('Failed to analyze $path', null)); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Handle an 'analysis.getNavigation' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<AnalysisGetNavigationResult> handleAnalysisGetNavigation( | |
| AnalysisGetNavigationParams params) async { | |
| return new AnalysisGetNavigationResult( | |
| <String>[], <NavigationTarget>[], <NavigationRegion>[]); | |
| } | |
| /** | |
| * Handle an 'analysis.handleWatchEvents' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<AnalysisHandleWatchEventsResult> handleAnalysisHandleWatchEvents( | |
| AnalysisHandleWatchEventsParams parameters) async { | |
| for (WatchEvent event in parameters.events) { | |
| switch (event.type) { | |
| case WatchEventType.ADD: | |
| // TODO(brianwilkerson) Handle the event. | |
| break; | |
| case WatchEventType.MODIFY: | |
| contentChanged(event.path); | |
| break; | |
| case WatchEventType.REMOVE: | |
| // TODO(brianwilkerson) Handle the event. | |
| break; | |
| default: | |
| // Ignore unhandled watch event types. | |
| break; | |
| } | |
| } | |
| return new AnalysisHandleWatchEventsResult(); | |
| } | |
| /** | |
| * Handle an 'analysis.setContextRoots' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<AnalysisSetContextRootsResult> handleAnalysisSetContextRoots( | |
| AnalysisSetContextRootsParams parameters) async { | |
| List<ContextRoot> contextRoots = parameters.roots; | |
| List<ContextRoot> oldRoots = driverMap.keys.toList(); | |
| for (ContextRoot contextRoot in contextRoots) { | |
| if (!oldRoots.remove(contextRoot)) { | |
| // The context is new, so we create a driver for it. Creating the driver | |
| // has the side-effect of adding it to the analysis driver scheduler. | |
| AnalysisDriverGeneric driver = createAnalysisDriver(contextRoot); | |
| driverMap[contextRoot] = driver; | |
| _addFilesToDriver( | |
| driver, | |
| resourceProvider.getResource(contextRoot.root), | |
| contextRoot.exclude); | |
| } | |
| } | |
| for (ContextRoot contextRoot in oldRoots) { | |
| // The context has been removed, so we remove its driver. | |
| AnalysisDriverGeneric driver = driverMap.remove(contextRoot); | |
| // The `dispose` method has the side-effect of removing the driver from | |
| // the analysis driver scheduler. | |
| driver.dispose(); | |
| } | |
| return new AnalysisSetContextRootsResult(); | |
| } | |
| /** | |
| * Handle an 'analysis.setPriorityFiles' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<AnalysisSetPriorityFilesResult> handleAnalysisSetPriorityFiles( | |
| AnalysisSetPriorityFilesParams parameters) async { | |
| List<String> files = parameters.files; | |
| Map<AnalysisDriverGeneric, List<String>> filesByDriver = | |
| <AnalysisDriverGeneric, List<String>>{}; | |
| for (String file in files) { | |
| ContextRoot contextRoot = contextRootContaining(file); | |
| if (contextRoot != null) { | |
| // TODO(brianwilkerson) Which driver should we use if there is no context root? | |
| AnalysisDriverGeneric driver = driverMap[contextRoot]; | |
| filesByDriver.putIfAbsent(driver, () => <String>[]).add(file); | |
| } | |
| } | |
| filesByDriver.forEach((AnalysisDriverGeneric driver, List<String> files) { | |
| driver.priorityFiles = files; | |
| }); | |
| return new AnalysisSetPriorityFilesResult(); | |
| } | |
| /** | |
| * Handle an 'analysis.setSubscriptions' request. Most subclasses should not | |
| * override this method, but should instead use the [subscriptionManager] to | |
| * access the list of subscriptions for any given file. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<AnalysisSetSubscriptionsResult> handleAnalysisSetSubscriptions( | |
| AnalysisSetSubscriptionsParams parameters) async { | |
| Map<AnalysisService, List<String>> subscriptions = parameters.subscriptions; | |
| Map<String, List<AnalysisService>> newSubscriptions = | |
| subscriptionManager.setSubscriptions(subscriptions); | |
| sendNotificationsForSubscriptions(newSubscriptions); | |
| return new AnalysisSetSubscriptionsResult(); | |
| } | |
| /** | |
| * Handle an 'analysis.updateContent' request. Most subclasses should not | |
| * override this method, but should instead use the [contentCache] to access | |
| * the current content of overlaid files. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<AnalysisUpdateContentResult> handleAnalysisUpdateContent( | |
| AnalysisUpdateContentParams parameters) async { | |
| Map<String, Object> files = parameters.files; | |
| files.forEach((String filePath, Object overlay) { | |
| if (overlay is AddContentOverlay) { | |
| fileContentOverlay[filePath] = overlay.content; | |
| } else if (overlay is ChangeContentOverlay) { | |
| String oldContents = fileContentOverlay[filePath]; | |
| String newContents; | |
| if (oldContents == null) { | |
| // The server should only send a ChangeContentOverlay if there is | |
| // already an existing overlay for the source. | |
| throw new RequestFailure( | |
| RequestErrorFactory.invalidOverlayChangeNoContent()); | |
| } | |
| try { | |
| newContents = SourceEdit.applySequence(oldContents, overlay.edits); | |
| } on RangeError { | |
| throw new RequestFailure( | |
| RequestErrorFactory.invalidOverlayChangeInvalidEdit()); | |
| } | |
| fileContentOverlay[filePath] = newContents; | |
| } else if (overlay is RemoveContentOverlay) { | |
| fileContentOverlay[filePath] = null; | |
| } | |
| contentChanged(filePath); | |
| }); | |
| return new AnalysisUpdateContentResult(); | |
| } | |
| /** | |
| * Handle a 'completion.getSuggestions' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<CompletionGetSuggestionsResult> handleCompletionGetSuggestions( | |
| CompletionGetSuggestionsParams parameters) async => | |
| new CompletionGetSuggestionsResult( | |
| -1, -1, const <CompletionSuggestion>[]); | |
| /** | |
| * Handle an 'edit.getAssists' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<EditGetAssistsResult> handleEditGetAssists( | |
| EditGetAssistsParams parameters) async => | |
| new EditGetAssistsResult(const <PrioritizedSourceChange>[]); | |
| /** | |
| * Handle an 'edit.getAvailableRefactorings' request. Subclasses that override | |
| * this method in order to participate in refactorings must also override the | |
| * method [handleEditGetRefactoring]. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<EditGetAvailableRefactoringsResult> handleEditGetAvailableRefactorings( | |
| EditGetAvailableRefactoringsParams parameters) async => | |
| new EditGetAvailableRefactoringsResult(const <RefactoringKind>[]); | |
| /** | |
| * Handle an 'edit.getFixes' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<EditGetFixesResult> handleEditGetFixes( | |
| EditGetFixesParams parameters) async => | |
| new EditGetFixesResult(const <AnalysisErrorFixes>[]); | |
| /** | |
| * Handle an 'edit.getRefactoring' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<EditGetRefactoringResult> handleEditGetRefactoring( | |
| EditGetRefactoringParams parameters) async => | |
| null; | |
| /** | |
| * Handle a 'kythe.getKytheEntries' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<KytheGetKytheEntriesResult> handleKytheGetKytheEntries( | |
| KytheGetKytheEntriesParams parameters) async => | |
| null; | |
| /** | |
| * Handle a 'plugin.shutdown' request. Subclasses can override this method to | |
| * perform any required clean-up, but cannot prevent the plugin from shutting | |
| * down. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<PluginShutdownResult> handlePluginShutdown( | |
| PluginShutdownParams parameters) async => | |
| new PluginShutdownResult(); | |
| /** | |
| * Handle a 'plugin.versionCheck' request. | |
| * | |
| * Throw a [RequestFailure] if the request could not be handled. | |
| */ | |
| Future<PluginVersionCheckResult> handlePluginVersionCheck( | |
| PluginVersionCheckParams parameters) async { | |
| String byteStorePath = parameters.byteStorePath; | |
| String sdkPath = parameters.sdkPath; | |
| String versionString = parameters.version; | |
| Version serverVersion = new Version.parse(versionString); | |
| _byteStore = new MemoryCachingByteStore( | |
| new FileByteStore(byteStorePath, | |
| tempNameSuffix: | |
| new DateTime.now().millisecondsSinceEpoch.toString()), | |
| 64 * M); | |
| _sdkManager = new DartSdkManager(sdkPath, true); | |
| return new PluginVersionCheckResult( | |
| isCompatibleWith(serverVersion), name, version, fileGlobsToAnalyze, | |
| contactInfo: contactInfo); | |
| } | |
| /** | |
| * Return `true` if this plugin is compatible with an analysis server that is | |
| * using the given version of the plugin API. | |
| */ | |
| bool isCompatibleWith(Version serverVersion) => | |
| serverVersion <= new Version.parse(version); | |
| /** | |
| * The method that is called when the analysis server closes the communication | |
| * channel. This method will not be invoked under normal conditions because | |
| * the server will send a shutdown request and the plugin will stop listening | |
| * to the channel before the server closes the channel. | |
| */ | |
| void onDone() {} | |
| /** | |
| * The method that is called when an error has occurred in the analysis | |
| * server. This method will not be invoked under normal conditions. | |
| */ | |
| void onError(Object exception, StackTrace stackTrace) {} | |
| /** | |
| * If the plugin provides folding information, send a folding notification | |
| * for the file with the given [path] to the server. | |
| */ | |
| Future<Null> sendFoldingNotification(String path) { | |
| return new Future.value(); | |
| } | |
| /** | |
| * If the plugin provides highlighting information, send a highlights | |
| * notification for the file with the given [path] to the server. | |
| */ | |
| Future<Null> sendHighlightsNotification(String path) { | |
| return new Future.value(); | |
| } | |
| /** | |
| * If the plugin provides navigation information, send a navigation | |
| * notification for the file with the given [path] to the server. | |
| */ | |
| Future<Null> sendNavigationNotification(String path) { | |
| return new Future.value(); | |
| } | |
| /** | |
| * Send notifications for the services subscribed to for the file with the | |
| * given [path]. | |
| * | |
| * This is a convenience method that subclasses can use to send notifications | |
| * after analysis has been performed on a file. | |
| */ | |
| void sendNotificationsForFile(String path) { | |
| for (AnalysisService service in subscriptionManager.servicesForFile(path)) { | |
| _sendNotificationForFile(path, service); | |
| } | |
| } | |
| /** | |
| * Send notifications corresponding to the given description of | |
| * [subscriptions]. The map is keyed by the path of each file for which | |
| * notifications should be sent and has values representing the list of | |
| * services associated with the notifications to send. | |
| * | |
| * This method is used when the set of subscribed notifications has been | |
| * changed and notifications need to be sent even when the specified files | |
| * have already been analyzed. | |
| */ | |
| void sendNotificationsForSubscriptions( | |
| Map<String, List<AnalysisService>> subscriptions) { | |
| subscriptions.forEach((String path, List<AnalysisService> services) { | |
| for (AnalysisService service in services) { | |
| _sendNotificationForFile(path, service); | |
| } | |
| }); | |
| } | |
| /** | |
| * If the plugin provides occurrences information, send an occurrences | |
| * notification for the file with the given [path] to the server. | |
| */ | |
| Future<Null> sendOccurrencesNotification(String path) { | |
| return new Future.value(); | |
| } | |
| /** | |
| * If the plugin provides outline information, send an outline notification | |
| * for the file with the given [path] to the server. | |
| */ | |
| Future<Null> sendOutlineNotification(String path) { | |
| return new Future.value(); | |
| } | |
| /** | |
| * Start this plugin by listening to the given communication [channel]. | |
| */ | |
| void start(PluginCommunicationChannel channel) { | |
| _channel = channel; | |
| _channel.listen(_onRequest, onError: onError, onDone: onDone); | |
| } | |
| /** | |
| * Add all of the files contained in the given [resource] that are not in the | |
| * list of [excluded] resources to the given [driver]. | |
| */ | |
| void _addFilesToDriver( | |
| AnalysisDriverGeneric driver, Resource resource, List<String> excluded) { | |
| String path = resource.path; | |
| if (excluded.contains(path)) { | |
| return; | |
| } | |
| if (resource is File) { | |
| driver.addFile(path); | |
| } else if (resource is Folder) { | |
| try { | |
| for (Resource child in resource.getChildren()) { | |
| _addFilesToDriver(driver, child, excluded); | |
| } | |
| } on FileSystemException { | |
| // The folder does not exist, so ignore it. | |
| } | |
| } | |
| } | |
| /** | |
| * Compute the response that should be returned for the given [request], or | |
| * `null` if the response has already been sent. | |
| */ | |
| Future<Response> _getResponse(Request request, int requestTime) async { | |
| ResponseResult result = null; | |
| switch (request.method) { | |
| case ANALYSIS_REQUEST_GET_NAVIGATION: | |
| var params = new AnalysisGetNavigationParams.fromRequest(request); | |
| result = await handleAnalysisGetNavigation(params); | |
| break; | |
| case ANALYSIS_REQUEST_HANDLE_WATCH_EVENTS: | |
| var params = new AnalysisHandleWatchEventsParams.fromRequest(request); | |
| result = await handleAnalysisHandleWatchEvents(params); | |
| break; | |
| case ANALYSIS_REQUEST_SET_CONTEXT_ROOTS: | |
| var params = new AnalysisSetContextRootsParams.fromRequest(request); | |
| result = await handleAnalysisSetContextRoots(params); | |
| break; | |
| case ANALYSIS_REQUEST_SET_PRIORITY_FILES: | |
| var params = new AnalysisSetPriorityFilesParams.fromRequest(request); | |
| result = await handleAnalysisSetPriorityFiles(params); | |
| break; | |
| case ANALYSIS_REQUEST_SET_SUBSCRIPTIONS: | |
| var params = new AnalysisSetSubscriptionsParams.fromRequest(request); | |
| result = await handleAnalysisSetSubscriptions(params); | |
| break; | |
| case ANALYSIS_REQUEST_UPDATE_CONTENT: | |
| var params = new AnalysisUpdateContentParams.fromRequest(request); | |
| result = await handleAnalysisUpdateContent(params); | |
| break; | |
| case COMPLETION_REQUEST_GET_SUGGESTIONS: | |
| var params = new CompletionGetSuggestionsParams.fromRequest(request); | |
| result = await handleCompletionGetSuggestions(params); | |
| break; | |
| case EDIT_REQUEST_GET_ASSISTS: | |
| var params = new EditGetAssistsParams.fromRequest(request); | |
| result = await handleEditGetAssists(params); | |
| break; | |
| case EDIT_REQUEST_GET_AVAILABLE_REFACTORINGS: | |
| var params = | |
| new EditGetAvailableRefactoringsParams.fromRequest(request); | |
| result = await handleEditGetAvailableRefactorings(params); | |
| break; | |
| case EDIT_REQUEST_GET_FIXES: | |
| var params = new EditGetFixesParams.fromRequest(request); | |
| result = await handleEditGetFixes(params); | |
| break; | |
| case EDIT_REQUEST_GET_REFACTORING: | |
| var params = new EditGetRefactoringParams.fromRequest(request); | |
| result = await handleEditGetRefactoring(params); | |
| break; | |
| case KYTHE_REQUEST_GET_KYTHE_ENTRIES: | |
| var params = new KytheGetKytheEntriesParams.fromRequest(request); | |
| result = await handleKytheGetKytheEntries(params); | |
| break; | |
| case PLUGIN_REQUEST_SHUTDOWN: | |
| var params = new PluginShutdownParams(); | |
| result = await handlePluginShutdown(params); | |
| _channel.sendResponse(result.toResponse(request.id, requestTime)); | |
| _channel.close(); | |
| return null; | |
| case PLUGIN_REQUEST_VERSION_CHECK: | |
| var params = new PluginVersionCheckParams.fromRequest(request); | |
| result = await handlePluginVersionCheck(params); | |
| break; | |
| } | |
| if (result == null) { | |
| return new Response(request.id, requestTime, | |
| error: RequestErrorFactory.unknownRequest(request.method)); | |
| } | |
| return result.toResponse(request.id, requestTime); | |
| } | |
| /** | |
| * The method that is called when a [request] is received from the analysis | |
| * server. | |
| */ | |
| Future<Null> _onRequest(Request request) async { | |
| int requestTime = new DateTime.now().millisecondsSinceEpoch; | |
| String id = request.id; | |
| Response response; | |
| try { | |
| response = await _getResponse(request, requestTime); | |
| } on RequestFailure catch (exception) { | |
| response = new Response(id, requestTime, error: exception.error); | |
| } catch (exception, stackTrace) { | |
| response = new Response(id, requestTime, | |
| error: new RequestError( | |
| RequestErrorCode.PLUGIN_ERROR, exception.toString(), | |
| stackTrace: stackTrace.toString())); | |
| } | |
| if (response != null) { | |
| _channel.sendResponse(response); | |
| } | |
| } | |
| /** | |
| * Send a notification for the file at the given [path] corresponding to the | |
| * given [service]. | |
| */ | |
| void _sendNotificationForFile(String path, AnalysisService service) { | |
| switch (service) { | |
| case AnalysisService.FOLDING: | |
| sendFoldingNotification(path); | |
| break; | |
| case AnalysisService.HIGHLIGHTS: | |
| sendHighlightsNotification(path); | |
| break; | |
| case AnalysisService.NAVIGATION: | |
| sendNavigationNotification(path); | |
| break; | |
| case AnalysisService.OCCURRENCES: | |
| sendOccurrencesNotification(path); | |
| break; | |
| case AnalysisService.OUTLINE: | |
| sendOutlineNotification(path); | |
| break; | |
| } | |
| } | |
| } |