IDEA-188654 Stream API methods autocomplete

GitOrigin-RevId: c4e2caba222324726aad91148ab37c0acf4fd86a
This commit is contained in:
Peter Gromov
2020-06-08 10:10:09 +02:00
committed by intellij-monorepo-bot
parent fb3d2a86d9
commit 847bb51000
10 changed files with 161 additions and 15 deletions

View File

@@ -49,7 +49,7 @@ class ChainedCallCompletion {
}
final ElementFilter filter = ReferenceExpressionCompletionContributor.getReferenceFilter(place, true);
for (final LookupElement item : ReferenceExpressionCompletionContributor.completeFinalReference(place, mockRef, filter, parameters)) {
for (LookupElement item : ReferenceExpressionCompletionContributor.completeFinalReference(place, mockRef, filter, expectedType, parameters.getParameters())) {
if (shouldChain(place, qualifierType, expectedType, item)) {
result.consume(new JavaChainLookupElement(qualifierItem, item) {
@Override

View File

@@ -367,6 +367,7 @@ public class JavaCompletionContributor extends CompletionContributor {
final List<ExpectedTypeInfo> expected = Arrays.asList(ExpectedTypesProvider.getExpectedTypes((PsiExpression)parent, true));
StreamConversion.addCollectConversion((PsiReferenceExpression)parent, expected,
lookupElement -> items.add(JavaSmartCompletionContributor.decorate(lookupElement, expected)));
items.addAll(StreamConversion.addToStreamConversion((PsiReferenceExpression)parent, parameters));
}
if (IMPORT_REFERENCE.accepts(position)) {

View File

@@ -41,7 +41,8 @@ public class JavaCompletionStatistician extends CompletionStatistician{
if (o instanceof PsiLocalVariable || o instanceof PsiParameter ||
o instanceof PsiThisExpression || o instanceof PsiKeyword ||
element.getUserData(JavaCompletionUtil.SUPER_METHOD_PARAMETERS) != null ||
FunctionalExpressionCompletionProvider.isFunExprItem(element)) {
FunctionalExpressionCompletionProvider.isFunExprItem(element) ||
element.as(StreamConversion.StreamMethodInvocation.class) != null) {
return StatisticsInfo.EMPTY;
}

View File

@@ -83,7 +83,7 @@ public class ReferenceExpressionCompletionContributor {
filter = new AndFilter(filter, new CheckInitialized(element));
}
for (final LookupElement item : completeFinalReference(element, reference, filter, parameters)) {
for (LookupElement item : completeFinalReference(element, reference, filter, parameters.getExpectedType(), parameters.getParameters())) {
result.consume(item);
}
@@ -109,9 +109,12 @@ public class ReferenceExpressionCompletionContributor {
return null;
}
static Set<LookupElement> completeFinalReference(final PsiElement element, PsiJavaCodeReferenceElement reference, ElementFilter filter,
final JavaSmartCompletionParameters parameters) {
final Set<PsiField> used = parameters.getParameters().getInvocationCount() < 2 ? findConstantsUsedInSwitch(element) : Collections.emptySet();
static Set<LookupElement> completeFinalReference(PsiElement element,
PsiJavaCodeReferenceElement reference,
ElementFilter filter,
PsiType expectedType,
CompletionParameters parameters) {
final Set<PsiField> used = parameters.getInvocationCount() < 2 ? findConstantsUsedInSwitch(element) : Collections.emptySet();
final Set<LookupElement> elements =
JavaSmartCompletionContributor.completeReference(element, reference, new AndFilter(filter, new ElementFilter() {
@@ -121,12 +124,10 @@ public class ReferenceExpressionCompletionContributor {
final CandidateInfo info = (CandidateInfo)o;
final PsiElement member = info.getElement();
final PsiType expectedType = parameters.getExpectedType();
if (expectedType.equals(PsiType.VOID)) {
return member instanceof PsiMethod;
}
//noinspection SuspiciousMethodCalls
if (member instanceof PsiEnumConstant && used.contains(CompletionUtil.getOriginalOrSelf(member))) {
return false;
}
@@ -140,12 +141,12 @@ public class ReferenceExpressionCompletionContributor {
public boolean isClassAcceptable(Class hintClass) {
return true;
}
}), false, true, parameters.getParameters(), PrefixMatcher.ALWAYS_TRUE);
}), false, true, parameters, PrefixMatcher.ALWAYS_TRUE);
for (LookupElement lookupElement : elements) {
if (lookupElement.getObject() instanceof PsiMethod) {
final JavaMethodCallElement item = lookupElement.as(JavaMethodCallElement.CLASS_CONDITION_KEY);
if (item != null) {
item.setInferenceSubstitutorFromExpectedType(element, parameters.getExpectedType());
item.setInferenceSubstitutorFromExpectedType(element, expectedType);
if (JavaCompletionSorting.isTooGeneric(lookupElement, item.getObject())) {
item.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
}

View File

@@ -1,15 +1,16 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.completion;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInsight.lookup.TypedLookupItem;
import com.intellij.codeInsight.lookup.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.filters.TrueFilter;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.intellij.util.Consumer;
@@ -21,13 +22,80 @@ import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collector;
import static com.intellij.codeInsight.completion.ReferenceExpressionCompletionContributor.getSpace;
import static com.intellij.psi.CommonClassNames.*;
/**
* @author peter
*/
class StreamConversion {
static List<LookupElement> addToStreamConversion(PsiReferenceExpression ref, CompletionParameters parameters) {
PsiExpression qualifier = ref.getQualifierExpression();
if (qualifier == null) return Collections.emptyList();
PsiType type = qualifier.getType();
if (type instanceof PsiClassType) {
PsiClass qualifierClass = ((PsiClassType)type).resolve();
if (qualifierClass == null) return Collections.emptyList();
PsiMethod streamMethod = ContainerUtil.find(qualifierClass.findMethodsByName("stream", true), m ->
!m.hasParameters() &&
InheritanceUtil.isInheritor(m.getReturnType(), JAVA_UTIL_STREAM_BASE_STREAM));
if (streamMethod == null) return Collections.emptyList();
return generateStreamSuggestions(parameters, qualifier, qualifier.getText() + ".stream()", context -> {
String space = getSpace(CodeStyle.getLanguageSettings(context.getFile()).SPACE_WITHIN_EMPTY_METHOD_CALL_PARENTHESES);
context.getDocument().insertString(context.getStartOffset(), "stream(" + space + ").");
});
}
else if (type instanceof PsiArrayType) {
String arraysStream = JAVA_UTIL_ARRAYS + ".stream";
return generateStreamSuggestions(parameters, qualifier, arraysStream + "(" + qualifier.getText() + ")",
context -> wrapQualifiedIntoMethodCall(context, arraysStream));
}
return Collections.emptyList();
}
@NotNull
private static List<LookupElement> generateStreamSuggestions(CompletionParameters parameters,
PsiExpression qualifier,
String changedQualifier,
Consumer<InsertionContext> beforeInsertion) {
PsiReferenceExpression asStream = (PsiReferenceExpression)PsiElementFactory.getInstance(qualifier.getProject())
.createExpressionFromText(changedQualifier + ".x", qualifier);
Set<LookupElement> streamSuggestions = ReferenceExpressionCompletionContributor
.completeFinalReference(qualifier, asStream, TrueFilter.INSTANCE,
PsiType.getJavaLangObject(qualifier.getManager(), qualifier.getResolveScope()),
parameters);
return ContainerUtil.map(streamSuggestions, e -> new StreamMethodInvocation(e, beforeInsertion));
}
private static void wrapQualifiedIntoMethodCall(@NotNull InsertionContext context, @NotNull String methodQualifiedName) {
PsiFile file = context.getFile();
PsiReferenceExpression ref =
PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiReferenceExpression.class, false);
if (ref != null) {
PsiElement qualifier = ref.getQualifier();
if (qualifier != null) {
TextRange range = qualifier.getTextRange();
int startOffset = range.getStartOffset();
String callSpace = getSpace(CodeStyle.getLanguageSettings(file).SPACE_WITHIN_METHOD_CALL_PARENTHESES);
context.getDocument().insertString(range.getEndOffset(), callSpace + ")");
context.getDocument().insertString(startOffset, methodQualifiedName + "(" + callSpace);
context.commitDocument();
Project project = context.getProject();
JavaCodeStyleManager.getInstance(project).shortenClassReferences(
file, startOffset, startOffset + methodQualifiedName.length());
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(context.getDocument());
}
}
}
static void addCollectConversion(PsiReferenceExpression ref, Collection<? extends ExpectedTypeInfo> expectedTypes, Consumer<? super LookupElement> consumer) {
PsiClass collectors = JavaPsiFacade.getInstance(ref.getProject()).findClass(JAVA_UTIL_STREAM_COLLECTORS, ref.getResolveScope());
if (collectors == null) return;
@@ -209,4 +277,25 @@ class StreamConversion {
return myExpectedType;
}
}
static class StreamMethodInvocation extends LookupElementDecorator<LookupElement> {
private final Consumer<? super InsertionContext> myBeforeInsertion;
StreamMethodInvocation(LookupElement e, Consumer<? super InsertionContext> beforeInsertion) {
super(e);
myBeforeInsertion = beforeInsertion;
}
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setItemText("stream()." + presentation.getItemText());
}
@Override
public void handleInsert(@NotNull InsertionContext context) {
myBeforeInsertion.consume(context);
super.handleInsert(context);
}
}
}

View File

@@ -0,0 +1,5 @@
class Foo {
void foo(String[] array) {
array.<caret>
}
}

View File

@@ -0,0 +1,7 @@
import java.util.Arrays;
class Foo {
void foo(String[] array) {
Arrays.stream(array).map(<caret>)
}
}

View File

@@ -0,0 +1,9 @@
class Foo {
void foo(MyList<String> list) {
list.<caret>
}
}
class MyList<T> extends java.util.List<T> {
void filter();
}

View File

@@ -0,0 +1,9 @@
class Foo {
void foo(MyList<String> list) {
list.stream().map(<caret>)
}
}
class MyList<T> extends java.util.List<T> {
void filter();
}

View File

@@ -414,4 +414,28 @@ class Test88 {
myFixture.completeBasic()
assert myFixture.lookupElementStrings == ['wait']
}
void testStreamMethodsOnCollection() {
configureByTestName()
myFixture.assertPreferredCompletionItems 0, 'filter'
assert LookupElementPresentation.renderElement(myFixture.lookupElements[0]).itemText == 'filter'
myFixture.type('ma')
myFixture.assertPreferredCompletionItems 0, 'map', 'mapToDouble'
assert LookupElementPresentation.renderElement(myFixture.lookupElements[0]).itemText == 'stream().map'
myFixture.type('\n')
checkResultByFileName()
}
void testStreamMethodsOnArray() {
configureByTestName()
myFixture.assertPreferredCompletionItems 0, 'length', 'clone'
myFixture.type('ma')
myFixture.assertPreferredCompletionItems 0, 'map', 'mapToDouble'
myFixture.type('\n')
checkResultByFileName()
}
}