|  | // 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. | 
|  |  | 
|  | import 'dart:async'; | 
|  | import 'dart:io' as io; | 
|  | import 'dart:io'; | 
|  |  | 
|  | import 'package:analysis_server/lsp_protocol/protocol.dart' as lsp; | 
|  | import 'package:analysis_server/src/analytics/analytics_manager.dart'; | 
|  | import 'package:analysis_server/src/collections.dart'; | 
|  | import 'package:analysis_server/src/context_manager.dart'; | 
|  | import 'package:analysis_server/src/legacy_analysis_server.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/error_or.dart' as lsp; | 
|  | import 'package:analysis_server/src/lsp/handlers/handler_execute_command.dart' | 
|  | as lsp; | 
|  | import 'package:analysis_server/src/lsp/handlers/handler_states.dart' as lsp; | 
|  | import 'package:analysis_server/src/lsp/handlers/handlers.dart' as lsp; | 
|  | import 'package:analysis_server/src/plugin/notification_manager.dart'; | 
|  | import 'package:analysis_server/src/plugin/plugin_manager.dart'; | 
|  | import 'package:analysis_server/src/plugin/plugin_watcher.dart'; | 
|  | import 'package:analysis_server/src/protocol_server.dart' | 
|  | as legacy | 
|  | show MessageType; | 
|  | import 'package:analysis_server/src/protocol_server.dart' as server; | 
|  | import 'package:analysis_server/src/scheduler/message_scheduler.dart'; | 
|  | import 'package:analysis_server/src/scheduler/scheduler_tracking_listener.dart'; | 
|  | import 'package:analysis_server/src/server/crash_reporting_attachments.dart'; | 
|  | import 'package:analysis_server/src/server/diagnostic_server.dart'; | 
|  | import 'package:analysis_server/src/services/completion/completion_performance.dart'; | 
|  | import 'package:analysis_server/src/services/correction/fix_performance.dart'; | 
|  | import 'package:analysis_server/src/services/correction/refactoring_performance.dart'; | 
|  | import 'package:analysis_server/src/services/dart_tooling_daemon/dtd_services.dart'; | 
|  | import 'package:analysis_server/src/services/pub/pub_api.dart'; | 
|  | import 'package:analysis_server/src/services/pub/pub_command.dart'; | 
|  | import 'package:analysis_server/src/services/pub/pub_package_service.dart'; | 
|  | import 'package:analysis_server/src/services/refactoring/legacy/refactoring.dart'; | 
|  | import 'package:analysis_server/src/services/search/element_visitors.dart'; | 
|  | import 'package:analysis_server/src/services/search/search_engine.dart'; | 
|  | import 'package:analysis_server/src/services/search/search_engine_internal.dart'; | 
|  | import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart'; | 
|  | import 'package:analysis_server/src/services/user_prompts/survey_manager.dart'; | 
|  | import 'package:analysis_server/src/services/user_prompts/user_prompts.dart'; | 
|  | import 'package:analysis_server/src/utilities/extensions/ast.dart'; | 
|  | import 'package:analysis_server/src/utilities/file_string_sink.dart'; | 
|  | import 'package:analysis_server/src/utilities/process.dart'; | 
|  | import 'package:analysis_server/src/utilities/request_statistics.dart'; | 
|  | import 'package:analysis_server/src/utilities/tee_string_sink.dart'; | 
|  | import 'package:analysis_server/src/utilities/timing_byte_store.dart'; | 
|  | import 'package:analysis_server_plugin/src/correction/assist_performance.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/element/element.dart'; | 
|  | import 'package:analyzer/exception/exception.dart'; | 
|  | import 'package:analyzer/file_system/file_system.dart'; | 
|  | import 'package:analyzer/file_system/overlay_file_system.dart'; | 
|  | import 'package:analyzer/file_system/physical_file_system.dart'; | 
|  | import 'package:analyzer/instrumentation/instrumentation.dart'; | 
|  | import 'package:analyzer/source/line_info.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/byte_store.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/driver.dart' as analysis; | 
|  | import 'package:analyzer/src/dart/analysis/driver.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/file_byte_store.dart' | 
|  | show EvictingFileByteStore; | 
|  | import 'package:analyzer/src/dart/analysis/file_content_cache.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/info_declaration_store.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/performance_logger.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/results.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/session.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/status.dart' as analysis; | 
|  | import 'package:analyzer/src/dart/analysis/unlinked_unit_store.dart'; | 
|  | import 'package:analyzer/src/dartdoc/dartdoc_directive_info.dart'; | 
|  | 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/extensions/analysis_session.dart'; | 
|  | import 'package:analyzer_plugin/protocol/protocol.dart'; | 
|  | import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' | 
|  | as analyzer_plugin; | 
|  | import 'package:analyzer_plugin/src/utilities/client_uri_converter.dart'; | 
|  | import 'package:collection/collection.dart'; | 
|  | import 'package:http/http.dart' as http; | 
|  | import 'package:meta/meta.dart'; | 
|  | import 'package:path/path.dart' as path; | 
|  | import 'package:watcher/watcher.dart'; | 
|  |  | 
|  | /// The function for sending `openUri` request to the client. | 
|  | typedef OpenUriNotificationSender = Future<void> Function(Uri uri); | 
|  |  | 
|  | /// The function for sending prompts to the user and collecting button presses. | 
|  | typedef UserPromptSender = | 
|  | Future<String?> Function( | 
|  | MessageType type, | 
|  | String message, | 
|  | List<String> actionLabels, | 
|  | ); | 
|  |  | 
|  | /// Implementations of [AnalysisServer] implement a server that listens | 
|  | /// on an [AbstractNotificationManager] for analysis messages and process them. | 
|  | abstract class AnalysisServer { | 
|  | /// A flag indicating whether plugins are supported in this build. | 
|  | static final bool supportsPlugins = true; | 
|  |  | 
|  | /// The options of this server instance. | 
|  | AnalysisServerOptions options; | 
|  |  | 
|  | /// The object through which analytics are to be sent. | 
|  | final AnalyticsManager analyticsManager; | 
|  |  | 
|  | /// The versions of each document known to the server (keyed by path), used to | 
|  | /// send back to the client for server-initiated edits so that the client can | 
|  | /// ensure they have a matching version of the document before applying them. | 
|  | /// | 
|  | /// Handlers should prefer to use the `getVersionedDocumentIdentifier` method | 
|  | /// which will return a null-versioned identifier if the document version is | 
|  | /// not known. | 
|  | final Map<String, int> documentVersions = {}; | 
|  |  | 
|  | /// A connection to DTD (the Dart Tooling Daemon) that allows other clients to | 
|  | /// call server functionality. | 
|  | /// | 
|  | /// Initialized by the client through a request that calls [connectToDtd]. | 
|  | DtdServices? dtd; | 
|  |  | 
|  | /// The object for managing showing surveys to users and recording their | 
|  | /// responses. | 
|  | /// | 
|  | /// `null` if surveys are not enabled. | 
|  | @visibleForTesting | 
|  | SurveyManager? surveyManager; | 
|  |  | 
|  | /// The builder for attachments that should be included into crash reports. | 
|  | final CrashReportingAttachmentsBuilder crashReportingAttachmentsBuilder; | 
|  |  | 
|  | /// The [ContextManager] that handles the mapping from analysis roots to | 
|  | /// context directories. | 
|  | late final ContextManager contextManager; | 
|  |  | 
|  | /// The object used to manage sending a subset of notifications to the client. | 
|  | /// The subset of notifications are those to which plugins may contribute. | 
|  | AbstractNotificationManager notificationManager; | 
|  |  | 
|  | /// A reference to the handler for executing commands. | 
|  | /// | 
|  | /// This allows the server to call commands itself, such as "Fix All in | 
|  | /// Workspace" triggered from the [DartFixPromptManager]. | 
|  | lsp.ExecuteCommandHandler? executeCommandHandler; | 
|  |  | 
|  | /// The object used to manage the execution of plugins. | 
|  | late PluginManager pluginManager; | 
|  |  | 
|  | /// The object used to manage the SDK's known to this server. | 
|  | final DartSdkManager sdkManager; | 
|  |  | 
|  | /// The [SearchEngine] for this server. | 
|  | late final SearchEngine searchEngine = SearchEngineImpl(driverMap.values); | 
|  |  | 
|  | /// The optional [ByteStore] to use as [byteStore]. | 
|  | final ByteStore? providedByteStore; | 
|  |  | 
|  | late ByteStore byteStore; | 
|  |  | 
|  | late FileContentCache fileContentCache; | 
|  |  | 
|  | final UnlinkedUnitStore unlinkedUnitStore = UnlinkedUnitStoreImpl(); | 
|  |  | 
|  | final InfoDeclarationStore infoDeclarationStore = InfoDeclarationStoreImpl(); | 
|  |  | 
|  | late final analysis.AnalysisDriverScheduler analysisDriverScheduler; | 
|  |  | 
|  | late StreamSubscription<Object?>? analysisDriverSchedulerEventsSubscription; | 
|  |  | 
|  | /// The DiagnosticServer for this AnalysisServer. If available, it can be used | 
|  | /// to start an http diagnostics server or return the port for an existing | 
|  | /// server. | 
|  | final DiagnosticServer? diagnosticServer; | 
|  |  | 
|  | /// Additional diagnostic info from the client editor to include in diagnostic | 
|  | /// reports. | 
|  | Object? clientDiagnosticInformation; | 
|  |  | 
|  | /// A [RecentBuffer] of the most recent exceptions encountered by the analysis | 
|  | /// server. | 
|  | final RecentBuffer<ServerException> exceptions = RecentBuffer(10); | 
|  |  | 
|  | /// The instrumentation service that is to be used by this analysis server. | 
|  | InstrumentationService instrumentationService; | 
|  |  | 
|  | /// Performance information after initial analysis is complete | 
|  | /// or `null` if the initial analysis is not yet complete | 
|  | ServerPerformance? performanceAfterStartup; | 
|  |  | 
|  | /// A client for making requests to the pub.dev API. | 
|  | final PubApi pubApi; | 
|  |  | 
|  | /// A service for fetching pub.dev package details. | 
|  | late final PubPackageService pubPackageService; | 
|  |  | 
|  | /// The class into which performance information is currently being recorded. | 
|  | /// During startup, this will be the same as [performanceDuringStartup] | 
|  | /// and after startup is complete, this switches to [performanceAfterStartup]. | 
|  | late ServerPerformance performance; | 
|  |  | 
|  | /// Performance information before initial analysis is complete. | 
|  | final ServerPerformance performanceDuringStartup = ServerPerformance(); | 
|  |  | 
|  | /// Performance about recent requests. | 
|  | final ServerRecentPerformance recentPerformance = ServerRecentPerformance(); | 
|  |  | 
|  | final RequestStatisticsHelper? requestStatistics; | 
|  |  | 
|  | final PerformanceLog<TeeStringSink> analysisPerformanceLogger = | 
|  | PerformanceLog<TeeStringSink>(TeeStringSink()); | 
|  |  | 
|  | /// Manages prompts telling the user about "dart fix". | 
|  | late final DartFixPromptManager _dartFixPrompt; | 
|  |  | 
|  | /// The set of the files that are currently priority. | 
|  | final Set<String> priorityFiles = <String>{}; | 
|  |  | 
|  | /// The [ResourceProvider] using which paths are converted into [Resource]s. | 
|  | final OverlayResourceProvider resourceProvider; | 
|  |  | 
|  | /// The [ClientUriConverter] to use to convert between URIs/Paths when | 
|  | /// communicating with the client. | 
|  | late ClientUriConverter _uriConverter = ClientUriConverter.noop( | 
|  | resourceProvider.pathContext, | 
|  | ); | 
|  |  | 
|  | /// The next modification stamp for a changed file in the [resourceProvider]. | 
|  | /// | 
|  | /// This value is increased each time it is used and used instead of real | 
|  | /// modification stamps. It's seeded with `millisecondsSinceEpoch` to reduce | 
|  | /// the chance of colliding with values used in previous analysis sessions if | 
|  | /// used as a cache key. | 
|  | int overlayModificationStamp = DateTime.now().millisecondsSinceEpoch; | 
|  |  | 
|  | /// Indicates whether the next time the server completes analysis is the first | 
|  | /// one since analysis contexts were built. | 
|  | /// | 
|  | /// This is useful for triggering things like [_dartFixPrompt] only when | 
|  | /// it may be useful to do so (after initial analysis and | 
|  | /// "pub get"/"pub upgrades"). | 
|  | bool _isFirstAnalysisSinceContextsBuilt = true; | 
|  |  | 
|  | /// A completer that tracks in-progress analysis context rebuilds. | 
|  | /// | 
|  | /// Starts completed and will be replaced each time a context rebuild starts. | 
|  | Completer<void> analysisContextRebuildCompleter = Completer()..complete(); | 
|  |  | 
|  | /// The workspace for rename refactorings. | 
|  | late final refactoringWorkspace = RefactoringWorkspace( | 
|  | driverMap.values, | 
|  | searchEngine, | 
|  | ); | 
|  |  | 
|  | /// The paths of files for which [ResolvedUnitResult] was produced since | 
|  | /// the last idle state. | 
|  | final Set<String> filesResolvedSinceLastIdle = {}; | 
|  |  | 
|  | /// A completer for [lspUninitialized]. | 
|  | final Completer<void> _lspUninitializedCompleter = Completer<void>(); | 
|  |  | 
|  | /// A scheduler that keeps track of all incoming messages and schedules them | 
|  | /// for processing. | 
|  | final MessageScheduler messageScheduler; | 
|  |  | 
|  | /// A [TimingByteStore] that records timings for reads from the byte store. | 
|  | TimingByteStore? _timingByteStore; | 
|  |  | 
|  | /// Whether notifications caused by analysis should be suppressed. | 
|  | /// | 
|  | /// This is used when an operation is temporarily modifying overlays and does | 
|  | /// not want the client to be notified of any analysis happening on the | 
|  | /// temporary content. | 
|  | bool suppressAnalysisResults = false; | 
|  |  | 
|  | AnalysisServer( | 
|  | this.options, | 
|  | this.sdkManager, | 
|  | this.diagnosticServer, | 
|  | this.analyticsManager, | 
|  | this.crashReportingAttachmentsBuilder, | 
|  | ResourceProvider baseResourceProvider, | 
|  | this.instrumentationService, | 
|  | http.Client? httpClient, | 
|  | ProcessRunner? processRunner, | 
|  | this.notificationManager, { | 
|  | this.requestStatistics, | 
|  | // Disable to avoid using this in unit tests. | 
|  | @visibleForTesting bool enableBlazeWatcher = false, | 
|  | DartFixPromptManager? dartFixPromptManager, | 
|  | this.providedByteStore, | 
|  | PluginManager? pluginManager, | 
|  | MessageSchedulerListener? messageSchedulerListener, | 
|  | }) : resourceProvider = OverlayResourceProvider(baseResourceProvider), | 
|  | pubApi = PubApi( | 
|  | instrumentationService, | 
|  | httpClient, | 
|  | Platform.environment['PUB_HOSTED_URL'], | 
|  | ), | 
|  | messageScheduler = MessageScheduler( | 
|  | listener: | 
|  | messageSchedulerListener ?? | 
|  | SchedulerTrackingListener(analyticsManager), | 
|  | ) { | 
|  | messageScheduler.server = this; | 
|  | // Set the default URI converter. This uses the resource providers path | 
|  | // context (unlike the initialized value) which allows tests to override it. | 
|  | uriConverter = ClientUriConverter.noop(baseResourceProvider.pathContext); | 
|  |  | 
|  | // We can only spawn processes (eg. to run pub commands) when backed by | 
|  | // a real file system, otherwise we may try to run commands in folders that | 
|  | // don't really exist. If processRunner was supplied, it's likely a mock | 
|  | // from a test in which case the pub command should still be created. | 
|  | if (baseResourceProvider is PhysicalResourceProvider) { | 
|  | processRunner ??= ProcessRunner(); | 
|  | } | 
|  | var disablePubCommandVariable = | 
|  | Platform.environment[PubCommand.disablePubCommandEnvironmentKey]; | 
|  | var pubCommand = | 
|  | processRunner != null && disablePubCommandVariable == null | 
|  | ? PubCommand( | 
|  | instrumentationService, | 
|  | resourceProvider.pathContext, | 
|  | processRunner, | 
|  | ) | 
|  | : null; | 
|  |  | 
|  | pubPackageService = PubPackageService( | 
|  | instrumentationService, | 
|  | baseResourceProvider, | 
|  | pubApi, | 
|  | pubCommand, | 
|  | ); | 
|  | performance = performanceDuringStartup; | 
|  |  | 
|  | PluginWatcher? pluginWatcher; | 
|  | if (supportsPlugins) { | 
|  | this.pluginManager = | 
|  | pluginManager ??= PluginManager( | 
|  | resourceProvider, | 
|  | resourceProvider.byteStorePath, | 
|  | sdkManager.defaultSdkDirectory, | 
|  | notificationManager, | 
|  | instrumentationService, | 
|  | ); | 
|  |  | 
|  | pluginWatcher = PluginWatcher(resourceProvider, pluginManager); | 
|  | } | 
|  |  | 
|  | var logName = options.newAnalysisDriverLog; | 
|  | if (logName != null) { | 
|  | if (logName == 'stdout') { | 
|  | analysisPerformanceLogger.sink.addSink(io.stdout); | 
|  | } else if (logName.startsWith('file:')) { | 
|  | var path = logName.substring('file:'.length); | 
|  | analysisPerformanceLogger.sink.addSink(FileStringSink(path)); | 
|  | } | 
|  | } | 
|  |  | 
|  | var requestStatistics = this.requestStatistics; | 
|  | if (requestStatistics != null) { | 
|  | analysisPerformanceLogger.sink.addSink( | 
|  | requestStatistics.perfLoggerStringSink, | 
|  | ); | 
|  | } | 
|  |  | 
|  | byteStore = createByteStore(resourceProvider); | 
|  | fileContentCache = FileContentCache(resourceProvider); | 
|  |  | 
|  | analysisDriverScheduler = analysis.AnalysisDriverScheduler( | 
|  | analysisPerformanceLogger, | 
|  | driverWatcher: pluginWatcher, | 
|  | ); | 
|  |  | 
|  | contextManager = ContextManagerImpl( | 
|  | resourceProvider, | 
|  | sdkManager, | 
|  | options.packagesFile, | 
|  | options.enabledExperiments, | 
|  | byteStore, | 
|  | fileContentCache, | 
|  | unlinkedUnitStore, | 
|  | infoDeclarationStore, | 
|  | analysisPerformanceLogger, | 
|  | analysisDriverScheduler, | 
|  | instrumentationService, | 
|  | enableBlazeWatcher: enableBlazeWatcher, | 
|  | ); | 
|  |  | 
|  | _dartFixPrompt = | 
|  | dartFixPromptManager ?? | 
|  | DartFixPromptManager( | 
|  | this, | 
|  | UserPromptPreferences(resourceProvider, instrumentationService), | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// A [Future] that completes when any in-progress analysis context rebuild | 
|  | /// completes. | 
|  | /// | 
|  | /// If no context rebuild is in progress, will return an already complete | 
|  | /// [Future]. | 
|  | Future<void> get analysisContextsRebuilt => | 
|  | analysisContextRebuildCompleter.future; | 
|  |  | 
|  | /// A list of timings for the byte store, or `null` if timing is not being | 
|  | /// tracked. | 
|  | List<ByteStoreTimings>? get byteStoreTimings => _timingByteStore?.timings; | 
|  |  | 
|  | /// The list of current analysis sessions in all contexts. | 
|  | Future<List<AnalysisSessionImpl>> get currentSessions async { | 
|  | var sessions = <AnalysisSessionImpl>[]; | 
|  | for (var driver in driverMap.values) { | 
|  | await driver.applyPendingFileChanges(); | 
|  | sessions.add(driver.currentSession); | 
|  | } | 
|  | return sessions; | 
|  | } | 
|  |  | 
|  | /// A table mapping [Folder]s to the [AnalysisDriver]s associated with them. | 
|  | Map<Folder, analysis.AnalysisDriver> get driverMap => | 
|  | contextManager.driverMap; | 
|  |  | 
|  | /// The capabilities of the editor that owns this server. | 
|  | /// | 
|  | /// Request handlers should be careful to use the correct clients capabilities | 
|  | /// if they are available to non-editor clients (such as over DTD). The | 
|  | /// capabilities of the caller are available in [lsp.MessageInfo] and should | 
|  | /// usually be used when computing the results for requests, but if those | 
|  | /// requests additionally trigger requests to the editor, those requests to | 
|  | /// the editor should consider these capabilities. | 
|  | /// | 
|  | /// For the legacy server, this set may be a fixed set that is not actually | 
|  | /// configured by the client, but matches what legacy protocol editors expect | 
|  | /// when using LSP-over-Legacy. | 
|  | lsp.LspClientCapabilities? get editorClientCapabilities; | 
|  |  | 
|  | /// The configuration (user/workspace settings) from the LSP client. | 
|  | /// | 
|  | /// For the legacy server, this set may be a fixed set that is not controlled | 
|  | /// by the client. | 
|  | lsp.LspClientConfiguration get lspClientConfiguration; | 
|  |  | 
|  | /// A [Future] that completes when the LSP server moves into the initialized | 
|  | /// state and can handle normal LSP requests. | 
|  | /// | 
|  | /// Completes with the [lsp.InitializedStateMessageHandler] that is active. | 
|  | /// | 
|  | /// When the server leaves the initialized state, [lspUninitialized] will | 
|  | /// complete. | 
|  | FutureOr<lsp.InitializedStateMessageHandler> get lspInitialized; | 
|  |  | 
|  | /// A [Future] that completes once the server transitions out of an | 
|  | /// initialized state. | 
|  | Future<void> get lspUninitialized => _lspUninitializedCompleter.future; | 
|  |  | 
|  | /// Returns the function that can send `openUri` request to the client. | 
|  | /// Returns `null` is the client does not support it. | 
|  | OpenUriNotificationSender? get openUriNotificationSender; | 
|  |  | 
|  | /// Returns owners of files. | 
|  | OwnedFiles get ownedFiles => contextManager.ownedFiles; | 
|  |  | 
|  | path.Context get pathContext => resourceProvider.pathContext; | 
|  |  | 
|  | /// Whether or not the client supports showMessageRequest to show the user | 
|  | /// a message and allow them to respond by clicking buttons. | 
|  | /// | 
|  | /// Callers should use [userPromptSender] instead of checking this directly. | 
|  | @protected | 
|  | bool get supportsShowMessageRequest; | 
|  |  | 
|  | /// Return the total time the server's been alive. | 
|  | Duration get uptime { | 
|  | var start = DateTime.fromMillisecondsSinceEpoch( | 
|  | performanceDuringStartup.startTime, | 
|  | ); | 
|  | return DateTime.now().difference(start); | 
|  | } | 
|  |  | 
|  | /// The [ClientUriConverter] to use to convert between URIs/Paths when | 
|  | /// communicating with the client. | 
|  | ClientUriConverter get uriConverter => _uriConverter; | 
|  |  | 
|  | /// Sets the [ClientUriConverter] to use to convert between URIs/Paths when | 
|  | /// communicating with the client. | 
|  | set uriConverter(ClientUriConverter value) { | 
|  | notificationManager.uriConverter = _uriConverter = value; | 
|  | } | 
|  |  | 
|  | /// Returns the function for sending prompts to the user and collecting button | 
|  | /// presses. | 
|  | /// | 
|  | /// Returns `null` is the client does not support it. | 
|  | UserPromptSender? get userPromptSender => | 
|  | supportsShowMessageRequest ? showUserPrompt : null; | 
|  |  | 
|  | void afterContextsCreated() { | 
|  | _timingByteStore?.newTimings('after contexts created'); | 
|  | _isFirstAnalysisSinceContextsBuilt = true; | 
|  | } | 
|  |  | 
|  | void afterContextsDestroyed() {} | 
|  |  | 
|  | /// Broadcast a request built from the given [requestParams] to all of the | 
|  | /// plugins that are currently associated with the context root from the given | 
|  | /// [driver]. Return a list containing futures that will complete when each of | 
|  | /// the plugins have sent a response, or an empty list if no [driver] is | 
|  | /// provided. | 
|  | Map<PluginInfo, Future<Response>> broadcastRequestToPlugins( | 
|  | analyzer_plugin.RequestParams requestParams, | 
|  | analysis.AnalysisDriver? driver, | 
|  | ) { | 
|  | if (driver == null || !AnalysisServer.supportsPlugins) { | 
|  | return <PluginInfo, Future<Response>>{}; | 
|  | } | 
|  | return pluginManager.broadcastRequest( | 
|  | requestParams, | 
|  | contextRoot: driver.analysisContext!.contextRoot, | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Display a message that will allow us to enable analytics on the next run. | 
|  | void checkAnalytics() { | 
|  | var unifiedAnalytics = analyticsManager.analytics; | 
|  | var prompt = userPromptSender; | 
|  | if (!unifiedAnalytics.shouldShowMessage || prompt == null) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | unawaited( | 
|  | prompt(MessageType.info, unifiedAnalytics.getConsentMessage, ['Ok']), | 
|  | ); | 
|  | unifiedAnalytics.clientShowedMessage(); | 
|  | } | 
|  |  | 
|  | /// Checks that all [sessions] are still consistent, throwing | 
|  | /// [InconsistentAnalysisException] if not. | 
|  | void checkConsistency(List<AnalysisSessionImpl> sessions) { | 
|  | for (var session in sessions) { | 
|  | session.checkConsistency(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Completes [lspUninitialized], signalling that the server has moved out | 
|  | /// of a state where it can handle standard LSP requests. | 
|  | void completeLspUninitialization() { | 
|  | if (!_lspUninitializedCompleter.isCompleted) { | 
|  | _lspUninitializedCompleter.complete(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Connects to DTD at [dtdUri]. | 
|  | /// | 
|  | /// If there is already an active connection to DTD or there is an error | 
|  | /// connecting, returns an error, otherwise returns `null`. | 
|  | Future<lsp.ErrorOr<Null>> connectToDtd( | 
|  | Uri dtdUri, { | 
|  | bool registerExperimentalHandlers = false, | 
|  | }) async { | 
|  | switch (dtd?.state) { | 
|  | case DtdConnectionState.Connecting || DtdConnectionState.Connected: | 
|  | return lsp.error( | 
|  | lsp.ServerErrorCodes.StateError, | 
|  | 'Server is already connected to DTD', | 
|  | ); | 
|  | case DtdConnectionState.Disconnected || DtdConnectionState.Error || null: | 
|  | var connectResult = await DtdServices.connect( | 
|  | this, | 
|  | dtdUri, | 
|  | registerExperimentalHandlers: registerExperimentalHandlers, | 
|  | ); | 
|  | return connectResult.mapResultSync((dtd) { | 
|  | this.dtd = dtd; | 
|  | return lsp.success(null); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// If the state location can be accessed, return the file byte store, | 
|  | /// otherwise return the memory byte store. | 
|  | ByteStore createByteStore(ResourceProvider resourceProvider) { | 
|  | const M = 1024 * 1024 /*1 MiB*/; | 
|  | const G = 1024 * 1024 * 1024 /*1 GiB*/; | 
|  |  | 
|  | const memoryCacheSize = 128 * M; | 
|  |  | 
|  | if (providedByteStore case var providedByteStore?) { | 
|  | return providedByteStore; | 
|  | } | 
|  |  | 
|  | if (options.disableFileByteStore ?? false) { | 
|  | return MemoryCachingByteStore(NullByteStore(), memoryCacheSize); | 
|  | } | 
|  |  | 
|  | if (resourceProvider is OverlayResourceProvider) { | 
|  | resourceProvider = resourceProvider.baseProvider; | 
|  | } | 
|  | if (resourceProvider is PhysicalResourceProvider) { | 
|  | var stateLocation = resourceProvider.getStateLocation('.analysis-driver'); | 
|  | if (stateLocation != null) { | 
|  | var timingByteStore = | 
|  | _timingByteStore = TimingByteStore( | 
|  | EvictingFileByteStore(stateLocation.path, G), | 
|  | ); | 
|  | return MemoryCachingByteStore(timingByteStore, memoryCacheSize); | 
|  | } | 
|  | } | 
|  |  | 
|  | return MemoryCachingByteStore(NullByteStore(), memoryCacheSize); | 
|  | } | 
|  |  | 
|  | void enableSurveys() { | 
|  | // TODO(dantup): This is currently gated on an LSP flag and should just | 
|  | //  be inlined into the constructor once we're happy for it to show up | 
|  | //  without a flag and for both protocols. | 
|  | surveyManager = SurveyManager( | 
|  | this, | 
|  | instrumentationService, | 
|  | analyticsManager.analytics, | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Return an analysis driver to which the file with the given [path] is | 
|  | /// added if one exists, otherwise a driver in which the file was analyzed if | 
|  | /// one exists, otherwise the first driver, otherwise `null`. | 
|  | analysis.AnalysisDriver? getAnalysisDriver(String path) { | 
|  | var drivers = driverMap.values.toList(); | 
|  | if (drivers.isNotEmpty) { | 
|  | // Sort the drivers so that more deeply nested contexts will be checked | 
|  | // before enclosing contexts. | 
|  | drivers.sort((first, second) { | 
|  | var firstRoot = first.analysisContext!.contextRoot.root.path; | 
|  | var secondRoot = second.analysisContext!.contextRoot.root.path; | 
|  | return secondRoot.length - firstRoot.length; | 
|  | }); | 
|  | var driver = drivers.firstWhereOrNull( | 
|  | (driver) => driver.analysisContext!.contextRoot.isAnalyzed(path), | 
|  | ); | 
|  | driver ??= drivers.firstWhereOrNull( | 
|  | (driver) => driver.fsState.getExistingFromPath(path) != null, | 
|  | ); | 
|  | driver ??= drivers.first; | 
|  | return driver; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Return an [AnalysisSession] in which the file with the given [path] | 
|  | /// should be analyzed, preferring the one in which it is analyzed, then | 
|  | /// the one where it is referenced, then the first, otherwise `null`. | 
|  | Future<AnalysisSession?> getAnalysisSession(String path) async { | 
|  | var analysisDriver = getAnalysisDriver(path); | 
|  | if (analysisDriver != null) { | 
|  | await analysisDriver.applyPendingFileChanges(); | 
|  | return analysisDriver.currentSession; | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | DartdocDirectiveInfo getDartdocDirectiveInfoFor(ResolvedUnitResult result) { | 
|  | return getDartdocDirectiveInfoForSession(result.session); | 
|  | } | 
|  |  | 
|  | DartdocDirectiveInfo getDartdocDirectiveInfoForSession( | 
|  | AnalysisSession session, | 
|  | ) { | 
|  | var analysisContext = session.analysisContext; | 
|  | analysisContext as DriverBasedAnalysisContext; | 
|  | return analysisContext.driver.dartdocDirectiveInfo; | 
|  | } | 
|  |  | 
|  | /// Gets the current version number of a document. | 
|  | int? getDocumentVersion(String path) => documentVersions[path]; | 
|  |  | 
|  | /// Return a [Future] that completes with the [Element] at the given | 
|  | /// [offset] of the given [file], or with `null` if there is no node at the | 
|  | /// [offset] or the node does not have an element. | 
|  | Future<Element?> getElementAtOffset(String file, int offset) async { | 
|  | var unitResult = await getResolvedUnit(file); | 
|  | if (unitResult == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | if (!priorityFiles.contains(file)) { | 
|  | var fragment = findFragmentByNameOffset( | 
|  | unitResult.libraryFragment, | 
|  | offset, | 
|  | ); | 
|  | if (fragment != null) { | 
|  | return fragment.element; | 
|  | } | 
|  | } | 
|  |  | 
|  | var node = unitResult.unit.nodeCovering(offset: offset); | 
|  | return node?.getElement(); | 
|  | } | 
|  |  | 
|  | /// Return a [LineInfo] for the file with the given [path]. | 
|  | /// | 
|  | /// If the file does not exist or cannot be read, returns `null`. | 
|  | /// | 
|  | /// This method supports non-Dart files but uses the current content of the | 
|  | /// file which may not be the latest analyzed version of the file if it was | 
|  | /// recently modified, so using the LineInfo from an analyzed result may be | 
|  | /// preferable. This method exists mainly to support plugins which do not | 
|  | /// provide us a matching [LineInfo] for the content they used. | 
|  | LineInfo? getLineInfo(String path) { | 
|  | try { | 
|  | // First try from the overlay because it may be more up-to-date than | 
|  | // the file state. | 
|  | var content = resourceProvider.getFile(path).readAsStringSync(); | 
|  | return LineInfo.fromContent(content); | 
|  | } on FileSystemException { | 
|  | // If the file does not exist or cannot be read, try the file state | 
|  | // because this could be something like a macro-generated file. | 
|  | var result = getAnalysisDriver(path)?.getFileSync(path); | 
|  | if (result is FileResult) { | 
|  | return result.lineInfo; | 
|  | } | 
|  |  | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Return the unresolved unit for the file with the given [path]. | 
|  | /// | 
|  | /// Callers should handle [InconsistentAnalysisException] exceptions that may | 
|  | /// occur if a file is modified during this operation. | 
|  | Future<ParsedUnitResult?> getParsedUnit(String path) async { | 
|  | if (!file_paths.isDart(resourceProvider.pathContext, path)) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | var session = await getAnalysisSession(path); | 
|  | if (session == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | var result = session.getParsedUnit(path); | 
|  | return result is ParsedUnitResult ? result : null; | 
|  | } | 
|  |  | 
|  | /// Return the resolved library for the library containing the file with the | 
|  | /// given [path]. The library is analyzed in one of the analysis drivers to | 
|  | /// which the file was added, otherwise in the first driver, otherwise `null` | 
|  | /// is returned. | 
|  | Future<ResolvedLibraryResult?> getResolvedLibrary(String path) async { | 
|  | if (!file_paths.isDart(resourceProvider.pathContext, path)) { | 
|  | return null; | 
|  | } | 
|  | var driver = getAnalysisDriver(path); | 
|  | if (driver == null) { | 
|  | return null; | 
|  | } | 
|  | try { | 
|  | return await driver.currentSession.getResolvedContainingLibrary(path); | 
|  | } on InconsistentAnalysisException { | 
|  | return null; | 
|  | } catch (exception, stackTrace) { | 
|  | instrumentationService.logException(exception, stackTrace); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Return the resolved unit for the file with the given [path]. The file is | 
|  | /// analyzed in one of the analysis drivers to which the file was added, | 
|  | /// otherwise in the first driver, otherwise `null` is returned. | 
|  | Future<ResolvedUnitResult?>? getResolvedUnit( | 
|  | String path, { | 
|  | bool sendCachedToStream = false, | 
|  | bool interactive = true, | 
|  | }) { | 
|  | if (!file_paths.isDart(resourceProvider.pathContext, path)) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | var driver = getAnalysisDriver(path); | 
|  | if (driver == null) { | 
|  | return Future.value(); | 
|  | } | 
|  |  | 
|  | return driver | 
|  | .getResolvedUnit( | 
|  | path, | 
|  | sendCachedToStream: sendCachedToStream, | 
|  | interactive: interactive, | 
|  | ) | 
|  | .then((value) => value is ResolvedUnitResult ? value : null) | 
|  | .catchError((Object e, StackTrace st) { | 
|  | instrumentationService.logException(e, st); | 
|  | return null; | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Gets the version of a document known to the server, returning a | 
|  | /// [lsp.OptionalVersionedTextDocumentIdentifier] with a version of `null` if the | 
|  | /// document version is not known. | 
|  | lsp.OptionalVersionedTextDocumentIdentifier getVersionedDocumentIdentifier( | 
|  | String path, | 
|  | ) { | 
|  | return lsp.OptionalVersionedTextDocumentIdentifier( | 
|  | uri: uriConverter.toClientUri(path), | 
|  | version: getDocumentVersion(path), | 
|  | ); | 
|  | } | 
|  |  | 
|  | @mustCallSuper | 
|  | void handleAnalysisEvent(Object event) { | 
|  | switch (event) { | 
|  | case FileResult(): | 
|  | contextManager.callbacks.handleFileResult(event); | 
|  | case analysis.AnalysisStatus(): | 
|  | handleAnalysisStatusChange(event); | 
|  | } | 
|  | } | 
|  |  | 
|  | @mustCallSuper | 
|  | FutureOr<void> handleAnalysisStatusChange(analysis.AnalysisStatus status) { | 
|  | if (_isFirstAnalysisSinceContextsBuilt && !status.isWorking) { | 
|  | _timingByteStore?.newTimings('initial analysis completed'); | 
|  | _isFirstAnalysisSinceContextsBuilt = false; | 
|  | _dartFixPrompt.triggerCheck(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Immediately handles an LSP message by delegating to the | 
|  | /// [lsp.InitializedStateMessageHandler]. This method does not schedule the | 
|  | /// message and is intended to be used by the scheduler (or LSP-over-Legacy | 
|  | /// where the original legacy wrapper was already scheduled). | 
|  | /// | 
|  | /// If the LSP server/support is not yet initialized, will wait until it is. | 
|  | FutureOr<lsp.ErrorOr<Object?>> immediatelyHandleLspMessage( | 
|  | lsp.IncomingMessage message, | 
|  | lsp.MessageInfo messageInfo, { | 
|  | lsp.CancellationToken? cancellationToken, | 
|  | }) async { | 
|  | // This is FutureOr<> because for the legacy server it's never a future, so | 
|  | // we can skip the await. | 
|  | var initializedLspHandler = lspInitialized; | 
|  | var handler = | 
|  | initializedLspHandler is lsp.InitializedStateMessageHandler | 
|  | ? initializedLspHandler | 
|  | : await initializedLspHandler; | 
|  |  | 
|  | return handler.handleMessage( | 
|  | message, | 
|  | messageInfo, | 
|  | cancellationToken: cancellationToken, | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Return `true` if the file or directory with the given [path] will be | 
|  | /// analyzed in one of the analysis contexts. | 
|  | bool isAnalyzed(String path) { | 
|  | return contextManager.isAnalyzed(path); | 
|  | } | 
|  |  | 
|  | void logExceptionResult(analysis.ExceptionResult result) { | 
|  | var message = 'Analysis failed: ${result.filePath}'; | 
|  | if (result.contextKey != null) { | 
|  | message += ' context: ${result.contextKey}'; | 
|  | } | 
|  |  | 
|  | var attachments = crashReportingAttachmentsBuilder.forExceptionResult( | 
|  | result, | 
|  | ); | 
|  |  | 
|  | // TODO(39284): should this exception be silent? | 
|  | instrumentationService.logException( | 
|  | SilentException.wrapInMessage(message, result.exception), | 
|  | null, | 
|  | attachments, | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Notify the declarations tracker that the file with the given [path] was | 
|  | /// changed - added, updated, or removed.  Schedule processing of the file. | 
|  | void notifyDeclarationsTracker(String path) { | 
|  | analysisDriverScheduler.notify(); | 
|  | } | 
|  |  | 
|  | /// Notify the flutter widget properties support that the file with the | 
|  | /// given [path] was changed - added, updated, or removed. | 
|  | void notifyFlutterWidgetDescriptions(String path) {} | 
|  |  | 
|  | /// Prevents the scheduler from processing new messages until [operation] | 
|  | /// completes. | 
|  | /// | 
|  | /// This can be used to obtain analysis results/resolved units consistent with | 
|  | /// the state of a file at the time this method was called, preventing | 
|  | /// changes by incoming file modifications. | 
|  | /// | 
|  | /// The contents of [operation] should be kept as short as possible and since | 
|  | /// cancellation requests will also be blocked for the duration of this | 
|  | /// operation, handlers should generally check the cancellation flag | 
|  | /// immediately after this function returns. | 
|  | Future<T> pauseSchedulerWhile<T>(FutureOr<T> Function() operation) async { | 
|  | // TODO(dantup): Prevent this method from locking responses from the client | 
|  | //  because this can lead to deadlocks if called during initialization where | 
|  | //  the server may wait for something (configuration) from the client. This | 
|  | //  might fit in with potential upcoming scheduler changes. | 
|  | // | 
|  | // This is currently used by Completion+FixAll (which are less likely, but | 
|  | // possible to be called during init). | 
|  | // | 
|  | // https://github.com/dart-lang/sdk/issues/56311#issuecomment-2250089185 | 
|  |  | 
|  | messageScheduler.pause(); | 
|  | try { | 
|  | // `await` here is important to ensure `finally` doesn't execute until | 
|  | // `operation()` completes (`whenComplete` is not available on | 
|  | // `FutureOr`). | 
|  | return await operation(); | 
|  | } finally { | 
|  | messageScheduler.resume(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Read all files, resolve all URIs, and perform required analysis in | 
|  | /// all current analysis drivers. | 
|  | Future<void> reanalyze() async { | 
|  | await contextManager.refresh(); | 
|  | } | 
|  |  | 
|  | /// Report analytics data related to the number and size of files that were | 
|  | /// analyzed. | 
|  | void reportAnalysisAnalytics() { | 
|  | if (!analyticsManager.needsAnslysisCompleteCall()) { | 
|  | return; | 
|  | } | 
|  | var packagesFileMap = <String, File?>{}; | 
|  | var immediateFileCount = 0; | 
|  | var immediateFileLineCount = 0; | 
|  | var transitiveFileCount = 0; | 
|  | var transitiveFileLineCount = 0; | 
|  | var transitiveFilePaths = <String>{}; | 
|  | var transitiveFileUniqueLineCount = 0; | 
|  | var driverMap = contextManager.driverMap; | 
|  | for (var entry in driverMap.entries) { | 
|  | var rootPath = entry.key.path; | 
|  | var driver = entry.value; | 
|  | var contextRoot = driver.analysisContext?.contextRoot; | 
|  | if (contextRoot != null) { | 
|  | packagesFileMap[rootPath] = contextRoot.packagesFile; | 
|  | } | 
|  | var fileSystemState = driver.fsState; | 
|  | for (var fileState in fileSystemState.knownFiles) { | 
|  | var isImmediate = fileState.path.startsWith(rootPath); | 
|  | if (isImmediate) { | 
|  | immediateFileCount++; | 
|  | immediateFileLineCount += fileState.lineInfo.lineCount; | 
|  | } else { | 
|  | var lineCount = fileState.lineInfo.lineCount; | 
|  | transitiveFileCount++; | 
|  | transitiveFileLineCount += lineCount; | 
|  | if (transitiveFilePaths.add(fileState.path)) { | 
|  | transitiveFileUniqueLineCount += lineCount; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | var transitiveFileUniqueCount = transitiveFilePaths.length; | 
|  |  | 
|  | var rootPaths = packagesFileMap.keys.toList(); | 
|  | rootPaths.sort((first, second) => first.length.compareTo(second.length)); | 
|  |  | 
|  | analyticsManager.analysisComplete( | 
|  | numberOfContexts: driverMap.length, | 
|  | immediateFileCount: immediateFileCount, | 
|  | immediateFileLineCount: immediateFileLineCount, | 
|  | transitiveFileCount: transitiveFileCount, | 
|  | transitiveFileLineCount: transitiveFileLineCount, | 
|  | transitiveFileUniqueCount: transitiveFileUniqueCount, | 
|  | transitiveFileUniqueLineCount: transitiveFileUniqueLineCount, | 
|  | ); | 
|  | } | 
|  |  | 
|  | Future<ResolvedForCompletionResultImpl?> resolveForCompletion({ | 
|  | required String path, | 
|  | required int offset, | 
|  | required OperationPerformanceImpl performance, | 
|  | }) async { | 
|  | if (!file_paths.isDart(resourceProvider.pathContext, path)) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | var driver = getAnalysisDriver(path); | 
|  | if (driver == null) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | try { | 
|  | await driver.applyPendingFileChanges(); | 
|  | return await driver.resolveForCompletion( | 
|  | path: path, | 
|  | offset: offset, | 
|  | performance: performance, | 
|  | ); | 
|  | } catch (e, st) { | 
|  | instrumentationService.logException(e, st); | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Sends an LSP notification to the client. | 
|  | /// | 
|  | /// The legacy server will wrap LSP notifications inside an | 
|  | /// 'lsp.notification' notification. | 
|  | void sendLspNotification(lsp.NotificationMessage notification); | 
|  |  | 
|  | /// Sends an LSP request with the given [params] to the client and waits for a | 
|  | /// response. Completes with the raw [lsp.ResponseMessage] which could be an | 
|  | /// error response. | 
|  | Future<lsp.ResponseMessage> sendLspRequest(lsp.Method method, Object params); | 
|  |  | 
|  | /// Sends an error notification to the user. | 
|  | void sendServerErrorNotification( | 
|  | String message, | 
|  | Object exception, | 
|  | StackTrace? stackTrace, { | 
|  | bool fatal = false, | 
|  | }); | 
|  |  | 
|  | /// Shows the user a prompt with some actions to select from using | 
|  | /// 'window/showMessageRequest'. | 
|  | /// | 
|  | /// Callers should use [userPromptSender] instead of calling this method | 
|  | /// directly because it returns null if the client does not support user | 
|  | /// prompts. | 
|  | @visibleForOverriding | 
|  | Future<String?> showUserPrompt( | 
|  | MessageType type, | 
|  | String message, | 
|  | List<String> actionLabels, | 
|  | ); | 
|  |  | 
|  | @mustCallSuper | 
|  | Future<void> shutdown() async { | 
|  | completeLspUninitialization(); | 
|  |  | 
|  | await analysisDriverSchedulerEventsSubscription?.cancel(); | 
|  | analysisDriverSchedulerEventsSubscription = null; | 
|  |  | 
|  | // For now we record plugins only on shutdown. We might want to record them | 
|  | // every time the set of plugins changes, in which case we'll need to listen | 
|  | // to the `PluginManager.pluginsChanged` stream. | 
|  | if (supportsPlugins) { | 
|  | analyticsManager.changedPlugins(pluginManager); | 
|  | } | 
|  | // For now we record context-dependent information only on shutdown. We | 
|  | // might want to record it on start-up as well. | 
|  | analyticsManager.createdAnalysisContexts(contextManager.analysisContexts); | 
|  |  | 
|  | pubPackageService.shutdown(); | 
|  | surveyManager?.shutdown(); | 
|  | await contextManager.dispose(); | 
|  | await analyticsManager.shutdown(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// ContextManager callbacks that operate on the base server regardless | 
|  | /// of protocol. | 
|  | abstract class CommonServerContextManagerCallbacks | 
|  | extends ContextManagerCallbacks { | 
|  | /// The [ResourceProvider] by which paths are converted into [Resource]s. | 
|  | final OverlayResourceProvider resourceProvider; | 
|  |  | 
|  | /// The set of files for which notifications were sent. | 
|  | final Set<String> filesToFlush = {}; | 
|  |  | 
|  | CommonServerContextManagerCallbacks(this.resourceProvider); | 
|  |  | 
|  | @override | 
|  | @mustCallSuper | 
|  | void afterContextsCreated() { | 
|  | analysisServer.afterContextsCreated(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | @mustCallSuper | 
|  | void afterContextsDestroyed() { | 
|  | flushResults(filesToFlush.toList()); | 
|  | filesToFlush.clear(); | 
|  | analysisServer.afterContextsDestroyed(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void afterWatchEvent(WatchEvent event) {} | 
|  |  | 
|  | @override | 
|  | @mustCallSuper | 
|  | void applyFileRemoved(String file) { | 
|  | // If the removed file doesn't have an overlay, we need to clear any | 
|  | // previous results. | 
|  | if (!resourceProvider.hasOverlay(file)) { | 
|  | // Clear the cached errors in the notification manager so we don't | 
|  | // re-send stale results if a plugin sends an update and we merge it with | 
|  | // previous results. | 
|  | analysisServer.notificationManager.errors.clearResultsForFile(file); | 
|  |  | 
|  | if (filesToFlush.remove(file)) { | 
|  | flushResults([file]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @override | 
|  | @mustCallSuper | 
|  | void broadcastWatchEvent(WatchEvent event) { | 
|  | analysisServer.notifyDeclarationsTracker(event.path); | 
|  | analysisServer.notifyFlutterWidgetDescriptions(event.path); | 
|  | if (AnalysisServer.supportsPlugins) { | 
|  | analysisServer.pluginManager.broadcastWatchEvent(event); | 
|  | } | 
|  | } | 
|  |  | 
|  | void flushResults(List<String> files); | 
|  |  | 
|  | @override | 
|  | @mustCallSuper | 
|  | void handleFileResult(FileResult result) { | 
|  | if (analysisServer.suppressAnalysisResults) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | var path = result.path; | 
|  | filesToFlush.add(path); | 
|  |  | 
|  | // If this is a virtual file and the client supports URIs, we need to notify | 
|  | // that it's been updated. | 
|  | var lspUri = analysisServer.uriConverter.toClientUri(result.path); | 
|  | if (!lspUri.isScheme('file')) { | 
|  | // TODO(dantup): Should we do any kind of tracking here to avoid sending | 
|  | //  lots of notifications if there aren't actual changes? | 
|  | // TODO(dantup): We may be able to skip sending this if the file is not | 
|  | //  open (priority) depending on the response to | 
|  | //  https://github.com/microsoft/vscode/issues/202017 | 
|  | var message = lsp.NotificationMessage( | 
|  | method: lsp.CustomMethods.dartTextDocumentContentDidChange, | 
|  | params: lsp.DartTextDocumentContentDidChangeParams(uri: lspUri), | 
|  | jsonrpc: lsp.jsonRpcVersion, | 
|  | ); | 
|  | analysisServer.sendLspNotification(message); | 
|  | } | 
|  |  | 
|  | if (result is AnalysisResultWithDiagnostics) { | 
|  | if (analysisServer.isAnalyzed(path)) { | 
|  | var serverErrors = server.doAnalysisError_listFromEngine(result); | 
|  | recordAnalysisErrors(path, serverErrors); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (result is ResolvedUnitResult) { | 
|  | analysisServer.filesResolvedSinceLastIdle.add(path); | 
|  | handleResolvedUnitResult(result); | 
|  | } | 
|  | } | 
|  |  | 
|  | void handleResolvedUnitResult(ResolvedUnitResult result); | 
|  |  | 
|  | @override | 
|  | @mustCallSuper | 
|  | void listenAnalysisDriver(analysis.AnalysisDriver driver) { | 
|  | driver.exceptions.listen(analysisServer.logExceptionResult); | 
|  | driver.priorityFiles = analysisServer.priorityFiles.toList(); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void pubspecChanged(String path) { | 
|  | analysisServer.pubPackageService.fetchPackageVersionsViaPubOutdated( | 
|  | path, | 
|  | pubspecWasModified: true, | 
|  | ); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void pubspecRemoved(String path) { | 
|  | analysisServer.pubPackageService.flushPackageCaches(path); | 
|  | } | 
|  |  | 
|  | @override | 
|  | @mustCallSuper | 
|  | void recordAnalysisErrors(String path, List<server.AnalysisError> errors) { | 
|  | filesToFlush.add(path); | 
|  | analysisServer.notificationManager.recordAnalysisErrors( | 
|  | NotificationManager.serverId, | 
|  | path, | 
|  | errors, | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A server-agnostic type for messages sent to the client. | 
|  | enum MessageType { | 
|  | error(lsp.MessageType.Error, legacy.MessageType.ERROR), | 
|  | warning(lsp.MessageType.Warning, legacy.MessageType.WARNING), | 
|  | info(lsp.MessageType.Info, legacy.MessageType.INFO), | 
|  | log(lsp.MessageType.Log, legacy.MessageType.LOG); | 
|  |  | 
|  | final lsp.MessageType forLsp; | 
|  | final legacy.MessageType forLegacy; | 
|  |  | 
|  | const MessageType(this.forLsp, this.forLegacy); | 
|  | } | 
|  |  | 
|  | class ServerRecentPerformance { | 
|  | /// The maximum number of performance measurements to keep. | 
|  | static const int performanceListMaxLength = 50; | 
|  |  | 
|  | /// The maximum number of slow performance measurements to keep. | 
|  | static const int slowRequestsListMaxLength = 1000; | 
|  |  | 
|  | /// The duration we use to categorize requests as slow. | 
|  | static const Duration slowRequestsThreshold = Duration(milliseconds: 500); | 
|  |  | 
|  | /// A list of code completion performance measurements for the latest | 
|  | /// completion operation up to [performanceListMaxLength] measurements. | 
|  | final RecentBuffer<CompletionPerformance> completion = | 
|  | RecentBuffer<CompletionPerformance>(performanceListMaxLength); | 
|  |  | 
|  | /// A list of performance measurements for the latest requests to get fixes | 
|  | /// up to [performanceListMaxLength] measurements. | 
|  | final RecentBuffer<GetAssistsPerformance> getAssists = | 
|  | RecentBuffer<GetAssistsPerformance>(performanceListMaxLength); | 
|  |  | 
|  | /// A list of performance measurements for the latest requests to get fixes | 
|  | /// up to [performanceListMaxLength] measurements. | 
|  | final RecentBuffer<GetFixesPerformance> getFixes = | 
|  | RecentBuffer<GetFixesPerformance>(performanceListMaxLength); | 
|  |  | 
|  | /// A list of performance measurements for the latest requests to get refactorings | 
|  | /// up to [performanceListMaxLength] measurements. | 
|  | final RecentBuffer<GetRefactoringsPerformance> getRefactorings = | 
|  | RecentBuffer<GetRefactoringsPerformance>(performanceListMaxLength); | 
|  |  | 
|  | /// A [RecentBuffer] for performance information about the most recent | 
|  | /// requests. | 
|  | final RecentBuffer<RequestPerformance> requests = RecentBuffer( | 
|  | performanceListMaxLength, | 
|  | ); | 
|  |  | 
|  | /// A [RecentBuffer] for performance information about the most recent | 
|  | /// slow requests. | 
|  | final RecentBuffer<RequestPerformance> slowRequests = RecentBuffer( | 
|  | slowRequestsListMaxLength, | 
|  | ); | 
|  | } | 
|  |  | 
|  | extension on OverlayResourceProvider { | 
|  | /// The path to the location of the byte store on disk, or `null` if there is | 
|  | /// no on-disk byte store. | 
|  | String? get byteStorePath { | 
|  | if (baseProvider is PhysicalResourceProvider) { | 
|  | var stateLocation = baseProvider.getStateLocation('.analysis-driver'); | 
|  | return stateLocation?.path; | 
|  | } | 
|  | return null; | 
|  | } | 
|  | } |