blob: b30d8f9977468dd5fc5be0c7c2c4a0448ab7a62a [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.lang.completion;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.Consumer;
import com.intellij.util.PairConsumer;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
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;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrCatchClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameterList;
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;
/**
* Created by Max Medvedev on 14/05/14
*/
public class GrMainCompletionProvider extends CompletionProvider<CompletionParameters> {
public static final ElementPattern<PsiElement> AFTER_AT = PlatformPatterns.psiElement().afterLeaf("@");
public static final ElementPattern<PsiElement> IN_CATCH_TYPE = PlatformPatterns
.psiElement().afterLeaf(PlatformPatterns.psiElement().withText("(").withParent(GrCatchClause.class));
private static void addUnfinishedMethodTypeParameters(@NotNull PsiElement position, @NotNull CompletionResultSet result) {
final GrTypeParameterList candidate = findTypeParameterListCandidate(position);
if (candidate != null) {
for (GrTypeParameter p : candidate.getTypeParameters()) {
result.addElement(new JavaPsiClassReferenceElement(p));
}
}
}
private static void suggestVariableNames(PsiElement context, CompletionResultSet result) {
final PsiElement parent = context.getParent();
if (GroovyCompletionUtil.isWildcardCompletion(context)) return;
if (parent instanceof GrVariable) {
final GrVariable variable = (GrVariable) parent;
if (context.equals(variable.getNameIdentifierGroovy())) {
final PsiType type = variable.getTypeGroovy();
if (type != null) {
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(context.getProject());
VariableKind kind = variable instanceof GrParameter ? VariableKind.PARAMETER :
variable instanceof GrField ? VariableKind.FIELD : VariableKind.LOCAL_VARIABLE;
SuggestedNameInfo suggestedNameInfo = codeStyleManager.suggestVariableName(kind, null, null, type);
String[] names = suggestedNameInfo.names;
if (names.length > 0) {
String name = names[0];
String newName = InlineMethodConflictSolver.suggestNewName(name, null, parent);
if (!name.equals(newName)) {
result.addElement(LookupElementBuilder.create(newName));
return;
}
}
for (String name : names) {
result.addElement(LookupElementBuilder.create(name));
}
}
GrExpression initializer = variable.getInitializerGroovy();
if (initializer != null) {
for (String name : GroovyNameSuggestionUtil.suggestVariableNames(initializer, new DefaultGroovyVariableNameValidator(variable),
variable.hasModifierProperty(PsiModifier.STATIC))) {
result.addElement(LookupElementBuilder.create(name));
}
}
}
}
}
@Nullable
private static GrReferenceElement findGroovyReference(@NotNull PsiElement position) {
final PsiElement parent = position.getParent();
if (parent instanceof GrReferenceElement) {
return (GrReferenceElement)parent;
}
if (couldContainReference(position)) {
return GroovyPsiElementFactory.getInstance(position.getProject()).createReferenceElementFromText("Foo", position);
}
return null;
}
private static boolean couldContainReference(PsiElement position) {
return IN_CATCH_TYPE.accepts(position) ||
AFTER_AT.accepts(position) ||
GroovyCompletionUtil.isFirstElementAfterPossibleModifiersInVariableDeclaration(position, true) ||
GroovyCompletionUtil.isTupleVarNameWithoutTypeDeclared(position);
}
@Nullable
private static GrTypeParameterList findTypeParameterListCandidate(@NotNull PsiElement position) {
final PsiElement parent = position.getParent();
if (parent instanceof GrVariable) {
final PsiElement pparent = parent.getParent();
if (pparent instanceof GrVariableDeclaration) {
final PsiElement errorElement = PsiUtil.skipWhitespacesAndComments(parent.getPrevSibling(), false);
if (errorElement instanceof PsiErrorElement) {
final PsiElement child = errorElement.getFirstChild();
if (child instanceof GrTypeParameterList) {
return (GrTypeParameterList)child;
}
}
}
}
return null;
}
public static boolean isClassNamePossible(PsiElement position) {
PsiElement parent = position.getParent();
if (parent instanceof GrReferenceElement) {
return ((GrReferenceElement)parent).getQualifier() == null;
}
return couldContainReference(position);
}
private static void addAllClasses(CompletionParameters parameters, final CompletionResultSet result, final InheritorsHolder inheritors) {
addAllClasses(parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
result.addElement(element);
}
}, inheritors, result.getPrefixMatcher());
}
public static void addAllClasses(CompletionParameters parameters,
final Consumer<LookupElement> consumer,
final InheritorsHolder inheritors, final PrefixMatcher matcher) {
final PsiElement position = parameters.getPosition();
final boolean afterNew = JavaClassNameCompletionContributor.AFTER_NEW.accepts(position);
AllClassesGetter.processJavaClasses(parameters, matcher, parameters.getInvocationCount() <= 1, new Consumer<PsiClass>() {
@Override
public void consume(PsiClass psiClass) {
for (JavaPsiClassReferenceElement element : JavaClassNameCompletionContributor
.createClassLookupItems(psiClass, afterNew, new GroovyClassNameInsertHandler(), new Condition<PsiClass>() {
@Override
public boolean value(PsiClass psiClass) {
return !inheritors.alreadyProcessed(psiClass);
}
})) {
consumer.consume(element);
}
}
});
}
@NotNull
static Runnable completeReference(final CompletionParameters parameters,
final GrReferenceElement reference,
final InheritorsHolder inheritorsHolder,
final PrefixMatcher matcher,
final Consumer<LookupElement> _consumer) {
final Consumer<LookupElement> consumer = new Consumer<LookupElement>() {
final Set<LookupElement> added = ContainerUtil.newHashSet();
@Override
public void consume(LookupElement element) {
if (added.add(element)) {
_consumer.consume(element);
}
}
};
final Map<PsiModifierListOwner, LookupElement> staticMembers = ContainerUtil.newHashMap();
final PsiElement qualifier = reference.getQualifier();
final PsiType qualifierType = qualifier instanceof GrExpression ? ((GrExpression)qualifier).getType() : null;
if (reference instanceof GrReferenceExpression && (qualifier instanceof GrExpression || qualifier == null)) {
for (String string : CompleteReferencesWithSameQualifier.getVariantsWithSameQualifier((GrReferenceExpression)reference, matcher, (GrExpression)qualifier)) {
consumer.consume(LookupElementBuilder.create(string).withItemTextUnderlined(true));
}
if (parameters.getInvocationCount() < 2 && qualifier != null && qualifierType == null &&
!(qualifier instanceof GrReferenceExpression && ((GrReferenceExpression)qualifier).resolve() instanceof PsiPackage)) {
if (parameters.getInvocationCount() == 1) {
showInfo();
}
return EmptyRunnable.INSTANCE;
}
}
final List<LookupElement> zeroPriority = ContainerUtil.newArrayList();
GroovyCompletionUtil.processVariants(reference, matcher, parameters, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement lookupElement) {
Object object = lookupElement.getObject();
if (object instanceof GroovyResolveResult) {
object = ((GroovyResolveResult)object).getElement();
}
if (isLightElementDeclaredDuringCompletion(object)) {
return;
}
if (!(lookupElement instanceof LookupElementBuilder) && inheritorsHolder.alreadyProcessed(lookupElement)) {
return;
}
if (object instanceof PsiMember && JavaCompletionUtil.isInExcludedPackage((PsiMember)object, true)) {
return;
}
if (!(object instanceof PsiClass)) {
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)) {
if (lookupElement.getLookupString().equals(((PsiMember)object).getName())) {
staticMembers.put(CompletionUtil.getOriginalOrSelf((PsiModifierListOwner)object), lookupElement);
}
}
PrioritizedLookupElement prio = lookupElement.as(PrioritizedLookupElement.CLASS_CONDITION_KEY);
if (prio == null || prio.getPriority() == 0) {
zeroPriority.add(lookupElement);
} else {
consumer.consume(lookupElement);
}
}
});
for (LookupElement element : zeroPriority) {
consumer.consume(element);
}
if (qualifier == null) {
return addStaticMembers(parameters, matcher, staticMembers, consumer);
}
return EmptyRunnable.INSTANCE;
}
private static boolean isLightElementDeclaredDuringCompletion(Object object) {
if (!(object instanceof LightElement && object instanceof PsiNamedElement)) return false;
final String name = ((PsiNamedElement)object).getName();
if (name == null) return false;
return name.contains(CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED.trim()) ||
name.contains(GrDummyIdentifierProvider.DUMMY_IDENTIFIER_DECAPITALIZED.trim());
}
private static Runnable addStaticMembers(CompletionParameters parameters,
final PrefixMatcher matcher,
final Map<PsiModifierListOwner, LookupElement> staticMembers, final Consumer<LookupElement> consumer) {
final StaticMemberProcessor processor = completeStaticMembers(parameters);
processor.processMembersOfRegisteredClasses(matcher, new PairConsumer<PsiMember, PsiClass>() {
@Override
public void consume(PsiMember member, PsiClass psiClass) {
if (member instanceof GrAccessorMethod) {
member = ((GrAccessorMethod)member).getProperty();
}
member = CompletionUtil.getOriginalOrSelf(member);
if (staticMembers.containsKey(member)) {
return;
}
final String name = member.getName();
if (name == null || !matcher.prefixMatches(name)) {
staticMembers.remove(member);
return;
}
JavaGlobalMemberLookupElement element = createGlobalMemberElement(member, psiClass, true);
staticMembers.put(member, element);
consumer.consume(element);
}
});
if (parameters.getInvocationCount() >= 2 && StringUtil.isNotEmpty(matcher.getPrefix())) {
return new Runnable() {
@Override
public void run() {
processor.processStaticMethodsGlobally(matcher, new Consumer<LookupElement>() {
@Override
public void consume(LookupElement element) {
PsiMember member = (PsiMember)element.getObject();
if (member instanceof GrAccessorMethod) {
member = ((GrAccessorMethod)member).getProperty();
}
member = CompletionUtil.getOriginalOrSelf(member);
if (staticMembers.containsKey(member)) {
return;
}
staticMembers.put(member, element);
consumer.consume(element);
}
});
}
};
}
return EmptyRunnable.INSTANCE;
}
private static void showInfo() {
CompletionService.getCompletionService()
.setAdvertisementText(GroovyBundle.message("invoke.completion.second.time.to.show.skipped.methods"));
}
private static boolean checkForIterator(PsiMethod method) {
if (!"next".equals(method.getName())) return false;
final PsiClass containingClass = method.getContainingClass();
if (containingClass == null) return false;
final PsiClass iterator = JavaPsiFacade.getInstance(method.getProject()).findClass(CommonClassNames.JAVA_UTIL_ITERATOR,
method.getResolveScope());
return InheritanceUtil.isInheritorOrSelf(containingClass, iterator, true);
}
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 -1;
}
// operator methods
if (object instanceof PsiMethod &&
PsiUtil.OPERATOR_METHOD_NAMES.contains(((PsiMethod)object).getName()) && !checkForIterator((PsiMethod)object)) {
return -3;
}
// accessors if there is no get, set, is prefix
if (object instanceof PsiMethod && GroovyPropertyUtils.isSimplePropertyAccessor((PsiMethod)object)) {
return -1;
}
// inaccessible elements
if (resolveResult != null && !resolveResult.isAccessible()) {
return -2;
}
return 0;
}
static StaticMemberProcessor completeStaticMembers(CompletionParameters parameters) {
final PsiElement position = parameters.getPosition();
final PsiElement originalPosition = parameters.getOriginalPosition();
final StaticMemberProcessor processor = new StaticMemberProcessor(position) {
@NotNull
@Override
protected LookupElement createLookupElement(@NotNull PsiMember member, @NotNull PsiClass containingClass, boolean shouldImport) {
shouldImport |= originalPosition != null && PsiTreeUtil.isAncestor(containingClass, originalPosition, false);
return createGlobalMemberElement(member, containingClass, shouldImport);
}
@Override
protected LookupElement createLookupElement(@NotNull List<PsiMethod> overloads,
@NotNull PsiClass containingClass,
boolean shouldImport) {
shouldImport |= originalPosition != null && PsiTreeUtil.isAncestor(containingClass, originalPosition, false);
return new JavaGlobalMemberLookupElement(overloads, containingClass, QualifiedMethodInsertHandler.INSTANCE, StaticImportInsertHandler.INSTANCE, shouldImport);
}
@Override
protected boolean isAccessible(PsiMember member) {
boolean result = super.isAccessible(member);
if (!result && member instanceof GrField) {
GrAccessorMethod[] getters = ((GrField)member).getGetters();
return getters.length > 0 && super.isAccessible(getters[0]);
}
return result;
}
};
final PsiFile file = position.getContainingFile();
if (file instanceof GroovyFile) {
for (GrImportStatement statement : ((GroovyFile)file).getImportStatements()) {
if (statement.isStatic()) {
GrCodeReferenceElement importReference = statement.getImportReference();
if (importReference != null) {
if (!statement.isOnDemand()) {
importReference = importReference.getQualifier();
}
if (importReference != null) {
final PsiElement target = importReference.resolve();
if (target instanceof PsiClass) {
processor.importMembersOf((PsiClass)target);
}
}
}
}
}
}
return processor;
}
static JavaGlobalMemberLookupElement createGlobalMemberElement(PsiMember member, PsiClass containingClass, boolean shouldImport) {
return new JavaGlobalMemberLookupElement(member, containingClass, QualifiedMethodInsertHandler.INSTANCE, StaticImportInsertHandler.INSTANCE, shouldImport);
}
public static void register(CompletionContributor contributor) {
contributor.extend(CompletionType.BASIC, PlatformPatterns.psiElement(PsiElement.class), new GrMainCompletionProvider());
}
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull final CompletionResultSet result) {
GroovyCompletionData.addGroovyDocKeywords(parameters, result);
PsiElement position = parameters.getPosition();
if (PlatformPatterns.psiElement().inside(false, PlatformPatterns.psiElement(PsiComment.class)).accepts(position)) {
return;
}
GroovyCompletionData.addGroovyKeywords(parameters, result);
addUnfinishedMethodTypeParameters(position, result);
suggestVariableNames(position, result);
GrReferenceElement reference = findGroovyReference(position);
if (reference == null) {
if (parameters.getInvocationCount() >= 2) {
result.stopHere();
addAllClasses(parameters, result.withPrefixMatcher(CompletionUtil.findJavaIdentifierPrefix(parameters)), new InheritorsHolder(parameters.getPosition(), result));
}
return;
}
if (reference.getParent() instanceof GrImportStatement && reference.getQualifier() != null) {
result.addElement(LookupElementBuilder.create("*"));
}
InheritorsHolder inheritors = new InheritorsHolder(position, result);
if (GroovySmartCompletionContributor.AFTER_NEW.accepts(position)) {
GroovySmartCompletionContributor.generateInheritorVariants(parameters, result.getPrefixMatcher(), inheritors);
}
Runnable addSlowVariants =
completeReference(parameters, reference, inheritors, result.getPrefixMatcher(), new Consumer<LookupElement>() {
@Override
public void consume(LookupElement lookupElement) {
result.addElement(lookupElement);
}
});
if (reference.getQualifier() == null) {
if (!GroovySmartCompletionContributor.AFTER_NEW.accepts(position)) {
GroovySmartCompletionContributor.addExpectedClassMembers(parameters, result);
}
if (isClassNamePossible(position) && JavaCompletionContributor.mayStartClassName(result)) {
result.stopHere();
if (parameters.getInvocationCount() >= 2) {
addAllClasses(parameters, result, inheritors);
} else {
JavaCompletionContributor.advertiseSecondCompletion(position.getProject(), result);
}
}
}
addSlowVariants.run();
}
}