Filter LSP completion requests when offset is mid-symbol

Change-Id: I28bf5ddda1d7d6f1177a602d9f2dd3091f35966d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151847
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Danny Tuppeny <danny@tuppeny.com>
This commit is contained in:
Danny Tuppeny 2020-06-23 11:38:20 +00:00 committed by commit-bot@chromium.org
parent f0c87e9f21
commit 2d73cb53a1
2 changed files with 64 additions and 12 deletions

View file

@ -362,26 +362,38 @@ class CompletionHandler
}
/// Return the pattern to match suggestions against, from the identifier
/// to the left of the caret. Return the empty string if cannot find the
/// identifier.
/// to the left of (or spanning) the caret. Return the empty string if cannot
/// find the identifier.
///
/// If the caret is within the identifier, the returned pattern will be truncated
/// to the position of the caret. For example at:
///
/// new MyClass^Foo
///
/// will return "MyClass" as the search pattern.
String _prefixMatchingPattern(DartCompletionRequestImpl request) {
final nodeAtOffsetVisitor =
_IdentifierEndingAtOffsetVisitor(request.offset);
request.target.containingNode.accept(nodeAtOffsetVisitor);
final nodeSpanningOffsetVisitor =
_IdentifierSpanningOffsetVisitor(request.offset);
request.target.containingNode.accept(nodeSpanningOffsetVisitor);
final node = nodeSpanningOffsetVisitor.matchingNode;
return nodeAtOffsetVisitor.matchingNode?.name ?? '';
final prefix = node != null && request.offset - node.offset < node.length
? node.name.substring(0, request.offset - node.offset)
: node?.name;
return prefix ?? '';
}
}
/// An AST visitor to locate a [SimpleIdentifier] that ends at the provided offset.
class _IdentifierEndingAtOffsetVisitor extends RecursiveAstVisitor<void> {
/// An AST visitor to locate a [SimpleIdentifier] that spans the provided offset.
class _IdentifierSpanningOffsetVisitor extends RecursiveAstVisitor<void> {
final int offset;
SimpleIdentifier _matchingNode;
_IdentifierEndingAtOffsetVisitor(this.offset);
_IdentifierSpanningOffsetVisitor(this.offset);
SimpleIdentifier get matchingNode => _matchingNode;
@override
void visitSimpleIdentifier(SimpleIdentifier node) {
if (node.end == offset) {
if (node.offset <= offset && node.end >= offset) {
_matchingNode = node;
}
}

View file

@ -417,7 +417,7 @@ class CompletionTest extends AbstractLspAnalysisServerTest {
expect(updated, contains('a.abcdefghij'));
}
Future<void> test_prefixFilter() async {
Future<void> test_prefixFilter_endOfSymbol() async {
final content = '''
class UniqueNamedClassForLspOne {}
class UniqueNamedClassForLspTwo {}
@ -425,7 +425,7 @@ class CompletionTest extends AbstractLspAnalysisServerTest {
main() {
// Should match only Two and Three
class UniqueNamedClassForLspT^
UniqueNamedClassForLspT^
}
''';
@ -437,6 +437,46 @@ class CompletionTest extends AbstractLspAnalysisServerTest {
expect(res.any((c) => c.label == 'UniqueNamedClassForLspThree'), isTrue);
}
Future<void> test_prefixFilter_midSymbol() async {
final content = '''
class UniqueNamedClassForLspOne {}
class UniqueNamedClassForLspTwo {}
class UniqueNamedClassForLspThree {}
main() {
// Should match only Two and Three
UniqueNamedClassForLspT^hree
}
''';
await initialize();
await openFile(mainFileUri, withoutMarkers(content));
final res = await getCompletion(mainFileUri, positionFromMarker(content));
expect(res.any((c) => c.label == 'UniqueNamedClassForLspOne'), isFalse);
expect(res.any((c) => c.label == 'UniqueNamedClassForLspTwo'), isTrue);
expect(res.any((c) => c.label == 'UniqueNamedClassForLspThree'), isTrue);
}
Future<void> test_prefixFilter_startOfSymbol() async {
final content = '''
class UniqueNamedClassForLspOne {}
class UniqueNamedClassForLspTwo {}
class UniqueNamedClassForLspThree {}
main() {
// Should match all three
^UniqueNamedClassForLspT
}
''';
await initialize();
await openFile(mainFileUri, withoutMarkers(content));
final res = await getCompletion(mainFileUri, positionFromMarker(content));
expect(res.any((c) => c.label == 'UniqueNamedClassForLspOne'), isTrue);
expect(res.any((c) => c.label == 'UniqueNamedClassForLspTwo'), isTrue);
expect(res.any((c) => c.label == 'UniqueNamedClassForLspThree'), isTrue);
}
Future<void> test_suggestionSets() async {
newFile(
join(projectFolderPath, 'other_file.dart'),