Enable the analyzer Diagnostics server for LSP

Change-Id: I3a555c301e8c09b1d8af8f952f9f65102961d3b7
Reviewed-on: https://dart-review.googlesource.com/c/88801
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 03fd356..3e0946e8 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -16,7 +16,6 @@
 import 'package:analysis_server/src/analysis_logger.dart';
 import 'package:analysis_server/src/analysis_server_abstract.dart';
 import 'package:analysis_server/src/channel/channel.dart';
-import 'package:analysis_server/src/collections.dart';
 import 'package:analysis_server/src/computer/computer_highlights.dart';
 import 'package:analysis_server/src/computer/computer_highlights2.dart';
 import 'package:analysis_server/src/computer/computer_outline.dart';
@@ -76,9 +75,6 @@
 /// Instances of the class [AnalysisServer] implement a server that listens on a
 /// [CommunicationChannel] for analysis requests and process them.
 class AnalysisServer extends AbstractAnalysisServer {
-  /// The options of this server instance.
-  AnalysisServerOptions options;
-
   /// The channel from which requests are received and to which responses should
   /// be sent.
   final ServerCommunicationChannel channel;
@@ -124,22 +120,6 @@
   /// notifications should be sent.
   Map<FlutterService, Set<String>> flutterServices = {};
 
-  /// Performance information before initial analysis is complete.
-  final ServerPerformance performanceDuringStartup = new ServerPerformance();
-
-  /// Performance information after initial analysis is complete
-  /// or `null` if the initial analysis is not yet complete
-  ServerPerformance performanceAfterStartup;
-
-  /// A [RecentBuffer] of the most recent exceptions encountered by the analysis
-  /// server.
-  final RecentBuffer<ServerException> exceptions = new RecentBuffer(10);
-
-  /// 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].
-  ServerPerformance _performance;
-
   /// The [Completer] that completes when analysis is complete.
   Completer _onAnalysisCompleteCompleter;
 
@@ -191,18 +171,16 @@
   AnalysisServer(
     this.channel,
     ResourceProvider baseResourceProvider,
-    this.options,
+    AnalysisServerOptions options,
     this.sdkManager,
     this.instrumentationService, {
     this.diagnosticServer,
     ResolverProvider fileResolverProvider: null,
     ResolverProvider packageResolverProvider: null,
     this.detachableFileSystemManager: null,
-  }) : super(baseResourceProvider) {
+  }) : super(options, baseResourceProvider) {
     notificationManager = new NotificationManager(channel, resourceProvider);
 
-    _performance = performanceDuringStartup;
-
     pluginManager = new PluginManager(
         resourceProvider,
         _getByteStorePath(),
@@ -252,7 +230,7 @@
     onAnalysisStarted.first.then((_) {
       onAnalysisComplete.then((_) {
         performanceAfterStartup = new ServerPerformance();
-        _performance = performanceAfterStartup;
+        performance = performanceAfterStartup;
       });
     });
     searchEngine = new SearchEngineImpl(driverMap.values);
@@ -302,13 +280,6 @@
     return _onAnalysisStartedController.stream;
   }
 
-  /// Return the total time the server's been alive.
-  Duration get uptime {
-    DateTime start = new DateTime.fromMillisecondsSinceEpoch(
-        performanceDuringStartup.startTime);
-    return new DateTime.now().difference(start);
-  }
-
   /// The socket from which requests are being read has been closed.
   void done() {}
 
@@ -340,7 +311,7 @@
 
   /// Handle a [request] that was read from the communication channel.
   void handleRequest(Request request) {
-    _performance.logRequest(request);
+    performance.logRequestTiming(request.clientRequestTime);
     runZoned(() {
       ServerPerformanceStatistics.serverRequests.makeCurrentWhile(() {
         int count = handlers.length;
@@ -1080,12 +1051,12 @@
   /// The number of requests with latency > 150 milliseconds.
   int slowRequestCount = 0;
 
-  /// Log performance information about the given request.
-  void logRequest(Request request) {
+  /// Log timing information for a request.
+  void logRequestTiming(int clientRequestTime) {
     ++requestCount;
-    if (request.clientRequestTime != null) {
+    if (clientRequestTime != null) {
       int latency =
-          new DateTime.now().millisecondsSinceEpoch - request.clientRequestTime;
+          new DateTime.now().millisecondsSinceEpoch - clientRequestTime;
       requestLatency += latency;
       maxLatency = max(maxLatency, latency);
       if (latency > 150) {
diff --git a/pkg/analysis_server/lib/src/analysis_server_abstract.dart b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
index bdc364e..cc27c636 100644
--- a/pkg/analysis_server/lib/src/analysis_server_abstract.dart
+++ b/pkg/analysis_server/lib/src/analysis_server_abstract.dart
@@ -5,6 +5,8 @@
 import 'dart:async';
 import 'dart:core';
 
+import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/collections.dart';
 import 'package:analysis_server/src/context_manager.dart';
 import 'package:analysis_server/src/services/correction/namespace.dart';
 import 'package:analysis_server/src/services/search/element_visitors.dart';
@@ -29,10 +31,29 @@
 /// Implementations of [AbstractAnalysisServer] implement a server that listens
 /// on a [CommunicationChannel] for analysis messages and process them.
 abstract class AbstractAnalysisServer {
+  /// The options of this server instance.
+  AnalysisServerOptions options;
+
   /// The [ContextManager] that handles the mapping from analysis roots to
   /// context directories.
   ContextManager contextManager;
 
+  /// A [RecentBuffer] of the most recent exceptions encountered by the analysis
+  /// server.
+  final RecentBuffer<ServerException> exceptions = new RecentBuffer(10);
+
+  /// Performance information after initial analysis is complete
+  /// or `null` if the initial analysis is not yet complete
+  ServerPerformance performanceAfterStartup;
+
+  /// 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].
+  ServerPerformance performance;
+
+  /// Performance information before initial analysis is complete.
+  final ServerPerformance performanceDuringStartup = new ServerPerformance();
+
   /// The set of the files that are currently priority.
   final Set<String> priorityFiles = new Set<String>();
 
@@ -52,8 +73,10 @@
   /// list is lazily created and should be accessed using [analyzedFilesGlobs].
   List<Glob> _analyzedFilesGlobs = null;
 
-  AbstractAnalysisServer(ResourceProvider baseResourceProvider)
-      : resourceProvider = OverlayResourceProvider(baseResourceProvider);
+  AbstractAnalysisServer(this.options, ResourceProvider baseResourceProvider)
+      : resourceProvider = OverlayResourceProvider(baseResourceProvider) {
+    performance = performanceDuringStartup;
+  }
 
   /// Return a list of the globs used to determine which files should be
   /// analyzed.
@@ -82,6 +105,13 @@
   /// A table mapping [Folder]s to the [AnalysisDriver]s associated with them.
   Map<Folder, nd.AnalysisDriver> get driverMap => contextManager.driverMap;
 
+  /// Return the total time the server's been alive.
+  Duration get uptime {
+    DateTime start = new DateTime.fromMillisecondsSinceEpoch(
+        performanceDuringStartup.startTime);
+    return new DateTime.now().difference(start);
+  }
+
   /// If the state location can be accessed, return the file byte store,
   /// otherwise return the memory byte store.
   ByteStore createByteStore(ResourceProvider resourceProvider) {
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_initialize.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_initialize.dart
index cdee37d..b5afe93 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_initialize.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_initialize.dart
@@ -34,7 +34,7 @@
       openWorkspacePaths.add(params.rootUri);
     }
 
-    server.setClientCapabilities(params.capabilities);
+    server.handleClientConnection(params.capabilities);
     server.messageHandler =
         new InitializingStateMessageHandler(server, openWorkspacePaths);
 
diff --git a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
index 245c116a..22f5abb 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_analysis_server.dart
@@ -21,6 +21,7 @@
 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/utilities/null_string_sink.dart';
+import 'package:analyzer/exception/exception.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/instrumentation/instrumentation.dart';
 import 'package:analyzer/source/line_info.dart';
@@ -45,11 +46,6 @@
   ClientCapabilities _clientCapabilities;
 
   /**
-   * The options of this server instance.
-   */
-  AnalysisServerOptions options;
-
-  /**
    * The channel from which messages are received and to which responses should
    * be sent.
    */
@@ -118,11 +114,11 @@
   LspAnalysisServer(
     this.channel,
     ResourceProvider baseResourceProvider,
-    this.options,
+    AnalysisServerOptions options,
     this.sdkManager,
     this.instrumentationService, {
     ResolverProvider packageResolverProvider: null,
-  }) : super(baseResourceProvider) {
+  }) : super(options, baseResourceProvider) {
     messageHandler = new UninitializedStateMessageHandler(this);
     defaultContextOptions.generateImplicitErrors = false;
     defaultContextOptions.useFastaParser = options.useFastaParser;
@@ -235,8 +231,7 @@
    * Handle a [message] that was read from the communication channel.
    */
   void handleMessage(Message message) {
-    // TODO(dantup): Put in all the things this server is missing, like:
-    //     _performance.logRequest(message);
+    performance.logRequestTiming(null);
     runZoned(() {
       ServerPerformanceStatistics.serverRequests.makeCurrentWhile(() async {
         try {
@@ -375,6 +370,17 @@
     // Log the full message since showMessage above may be truncated or formatted
     // badly (eg. VS Code takes the newlines out).
     logError(fullError.toString());
+
+    // remember the last few exceptions
+    if (exception is CaughtException) {
+      stackTrace ??= exception.stackTrace;
+    }
+    exceptions.add(new ServerException(
+      message,
+      exception,
+      stackTrace is StackTrace ? stackTrace : null,
+      false,
+    ));
   }
 
   void setAnalysisRoots(List<String> includedPaths, List<String> excludedPaths,
@@ -382,8 +388,11 @@
     contextManager.setRoots(includedPaths, excludedPaths, packageRoots);
   }
 
-  void setClientCapabilities(ClientCapabilities capabilities) {
+  void handleClientConnection(ClientCapabilities capabilities) {
     _clientCapabilities = capabilities;
+
+    performanceAfterStartup = new ServerPerformance();
+    performance = performanceAfterStartup;
   }
 
   /**
diff --git a/pkg/analysis_server/lib/src/lsp/lsp_socket_server.dart b/pkg/analysis_server/lib/src/lsp/lsp_socket_server.dart
index 47261dc..25a15e1 100644
--- a/pkg/analysis_server/lib/src/lsp/lsp_socket_server.dart
+++ b/pkg/analysis_server/lib/src/lsp/lsp_socket_server.dart
@@ -19,7 +19,7 @@
  * the SocketServer is to manage the lifetime of the AnalysisServer and to
  * encode and decode the JSON messages exchanged with the client.
  */
-class LspSocketServer {
+class LspSocketServer implements AbstractSocketServer {
   final AnalysisServerOptions analysisServerOptions;
   /**
    * The analysis server that was created when a client established a
diff --git a/pkg/analysis_server/lib/src/server/driver.dart b/pkg/analysis_server/lib/src/server/driver.dart
index 21d5cc8..b204cf5 100644
--- a/pkg/analysis_server/lib/src/server/driver.dart
+++ b/pkg/analysis_server/lib/src/server/driver.dart
@@ -407,37 +407,10 @@
         defaultSdk.sdkVersion);
     AnalysisEngine.instance.instrumentationService = instrumentationService;
 
-    if (analysisServerOptions.useLanguageServerProtocol) {
-      startLspServer(results, analysisServerOptions, dartSdkManager,
-          instrumentationService);
-    } else {
-      startAnalysisServer(results, analysisServerOptions, parser,
-          dartSdkManager, instrumentationService, analytics);
-    }
-  }
-
-  void startAnalysisServer(
-      ArgResults results,
-      AnalysisServerOptions analysisServerOptions,
-      CommandLineParser parser,
-      DartSdkManager dartSdkManager,
-      InstrumentationService instrumentationService,
-      telemetry.Analytics analytics) {
-    String trainDirectory = results[TRAIN_USING];
-    if (trainDirectory != null) {
-      if (!FileSystemEntity.isDirectorySync(trainDirectory)) {
-        print("Training directory '$trainDirectory' not found.\n");
-        exitCode = 1;
-        return null;
-      }
-    }
-
-    int port;
-    bool serve_http = false;
+    int diagnosticServerPort;
     if (results[PORT_OPTION] != null) {
       try {
-        port = int.parse(results[PORT_OPTION]);
-        serve_http = true;
+        diagnosticServerPort = int.parse(results[PORT_OPTION]);
       } on FormatException {
         print('Invalid port number: ${results[PORT_OPTION]}');
         print('');
@@ -447,6 +420,40 @@
       }
     }
 
+    if (analysisServerOptions.useLanguageServerProtocol) {
+      startLspServer(results, analysisServerOptions, dartSdkManager,
+          instrumentationService, diagnosticServerPort);
+    } else {
+      startAnalysisServer(
+          results,
+          analysisServerOptions,
+          parser,
+          dartSdkManager,
+          instrumentationService,
+          analytics,
+          diagnosticServerPort);
+    }
+  }
+
+  void startAnalysisServer(
+    ArgResults results,
+    AnalysisServerOptions analysisServerOptions,
+    CommandLineParser parser,
+    DartSdkManager dartSdkManager,
+    InstrumentationService instrumentationService,
+    telemetry.Analytics analytics,
+    int diagnosticServerPort,
+  ) {
+    String trainDirectory = results[TRAIN_USING];
+    if (trainDirectory != null) {
+      if (!FileSystemEntity.isDirectorySync(trainDirectory)) {
+        print("Training directory '$trainDirectory' not found.\n");
+        exitCode = 1;
+        return null;
+      }
+    }
+    final serve_http = diagnosticServerPort != null;
+
     //
     // Process all of the plugins so that extensions are registered.
     //
@@ -474,7 +481,7 @@
 
     diagnosticServer.httpServer = httpServer;
     if (serve_http) {
-      diagnosticServer.startOnPort(port);
+      diagnosticServer.startOnPort(diagnosticServerPort);
     }
 
     if (trainDirectory != null) {
@@ -539,13 +546,25 @@
     AnalysisServerOptions analysisServerOptions,
     DartSdkManager dartSdkManager,
     InstrumentationService instrumentationService,
+    int diagnosticServerPort,
   ) {
+    final serve_http = diagnosticServerPort != null;
+
+    _DiagnosticServerImpl diagnosticServer = new _DiagnosticServerImpl();
+
     final socketServer = new LspSocketServer(
       analysisServerOptions,
       dartSdkManager,
       instrumentationService,
     );
 
+    httpServer = new HttpAnalysisServer(socketServer);
+
+    diagnosticServer.httpServer = httpServer;
+    if (serve_http) {
+      diagnosticServer.startOnPort(diagnosticServerPort);
+    }
+
     _captureLspExceptions(socketServer, instrumentationService, () {
       LspStdioAnalysisServer stdioServer =
           new LspStdioAnalysisServer(socketServer);
diff --git a/pkg/analysis_server/lib/src/server/http_server.dart b/pkg/analysis_server/lib/src/server/http_server.dart
index 5b51304..8cb1391 100644
--- a/pkg/analysis_server/lib/src/server/http_server.dart
+++ b/pkg/analysis_server/lib/src/server/http_server.dart
@@ -35,7 +35,7 @@
    * An object that can handle either a WebSocket connection or a connection
    * to the client over stdio.
    */
-  SocketServer socketServer;
+  AbstractSocketServer socketServer;
 
   /**
    * An object that can handle GET requests.
diff --git a/pkg/analysis_server/lib/src/socket_server.dart b/pkg/analysis_server/lib/src/socket_server.dart
index 9e22d01..ad77e8a 100644
--- a/pkg/analysis_server/lib/src/socket_server.dart
+++ b/pkg/analysis_server/lib/src/socket_server.dart
@@ -5,6 +5,7 @@
 import 'package:analysis_server/protocol/protocol.dart';
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/analysis_server_abstract.dart';
 import 'package:analysis_server/src/channel/channel.dart';
 import 'package:analysis_server/src/server/detachable_filesystem_manager.dart';
 import 'package:analysis_server/src/server/diagnostic_server.dart';
@@ -13,13 +14,18 @@
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/plugin/resolver_provider.dart';
 
+abstract class AbstractSocketServer {
+  AnalysisServerOptions get analysisServerOptions;
+  AbstractAnalysisServer get analysisServer;
+}
+
 /**
  * Instances of the class [SocketServer] implement the common parts of
  * http-based and stdio-based analysis servers.  The primary responsibility of
  * the SocketServer is to manage the lifetime of the AnalysisServer and to
  * encode and decode the JSON messages exchanged with the client.
  */
-class SocketServer {
+class SocketServer implements AbstractSocketServer {
   final AnalysisServerOptions analysisServerOptions;
 
   /**
diff --git a/pkg/analysis_server/lib/src/status/diagnostics.dart b/pkg/analysis_server/lib/src/status/diagnostics.dart
index 44fff0a..be7a383 100644
--- a/pkg/analysis_server/lib/src/status/diagnostics.dart
+++ b/pkg/analysis_server/lib/src/status/diagnostics.dart
@@ -11,6 +11,7 @@
     show PROTOCOL_VERSION;
 import 'package:analysis_server/protocol/protocol_generated.dart';
 import 'package:analysis_server/src/analysis_server.dart';
+import 'package:analysis_server/src/analysis_server_abstract.dart';
 import 'package:analysis_server/src/domain_completion.dart';
 import 'package:analysis_server/src/plugin/plugin_manager.dart';
 import 'package:analysis_server/src/server/http_server.dart';
@@ -222,6 +223,11 @@
   Future generateContent(Map<String, String> params) async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
+
+    // TODO(dantup): The latency stats are never populated for LSP because the
+    // client will never send timestamps (it's also misleading for non-LSP
+    // if the client does not send timestamps (VS Code doesn't) as it shows 0ms).
+
     void writeRow(List<String> data, {List<String> classes}) {
       buf.write("<tr>");
       for (int i = 0; i < data.length; i++) {
@@ -312,6 +318,16 @@
   Future generateContent(Map<String, String> params) async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
+
+    // TODO(dantup): This one should be supported, just isn't yet.
+    if (this.server is! AnalysisServer) {
+      generateNotApplicablePage();
+      return;
+    }
+
+    // TODO(dantup): ...
+    final server = this.server as AnalysisServer;
+
     CompletionDomainHandler completionDomain = server.handlers
         .firstWhere((handler) => handler is CompletionDomainHandler);
 
@@ -582,7 +598,7 @@
 
   bool get isNavPage => false;
 
-  AnalysisServer get server =>
+  AbstractAnalysisServer get server =>
       (site as DiagnosticsSite).socketServer.analysisServer;
 
   Future<void> generateContainer(Map<String, String> params) async {
@@ -706,12 +722,16 @@
 
     buf.writeln('</div>');
   }
+
+  void generateNotApplicablePage() {
+    buf.write('This page is not applicable for this server.');
+  }
 }
 
 class DiagnosticsSite extends Site implements AbstractGetHandler {
   /// An object that can handle either a WebSocket connection or a connection
   /// to the client over stdio.
-  SocketServer socketServer;
+  AbstractSocketServer socketServer;
 
   /// The last few lines printed.
   final List<String> lastPrintedLines;
@@ -1037,6 +1057,18 @@
   Future generateContent(Map<String, String> params) async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
+
+    // TODO(dantup): Maybe we shouldn't show this in the nav at all?
+    if (this.server is! AnalysisServer) {
+      generateNotApplicablePage();
+      return;
+    }
+
+    // TODO(dantup): Is there a better way? The is! above doesn't seem to
+    // work, possible because 'server' is not immutable (it's a better) and maybe
+    // because the code below is in a callback?
+    final server = this.server as AnalysisServer;
+
     h3('Analysis plugins');
     List<PluginInfo> analysisPlugins = server.pluginManager.plugins;
 
@@ -1293,6 +1325,18 @@
   Future generateContent(Map<String, String> params) async {
     // TODO(brianwilkerson) Determine whether this await is necessary.
     await null;
+
+    // TODO(dantup): Maybe we shouldn't show this in the nav at all?
+    if (this.server is! AnalysisServer) {
+      generateNotApplicablePage();
+      return;
+    }
+
+    // TODO(dantup): Is there a better way? The is! above doesn't seem to
+    // work, possible because 'server' is not immutable (it's a better) and maybe
+    // because the code below is in a callback?
+    final server = this.server as AnalysisServer;
+
     // server domain
     h3('Server domain subscriptions');
     ul(ServerService.VALUES, (item) {