Change LSP edit mapping to support multi-file edits

Change-Id: I1f29b8b49d27b4e3b7f44ea86aa1a33c7b6ee48b
Reviewed-on: https://dart-review.googlesource.com/c/86926
Commit-Queue: Danny Tuppeny <dantup@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/commands/simple_edit_handler.dart b/pkg/analysis_server/lib/src/lsp/handlers/commands/simple_edit_handler.dart
index 202b79d..24c7b7d 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/commands/simple_edit_handler.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/commands/simple_edit_handler.dart
@@ -8,6 +8,7 @@
 import 'package:analysis_server/src/lsp/handlers/handlers.dart';
 import 'package:analysis_server/src/lsp/lsp_analysis_server.dart';
 import 'package:analysis_server/src/lsp/mapping.dart';
+import 'package:analysis_server/src/lsp/source_edits.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/error/error.dart' as engine;
 import 'package:analyzer/src/dart/scanner/scanner.dart' as engine;
@@ -34,9 +35,7 @@
 
     final workspaceEdit = toWorkspaceEdit(
       server.clientCapabilities?.workspace,
-      docIdentifier,
-      unit.lineInfo,
-      edits,
+      [new FileEditInformation(docIdentifier, unit.lineInfo, edits)],
     );
 
     // Send the edit to the client via a applyEdit request (this is a request
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
index 2deb2ca..cb8ecd3 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_code_actions.dart
@@ -14,6 +14,9 @@
 import 'package:analyzer/dart/analysis/results.dart';
 import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
 
+typedef ActionHandler = Future<List<Either2<Command, CodeAction>>> Function(
+    HashSet<CodeActionKind>, bool, String, Range, ResolvedUnitResult);
+
 class CodeActionHandler extends MessageHandler<CodeActionParams,
     List<Either2<Command, CodeAction>>> {
   CodeActionHandler(LspAnalysisServer server) : super(server);
@@ -53,71 +56,78 @@
         clientSupportedCodeActionKinds,
         clientSupportsLiteralCodeActions,
         path.result,
+        params.range,
         unit));
   }
 
-  List<Either2<Command, CodeAction>> _getAssistActions(
+  Future<List<Either2<Command, CodeAction>>> _getAssistActions(
     HashSet<CodeActionKind> clientSupportedCodeActionKinds,
     bool clientSupportsLiteralCodeActions,
     String path,
+    Range range,
     ResolvedUnitResult unit,
-  ) {
+  ) async {
     // TODO(dantup): Implement assists.
     return [];
   }
 
-  ErrorOr<List<Either2<Command, CodeAction>>> _getCodeActions(
+  Future<ErrorOr<List<Either2<Command, CodeAction>>>> _getCodeActions(
     HashSet<CodeActionKind> clientSupportedCodeActionKinds,
     bool clientSupportsLiteralCodeActions,
     String path,
+    Range range,
     ResolvedUnitResult unit,
-  ) {
+  ) async {
     // Join the results of computing all of our different types.
-    final allActions = [
+    final List<ActionHandler> handlers = [
       _getSourceActions,
       _getAssistActions,
       _getRefactorActions,
       _getFixActions,
-    ]
-        .expand((f) => f(
-              clientSupportedCodeActionKinds,
-              clientSupportsLiteralCodeActions,
-              path,
-              unit,
-            ))
-        .toList();
-
-    return success(allActions);
+    ];
+    final futures = handlers.map((f) => f(
+          clientSupportedCodeActionKinds,
+          clientSupportsLiteralCodeActions,
+          path,
+          range,
+          unit,
+        ));
+    final results = await Future.wait(futures);
+    final flatResults = results.expand((x) => x).toList();
+    return success(flatResults);
   }
 
-  List<Either2<Command, CodeAction>> _getFixActions(
+  Future<List<Either2<Command, CodeAction>>> _getFixActions(
     HashSet<CodeActionKind> clientSupportedCodeActionKinds,
     bool clientSupportsLiteralCodeActions,
     String path,
+    Range range,
     ResolvedUnitResult unit,
-  ) {
+  ) async {
     // TODO(dantup): Implement fixes.
     return [];
   }
 
-  List<Either2<Command, CodeAction>> _getRefactorActions(
+  Future<List<Either2<Command, CodeAction>>> _getRefactorActions(
     HashSet<CodeActionKind> clientSupportedCodeActionKinds,
     bool clientSupportsLiteralCodeActions,
     String path,
+    Range range,
     ResolvedUnitResult unit,
-  ) {
+  ) async {
     // TODO(dantup): Implement refactors.
     return [];
   }
 
   /// Gets "Source" CodeActions, which are actions that apply to whole files of
   /// source such as Sort Members and Organise Imports.
-  List<Either2<Command, CodeAction>> _getSourceActions(
+  Future<List<Either2<Command, CodeAction>>> _getSourceActions(
     HashSet<CodeActionKind> clientSupportedCodeActionKinds,
     bool clientSupportsLiteralCodeActions,
     String path,
+    Range range,
     ResolvedUnitResult unit,
-  ) {
+  ) async {
     // The source actions supported are only valid for Dart files.
     if (!AnalysisEngine.isDartFileName(path)) {
       return [];
diff --git a/pkg/analysis_server/lib/src/lsp/mapping.dart b/pkg/analysis_server/lib/src/lsp/mapping.dart
index dc7f763..acf79d6 100644
--- a/pkg/analysis_server/lib/src/lsp/mapping.dart
+++ b/pkg/analysis_server/lib/src/lsp/mapping.dart
@@ -7,12 +7,14 @@
 import 'package:analysis_server/src/lsp/constants.dart' as lsp;
 import 'package:analysis_server/src/lsp/dartdoc.dart';
 import 'package:analysis_server/src/lsp/lsp_analysis_server.dart' as lsp;
+import 'package:analysis_server/src/lsp/source_edits.dart';
 import 'package:analysis_server/src/protocol_server.dart' as server
     hide AnalysisError;
 import 'package:analyzer/dart/analysis/results.dart' as server;
 import 'package:analyzer/error/error.dart' as server;
 import 'package:analyzer/source/line_info.dart' as server;
 import 'package:analyzer/src/generated/source.dart' as server;
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart' as server;
 
 const languageSourceName = 'dart';
 
@@ -486,14 +488,10 @@
   );
 }
 
-lsp.TextDocumentEdit toTextDocumentEdit(
-  VersionedTextDocumentIdentifier doc,
-  server.LineInfo lineInfo,
-  List<server.SourceEdit> edits,
-) {
+lsp.TextDocumentEdit toTextDocumentEdit(FileEditInformation edit) {
   return new TextDocumentEdit(
-    doc,
-    edits.map((e) => toTextEdit(lineInfo, e)).toList(),
+    edit.doc,
+    edit.edits.map((e) => toTextEdit(edit.lineInfo, e)).toList(),
   );
 }
 
@@ -506,9 +504,7 @@
 
 lsp.WorkspaceEdit toWorkspaceEdit(
   WorkspaceClientCapabilities capabilities,
-  VersionedTextDocumentIdentifier doc,
-  server.LineInfo lineInfo,
-  List<server.SourceEdit> edits,
+  List<FileEditInformation> edits,
 ) {
   final clientSupportsTextDocumentEdits =
       capabilities?.workspaceEdit?.documentChanges == true;
@@ -520,23 +516,22 @@
             List<
                 Either4<TextDocumentEdit, CreateFile, RenameFile,
                     DeleteFile>>>.t1(
-          [toTextDocumentEdit(doc, lineInfo, edits)],
+          edits.map(toTextDocumentEdit).toList(),
         ));
   } else {
-    return new WorkspaceEdit(
-        toWorkspaceEditChanges(doc, lineInfo, edits), null);
+    return new WorkspaceEdit(toWorkspaceEditChanges(edits), null);
   }
 }
 
 Map<String, List<TextEdit>> toWorkspaceEditChanges(
-  VersionedTextDocumentIdentifier doc,
-  server.LineInfo lineInfo,
-  List<server.SourceEdit> edits,
-) {
-  // TODO(dantup): Fix codegen for WorkspaceEditChanges to be Map<String, TextEdit>
-  return Map<String, List<TextEdit>>.fromEntries(
-    edits.map((e) => new MapEntry(doc.uri, [toTextEdit(lineInfo, e)])),
-  );
+    List<FileEditInformation> edits) {
+  createEdit(FileEditInformation file) {
+    final edits =
+        file.edits.map((edit) => toTextEdit(file.lineInfo, edit)).toList();
+    return new MapEntry(file.doc.uri, edits);
+  }
+
+  return Map<String, List<TextEdit>>.fromEntries(edits.map(createEdit));
 }
 
 lsp.MarkupContent _asMarkup(
diff --git a/pkg/analysis_server/lib/src/lsp/source_edits.dart b/pkg/analysis_server/lib/src/lsp/source_edits.dart
index 200ac92..76cee3ef 100644
--- a/pkg/analysis_server/lib/src/lsp/source_edits.dart
+++ b/pkg/analysis_server/lib/src/lsp/source_edits.dart
@@ -1,6 +1,8 @@
 import 'package:analysis_server/lsp_protocol/protocol_generated.dart';
 import 'package:analysis_server/lsp_protocol/protocol_special.dart';
 import 'package:analysis_server/src/lsp/mapping.dart';
+import 'package:analysis_server/src/protocol_server.dart' as server
+    show SourceEdit;
 import 'package:analyzer/source/line_info.dart';
 import 'package:dart_style/dart_style.dart';
 
@@ -59,3 +61,13 @@
     )
   ];
 }
+
+/// Helper class that bundles up all information required when converting server
+/// SourceEdits into LSP-compatible WorkspaceEdits.
+class FileEditInformation {
+  final VersionedTextDocumentIdentifier doc;
+  final LineInfo lineInfo;
+  final List<server.SourceEdit> edits;
+
+  FileEditInformation(this.doc, this.lineInfo, this.edits);
+}