Use the max number of suggestions to limit the number of candidate suggestions being retained

Change-Id: I4a60569fb4efe4af576b9adbb375c6e2e0b1727c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370720
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Keerti Parthasarathy <keertip@google.com>
diff --git a/pkg/analysis_server/lib/src/cider/completion.dart b/pkg/analysis_server/lib/src/cider/completion.dart
index 50713af..d88d035 100644
--- a/pkg/analysis_server/lib/src/cider/completion.dart
+++ b/pkg/analysis_server/lib/src/cider/completion.dart
@@ -102,6 +102,7 @@
               performance,
               enableOverrideContributor: false,
               enableUriContributor: false,
+              maxSuggestions: -1,
               useFilter: true,
             );
           });
diff --git a/pkg/analysis_server/lib/src/domains/execution/completion.dart b/pkg/analysis_server/lib/src/domains/execution/completion.dart
index 30342f7..3918a83 100644
--- a/pkg/analysis_server/lib/src/domains/execution/completion.dart
+++ b/pkg/analysis_server/lib/src/domains/execution/completion.dart
@@ -79,6 +79,7 @@
     ).computeSuggestions(
       dartRequest,
       OperationPerformanceImpl('<root>'),
+      maxSuggestions: -1,
       useFilter: false,
     );
     var suggestions = serverSuggestions.map((e) => e.build()).toList();
diff --git a/pkg/analysis_server/lib/src/handler/legacy/completion_get_suggestions2.dart b/pkg/analysis_server/lib/src/handler/legacy/completion_get_suggestions2.dart
index 8de8f53..6a2832a 100644
--- a/pkg/analysis_server/lib/src/handler/legacy/completion_get_suggestions2.dart
+++ b/pkg/analysis_server/lib/src/handler/legacy/completion_get_suggestions2.dart
@@ -42,6 +42,7 @@
     required CompletionBudget budget,
     required OperationPerformanceImpl performance,
     required DartCompletionRequest request,
+    required int maxSuggestions,
     NotImportedSuggestions? notImportedSuggestions,
     required bool useFilter,
   }) async {
@@ -66,6 +67,7 @@
         await manager.computeSuggestions(
           request,
           performance,
+          maxSuggestions: maxSuggestions,
           useFilter: useFilter,
         ),
       );
@@ -205,6 +207,7 @@
             budget: budget,
             performance: performance,
             request: completionRequest,
+            maxSuggestions: params.maxResults,
             notImportedSuggestions: notImportedSuggestions,
             useFilter: true,
           );
diff --git a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
index fc00f02..e894785 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -167,6 +167,7 @@
                 offset,
                 triggerCharacter,
                 token,
+                maxResults,
               );
             },
           );
@@ -360,6 +361,7 @@
     int offset,
     String? triggerCharacter,
     CancellationToken token,
+    int maxSuggestions,
   ) async {
     var useNotImportedCompletions =
         suggestFromUnimportedLibraries && capabilities.applyEdit;
@@ -397,6 +399,7 @@
         var suggestions = await contributor.computeSuggestions(
           completionRequest,
           performance,
+          maxSuggestions: maxSuggestions,
           useFilter: true,
         );
 
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
index b834eec..c2d0f056 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/completion_manager.dart
@@ -88,6 +88,7 @@
     OperationPerformanceImpl performance, {
     bool enableOverrideContributor = true,
     bool enableUriContributor = true,
+    required int maxSuggestions,
     required bool useFilter,
   }) async {
     request.checkAborted();
@@ -108,15 +109,15 @@
 
     var notImportedSuggestions = this.notImportedSuggestions;
 
-    var collector = SuggestionCollector();
+    var collector = SuggestionCollector(maxSuggestions: maxSuggestions);
     try {
       var selection = request.unit.select(offset: request.offset, length: 0);
       if (selection == null) {
         throw AbortCompletion();
       }
-      var matcher = request.targetPrefix.isEmpty
-          ? NoPrefixMatcher()
-          : FuzzyMatcher(request.targetPrefix);
+      var targetPrefix = request.targetPrefix;
+      var matcher =
+          targetPrefix.isEmpty ? NoPrefixMatcher() : FuzzyMatcher(targetPrefix);
       var state = CompletionState(request, selection, budget, matcher);
       var operations = performance.run(
         'InScopeCompletionPass',
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_collector.dart b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_collector.dart
index 2d099be..327d486 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/suggestion_collector.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/suggestion_collector.dart
@@ -6,6 +6,9 @@
 
 /// An object that collects the candidate suggestions produced by the steps.
 class SuggestionCollector {
+  /// The maximum number of suggestions that will be returned.
+  final int maxSuggestions;
+
   /// The list of candidate suggestions that have been collected.
   final List<CandidateSuggestion> suggestions = [];
 
@@ -20,15 +23,56 @@
   /// exception being thrown.
   bool isIncomplete = false;
 
-  /// Return `true` if the context prefers a constant expression. This is used
-  /// to compute relevance.
+  /// Whether the context prefers a constant expression. This is used to compute
+  /// relevance.
   bool preferConstants = false;
 
   /// Initializes a newly created collector to collect candidate suggestions.
-  SuggestionCollector();
+  ///
+  /// The [maxSuggestions] is the maximum number of suggestions that will be
+  /// returned to the client, or `-1` if all of the suggestions should be
+  /// returned. This is used to truncate the list of suggestions early, which
+  /// - reduces the amount of memory used during completion
+  /// - reduces the number of suggestions that need to have relevance scores and
+  ///   that need to be converted to the form used by the protocol
+  SuggestionCollector({required this.maxSuggestions});
 
   /// Adds the candidate [suggestion] to the list of suggestions.
   void addSuggestion(CandidateSuggestion suggestion) {
-    suggestions.add(suggestion);
+    // Insert the suggestion into the list in sorted order.
+    if (suggestions.isEmpty) {
+      suggestions.add(suggestion);
+      return;
+    }
+    var score = suggestion.matcherScore;
+    var added = false;
+    for (var i = suggestions.length - 1; i >= 0; i--) {
+      var currentSuggestion = suggestions[i];
+      if (currentSuggestion.matcherScore >= score) {
+        suggestions.insert(i + 1, suggestion);
+        added = true;
+        break;
+      }
+    }
+    if (!added) {
+      suggestions.insert(0, suggestion);
+    }
+    // If there are suggestions whose matcher score is too low, remove them.
+    //
+    // Note that this will allow the list of suggestions to be longer than the
+    // maximum number of suggestions as long as at least one suggestion with the
+    // lowest score would be kept. Suggestions with the same score will later be
+    // sorted by the relevance score and then the lowest bucket will be
+    // truncated.
+    if (maxSuggestions >= 0 && suggestions.length > maxSuggestions) {
+      var minScoreToKeep = suggestions[maxSuggestions].matcherScore;
+      while (suggestions.length > maxSuggestions) {
+        if (suggestions.last.matcherScore < minScoreToKeep) {
+          suggestions.removeLast();
+        } else {
+          break;
+        }
+      }
+    }
   }
 }
diff --git a/pkg/analysis_server/test/stress/completion/completion_runner.dart b/pkg/analysis_server/test/stress/completion/completion_runner.dart
index 58ddd89..1adf3f3 100644
--- a/pkg/analysis_server/test/stress/completion/completion_runner.dart
+++ b/pkg/analysis_server/test/stress/completion/completion_runner.dart
@@ -106,6 +106,7 @@
           var suggestions = await contributor.computeSuggestions(
             dartRequest,
             OperationPerformanceImpl('<root>'),
+            maxSuggestions: -1,
             useFilter: true,
           );
           timer.stop();
diff --git a/pkg/analysis_server/tool/code_completion/benchmark/flutter.dart b/pkg/analysis_server/tool/code_completion/benchmark/flutter.dart
index 194066c..364a9e6 100644
--- a/pkg/analysis_server/tool/code_completion/benchmark/flutter.dart
+++ b/pkg/analysis_server/tool/code_completion/benchmark/flutter.dart
@@ -93,6 +93,7 @@
       ).computeSuggestions(
         dartRequest,
         OperationPerformanceImpl('<root>'),
+        maxSuggestions: -1,
         useFilter: false,
       );
     }
diff --git a/pkg/analysis_server/tool/code_completion/completion_metrics.dart b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
index 6a31083..5e85216 100644
--- a/pkg/analysis_server/tool/code_completion/completion_metrics.dart
+++ b/pkg/analysis_server/tool/code_completion/completion_metrics.dart
@@ -1410,6 +1410,7 @@
     ).computeSuggestions(
       dartRequest,
       performance,
+      maxSuggestions: -1,
       useFilter: true,
     );