[analysis_server] Apply fuzzy prefix matching to LSP YAML completions

Change-Id: I4b6a45d2601c10ed08c3bdcf4e5c007d5063dbff
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/243328
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
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 97c569e..ac13d91 100644
--- a/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
+++ b/pkg/analysis_server/lib/src/lsp/handlers/handler_completion.dart
@@ -597,7 +597,15 @@
     final insertionRange =
         toRange(lineInfo, suggestions.replacementOffset, insertLength);
 
+    // Perform fuzzy matching based on the identifier in front of the caret to
+    // reduce the size of the payload.
+    final fuzzyPattern = suggestions.targetPrefix;
+    final fuzzyMatcher =
+        FuzzyMatcher(fuzzyPattern, matchStyle: MatchStyle.TEXT);
+
     final completionItems = suggestions.suggestions
+        .where((item) =>
+            fuzzyMatcher.score(item.displayText ?? item.completion) > 0)
         .map(
           (item) => toCompletionItem(
             capabilities,
diff --git a/pkg/analysis_server/lib/src/services/completion/yaml/yaml_completion_generator.dart b/pkg/analysis_server/lib/src/services/completion/yaml/yaml_completion_generator.dart
index 53c3de6..ec3e06f 100644
--- a/pkg/analysis_server/lib/src/services/completion/yaml/yaml_completion_generator.dart
+++ b/pkg/analysis_server/lib/src/services/completion/yaml/yaml_completion_generator.dart
@@ -84,17 +84,23 @@
       }
     }
     final node = nodePath.isNotEmpty ? nodePath.last : null;
+    String targetPrefix;
     int replacementOffset;
     int replacementLength;
     if (node is YamlScalar && node.containsOffset(offset)) {
+      targetPrefix = node.span.text.substring(
+        0,
+        offset - node.span.start.offset,
+      );
       replacementOffset = node.span.start.offset;
       replacementLength = node.span.length;
     } else {
+      targetPrefix = '';
       replacementOffset = offset;
       replacementLength = 0;
     }
     return YamlCompletionResults(
-        suggestions, replacementOffset, replacementLength);
+        suggestions, targetPrefix, replacementOffset, replacementLength);
   }
 
   /// Return the result of parsing the file [content] into a YAML node.
@@ -196,14 +202,16 @@
 
 class YamlCompletionResults {
   final List<CompletionSuggestion> suggestions;
+  final String targetPrefix;
   final int replacementOffset;
   final int replacementLength;
 
-  const YamlCompletionResults(
-      this.suggestions, this.replacementOffset, this.replacementLength);
+  const YamlCompletionResults(this.suggestions, this.targetPrefix,
+      this.replacementOffset, this.replacementLength);
 
   const YamlCompletionResults.empty()
       : suggestions = const [],
+        targetPrefix = '',
         replacementOffset = 0,
         replacementLength = 0;
 }
diff --git a/pkg/analysis_server/test/lsp/completion_yaml_test.dart b/pkg/analysis_server/test/lsp/completion_yaml_test.dart
index cee886f..9d8ff8a 100644
--- a/pkg/analysis_server/test/lsp/completion_yaml_test.dart
+++ b/pkg/analysis_server/test/lsp/completion_yaml_test.dart
@@ -295,7 +295,7 @@
     await verifyCompletions(
       pubspecFileUri,
       content,
-      expectCompletions: ['flutter: ', 'sdk: '],
+      expectCompletions: ['sdk: '],
       applyEditsFor: 'sdk: ',
       expectedContent: expected,
     );
@@ -576,6 +576,32 @@
     expect(completionResults, isEmpty);
   }
 
+  Future<void> test_prefixFilter() async {
+    httpClient.sendHandler = (BaseRequest request) async {
+      if (request.url.toString().endsWith(PubApi.packageNameListPath)) {
+        return Response(samplePackageList, 200);
+      } else {
+        throw UnimplementedError();
+      }
+    };
+
+    final content = '''
+name: foo
+version: 1.0.0
+
+dependencies:
+  on^''';
+
+    await initialize();
+    await openFile(pubspecFileUri, content);
+    await pumpEventQueue();
+
+    completionResults =
+        await getCompletion(pubspecFileUri, positionFromMarker(content));
+    expect(completionResults.length, equals(1));
+    expect(completionResults.single.label, equals('one: '));
+  }
+
   Future<void> test_topLevel() async {
     final content = '''
 version: 1.0.0
@@ -602,7 +628,7 @@
     await verifyCompletions(
       pubspecFileUri,
       content,
-      expectCompletions: ['name: ', 'description: '],
+      expectCompletions: ['name: '],
       applyEditsFor: 'name: ',
       expectedContent: expected,
     );