IDEA-58766 Recognize dot as completion prefix part for chained call suggestions, groovy

This commit is contained in:
peter
2012-02-08 19:22:41 +01:00
parent 846ca5cf0a
commit 3bf3a8f187
9 changed files with 220 additions and 76 deletions
@@ -65,7 +65,11 @@ public class JavaChainLookupElement extends LookupElementDecorator<LookupElement
}
private String maybeAddParentheses(String s) {
return myQualifier.getObject() instanceof PsiMethod ? s + "()" : s;
Object qObject = myQualifier.getObject();
if (qObject instanceof ResolveResult) {
qObject = ((ResolveResult)qObject).getElement();
}
return qObject instanceof PsiMethod ? s + "()" : s;
}
@Override
@@ -99,7 +103,7 @@ public class JavaChainLookupElement extends LookupElementDecorator<LookupElement
CompletionUtil.emulateInsertion(getDelegate(), context.getTailOffset(), context);
}
private static boolean shouldParenthesizeQualifier(final PsiFile file, final int startOffset, final int endOffset) {
protected boolean shouldParenthesizeQualifier(final PsiFile file, final int startOffset, final int endOffset) {
PsiElement element = file.findElementAt(startOffset);
if (element == null) {
return false;
@@ -346,6 +346,9 @@ public class JavaCompletionUtil {
@Nullable
private static PsiType getPsiType(final Object o) {
if (o instanceof ResolveResult) {
return getPsiType(((ResolveResult)o).getElement());
}
if (o instanceof PsiVariable) {
return ((PsiVariable)o).getType();
}
@@ -46,6 +46,7 @@ import com.intellij.util.ProcessingContext;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyBundle;
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
@@ -64,18 +65,18 @@ import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatem
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameterList;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.CompleteReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.refactoring.DefaultGroovyVariableNameValidator;
import org.jetbrains.plugins.groovy.refactoring.GroovyNameSuggestionUtil;
import org.jetbrains.plugins.groovy.refactoring.inline.InlineMethodConflictSolver;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.PsiJavaPatterns.elementType;
import static com.intellij.util.containers.CollectionFactory.arrayList;
import static com.intellij.util.containers.CollectionFactory.hashMap;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*;
import static org.jetbrains.plugins.groovy.lang.lexer.TokenSets.SEPARATORS;
@@ -176,18 +177,20 @@ public class GroovyCompletionContributor extends CompletionContributor {
private static void addAllClasses(CompletionParameters parameters, final CompletionResultSet result, final InheritorsHolder inheritors) {
addAllClasses(parameters, result, new Consumer<LookupElement>() {
addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
result.addElement(element);
}
}, inheritors);
}, inheritors, result.getPrefixMatcher());
}
public static void addAllClasses(CompletionParameters parameters, CompletionResultSet result, final Consumer<LookupElement> consumer, final InheritorsHolder inheritors) {
public static void addAllClasses(CompletionParameters parameters,
final Consumer<LookupElement> consumer,
final InheritorsHolder inheritors, final PrefixMatcher matcher) {
final PsiElement position = parameters.getPosition();
final ElementFilter filter = getClassFilter(position);
AllClassesGetter.processJavaClasses(parameters, result.getPrefixMatcher(), parameters.getInvocationCount() <= 1,
AllClassesGetter.processJavaClasses(parameters, matcher, parameters.getInvocationCount() <= 1,
new Consumer<PsiClass>() {
@Override
public void consume(PsiClass psiClass) {
@@ -324,12 +327,7 @@ public class GroovyCompletionContributor extends CompletionContributor {
GroovySmartCompletionContributor.generateInheritorVariants(parameters, result.getPrefixMatcher(), inheritors);
}
completeReference(parameters, reference, inheritors, result.getPrefixMatcher(), new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
result.addElement(element);
}
});
result.addAllElements(completeReference(parameters, reference, inheritors, result.getPrefixMatcher()));
if (reference.getQualifier() == null) {
GroovySmartCompletionContributor.addExpectedClassMembers(parameters, result);
@@ -424,16 +422,30 @@ public class GroovyCompletionContributor extends CompletionContributor {
super.fillCompletionVariants(parameters, result);
}
private static void completeReference(final CompletionParameters parameters,
GrReferenceElement reference, final InheritorsHolder inheritorsHolder, final PrefixMatcher matcher, final Consumer<LookupElement> result) {
static LinkedHashSet<LookupElement> completeReference(final CompletionParameters parameters,
GrReferenceElement reference, final InheritorsHolder inheritorsHolder, final PrefixMatcher matcher) {
final PsiElement position = parameters.getPosition();
final Map<PsiModifierListOwner, LookupElement> staticMembers = hashMap();
final PsiElement qualifier = reference.getQualifier();
final PsiType qualifierType = qualifier instanceof GrExpression ? ((GrExpression)qualifier).getType() : null;
LinkedHashSet<LookupElement> result = new LinkedHashSet<LookupElement>();
if (reference instanceof GrReferenceExpression && (qualifier instanceof GrExpression || qualifier == null)) {
for (String string : CompleteReferenceExpression.getVariantsWithSameQualifier(matcher, (GrExpression)qualifier, (GrReferenceExpression)reference)) {
result.add(GroovyCompletionUtil.getLookupElement(string));
}
if (parameters.getInvocationCount() < 2 && qualifier != null && qualifierType == null) {
if (parameters.getInvocationCount() == 1) {
showInfo();
}
return result;
}
}
final ElementFilter classFilter = getClassFilter(position);
final List<LookupElement> items = arrayList();
reference.processVariants(matcher, parameters, new Consumer<Object>() {
public void consume(Object element) {
if (element instanceof PsiClass && inheritorsHolder.alreadyProcessed((PsiClass)element)) {
@@ -448,22 +460,20 @@ public class GroovyCompletionContributor extends CompletionContributor {
}
LookupElement lookupElement = element instanceof PsiClass
? GroovyCompletionUtil.createClassLookupItem(CompletionUtil.getOriginalOrSelf((PsiClass)element))
: GroovyCompletionUtil.getLookupElement(element);
? GroovyCompletionUtil.createClassLookupItem(CompletionUtil.getOriginalOrSelf((PsiClass)element))
: GroovyCompletionUtil.getLookupElement(element);
Object object = lookupElement.getObject();
PsiSubstitutor substitutor = null;
GroovyResolveResult resolveResult = null;
if (object instanceof GroovyResolveResult) {
resolveResult = (GroovyResolveResult)object;
substitutor = resolveResult.getSubstitutor();
object = ((GroovyResolveResult)object).getElement();
}
if (object instanceof PsiMember && JavaCompletionUtil.isInExcludedPackage((PsiMember)object, true)) {
return;
}
lookupElement = tweakPriority(lookupElement, object, substitutor, resolveResult, qualifierType);
int priority = assignPriority(lookupElement, qualifierType);
lookupElement = JavaCompletionUtil.highlightIfNeeded(qualifierType,
PrioritizedLookupElement.withPriority(lookupElement, priority), object);
if ((object instanceof PsiMethod || object instanceof PsiField) &&
((PsiModifierListOwner)object).hasModifierProperty(PsiModifier.STATIC)) {
@@ -475,7 +485,8 @@ public class GroovyCompletionContributor extends CompletionContributor {
if (object instanceof PsiClass && !classFilter.isAcceptable(object, position)) {
return;
}
result.consume(JavaCompletionUtil.highlightIfNeeded(qualifierType, lookupElement, object));
items.add(lookupElement);
}
});
@@ -497,8 +508,25 @@ public class GroovyCompletionContributor extends CompletionContributor {
});
}
for (LookupElement element : staticMembers.values()) {
result.consume(element);
items.addAll(staticMembers.values());
for (Iterator<LookupElement> iterator = items.iterator(); iterator.hasNext(); ) {
LookupElement element = iterator.next();
PrioritizedLookupElement prio = element.as(PrioritizedLookupElement.CLASS_CONDITION_KEY);
if (prio == null || prio.getPriority() >= 0) {
result.add(element);
iterator.remove();
}
}
result.addAll(items);
return result;
}
private static void showInfo() {
if (StringUtil.isEmpty(CompletionService.getCompletionService().getAdvertisementText())) {
CompletionService.getCompletionService()
.setAdvertisementText(GroovyBundle.message("invoke.completion.second.time.to.show.skipped.methods"));
}
}
@@ -513,33 +541,38 @@ public class GroovyCompletionContributor extends CompletionContributor {
}
private static LookupElement tweakPriority(LookupElement lookupElement,
Object object,
PsiSubstitutor substitutor,
GroovyResolveResult resolveResult,
PsiType qualifierType) {
private static int assignPriority(LookupElement lookupElement, PsiType qualifierType) {
Object object = lookupElement.getObject();
PsiSubstitutor substitutor = null;
GroovyResolveResult resolveResult = null;
if (object instanceof GroovyResolveResult) {
resolveResult = (GroovyResolveResult)object;
substitutor = resolveResult.getSubstitutor();
object = ((GroovyResolveResult)object).getElement();
}
// default groovy methods
if (object instanceof GrGdkMethod &&
GroovyCompletionUtil.skipDefGroovyMethod((GrGdkMethod)object, substitutor, qualifierType)) {
return PrioritizedLookupElement.withPriority(lookupElement, -1);
return -1;
}
// operator methods
if (object instanceof PsiMethod &&
GroovyCompletionUtil.OPERATOR_METHOD_NAMES.contains(((PsiMethod)object).getName()) && !checkForIterator((PsiMethod)object)) {
return PrioritizedLookupElement.withPriority(lookupElement, -3);
return -3;
}
// accessors if there is no get, set, is prefix
if (object instanceof PsiMethod && GroovyPropertyUtils.isSimplePropertyAccessor((PsiMethod)object)) {
return PrioritizedLookupElement.withPriority(lookupElement, -1);
return -1;
}
// inaccessible elements
if (resolveResult != null && !resolveResult.isAccessible()) {
return PrioritizedLookupElement.withPriority(lookupElement, -2);
return -2;
}
return lookupElement;
return 0;
}
static StaticMemberProcessor completeStaticMembers(CompletionParameters parameters) {
@@ -16,9 +16,21 @@
package org.jetbrains.plugins.groovy.lang.completion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiType;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* @author peter
@@ -27,27 +39,114 @@ public class GroovyNoVariantsDelegator extends NoVariantsDelegator {
@Override
protected void delegate(CompletionParameters parameters, CompletionResultSet result, Consumer<CompletionResult> passResult) {
if (parameters.getCompletionType() == CompletionType.BASIC &&
parameters.getInvocationCount() <= 1 &&
JavaCompletionContributor.mayStartClassName(result, false) &&
GroovyCompletionContributor.isClassNamePossible(parameters.getPosition()) &&
!MapArgumentCompletionProvider.isMapKeyCompletion(parameters)) {
final ClassByNameMerger merger = new ClassByNameMerger(parameters.getInvocationCount() == 0, result);
GroovyCompletionContributor.addAllClasses(parameters, result,
new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
JavaPsiClassReferenceElement classElement = element.as(JavaPsiClassReferenceElement.CLASS_CONDITION_KEY);
if (classElement != null) {
classElement.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
}
merger.consume(classElement);
}
}, new InheritorsHolder(parameters.getPosition(), result));
merger.finishedClassProcessing();
if (parameters.getCompletionType() == CompletionType.BASIC) {
if (parameters.getInvocationCount() <= 1 &&
JavaCompletionContributor.mayStartClassName(result, false) &&
GroovyCompletionContributor.isClassNamePossible(parameters.getPosition()) &&
!MapArgumentCompletionProvider.isMapKeyCompletion(parameters)) {
suggestNonImportedClasses(parameters, result);
}
suggestChainedCalls(parameters, result);
}
}
private static void suggestNonImportedClasses(CompletionParameters parameters, CompletionResultSet result) {
final ClassByNameMerger merger = new ClassByNameMerger(parameters.getInvocationCount() == 0, result);
GroovyCompletionContributor.addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
JavaPsiClassReferenceElement classElement =
element.as(JavaPsiClassReferenceElement.CLASS_CONDITION_KEY);
if (classElement != null) {
classElement.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
}
merger.consume(classElement);
}
}, new InheritorsHolder(parameters.getPosition(), result), result.getPrefixMatcher());
merger.finishedClassProcessing();
}
private static void suggestChainedCalls(CompletionParameters parameters, CompletionResultSet result) {
PsiElement position = parameters.getPosition();
PsiElement parent = position.getParent();
if (!(parent instanceof GrReferenceElement)) {
return;
}
PsiElement qualifier = ((GrReferenceElement)parent).getQualifier();
if (!(qualifier instanceof GrReferenceElement) ||
((GrReferenceElement)qualifier).getQualifier() != null ||
((GrReferenceElement)qualifier).resolve() != null) {
return;
}
String fullPrefix = position.getContainingFile().getText().substring(parent.getTextRange().getStartOffset(), parameters.getOffset());
final CompletionResultSet qualifiedCollector = result.withPrefixMatcher(fullPrefix);
InheritorsHolder inheritors = new InheritorsHolder(position, result);
for (final LookupElement base : suggestQualifierItems(parameters, (GrReferenceElement)qualifier, inheritors)) {
final PsiType type = JavaCompletionUtil.getLookupElementType(base);
if (type != null && !PsiType.VOID.equals(type)) {
GrReferenceElement ref = createMockReference(position, type, base);
PsiElement refName = ref.getReferenceNameElement();
assert refName != null;
for (LookupElement element : GroovyCompletionContributor.completeReference(
parameters.withPosition(refName, refName.getTextRange().getStartOffset()), ref, inheritors, qualifiedCollector.getPrefixMatcher())) {
qualifiedCollector.addElement(new JavaChainLookupElement(base, element) {
@Override
protected boolean shouldParenthesizeQualifier(PsiFile file, int startOffset, int endOffset) {
return false;
}
});
}
}
}
}
private static GrReferenceElement createMockReference(final PsiElement place, @NotNull PsiType qualifierType, LookupElement qualifierItem) {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(place.getProject());
if (qualifierItem.getObject() instanceof PsiClass) {
return factory
.createReferenceExpressionFromText(((PsiClass)qualifierItem.getObject()).getQualifiedName() + ".xxx", place);
}
return factory.createReferenceExpressionFromText("xxx.xxx",
ReferenceExpressionCompletionContributor
.createContextWithXxxVariable(place, qualifierType));
}
private static Set<LookupElement> suggestQualifierItems(CompletionParameters _parameters,
GrReferenceElement qualifier,
InheritorsHolder inheritors) {
CompletionParameters parameters =
_parameters.withPosition(qualifier.getReferenceNameElement(), qualifier.getTextRange().getEndOffset());
String referenceName = qualifier.getReferenceName();
if (referenceName == null) {
return Collections.emptySet();
}
final PrefixMatcher qMatcher = new CamelHumpMatcher(referenceName);
final Set<LookupElement> variants = new LinkedHashSet<LookupElement>();
for (LookupElement element : GroovyCompletionContributor.completeReference(parameters, qualifier, inheritors, qMatcher)) {
if (qMatcher.prefixMatches(element)) {
variants.add(element);
}
}
if (variants.isEmpty()) {
GroovyCompletionContributor.addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
if (qMatcher.prefixMatches(element)) {
variants.add(element);
}
}
}, inheritors, qMatcher);
}
return variants;
}
}
@@ -25,7 +25,6 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NotNull;
@@ -48,7 +47,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrRe
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GrClassImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ClosureMissingMethodContributor;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
@@ -88,7 +86,6 @@ public class CompleteReferenceExpression {
private static void getVariantsImpl(PrefixMatcher matcher, GrReferenceExpression refExpr, CompleteReferenceProcessor processor) {
GrExpression qualifier = refExpr.getQualifierExpression();
getVariantsWithSameQualifier(matcher, qualifier, refExpr, processor);
if (qualifier == null) {
ResolveUtil.treeWalkUp(refExpr, processor, true);
@@ -228,7 +225,8 @@ public class CompleteReferenceExpression {
return;
}
}
getVariantsFromQualifierType(refExpr, processor, GrClassImplUtil.getGroovyObjectType(refExpr), project);
getVariantsFromQualifierType(refExpr, processor,
PsiType.getJavaLangObject(refExpr.getManager(), qualifier.getResolveScope()), project);
}
else if (qualifierType instanceof PsiIntersectionType) {
for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
@@ -256,20 +254,19 @@ public class CompleteReferenceExpression {
}
}
private static String[] getVariantsWithSameQualifier(PrefixMatcher matcher,GrExpression qualifier, GrReferenceExpression refExpr, CompleteReferenceProcessor processor) {
if (qualifier != null && qualifier.getType() != null) return ArrayUtil.EMPTY_STRING_ARRAY;
public static Set<String> getVariantsWithSameQualifier(PrefixMatcher matcher, @Nullable GrExpression qualifier, GrReferenceExpression refExpr) {
if (qualifier != null && qualifier.getType() != null) return Collections.emptySet();
final PsiElement scope = PsiTreeUtil.getParentOfType(refExpr, GrMember.class, PsiFile.class);
Set<String> result = new LinkedHashSet<String>();
addVariantsWithSameQualifier(matcher, scope, refExpr, qualifier, result, processor);
return ArrayUtil.toStringArray(result);
addVariantsWithSameQualifier(matcher, scope, refExpr, qualifier, result);
return result;
}
private static void addVariantsWithSameQualifier(PrefixMatcher matcher, PsiElement element,
GrReferenceExpression patternExpression,
GrExpression patternQualifier,
Set<String> result,
CompleteReferenceProcessor processor) {
Set<String> result) {
if (element instanceof GrReferenceExpression && element != patternExpression && !PsiUtil.isLValue((GroovyPsiElement)element)) {
final GrReferenceExpression refExpr = (GrReferenceExpression)element;
final String refName = refExpr.getReferenceName();
@@ -279,21 +276,19 @@ public class CompleteReferenceExpression {
if (PsiEquivalenceUtil.areElementsEquivalent(hisQualifier, patternQualifier)) {
if (refExpr.resolve() == null) {
result.add(refName);
processor.consume(refName);
}
}
}
else if (hisQualifier == null && patternQualifier == null) {
if (refExpr.resolve() == null) {
result.add(refName);
processor.consume(refName);
}
}
}
}
for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
addVariantsWithSameQualifier(matcher, child, patternExpression, patternQualifier, result, processor);
addVariantsWithSameQualifier(matcher, child, patternExpression, patternQualifier, result);
}
}
@@ -56,7 +56,7 @@ class GroovyAutoPopupTest extends CompletionAutoPopupTestCase {
public void testNoLookupFocusOnUnresolvedQualifier() {
myFixture.configureByText("a.groovy", """xxx.<caret>""")
type 'h' //hashCode
assert !lookup.focused
assert !lookup
}
public void testNoLookupFocusOnUntypedQualifier() {
@@ -65,7 +65,7 @@ class GroovyAutoPopupTest extends CompletionAutoPopupTestCase {
xxx.<caret>
}""")
type 'h'
assert !lookup.focused
assert !lookup
}
public void testPossibleClosureParameter() {
@@ -1108,6 +1108,18 @@ public class KeyVO {
checkSingleItemCompletion 'class Foo impl<caret> {}', 'class Foo implements <caret> {}'
}
public void testAmbiguousClassQualifier() {
myFixture.addClass("package foo; public class Util { public static void foo() {} }")
myFixture.addClass("package bar; public class Util { public static void bar() {} }")
myFixture.configureByText 'a.groovy', 'Util.<caret>'
myFixture.completeBasic()
assert myFixture.lookupElementStrings[0..1] == ['Util.bar', 'Util.foo']
myFixture.type 'f\n'
myFixture.checkResult '''import foo.Util
Util.foo()<caret>'''
}
public void testPreferInterfacesInImplements() {
myFixture.addClass('interface FooIntf {}')
myFixture.addClass('class FooClass {}')
@@ -1,4 +1,3 @@
def (street, home, postCode)
def s = street.toS<caret>
-----
toString
-----
@@ -1,3 +1,2 @@
unknown.metaCl<caret>
-----
metaClass
-----