| // Copyright (c) 2014, 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. | |
| /// @docImport 'package:analysis_server_plugin/src/plugin_server.dart'; | |
| library; | |
| import 'dart:async'; | |
| import 'dart:io' as io; | |
| import 'dart:math' show max; | |
| import 'package:analysis_server/lsp_protocol/protocol.dart' as lsp; | |
| import 'package:analysis_server/protocol/protocol.dart'; | |
| import 'package:analysis_server/protocol/protocol_constants.dart'; | |
| import 'package:analysis_server/protocol/protocol_generated.dart' | |
| hide AnalysisOptions, MessageType; | |
| import 'package:analysis_server/src/analysis_server.dart'; | |
| import 'package:analysis_server/src/analytics/analytics_manager.dart'; | |
| import 'package:analysis_server/src/channel/channel.dart'; | |
| import 'package:analysis_server/src/computer/computer_highlights.dart'; | |
| import 'package:analysis_server/src/domains/analysis/occurrences.dart'; | |
| import 'package:analysis_server/src/domains/analysis/occurrences_dart.dart'; | |
| import 'package:analysis_server/src/flutter/flutter_notifications.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_get_errors.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_get_hover.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_get_imported_elements.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_get_navigation.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_get_signature.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_reanalyze.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_set_analysis_roots.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_set_general_subscriptions.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_set_priority_files.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_set_subscriptions.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_update_content.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analysis_update_options.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analytics_enable.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analytics_is_enabled.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analytics_send_event.dart'; | |
| import 'package:analysis_server/src/handler/legacy/analytics_send_timing.dart'; | |
| import 'package:analysis_server/src/handler/legacy/completion_get_suggestion_details2.dart'; | |
| import 'package:analysis_server/src/handler/legacy/completion_get_suggestions2.dart'; | |
| import 'package:analysis_server/src/handler/legacy/diagnostic_get_diagnostics.dart'; | |
| import 'package:analysis_server/src/handler/legacy/diagnostic_get_server_port.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_bulk_fixes.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_format.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_format_if_enabled.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_get_assists.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_get_available_refactorings.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_get_fixes.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_get_postfix_completion.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_get_refactoring.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_get_statement_completion.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_import_elements.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_is_postfix_completion_applicable.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_list_postfix_completion_templates.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_organize_directives.dart'; | |
| import 'package:analysis_server/src/handler/legacy/edit_sort_members.dart'; | |
| import 'package:analysis_server/src/handler/legacy/execution_create_context.dart'; | |
| import 'package:analysis_server/src/handler/legacy/execution_delete_context.dart'; | |
| import 'package:analysis_server/src/handler/legacy/execution_get_suggestions.dart'; | |
| import 'package:analysis_server/src/handler/legacy/execution_map_uri.dart'; | |
| import 'package:analysis_server/src/handler/legacy/execution_set_subscriptions.dart'; | |
| import 'package:analysis_server/src/handler/legacy/flutter_get_widget_description.dart'; | |
| import 'package:analysis_server/src/handler/legacy/flutter_set_subscriptions.dart'; | |
| import 'package:analysis_server/src/handler/legacy/flutter_set_widget_property_value.dart'; | |
| import 'package:analysis_server/src/handler/legacy/legacy_handler.dart'; | |
| import 'package:analysis_server/src/handler/legacy/lsp_notification_over_legacy_handler.dart'; | |
| import 'package:analysis_server/src/handler/legacy/lsp_over_legacy_handler.dart'; | |
| import 'package:analysis_server/src/handler/legacy/search_find_element_references.dart'; | |
| import 'package:analysis_server/src/handler/legacy/search_find_member_declarations.dart'; | |
| import 'package:analysis_server/src/handler/legacy/search_find_member_references.dart'; | |
| import 'package:analysis_server/src/handler/legacy/search_find_top_level_declarations.dart'; | |
| import 'package:analysis_server/src/handler/legacy/search_get_element_declarations.dart'; | |
| import 'package:analysis_server/src/handler/legacy/search_get_type_hierarchy.dart'; | |
| import 'package:analysis_server/src/handler/legacy/server_cancel_request.dart'; | |
| import 'package:analysis_server/src/handler/legacy/server_get_version.dart'; | |
| import 'package:analysis_server/src/handler/legacy/server_set_client_capabilities.dart'; | |
| import 'package:analysis_server/src/handler/legacy/server_set_subscriptions.dart'; | |
| import 'package:analysis_server/src/handler/legacy/server_shutdown.dart'; | |
| import 'package:analysis_server/src/handler/legacy/unsupported_request.dart'; | |
| import 'package:analysis_server/src/lsp/client_capabilities.dart' as lsp; | |
| import 'package:analysis_server/src/lsp/client_configuration.dart' as lsp; | |
| import 'package:analysis_server/src/lsp/constants.dart' as lsp; | |
| import 'package:analysis_server/src/lsp/handlers/handler_states.dart'; | |
| import 'package:analysis_server/src/lsp/lsp_analysis_server.dart'; | |
| import 'package:analysis_server/src/operation/operation_analysis.dart'; | |
| import 'package:analysis_server/src/plugin/notification_manager.dart'; | |
| import 'package:analysis_server/src/protocol_server.dart' as server; | |
| import 'package:analysis_server/src/scheduler/scheduled_message.dart'; | |
| import 'package:analysis_server/src/server/crash_reporting_attachments.dart'; | |
| import 'package:analysis_server/src/server/debounce_requests.dart'; | |
| import 'package:analysis_server/src/server/detachable_filesystem_manager.dart'; | |
| import 'package:analysis_server/src/server/diagnostic_server.dart'; | |
| import 'package:analysis_server/src/server/error_notifier.dart'; | |
| import 'package:analysis_server/src/server/features.dart'; | |
| import 'package:analysis_server/src/server/sdk_configuration.dart'; | |
| import 'package:analysis_server/src/services/completion/completion_state.dart'; | |
| import 'package:analysis_server/src/services/execution/execution_context.dart'; | |
| import 'package:analysis_server/src/services/flutter/widget_descriptions.dart'; | |
| import 'package:analysis_server/src/services/refactoring/legacy/refactoring_manager.dart'; | |
| import 'package:analysis_server/src/session_logger/session_logger.dart'; | |
| import 'package:analysis_server/src/status/performance_logger.dart'; | |
| import 'package:analysis_server/src/utilities/extensions/resource_provider.dart'; | |
| import 'package:analysis_server/src/utilities/process.dart'; | |
| import 'package:analysis_server_plugin/src/correction/performance.dart'; | |
| import 'package:analyzer/dart/analysis/results.dart'; | |
| import 'package:analyzer/dart/analysis/session.dart'; | |
| import 'package:analyzer/dart/ast/ast.dart'; | |
| import 'package:analyzer/exception/exception.dart'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/instrumentation/instrumentation.dart'; | |
| import 'package:analyzer/src/analysis_options/analysis_options.dart'; | |
| import 'package:analyzer/src/dart/analysis/status.dart' as analysis; | |
| import 'package:analyzer/src/generated/sdk.dart'; | |
| import 'package:analyzer/src/util/file_paths.dart' as file_paths; | |
| import 'package:analyzer/src/util/performance/operation_performance.dart'; | |
| import 'package:analyzer/src/utilities/cancellation.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element; | |
| import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart'; | |
| import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart'; | |
| import 'package:analyzer_plugin/src/utilities/navigation/navigation_dart.dart'; | |
| import 'package:http/http.dart' as http; | |
| import 'package:language_server_protocol/json_parsing.dart' as lsp; | |
| import 'package:meta/meta.dart'; | |
| import 'package:telemetry/crash_reporting.dart'; | |
| import 'package:watcher/watcher.dart'; | |
| typedef AnalysisOptionsBuilderUpdater = void Function( | |
| AnalysisOptionsBuilder analysisOptionsBuilder, | |
| ); | |
| /// A function that can be executed to create a handler for a request. | |
| typedef HandlerGenerator = LegacyHandler Function( | |
| LegacyAnalysisServer, | |
| Request, | |
| CancellationToken, | |
| OperationPerformanceImpl, | |
| ); | |
| /// Various IDE options. | |
| class AnalysisServerOptions { | |
| String? newAnalysisDriverLog; | |
| String? clientId; | |
| String? clientVersion; | |
| /// Base path where to cache data. | |
| String? cacheFolder; | |
| /// If `true`, don't create a persistent byte store. | |
| /// | |
| /// Intended for benchmarking as it makes "instructions:u" in `perf stat` more | |
| /// stable, although it then doesn't benchmark that code. | |
| bool? disableFileByteStore; | |
| /// Whether to use the experimental single-file persistent byte store. | |
| /// Ignored when [disableFileByteStore] is `true` or a byte store is supplied. | |
| bool useSingleFileByteStore = false; | |
| /// The path to the package config file override. | |
| /// If `null`, then the default discovery mechanism is used. | |
| String? packagesFile; | |
| /// The crash report sender instance; note, this object can be `null`, and | |
| /// should be accessed via a null-aware operator. | |
| CrashReportSender? crashReportSender; | |
| /// An optional set of configuration overrides specified by the SDK. | |
| /// | |
| /// These overrides can provide new values for configuration settings, and are | |
| /// generally used in specific SDKs (like the internal google3 one). | |
| SdkConfiguration? configurationOverrides; | |
| /// Whether to use the Language Server Protocol. | |
| bool useLanguageServerProtocol = false; | |
| /// The set of enabled features. | |
| FeatureSet featureSet = FeatureSet(); | |
| /// If set, this string will be reported as the protocol version. | |
| String? reportProtocolVersion; | |
| /// Experiments which have been enabled (or disabled) via the | |
| /// `--enable-experiment` command-line option. | |
| List<String> enabledExperiments = []; | |
| /// Whether to enable fine-grained dependencies. | |
| bool withFineDependencies = false; | |
| bool usePlugins = true; | |
| } | |
| /// Instances of the class [LegacyAnalysisServer] implement a server that | |
| /// listens on a [ServerCommunicationChannel] for analysis requests and | |
| /// processes them. | |
| class LegacyAnalysisServer extends AnalysisServer { | |
| /// A map from the name of a request to a function used to create a request | |
| /// handler. | |
| /// | |
| /// Requests that don't match anything in this map will be passed to | |
| /// [LspOverLegacyHandler] for possible handling before returning an error. | |
| static final Map<String, HandlerGenerator> requestHandlerGenerators = { | |
| analysisRequestGetErrors: AnalysisGetErrorsHandler.new, | |
| analysisRequestGetHover: AnalysisGetHoverHandler.new, | |
| analysisRequestGetImportedElements: AnalysisGetImportedElementsHandler.new, | |
| analysisRequestGetLibraryDependencies: UnsupportedRequestHandler.new, | |
| analysisRequestGetNavigation: AnalysisGetNavigationHandler.new, | |
| analysisRequestGetReachableSources: UnsupportedRequestHandler.new, | |
| analysisRequestGetSignature: AnalysisGetSignatureHandler.new, | |
| analysisRequestReanalyze: AnalysisReanalyzeHandler.new, | |
| analysisRequestSetAnalysisRoots: AnalysisSetAnalysisRootsHandler.new, | |
| analysisRequestSetGeneralSubscriptions: | |
| AnalysisSetGeneralSubscriptionsHandler.new, | |
| analysisRequestSetPriorityFiles: AnalysisSetPriorityFilesHandler.new, | |
| analysisRequestSetSubscriptions: AnalysisSetSubscriptionsHandler.new, | |
| analysisRequestUpdateContent: AnalysisUpdateContentHandler.new, | |
| analysisRequestUpdateOptions: AnalysisUpdateOptionsHandler.new, | |
| analyticsRequestIsEnabled: AnalyticsIsEnabledHandler.new, | |
| analyticsRequestEnable: AnalyticsEnableHandler.new, | |
| analyticsRequestSendEvent: AnalyticsSendEventHandler.new, | |
| analyticsRequestSendTiming: AnalyticsSendTimingHandler.new, | |
| completionRequestGetSuggestionDetails2: | |
| CompletionGetSuggestionDetails2Handler.new, | |
| completionRequestGetSuggestions2: CompletionGetSuggestions2Handler.new, | |
| diagnosticRequestGetDiagnostics: DiagnosticGetDiagnosticsHandler.new, | |
| diagnosticRequestGetServerPort: DiagnosticGetServerPortHandler.new, | |
| editRequestFormat: EditFormatHandler.new, | |
| editRequestFormatIfEnabled: EditFormatIfEnabledHandler.new, | |
| editRequestGetAssists: EditGetAssistsHandler.new, | |
| editRequestGetAvailableRefactorings: | |
| EditGetAvailableRefactoringsHandler.new, | |
| editRequestBulkFixes: EditBulkFixes.new, | |
| editRequestGetFixes: EditGetFixesHandler.new, | |
| editRequestGetRefactoring: EditGetRefactoringHandler.new, | |
| editRequestImportElements: EditImportElementsHandler.new, | |
| editRequestOrganizeDirectives: EditOrganizeDirectivesHandler.new, | |
| editRequestSortMembers: EditSortMembersHandler.new, | |
| editRequestGetStatementCompletion: EditGetStatementCompletionHandler.new, | |
| editRequestIsPostfixCompletionApplicable: | |
| EditIsPostfixCompletionApplicableHandler.new, | |
| editRequestGetPostfixCompletion: EditGetPostfixCompletionHandler.new, | |
| editRequestListPostfixCompletionTemplates: | |
| EditListPostfixCompletionTemplatesHandler.new, | |
| executionRequestCreateContext: ExecutionCreateContextHandler.new, | |
| executionRequestDeleteContext: ExecutionDeleteContextHandler.new, | |
| executionRequestGetSuggestions: ExecutionGetSuggestionsHandler.new, | |
| executionRequestMapUri: ExecutionMapUriHandler.new, | |
| executionRequestSetSubscriptions: ExecutionSetSubscriptionsHandler.new, | |
| flutterRequestGetWidgetDescription: FlutterGetWidgetDescriptionHandler.new, | |
| flutterRequestSetWidgetPropertyValue: | |
| FlutterSetWidgetPropertyValueHandler.new, | |
| flutterRequestSetSubscriptions: FlutterSetSubscriptionsHandler.new, | |
| searchRequestFindElementReferences: SearchFindElementReferencesHandler.new, | |
| searchRequestFindMemberDeclarations: | |
| SearchFindMemberDeclarationsHandler.new, | |
| searchRequestFindMemberReferences: SearchFindMemberReferencesHandler.new, | |
| searchRequestFindTopLevelDeclarations: | |
| SearchFindTopLevelDeclarationsHandler.new, | |
| searchRequestGetElementDeclarations: | |
| SearchGetElementDeclarationsHandler.new, | |
| searchRequestGetTypeHierarchy: SearchGetTypeHierarchyHandler.new, | |
| serverRequestCancelRequest: ServerCancelRequestHandler.new, | |
| serverRequestGetVersion: ServerGetVersionHandler.new, | |
| serverRequestSetClientCapabilities: ServerSetClientCapabilitiesHandler.new, | |
| serverRequestSetSubscriptions: ServerSetSubscriptionsHandler.new, | |
| serverRequestShutdown: ServerShutdownHandler.new, | |
| lspRequestHandle: LspOverLegacyHandler.new, | |
| }; | |
| /// The channel from which requests are received and to which responses should | |
| /// be sent. | |
| final ServerCommunicationChannel channel; | |
| /// The handler that is used for LSP messages once LSP initialization is | |
| /// complete (see [completeLspInitialization]). | |
| late final InitializedStateMessageHandler lspMessageHandler = | |
| InitializedStateMessageHandler(this); | |
| /// Whether the client has sent `server.setClientCapabilities`. | |
| /// | |
| /// A client that never sends it uses the default capabilities and | |
| /// configuration, so LSP initialization does not need to wait for anything | |
| /// (see [ensureLspInitializedForClientWithoutCapabilities]). | |
| bool _hasReceivedClientCapabilities = false; | |
| /// Whether either the last status message sent to the client or the last | |
| /// status message sent from any [PluginServer] indicated `isWorking: true`. | |
| @visibleForTesting | |
| bool statusAnalyzing = false; | |
| /// Whether the analysis server is currently analyzing (not including any | |
| /// plugins). | |
| bool serverStatusAnalyzing = false; | |
| /// A set of the [ServerService]s to send notifications for. | |
| Set<ServerService> serverServices = {}; | |
| /// A table mapping request ids to cancellation tokens that allow cancelling | |
| /// the request. | |
| /// | |
| /// Tokens are removed once a request completes and should not be assumed to | |
| /// exist in this table just because cancellation was requested. | |
| Map<String, CancelableToken> cancellationTokens = {}; | |
| /// A set of the [GeneralAnalysisService]s to send notifications for. | |
| Set<GeneralAnalysisService> generalAnalysisServices = {}; | |
| /// A table mapping [AnalysisService]s to the file paths for which these | |
| /// notifications should be sent. | |
| Map<AnalysisService, Set<String>> analysisServices = {}; | |
| /// The most recently registered set of client capabilities. The default is to | |
| /// have no registered requests and no additional capabilities. | |
| /// | |
| /// Must be modified through the [clientCapabilities] setter. | |
| ServerSetClientCapabilitiesParams _clientCapabilities = | |
| ServerSetClientCapabilitiesParams([]); | |
| /// See [editorClientCapabilities]. | |
| var _editorClientCapabilities = lsp.fixedBasicLspClientCapabilities; | |
| @override | |
| final lsp.LspClientConfiguration lspClientConfiguration; | |
| /// A table mapping [FlutterService]s to the file paths for which these | |
| /// notifications should be sent. | |
| Map<FlutterService, Set<String>> flutterServices = {}; | |
| /// The support for Flutter properties. | |
| WidgetDescriptions flutterWidgetDescriptions = WidgetDescriptions(); | |
| /// The state used by the completion domain handlers. | |
| final CompletionState completionState = CompletionState(); | |
| /// The object used to manage uncompleted refactorings. | |
| late RefactoringManager? _refactoringManager; | |
| /// The context used by the execution domain handlers. | |
| final ExecutionContext executionContext = ExecutionContext(); | |
| /// The next search response id. | |
| int nextSearchId = 0; | |
| /// The [Completer] that completes when analysis is complete. | |
| /// | |
| /// This Completer is not used for communicating to the client whether we are | |
| /// analyzing; it is only used by some 'search_find' handlers, and in some tests. | |
| Completer<void>? _onAnalysisCompleteCompleter; | |
| /// The controller that is notified when analysis is started. | |
| final StreamController<bool> _onAnalysisStartedController = | |
| StreamController.broadcast(); | |
| /// If the "analysis.analyzedFiles" notification is currently being subscribed | |
| /// to (see [generalAnalysisServices]), and at least one such notification has | |
| /// been sent since the subscription was enabled, the set of analyzed files | |
| /// that was delivered in the most recently sent notification. Otherwise | |
| /// `null`. | |
| Set<String>? prevAnalyzedFiles; | |
| /// The controller for [onAnalysisSetChanged]. | |
| final StreamController<void> _onAnalysisSetChangedController = | |
| StreamController.broadcast(sync: true); | |
| /// An optional manager to handle file systems which may not always be | |
| /// available. | |
| final DetachableFileSystemManager? detachableFileSystemManager; | |
| /// The broadcast stream of requests that were discarded because there | |
| /// was another request that made this one irrelevant. | |
| @visibleForTesting | |
| final StreamController<Request> discardedRequests = | |
| StreamController.broadcast(sync: true); | |
| /// The index of the next request from the server to the client. | |
| int nextServerRequestId = 0; | |
| /// A table mapping the ids of requests sent from the server to the client | |
| /// that have not yet received a response, to the completer used to return the | |
| /// response when it has been received. | |
| Map<String, Completer<Response>> pendingServerRequests = {}; | |
| /// Whether the server should send LSP notifications. | |
| /// | |
| /// This is set once the client sends any LSP request or client capability | |
| /// that depends on LSP functionality. | |
| bool sendLspNotifications = false; | |
| /// Initialize a newly created server to receive requests from and send | |
| /// responses to the given [channel]. | |
| new( | |
| this.channel, | |
| ResourceProvider baseResourceProvider, | |
| AnalysisServerOptions options, | |
| DartSdkManager sdkManager, | |
| AnalyticsManager analyticsManager, | |
| CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder, | |
| InstrumentationService instrumentationService, | |
| SessionLogger sessionLogger, { | |
| http.Client? httpClient, | |
| ProcessRunner? processRunner, | |
| super.requestStatistics, | |
| DiagnosticServer? diagnosticServer, | |
| this.detachableFileSystemManager, | |
| super.enableBlazeWatcher, | |
| super.dartFixPromptManager, | |
| super.providedByteStore, | |
| super.pluginManager, | |
| super.messageSchedulerListener, | |
| PerformanceLogger? performanceLogger, | |
| super.environment, | |
| }) : lspClientConfiguration = lsp.LspClientConfiguration( | |
| baseResourceProvider.pathContext, | |
| ), | |
| super( | |
| options, | |
| sdkManager, | |
| diagnosticServer, | |
| analyticsManager, | |
| crashReportingAttachmentsBuilder, | |
| baseResourceProvider, | |
| instrumentationService, | |
| sessionLogger, | |
| httpClient, | |
| processRunner, | |
| NotificationManager(channel, baseResourceProvider.pathContext), | |
| usePlugins: options.usePlugins, | |
| ) { | |
| notificationManager.analysisServer = this; | |
| var contextManagerCallbacks = ServerContextManagerCallbacks( | |
| this, | |
| resourceProvider, | |
| ); | |
| contextManager.callbacks = contextManagerCallbacks; | |
| analysisDriverSchedulerEventsSubscription = analysisDriverScheduler.events | |
| .listen(handleAnalysisEvent); | |
| analysisDriverScheduler.start(); | |
| onAnalysisStarted.first.then((_) { | |
| onAnalysisComplete.then((_) { | |
| performance = performanceAfterStartup = ServerPerformance(); | |
| }); | |
| }); | |
| channel.sendNotification( | |
| ServerConnectedParams( | |
| options.reportProtocolVersion ?? PROTOCOL_VERSION, | |
| io.pid, | |
| ).toNotification(clientUriConverter: uriConverter), | |
| ); | |
| debounceRequests( | |
| channel, | |
| discardedRequests, | |
| ).listen(handleClientMessage, onDone: done, onError: error); | |
| _newRefactoringManager(); | |
| pluginManager.initializedCompleter.future.then((_) { | |
| // Perform "on idle" tasks in case the `pluginManger` determines that no | |
| // plugins should be run, _after_ the analysis server has reported its | |
| // final `isAnalyzing: false` status. | |
| _performOnIdleActions( | |
| // Use the existing plugin analyzing status. | |
| isPluginAnalyzing: notificationManager.pluginStatusAnalyzing, | |
| ); | |
| }); | |
| notificationManager.pluginAnalysisStatusChanges.listen(( | |
| pluginStatusAnalyzing, | |
| ) { | |
| if (!pluginManager.initializedCompleter.isCompleted) { | |
| // Without `this.`, some portion of the analyzer believes we are | |
| // accessing the super parameter, instead of the field in the super | |
| // class. See https://github.com/dart-lang/sdk/issues/59996. | |
| // ignore: unnecessary_this | |
| this.pluginManager.initializedCompleter.complete(); | |
| } else { | |
| _performOnIdleActions(isPluginAnalyzing: pluginStatusAnalyzing); | |
| } | |
| }); | |
| } | |
| /// The most recently registered set of client capabilities. The default is to | |
| /// have no registered requests and no additional capabilities. | |
| ServerSetClientCapabilitiesParams get clientCapabilities => | |
| _clientCapabilities; | |
| /// Updates the current set of client capabilities. | |
| set clientCapabilities(ServerSetClientCapabilitiesParams capabilities) { | |
| _clientCapabilities = capabilities; | |
| _hasReceivedClientCapabilities = true; | |
| if (capabilities.supportsUris ?? false) { | |
| // URI support implies LSP, as that's the only way to access (and get | |
| // change notifications for) custom-scheme files. | |
| uriConverter = ClientUriConverter.withVirtualFileSupport( | |
| resourceProvider.pathContext, | |
| ); | |
| // supportsUris implies LSP-over-Legacy support. | |
| initializeLspOverLegacy(); | |
| } else { | |
| uriConverter = ClientUriConverter.noop(resourceProvider.pathContext); | |
| } | |
| if (capabilities.lspCapabilities | |
| case Map<Object?, Object?> lspCapabilities) { | |
| initializeLspOverLegacy(); | |
| // First validate the capabilities so we can get a better message if it's | |
| // invalid. | |
| var reporter = lsp.LspJsonReporter(); | |
| if (!lsp.ClientCapabilities.canParse(lspCapabilities, reporter)) { | |
| throw RequestError( | |
| RequestErrorCode.INVALID_PARAMETER, | |
| "The 'lspCapabilities' parameter was invalid: ${reporter.errors.join(', ')}", | |
| ); | |
| } | |
| _editorClientCapabilities = lsp.LspClientCapabilities( | |
| lsp.ClientCapabilities.fromJson(lspCapabilities.cast<String, Object>()), | |
| ); | |
| } | |
| } | |
| @override | |
| lsp.LspClientCapabilities get editorClientCapabilities => | |
| _editorClientCapabilities; | |
| @override | |
| // Currently, we don't support this for LSP-over-Legacy. | |
| LspInitializationOptions? get initializationOptions => null; | |
| @override | |
| NotificationManager get notificationManager => | |
| super.notificationManager as NotificationManager; | |
| /// The [Future] that completes when analysis is complete. | |
| /// | |
| /// This Future is not used for communicating to the client whether we are | |
| /// analyzing; it is only used by 'search_find' handlers, tests, and for | |
| /// performance calculations. | |
| Future<void> get onAnalysisComplete { | |
| if (_isAnalysisComplete) { | |
| return Future.value(); | |
| } | |
| var completer = _onAnalysisCompleteCompleter ??= Completer<void>(); | |
| return completer.future; | |
| } | |
| /// The stream that is notified when the analysis set is changed - this might | |
| /// be a change to a file, external via a watch event, or internal via | |
| /// overlay. This means that the resolved world might have changed. | |
| /// | |
| /// The type of produced elements is not specified and should not be used. | |
| Stream<void> get onAnalysisSetChanged => | |
| _onAnalysisSetChangedController.stream; | |
| /// The stream that is notified with `true` when analysis is started. | |
| Stream<bool> get onAnalysisStarted { | |
| return _onAnalysisStartedController.stream; | |
| } | |
| @override | |
| OpenUriNotificationSender? get openUriNotificationSender { | |
| if (!clientCapabilities.requests.contains('openUrlRequest')) { | |
| return null; | |
| } | |
| return (Uri uri) async { | |
| var requestId = '${nextServerRequestId++}'; | |
| await sendRequest( | |
| ServerOpenUrlRequestParams('$uri') | |
| .toRequest(requestId, clientUriConverter: uriConverter), | |
| ); | |
| }; | |
| } | |
| RefactoringManager? get refactoringManager { | |
| var refactoringManager = _refactoringManager; | |
| if (refactoringManager == null) { | |
| return null; | |
| } | |
| if (refactoringManager.hasPendingRequest) { | |
| refactoringManager.cancel(); | |
| _newRefactoringManager(); | |
| } | |
| return _refactoringManager; | |
| } | |
| String get sdkPath { | |
| return sdkManager.defaultSdkDirectory; | |
| } | |
| @override | |
| @protected | |
| bool get supportsShowMessageRequest => | |
| clientCapabilities.requests.contains('showMessageRequest'); | |
| // TODO(srawlins): Do we need to alter this to account for plugin status? | |
| bool get _isAnalysisComplete => !analysisDriverScheduler.isWorking; | |
| void cancelRequest(String id) { | |
| cancellationTokens[id]?.cancel(); | |
| } | |
| /// Completes LSP initialization unless that has already happened. | |
| /// | |
| /// Unlike the LSP server, which completes initialization exactly once from | |
| /// the `initialized` notification, the legacy server has several triggers and | |
| /// some of them can run more than once: `server.setClientCapabilities` (which | |
| /// a client could send multiple times, but is not recommended) completes it | |
| /// either immediately or once the configuration fetch it started has | |
| /// finished, and every LSP-over-Legacy message from a client that has not | |
| /// sent capabilities completes it as well. Whichever runs first completes | |
| /// the initialization, the others must be no-ops. | |
| void completeLspInitializationIfNeeded() { | |
| if (!isLspInitialized) { | |
| completeLspInitialization(lspMessageHandler); | |
| } | |
| } | |
| /// The socket from which requests are being read has been closed. | |
| void done() {} | |
| /// Completes LSP initialization for a client that will never send | |
| /// `server.setClientCapabilities`. | |
| /// | |
| /// Such a client gets the default capabilities and configuration, so there is | |
| /// nothing to wait for. A client that does send capabilities completes the | |
| /// initialization itself once it has provided its configuration (see | |
| /// [ServerSetClientCapabilitiesHandler]). | |
| /// | |
| /// This is invoked for every LSP-over-Legacy message, so it is a no-op after | |
| /// the first one. | |
| void ensureLspInitializedForClientWithoutCapabilities() { | |
| if (!_hasReceivedClientCapabilities) { | |
| completeLspInitializationIfNeeded(); | |
| } | |
| } | |
| /// There was an error related to the socket from which requests are being | |
| /// read. | |
| void error(Object argument) {} | |
| /// Return the cached analysis result for the file with the given [path]. | |
| /// If there is no cached result, return `null`. | |
| ResolvedUnitResult? getCachedResolvedUnit(String path) { | |
| if (!file_paths.isDart(resourceProvider.pathContext, path)) { | |
| return null; | |
| } | |
| var driver = getAnalysisDriver(path); | |
| return driver?.getCachedResolvedUnit(path); | |
| } | |
| @override | |
| FutureOr<void> handleAnalysisStatusChange(analysis.AnalysisStatus status) { | |
| super.handleAnalysisStatusChange(status); | |
| sendStatusNotificationNew(status); | |
| } | |
| /// Handle a [message] that was read from the communication channel. | |
| void handleClientMessage(ClientMessage message) { | |
| switch (message) { | |
| case Request(): | |
| var cancellationToken = CancelableToken(); | |
| cancellationTokens[message.id] = cancellationToken; | |
| messageScheduler.add( | |
| LegacyMessage(request: message, cancellationToken: cancellationToken), | |
| ); | |
| case Response(): | |
| handleResponse(message); | |
| case Notification(): | |
| messageScheduler.add(LegacyNotificationMessage(notification: message)); | |
| } | |
| } | |
| /// Handle a [notification] that was read from the communication channel. The | |
| /// completer is used to indicate when the notification handling is done. | |
| void handleNotification( | |
| Notification notification, | |
| Completer<void> completer, | |
| ) { | |
| // Because we don't `await` the execution of the handler, we wrap the | |
| // execution in order to have one central place to handle exceptions. | |
| runZonedGuarded( | |
| () async { | |
| if (notification.event == lspNotificationNotification) { | |
| await LspNotificationOverLegacyHandler(this, notification).handle(); | |
| } else { | |
| // There is no response to carry an error back to the client, so an | |
| // unknown notification can only be logged. | |
| instrumentationService.logError( | |
| 'Unknown notification ${notification.event}', | |
| ); | |
| sessionLogger.logException( | |
| exception: 'Unknown notification ${notification.event}', | |
| ); | |
| } | |
| completer.complete(); | |
| }, | |
| (exception, stackTrace) { | |
| instrumentationService.logException( | |
| FatalException( | |
| 'Failed to handle notification: ${notification.event}', | |
| exception, | |
| stackTrace, | |
| ), | |
| null, | |
| crashReportingAttachmentsBuilder.forException(exception), | |
| ); | |
| completer.complete(); | |
| }, | |
| ); | |
| } | |
| /// Handle a [request] that was read from the communication channel. The completer | |
| /// is used to indicate when the request handling is done. | |
| void handleRequest( | |
| Request request, | |
| Completer<void> completer, | |
| CancelableToken? cancellationToken, | |
| ) { | |
| var startTime = DateTime.now(); | |
| performance.logRequestTiming(request.clientRequestTime); | |
| // Because we don't `await` the execution of the handlers, we wrap the | |
| // execution in order to have one central place to handle exceptions. | |
| runZonedGuarded( | |
| () async { | |
| // Record performance information for the request. | |
| var rootPerformance = OperationPerformanceImpl('<root>'); | |
| RequestPerformance? requestPerformance; | |
| await rootPerformance.runAsync('request', (performance) async { | |
| requestPerformance = RequestPerformance( | |
| operation: request.method, | |
| performance: performance, | |
| requestLatency: request.timeSinceRequest, | |
| startTime: startTime, | |
| ); | |
| recentPerformance.requests.add(requestPerformance!); | |
| var token = cancellationToken ??= CancelableToken(); | |
| cancellationTokens[request.id] = token; | |
| var generator = requestHandlerGenerators[request.method]; | |
| if (generator != null) { | |
| var handler = generator(this, request, token, performance); | |
| if (!handler.recordsOwnAnalytics) { | |
| analyticsManager.startedRequest( | |
| request: request, | |
| startTime: startTime, | |
| ); | |
| } | |
| await handler.handle(); | |
| } else { | |
| analyticsManager.startedRequest( | |
| request: request, | |
| startTime: startTime, | |
| ); | |
| sendResponse(Response.unknownRequest(request)); | |
| } | |
| }); | |
| if (requestPerformance != null && | |
| requestPerformance!.performance.elapsed > | |
| ServerRecentPerformance.slowRequestsThreshold) { | |
| recentPerformance.slowRequests.add(requestPerformance!); | |
| } | |
| completer.complete(); | |
| }, | |
| (exception, stackTrace) { | |
| if (exception is InconsistentAnalysisException) { | |
| sendResponse(Response.contentModified(request)); | |
| } else if (exception is RequestFailure) { | |
| sendResponse(exception.response); | |
| } else { | |
| // Log the exception. | |
| var fatalException = FatalException( | |
| 'Failed to handle request: ${request.method}', | |
| exception, | |
| stackTrace, | |
| ); | |
| var attachments = crashReportingAttachmentsBuilder.forException( | |
| exception, | |
| ); | |
| instrumentationService.logException( | |
| fatalException, | |
| null, | |
| attachments, | |
| ); | |
| sessionLogger.logException( | |
| exception: fatalException, | |
| attachments: attachments, | |
| ); | |
| // Then return an error response to the client. | |
| var error = RequestError( | |
| RequestErrorCode.SERVER_ERROR, | |
| exception.toString(), | |
| ); | |
| error.stackTrace = stackTrace.toString(); | |
| var response = Response(request.id, error: error); | |
| sendResponse(response); | |
| } | |
| completer.complete(); | |
| }, | |
| zoneValues: { | |
| // A container for capturing additional timings (such as analysis) stored | |
| // in zoneValues so it can be accessed anywhere in the processing of this | |
| // request. | |
| RequestPerformanceAdditionalTimings.zoneValueKey: | |
| RequestPerformanceAdditionalTimings(), | |
| }, | |
| ); | |
| } | |
| /// Handle a [response] that was read from the communication channel. | |
| void handleResponse(Response response) { | |
| var completer = pendingServerRequests.remove(response.id); | |
| if (completer != null) { | |
| completer.complete(response); | |
| } | |
| } | |
| /// Initializes LSP support over the legacy server. | |
| /// | |
| /// This method is called when the client sends an LSP request, or indicates | |
| /// that it will use LSP-overy-Legacy via client capabilities. | |
| /// | |
| /// This only applies to LSP over the legacy protocol and not DTD, since we | |
| /// do not want a DTD-LSP client to trigger LSP notifications going to the | |
| /// legacy protocol client, only the legacy protocol client should do that. | |
| /// | |
| /// For convenience, this method can be called multiple times from different | |
| /// code paths. | |
| void initializeLspOverLegacy() { | |
| sendLspNotifications = true; | |
| } | |
| @override | |
| void notifyFlutterWidgetDescriptions(String path) { | |
| flutterWidgetDescriptions.flush(); | |
| } | |
| /// Send the given LSP [notification] to the client. | |
| @override | |
| void sendLspNotification(lsp.NotificationMessage notification) { | |
| if (!sendLspNotifications) { | |
| return; | |
| } | |
| channel.sendNotification( | |
| LspNotificationParams(notification) | |
| .toNotification(clientUriConverter: uriConverter), | |
| ); | |
| } | |
| /// Sends an LSP request to the server (wrapped in 'lsp.handle') and unwraps | |
| /// the LSP response from the result of the legacy response. | |
| @override | |
| Future<lsp.ResponseMessage> sendLspRequest( | |
| lsp.Method method, | |
| Object params, | |
| ) async { | |
| var id = nextServerRequestId++; | |
| // Build the complete LSP RequestMessage to send. | |
| var lspMessage = lsp.RequestMessage( | |
| id: lsp.Either2<int, String>.t1(id), | |
| jsonrpc: lsp.jsonRpcVersion, | |
| method: method, | |
| params: params, | |
| ); | |
| // Wrap the LSP message inside a call to lsp.handle. | |
| var response = await sendRequest( | |
| Request( | |
| id.toString(), | |
| lspRequestHandle, | |
| LspHandleParams(lspMessage).toJson(clientUriConverter: uriConverter), | |
| ), | |
| ); | |
| // Unwrap the LSP response from the legacy response. | |
| var result = LspHandleResult.fromResponse( | |
| response, | |
| clientUriConverter: uriConverter, | |
| ); | |
| var lspResponse = result.lspResponse; | |
| return lspResponse is Map<String, Object?> | |
| ? lsp.ResponseMessage.fromJson(lspResponse) | |
| : lsp.ResponseMessage( | |
| jsonrpc: lsp.jsonRpcVersion, | |
| error: lsp.ResponseError( | |
| code: lsp.ServerErrorCodes.unhandledError, | |
| message: | |
| "The client responded to a '$method' LSP request but" | |
| ' did not include a valid response in the lspResponse field', | |
| ), | |
| ); | |
| } | |
| /// Send the given [notification] to the client. | |
| void sendNotification(Notification notification) { | |
| channel.sendNotification(notification); | |
| } | |
| /// Send the given [request] to the client. | |
| Future<Response> sendRequest(Request request) { | |
| var completer = Completer<Response>(); | |
| pendingServerRequests[request.id] = completer; | |
| channel.sendRequest(request); | |
| return completer.future; | |
| } | |
| /// Send the given [response] to the client. | |
| void sendResponse(Response response) { | |
| channel.sendResponse(response); | |
| analyticsManager.sentResponse(response: response); | |
| cancellationTokens.remove(response.id); | |
| } | |
| /// If the [path] is not a valid file path, that is absolute and normalized, | |
| /// send an error response, and return `true`. If OK then return `false`. | |
| bool sendResponseErrorIfInvalidFilePath(Request request, String path) { | |
| if (!resourceProvider.isAbsoluteAndNormalized(path)) { | |
| sendResponse(Response.invalidFilePathFormat(request, path)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /// Sends a `server.error` notification. | |
| @override | |
| void sendServerErrorNotification( | |
| String message, | |
| Object exception, | |
| StackTrace? stackTrace, { | |
| bool fatal = false, | |
| }) { | |
| var msg = '$message: $exception'; | |
| if (stackTrace == null && exception is! CaughtException) { | |
| stackTrace = StackTrace.current; | |
| } | |
| // send the notification | |
| channel.sendNotification( | |
| ServerErrorParams( | |
| fatal, | |
| msg, | |
| '$stackTrace', | |
| ).toNotification(clientUriConverter: uriConverter), | |
| ); | |
| // remember the last few exceptions | |
| if (exception is CaughtException) { | |
| stackTrace ??= exception.stackTrace; | |
| } | |
| exceptions.add( | |
| ServerException( | |
| message, | |
| exception, | |
| stackTrace is StackTrace ? stackTrace : StackTrace.current, | |
| fatal, | |
| ), | |
| ); | |
| } | |
| /// Send status notification to the client. The state of analysis is given by | |
| /// the [status] information. | |
| void sendStatusNotificationNew(analysis.AnalysisStatus status) { | |
| var isServerAnalyzing = status.isWorking; | |
| if (isServerAnalyzing) { | |
| _onAnalysisStartedController.add(true); | |
| } | |
| var onAnalysisCompleteCompleter = _onAnalysisCompleteCompleter; | |
| if (onAnalysisCompleteCompleter != null && !isServerAnalyzing) { | |
| onAnalysisCompleteCompleter.complete(); | |
| _onAnalysisCompleteCompleter = null; | |
| } | |
| serverStatusAnalyzing = isServerAnalyzing; | |
| _performOnIdleActions( | |
| // Use the existing plugin analyzing status. | |
| isPluginAnalyzing: notificationManager.pluginStatusAnalyzing, | |
| ); | |
| } | |
| /// Implementation for `analysis.setAnalysisRoots`. | |
| /// | |
| // TODO(scheglov): implement complete projects/contexts semantics. | |
| // | |
| // The current implementation is intentionally simplified and expected | |
| // that only folders are given each given folder corresponds to the exactly | |
| // one context. | |
| // | |
| // So, we can start working in parallel on adding services and improving | |
| // projects/contexts support. | |
| Future<void> setAnalysisRoots( | |
| String requestId, | |
| List<String> includedPaths, | |
| List<String> excludedPaths, | |
| ) async { | |
| var completer = analysisContextRebuildCompleter = Completer(); | |
| try { | |
| notificationManager.setAnalysisRoots(includedPaths, excludedPaths); | |
| try { | |
| await contextManager.setRoots(includedPaths, excludedPaths); | |
| } on UnimplementedError catch (e) { | |
| throw RequestFailure( | |
| Response.unsupportedFeature( | |
| requestId, | |
| e.message ?? 'Unsupported feature.', | |
| ), | |
| ); | |
| } | |
| } finally { | |
| completer.complete(); | |
| } | |
| } | |
| /// Implementation for `analysis.setSubscriptions`. | |
| void setAnalysisSubscriptions( | |
| Map<AnalysisService, Set<String>> subscriptions, | |
| ) { | |
| notificationManager.setSubscriptions(subscriptions); | |
| analysisServices = subscriptions; | |
| _sendSubscriptions(analysis: true); | |
| } | |
| /// Implementation for `flutter.setSubscriptions`. | |
| void setFlutterSubscriptions(Map<FlutterService, Set<String>> subscriptions) { | |
| flutterServices = subscriptions; | |
| _sendSubscriptions(flutter: true); | |
| } | |
| /// Implementation for `analysis.setGeneralSubscriptions`. | |
| void setGeneralAnalysisSubscriptions( | |
| List<GeneralAnalysisService> subscriptions, | |
| ) { | |
| var newServices = subscriptions.toSet(); | |
| var newServicesContainsAnalyzedFiles = newServices.contains( | |
| GeneralAnalysisService.ANALYZED_FILES, | |
| ); | |
| var generalServicesContainsAnalyzedFiles = generalAnalysisServices.contains( | |
| GeneralAnalysisService.ANALYZED_FILES, | |
| ); | |
| if (newServicesContainsAnalyzedFiles && | |
| !generalServicesContainsAnalyzedFiles && | |
| _isAnalysisComplete) { | |
| sendAnalysisNotificationAnalyzedFiles(this); | |
| } else if (!newServicesContainsAnalyzedFiles && | |
| generalServicesContainsAnalyzedFiles) { | |
| prevAnalyzedFiles = null; | |
| } | |
| generalAnalysisServices = newServices; | |
| } | |
| /// Set the priority files to the given [files]. | |
| void setPriorityFiles(String requestId, List<String> files) { | |
| bool isPubspec(String filePath) => | |
| file_paths.isPubspecYaml(resourceProvider.pathContext, filePath); | |
| // When pubspecs are opened, trigger pre-loading of pub package names and | |
| // versions. | |
| var pubspecs = files.where(isPubspec).toList(); | |
| if (pubspecs.isNotEmpty) { | |
| pubPackageService.beginCachePreloads(pubspecs); | |
| } | |
| priorityFiles.clear(); | |
| priorityFiles.addAll(files); | |
| // Set priority files in drivers. | |
| for (var driver in driverMap.values) { | |
| driver.priorityFiles = files; | |
| } | |
| } | |
| @override | |
| @visibleForOverriding | |
| Future<String?> showUserPrompt( | |
| MessageType type, | |
| String message, | |
| List<String> actionLabels, | |
| CancellationToken cancellationToken, | |
| ) async { | |
| assert(supportsShowMessageRequest); | |
| var requestId = (nextServerRequestId++).toString(); | |
| var actions = actionLabels.map((label) => MessageAction(label)).toList(); | |
| var request = ServerShowMessageRequestParams( | |
| type.forLegacy, | |
| message, | |
| actions, | |
| ).toRequest(requestId, clientUriConverter: uriConverter); | |
| var responseFuture = sendRequest(request); | |
| // Wait for either the result, or cancellation. | |
| await Future.any([responseFuture, cancellationToken.whenCancelled]); | |
| if (cancellationToken.isCancellationRequested) { | |
| return null; | |
| } else { | |
| // If we didn't enter the branch above, we know this future completed. | |
| var response = await responseFuture; | |
| return response.result?['action'] as String?; | |
| } | |
| } | |
| @override | |
| Future<void> shutdown() async { | |
| await super.shutdown(); | |
| pubApi.close(); | |
| detachableFileSystemManager?.dispose(); | |
| // Defer closing the channel and shutting down the instrumentation server so | |
| // that the shutdown response can be sent and logged. | |
| unawaited( | |
| Future(() { | |
| instrumentationService.shutdown(); | |
| channel.close(); | |
| }), | |
| ); | |
| } | |
| /// Implementation for `analysis.updateContent`. | |
| void updateContent(String id, Map<String, Object?> changes) { | |
| _onAnalysisSetChangedController.add(null); | |
| changes.forEach((file, change) { | |
| // Prepare the old overlay contents. | |
| String? oldContents; | |
| try { | |
| if (resourceProvider.hasOverlay(file)) { | |
| oldContents = resourceProvider.getFile(file).readAsStringSync(); | |
| } | |
| } catch (_) {} | |
| // Prepare the new contents. | |
| String? newContents; | |
| int? newVersion; | |
| if (change is AddContentOverlay) { | |
| newContents = change.content; | |
| newVersion = change.version; | |
| } else if (change is ChangeContentOverlay) { | |
| if (oldContents == null) { | |
| // The client may only send a ChangeContentOverlay if there is | |
| // already an existing overlay for the source. | |
| throw RequestFailure( | |
| Response( | |
| id, | |
| error: RequestError( | |
| RequestErrorCode.INVALID_OVERLAY_CHANGE, | |
| 'Invalid overlay change', | |
| ), | |
| ), | |
| ); | |
| } | |
| try { | |
| newContents = SourceEdit.applySequence(oldContents, change.edits); | |
| newVersion = change.version; | |
| } on RangeError { | |
| throw RequestFailure( | |
| Response( | |
| id, | |
| error: RequestError( | |
| RequestErrorCode.INVALID_OVERLAY_CHANGE, | |
| 'Invalid overlay change', | |
| ), | |
| ), | |
| ); | |
| } | |
| } else if (change is RemoveContentOverlay) { | |
| newContents = null; | |
| newVersion = null; | |
| } else { | |
| // Protocol parsing should have ensured that we never get here. | |
| throw AnalysisException('Illegal change type'); | |
| } | |
| if (newContents != null) { | |
| resourceProvider.setOverlay( | |
| file, | |
| content: newContents, | |
| modificationStamp: overlayModificationStamp++, | |
| ); | |
| } else { | |
| resourceProvider.removeOverlay(file); | |
| } | |
| if (newVersion != null) { | |
| documentVersions[file] = newVersion; | |
| } else { | |
| documentVersions.remove(file); | |
| } | |
| for (var driver in driverMap.values) { | |
| driver.changeFile(file); | |
| } | |
| // If the file did not exist, and is "overlay only", it still should be | |
| // analyzed. Add it to driver to which it should have been added. | |
| contextManager.getDriverFor(file)?.addFile(file); | |
| notifyFlutterWidgetDescriptions(file); | |
| // TODO(scheglov): implement other cases | |
| }); | |
| } | |
| /// Use the given updaters to configure the analysis options builders for | |
| /// existing analysis contexts. | |
| void updateOptions(List<AnalysisOptionsBuilderUpdater> builderUpdaters) { | |
| // TODO(scheglov): implement for the new analysis driver | |
| // // | |
| // // Update existing contexts. | |
| // // | |
| // for (AnalysisContext context in analysisContexts) { | |
| // var builder = AnalysisOptionsBuilder.from(context.analysisOptions); | |
| // builderUpdaters.forEach(( | |
| // AnalysisOptionsBuilderUpdater builderUpdater, | |
| // ) { | |
| // builderUpdater(builder); | |
| // }); | |
| // context.analysisOptions = builder.build(); | |
| // // `TODO`(brianwilkerson) As far as I can tell, this doesn't cause analysis | |
| // // to be scheduled for this context. | |
| // } | |
| // // | |
| // // Update the defaults used to create new contexts. | |
| // // | |
| // builderUpdaters.forEach(( | |
| // AnalysisOptionsBuilderUpdater builderUpdater, | |
| // ) { | |
| // builderUpdater(defaultContextOptions); | |
| // }); | |
| } | |
| /// Returns `true` if there is a subscription for the given [service] and | |
| /// [file]. | |
| bool _hasAnalysisServiceSubscription(AnalysisService service, String file) { | |
| return analysisServices[service]?.contains(file) ?? false; | |
| } | |
| bool _hasFlutterServiceSubscription(FlutterService service, String file) { | |
| return flutterServices[service]?.contains(file) ?? false; | |
| } | |
| /// Initializes [_refactoringManager] with a new instance. | |
| void _newRefactoringManager() { | |
| _refactoringManager = RefactoringManager(this, refactoringWorkspace); | |
| } | |
| /// Performs "on idle" actions, given either a new status for whether the | |
| /// server is analyzing, or a new status for whether the plugin isolate is | |
| /// analyzing. | |
| void _performOnIdleActions({required bool isPluginAnalyzing}) { | |
| // Perform on-idle actions. | |
| var isAnalyzing = | |
| serverStatusAnalyzing || | |
| isPluginAnalyzing || | |
| !pluginManager.initializedCompleter.isCompleted; | |
| if (!serverStatusAnalyzing) { | |
| if (generalAnalysisServices.contains( | |
| GeneralAnalysisService.ANALYZED_FILES, | |
| )) { | |
| sendAnalysisNotificationAnalyzedFiles(this); | |
| } | |
| _scheduleAnalysisImplementedNotification(); | |
| filesResolvedSinceLastIdle.clear(); | |
| } | |
| // Only send status when subscribed. | |
| if (!serverServices.contains(ServerService.STATUS)) { | |
| return; | |
| } | |
| // Only send status when it changes. | |
| if (statusAnalyzing == isAnalyzing) { | |
| return; | |
| } | |
| statusAnalyzing = isAnalyzing; | |
| if (!serverStatusAnalyzing) { | |
| // Only send analysis analytics after analysis is complete. | |
| reportAnalysisAnalytics(); | |
| } | |
| var analysis = AnalysisStatus(isAnalyzing); | |
| channel.sendNotification( | |
| ServerStatusParams(analysis: analysis) | |
| .toNotification(clientUriConverter: uriConverter), | |
| ); | |
| } | |
| void _scheduleAnalysisImplementedNotification() { | |
| var subscribed = analysisServices[AnalysisService.IMPLEMENTED]; | |
| if (subscribed == null) { | |
| return; | |
| } | |
| var toSend = subscribed.intersection(filesResolvedSinceLastIdle); | |
| if (toSend.isEmpty) { | |
| return; | |
| } | |
| scheduleImplementedNotification(this, toSend); | |
| } | |
| void _sendSubscriptions({bool analysis = false, bool flutter = false}) { | |
| var files = <String>{}; | |
| if (analysis) { | |
| for (var serviceFiles in analysisServices.values) { | |
| files.addAll(serviceFiles); | |
| } | |
| } | |
| if (flutter) { | |
| for (var serviceFiles in flutterServices.values) { | |
| files.addAll(serviceFiles); | |
| } | |
| } | |
| for (var file in files) { | |
| // The result will be produced by the "results" stream with | |
| // the fully resolved unit, and processed with sending analysis | |
| // notifications as it happens after content changes. | |
| if (file_paths.isDart(resourceProvider.pathContext, file)) { | |
| getResolvedUnit(file, sendCachedToStream: true, interactive: false); | |
| } | |
| } | |
| } | |
| } | |
| class ServerContextManagerCallbacks | |
| extends CommonServerContextManagerCallbacks { | |
| @override | |
| final LegacyAnalysisServer analysisServer; | |
| new(this.analysisServer, super.resourceProvider); | |
| AbstractNotificationManager get _notificationManager => | |
| analysisServer.notificationManager; | |
| @override | |
| void afterContextsCreated() { | |
| super.afterContextsCreated(); | |
| analysisServer._sendSubscriptions(analysis: true, flutter: true); | |
| } | |
| @override | |
| void afterWatchEvent(WatchEvent event) { | |
| analysisServer._onAnalysisSetChangedController.add(null); | |
| } | |
| @override | |
| void flushResults(List<String> files) { | |
| sendAnalysisNotificationFlushResults(analysisServer, files); | |
| } | |
| @override | |
| void handleResolvedUnitResult(ResolvedUnitResult result) { | |
| var path = result.path; | |
| var unit = result.unit; | |
| if (analysisServer._hasAnalysisServiceSubscription( | |
| AnalysisService.HIGHLIGHTS, | |
| path, | |
| )) { | |
| _runDelayed(() { | |
| _notificationManager.recordHighlightRegions( | |
| NotificationManager.serverId, | |
| path, | |
| _computeHighlightRegions(unit), | |
| ); | |
| }); | |
| } | |
| if (analysisServer._hasAnalysisServiceSubscription( | |
| AnalysisService.NAVIGATION, | |
| path, | |
| )) { | |
| _runDelayed(() { | |
| _notificationManager.recordNavigationParams( | |
| NotificationManager.serverId, | |
| path, | |
| _computeNavigationParams(path, result), | |
| ); | |
| }); | |
| } | |
| if (analysisServer._hasAnalysisServiceSubscription( | |
| AnalysisService.OCCURRENCES, | |
| path, | |
| )) { | |
| _runDelayed(() { | |
| _notificationManager.recordOccurrences( | |
| NotificationManager.serverId, | |
| path, | |
| _computeOccurrences(unit), | |
| ); | |
| }); | |
| } | |
| // if (analysisServer._hasAnalysisServiceSubscription( | |
| // AnalysisService.OUTLINE, path)) { | |
| // _runDelayed(() { | |
| // // `TODO`(brianwilkerson) Change NotificationManager to store params | |
| // // so that fileKind and libraryName can be recorded / passed along. | |
| // notificationManager.recordOutlines(NotificationManager.serverId, path, | |
| // _computeOutlineParams(path, unit, result.lineInfo)); | |
| // }); | |
| // } | |
| if (analysisServer._hasAnalysisServiceSubscription( | |
| AnalysisService.CLOSING_LABELS, | |
| path, | |
| )) { | |
| // Closing labels are controlled by the subscription for the legacy server | |
| // even if we are configured to send the notifications over LSP. For | |
| // the real LSP server, closing labels are are automatically sent for all | |
| // open (priority files). | |
| _runDelayed(() { | |
| sendAnalysisNotificationClosingLabels( | |
| analysisServer, | |
| path, | |
| result.lineInfo, | |
| unit, | |
| ); | |
| }); | |
| } | |
| if (analysisServer._hasAnalysisServiceSubscription( | |
| AnalysisService.FOLDING, | |
| path, | |
| )) { | |
| _runDelayed(() { | |
| sendAnalysisNotificationFolding( | |
| analysisServer, | |
| path, | |
| result.lineInfo, | |
| unit, | |
| ); | |
| }); | |
| } | |
| if (analysisServer._hasAnalysisServiceSubscription( | |
| AnalysisService.OUTLINE, | |
| path, | |
| )) { | |
| _runDelayed(() { | |
| sendAnalysisNotificationOutline(analysisServer, result); | |
| }); | |
| } | |
| if (analysisServer._hasAnalysisServiceSubscription( | |
| AnalysisService.OVERRIDES, | |
| path, | |
| )) { | |
| _runDelayed(() { | |
| sendAnalysisNotificationOverrides(analysisServer, path, unit); | |
| }); | |
| } | |
| if (analysisServer._hasFlutterServiceSubscription( | |
| FlutterService.OUTLINE, | |
| path, | |
| )) { | |
| _runDelayed(() { | |
| sendFlutterNotificationOutline(analysisServer, result); | |
| }); | |
| } | |
| } | |
| List<HighlightRegion> _computeHighlightRegions(CompilationUnit unit) { | |
| return DartUnitHighlightsComputer(unit).compute(); | |
| } | |
| server.AnalysisNavigationParams _computeNavigationParams( | |
| String path, | |
| ParsedUnitResult result, | |
| ) { | |
| var collector = NavigationCollectorImpl(); | |
| computeDartNavigation(resourceProvider, collector, result, null, null); | |
| collector.createRegions(); | |
| return server.AnalysisNavigationParams( | |
| path, | |
| collector.regions, | |
| collector.targets, | |
| collector.files, | |
| ); | |
| } | |
| List<Occurrences> _computeOccurrences(CompilationUnit unit) { | |
| var collector = OccurrencesCollectorImpl(); | |
| addDartOccurrences(collector, unit); | |
| return collector.allOccurrences; | |
| } | |
| /// Run [f] in a new [Future]. | |
| /// | |
| /// This method is used to delay sending notifications. If there is a more | |
| /// important consumer of an analysis results, specifically a code completion | |
| /// computer, we want it to run before spending time of sending notifications. | |
| /// | |
| // TODO(scheglov): Consider replacing this with full priority based scheduler. | |
| // | |
| // TODO(scheglov): Alternatively, if code completion work in a way that does | |
| // not produce (at first) fully resolved unit, but only part of it - a single | |
| // method, or a top-level declaration, we would not have this problem - the | |
| // completion computer would be the only consumer of the partial analysis | |
| // result. | |
| void _runDelayed(void Function() f) { | |
| Future(f); | |
| } | |
| } | |
| /// Used to record server exceptions. | |
| class ServerException { | |
| final String message; | |
| final dynamic exception; | |
| final StackTrace stackTrace; | |
| final bool fatal; | |
| new(this.message, this.exception, this.stackTrace, this.fatal); | |
| @override | |
| String toString() => message; | |
| } | |
| /// A class used by [LegacyAnalysisServer] to record performance information | |
| /// such as request latency. | |
| class ServerPerformance { | |
| /// The creation time and the time when performance information | |
| /// started to be recorded here. | |
| final int startTime = DateTime.now().millisecondsSinceEpoch; | |
| /// The number of requests. | |
| int requestCount = 0; | |
| /// The number of requests that recorded latency information. | |
| int latencyCount = 0; | |
| /// The total latency (milliseconds) for all recorded requests. | |
| int requestLatency = 0; | |
| /// The maximum latency (milliseconds) for all recorded requests. | |
| int maxLatency = 0; | |
| /// The number of requests with latency > 150 milliseconds. | |
| int slowRequestCount = 0; | |
| /// Log timing information for a request. | |
| void logRequestTiming(int? clientRequestTime) { | |
| ++requestCount; | |
| if (clientRequestTime != null) { | |
| var latency = DateTime.now().millisecondsSinceEpoch - clientRequestTime; | |
| ++latencyCount; | |
| requestLatency += latency; | |
| maxLatency = max(maxLatency, latency); | |
| if (latency > 150) { | |
| ++slowRequestCount; | |
| } | |
| } | |
| } | |
| } |