mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-06 09:36:56 +07:00
1504 lines
70 KiB
Java
1504 lines
70 KiB
Java
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
|
package com.intellij.codeInsight.completion;
|
|
|
|
import com.intellij.application.options.CodeStyle;
|
|
import com.intellij.codeInsight.*;
|
|
import com.intellij.codeInsight.completion.scope.CompletionElement;
|
|
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
|
|
import com.intellij.codeInsight.daemon.impl.analysis.GenericsHighlightUtil;
|
|
import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
|
|
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
|
import com.intellij.codeInsight.daemon.impl.analysis.LambdaHighlightingUtil;
|
|
import com.intellij.codeInsight.daemon.impl.quickfix.BringVariableIntoScopeFix;
|
|
import com.intellij.codeInsight.lookup.*;
|
|
import com.intellij.featureStatistics.FeatureUsageTracker;
|
|
import com.intellij.icons.AllIcons;
|
|
import com.intellij.java.JavaBundle;
|
|
import com.intellij.lang.LangBundle;
|
|
import com.intellij.lang.java.JavaLanguage;
|
|
import com.intellij.lang.jvm.types.JvmPrimitiveTypeKind;
|
|
import com.intellij.openapi.actionSystem.IdeActions;
|
|
import com.intellij.openapi.editor.Document;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.keymap.KeymapUtil;
|
|
import com.intellij.openapi.module.Module;
|
|
import com.intellij.openapi.module.ModuleUtilCore;
|
|
import com.intellij.openapi.options.advanced.AdvancedSettings;
|
|
import com.intellij.openapi.project.DumbAware;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.roots.ModuleRootManager;
|
|
import com.intellij.openapi.util.Condition;
|
|
import com.intellij.openapi.util.NlsContexts;
|
|
import com.intellij.openapi.util.Ref;
|
|
import com.intellij.openapi.util.TextRange;
|
|
import com.intellij.openapi.util.registry.Registry;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.openapi.vfs.JarFileSystem;
|
|
import com.intellij.openapi.vfs.VirtualFile;
|
|
import com.intellij.patterns.ElementPattern;
|
|
import com.intellij.patterns.PatternCondition;
|
|
import com.intellij.patterns.PsiJavaElementPattern;
|
|
import com.intellij.patterns.PsiNameValuePairPattern;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.codeStyle.CodeStyleManager;
|
|
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
|
import com.intellij.psi.filters.*;
|
|
import com.intellij.psi.filters.classes.AnnotationTypeFilter;
|
|
import com.intellij.psi.filters.classes.AssignableFromContextFilter;
|
|
import com.intellij.psi.filters.classes.NoFinalLibraryClassesFilter;
|
|
import com.intellij.psi.filters.element.ExcludeDeclaredFilter;
|
|
import com.intellij.psi.filters.element.ModifierFilter;
|
|
import com.intellij.psi.filters.getters.ClassLiteralGetter;
|
|
import com.intellij.psi.filters.getters.ExpectedTypesGetter;
|
|
import com.intellij.psi.filters.getters.JavaMembersGetter;
|
|
import com.intellij.psi.filters.types.AssignableFromFilter;
|
|
import com.intellij.psi.impl.java.stubs.index.JavaAutoModuleNameIndex;
|
|
import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex;
|
|
import com.intellij.psi.impl.java.stubs.index.JavaSourceModuleNameIndex;
|
|
import com.intellij.psi.impl.light.LightJavaModule;
|
|
import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl;
|
|
import com.intellij.psi.impl.source.PsiLabelReference;
|
|
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
|
import com.intellij.psi.scope.ElementClassFilter;
|
|
import com.intellij.psi.search.GlobalSearchScope;
|
|
import com.intellij.psi.search.ProjectScope;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.intellij.psi.util.PsiUtil;
|
|
import com.intellij.psi.util.PsiUtilCore;
|
|
import com.intellij.psi.util.TypeConversionUtil;
|
|
import com.intellij.util.*;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.siyeh.ig.psiutils.JavaDeprecationUtils;
|
|
import com.siyeh.ig.psiutils.TypeUtils;
|
|
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
|
|
import one.util.streamex.EntryStream;
|
|
import one.util.streamex.StreamEx;
|
|
import org.jetbrains.annotations.*;
|
|
|
|
import java.util.*;
|
|
|
|
import static com.intellij.codeInsight.completion.JavaQualifierAsArgumentContributor.JavaQualifierAsArgumentStaticMembersProcessor;
|
|
import static com.intellij.patterns.PsiJavaPatterns.*;
|
|
import static com.intellij.psi.CommonClassNames.JAVA_LANG_COMPARABLE;
|
|
import static com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT;
|
|
|
|
public final class JavaCompletionContributor extends CompletionContributor implements DumbAware {
|
|
private static final ElementPattern<PsiElement> UNEXPECTED_REFERENCE_AFTER_DOT = or(
|
|
// dot at the statement beginning
|
|
psiElement().afterLeaf(".").insideStarting(psiExpressionStatement()),
|
|
// like `call(Cls::methodRef.<caret>`
|
|
psiElement().afterLeaf(psiElement(JavaTokenType.DOT).afterSibling(psiElement(PsiMethodCallExpression.class).withLastChild(
|
|
psiElement(PsiExpressionList.class).withLastChild(psiElement(PsiErrorElement.class))))));
|
|
private static final PsiNameValuePairPattern NAME_VALUE_PAIR =
|
|
psiNameValuePair().withSuperParent(2, psiElement(PsiAnnotation.class));
|
|
private static final ElementPattern<PsiElement> ANNOTATION_ATTRIBUTE_NAME =
|
|
or(psiElement(PsiIdentifier.class).withParent(NAME_VALUE_PAIR),
|
|
psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)));
|
|
private static final PsiJavaElementPattern.Capture<PsiElement> IN_TYPE_PARAMETER =
|
|
psiElement().afterLeaf(PsiKeyword.EXTENDS, PsiKeyword.SUPER, "&").withParent(
|
|
psiElement(PsiReferenceList.class).withParent(PsiTypeParameter.class));
|
|
|
|
public static final ElementPattern<PsiElement> IN_SWITCH_LABEL =
|
|
psiElement().withSuperParent(2, psiElement(PsiCaseLabelElementList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2, PsiSwitchBlock.class)));
|
|
private static final ElementPattern<PsiElement> IN_ENUM_SWITCH_LABEL =
|
|
psiElement().withSuperParent(2, psiElement(PsiCaseLabelElementList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2,
|
|
psiElement(PsiSwitchBlock.class).with(new PatternCondition<>("enumExpressionType") {
|
|
@Override
|
|
public boolean accepts(@NotNull PsiSwitchBlock psiSwitchBlock, ProcessingContext context) {
|
|
PsiExpression expression = psiSwitchBlock.getExpression();
|
|
if (expression == null) return false;
|
|
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
|
|
return aClass != null && aClass.isEnum();
|
|
}
|
|
}))));
|
|
static final PsiJavaElementPattern.Capture<PsiElement> IN_CASE_LABEL_ELEMENT_LIST =
|
|
psiElement().withSuperParent(2, psiElement(PsiCaseLabelElementList.class));
|
|
|
|
private static final ElementPattern<PsiElement> AFTER_NUMBER_LITERAL =
|
|
psiElement().afterLeaf(psiElement().withElementType(
|
|
elementType().oneOf(JavaTokenType.DOUBLE_LITERAL, JavaTokenType.LONG_LITERAL, JavaTokenType.FLOAT_LITERAL, JavaTokenType.INTEGER_LITERAL)));
|
|
private static final ElementPattern<PsiElement> IMPORT_REFERENCE =
|
|
psiElement().withParent(psiElement(PsiJavaCodeReferenceElement.class).withParent(PsiImportStatementBase.class));
|
|
private static final ElementPattern<PsiElement> CATCH_OR_FINALLY = psiElement().afterLeaf(
|
|
psiElement().withText("}").withParent(
|
|
psiElement(PsiCodeBlock.class).afterLeaf(PsiKeyword.TRY)));
|
|
private static final ElementPattern<PsiElement> INSIDE_CONSTRUCTOR = psiElement().inside(psiMethod().constructor(true));
|
|
private static final ElementPattern<PsiElement> AFTER_ENUM_CONSTANT =
|
|
psiElement().inside(PsiTypeElement.class).afterLeaf(
|
|
psiElement().inside(true, psiElement(PsiEnumConstant.class), psiElement(PsiClass.class, PsiExpressionList.class)));
|
|
static final ElementPattern<PsiElement> IN_EXTENDS_OR_IMPLEMENTS = psiElement().afterLeaf(
|
|
psiElement()
|
|
.withText(string().oneOf(PsiKeyword.EXTENDS, PsiKeyword.IMPLEMENTS, ",", "&"))
|
|
.withParent(PsiReferenceList.class));
|
|
static final ElementPattern<PsiElement> IN_PERMITS_LIST = psiElement().afterLeaf(
|
|
psiElement()
|
|
.withText(string().oneOf(PsiKeyword.PERMITS, ","))
|
|
.withParent(psiElement(PsiReferenceList.class).withFirstChild(psiElement(PsiKeyword.class).withText(PsiKeyword.PERMITS))));
|
|
private static final ElementPattern<PsiElement> IN_VARIABLE_TYPE = psiElement()
|
|
.withParents(PsiJavaCodeReferenceElement.class, PsiTypeElement.class, PsiDeclarationStatement.class)
|
|
.afterLeaf(psiElement().inside(psiAnnotation()));
|
|
private static final ElementPattern<PsiElement> TOP_LEVEL_VAR_IN_MODULE = psiElement().withSuperParent(3, PsiJavaFile.class)
|
|
.inVirtualFile(virtualFile().withName("module-info.java"));
|
|
|
|
/**
|
|
* @param position completion invocation position
|
|
* @return filter for acceptable references; if null then no references are accepted at a given position
|
|
*/
|
|
@Nullable
|
|
public static ElementFilter getReferenceFilter(PsiElement position) {
|
|
PsiClass containingClass = PsiTreeUtil.getParentOfType(position, PsiClass.class, false,
|
|
PsiCodeBlock.class, PsiMethod.class,
|
|
PsiExpressionList.class, PsiVariable.class, PsiAnnotation.class);
|
|
if (containingClass != null) {
|
|
if (IN_PERMITS_LIST.accepts(position)) {
|
|
return createPermitsListFilter();
|
|
}
|
|
|
|
if (IN_EXTENDS_OR_IMPLEMENTS.accepts(position)) {
|
|
AndFilter filter = new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter()),
|
|
new ExcludeDeclaredFilter(new ClassFilter(PsiClass.class)));
|
|
PsiElement cls = position.getParent().getParent().getParent();
|
|
if (cls instanceof PsiClass && !(cls instanceof PsiTypeParameter)) {
|
|
filter = new AndFilter(filter, new NoFinalLibraryClassesFilter());
|
|
}
|
|
return filter;
|
|
}
|
|
if (IN_TYPE_PARAMETER.accepts(position)) {
|
|
return new ExcludeDeclaredFilter(new ClassFilter(PsiTypeParameter.class));
|
|
}
|
|
}
|
|
|
|
if (getAnnotationNameIfInside(position) != null) {
|
|
return new OrFilter(ElementClassFilter.PACKAGE, new AnnotationTypeFilter());
|
|
}
|
|
|
|
if (JavaKeywordCompletion.isDeclarationStart(position) ||
|
|
JavaKeywordCompletion.isInsideParameterList(position) ||
|
|
isInsideAnnotationName(position) ||
|
|
PsiTreeUtil.getParentOfType(position, PsiReferenceParameterList.class, false, PsiAnnotation.class) != null ||
|
|
IN_VARIABLE_TYPE.accepts(position)) {
|
|
return new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.PACKAGE);
|
|
}
|
|
|
|
if (psiElement().afterLeaf(PsiKeyword.INSTANCEOF).accepts(position) ||
|
|
position.getParent() instanceof PsiJavaCodeReferenceElement ref &&
|
|
ref.getParent() instanceof PsiTypeElement typeElement &&
|
|
(typeElement.getParent() instanceof PsiPatternVariable ||
|
|
typeElement.getParent() instanceof PsiDeconstructionList)) {
|
|
return new ElementFilter() {
|
|
@Override
|
|
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
|
|
return element instanceof PsiClass && !(element instanceof PsiTypeParameter);
|
|
}
|
|
|
|
@Override
|
|
public boolean isClassAcceptable(Class<?> hintClass) {
|
|
return PsiClass.class.isAssignableFrom(hintClass) && !PsiTypeParameter.class.isAssignableFrom(hintClass);
|
|
}
|
|
};
|
|
}
|
|
|
|
if (JavaKeywordCompletion.VARIABLE_AFTER_FINAL.accepts(position)) {
|
|
return ElementClassFilter.CLASS;
|
|
}
|
|
|
|
if (CATCH_OR_FINALLY.accepts(position) ||
|
|
JavaKeywordCompletion.START_SWITCH.accepts(position) ||
|
|
JavaKeywordCompletion.isInstanceofPlace(position) ||
|
|
JavaKeywordCompletion.isAfterPrimitiveOrArrayType(position)) {
|
|
return null;
|
|
}
|
|
|
|
if (JavaKeywordCompletion.START_FOR.withParents(PsiJavaCodeReferenceElement.class, PsiExpressionStatement.class, PsiForStatement.class).accepts(position)) {
|
|
return new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.VARIABLE);
|
|
}
|
|
|
|
if (JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) {
|
|
return ElementClassFilter.CLASS;
|
|
}
|
|
|
|
if (psiElement().inside(PsiAnnotationParameterList.class).accepts(position)) {
|
|
return createAnnotationFilter();
|
|
}
|
|
|
|
PsiVariable var = PsiTreeUtil.getParentOfType(position, PsiVariable.class, false, PsiClass.class);
|
|
if (var != null && PsiTreeUtil.isAncestor(var.getInitializer(), position, false)) {
|
|
return new ExcludeFilter(var);
|
|
}
|
|
|
|
if (IN_CASE_LABEL_ELEMENT_LIST.accepts(position)) {
|
|
return getCaseLabelElementListFilter(position);
|
|
}
|
|
|
|
PsiForeachStatement loop = PsiTreeUtil.getParentOfType(position, PsiForeachStatement.class);
|
|
if (loop != null && PsiTreeUtil.isAncestor(loop.getIteratedValue(), position, false)) {
|
|
return new ExcludeFilter(loop.getIterationParameter());
|
|
}
|
|
|
|
if (PsiTreeUtil.getParentOfType(position, PsiPackageAccessibilityStatement.class) != null) {
|
|
return applyScopeFilter(ElementClassFilter.PACKAGE, position);
|
|
}
|
|
|
|
if (PsiTreeUtil.getParentOfType(position, PsiUsesStatement.class, PsiProvidesStatement.class) != null) {
|
|
ElementFilter filter = new OrFilter(ElementClassFilter.CLASS, ElementClassFilter.PACKAGE);
|
|
if (PsiTreeUtil.getParentOfType(position, PsiReferenceList.class) != null) {
|
|
filter = applyScopeFilter(filter, position);
|
|
}
|
|
return filter;
|
|
}
|
|
|
|
if (position.getParent() instanceof PsiReferenceExpression) {
|
|
PsiClass enumClass = GenericsHighlightUtil.getEnumClassForExpressionInInitializer((PsiReferenceExpression)position.getParent());
|
|
if (enumClass != null) {
|
|
return new EnumStaticFieldsFilter(enumClass);
|
|
}
|
|
}
|
|
|
|
return TrueFilter.INSTANCE;
|
|
}
|
|
|
|
@Contract(pure = true)
|
|
private static @NotNull ElementFilter getCaseLabelElementListFilter(@NotNull PsiElement position) {
|
|
PsiSwitchBlock switchBlock = PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class);
|
|
if (switchBlock == null) return TrueFilter.INSTANCE;
|
|
|
|
PsiExpression selector = switchBlock.getExpression();
|
|
if (selector == null || selector.getType() == null) return TrueFilter.INSTANCE;
|
|
|
|
PsiType selectorType = selector.getType();
|
|
|
|
if (IN_ENUM_SWITCH_LABEL.accepts(position)) {
|
|
ClassFilter enumClassFilter = new ClassFilter(PsiField.class) {
|
|
@Override
|
|
public boolean isAcceptable(Object element, PsiElement context) {
|
|
return element instanceof PsiEnumConstant;
|
|
}
|
|
};
|
|
if (!HighlightingFeature.PATTERNS_IN_SWITCH.isAvailable(position)) {
|
|
return enumClassFilter;
|
|
}
|
|
|
|
ClassFilter inheritorsEnumFilter = new ClassFilter(PsiClass.class) {
|
|
@Override
|
|
public boolean isAcceptable(Object element, PsiElement context) {
|
|
return element instanceof PsiClass psiClass &&
|
|
(JAVA_LANG_OBJECT.equals(psiClass.getQualifiedName()) || psiClass.isEnum() || psiClass.isInterface()) &&
|
|
//it will be covered by enum class, and it looks like noise
|
|
!JAVA_LANG_COMPARABLE.equals(psiClass.getQualifiedName()) &&
|
|
TypeConversionUtil.areTypesConvertible(TypeUtils.getType(psiClass), selectorType);
|
|
}
|
|
};
|
|
|
|
return new OrFilter(enumClassFilter, inheritorsEnumFilter);
|
|
}
|
|
|
|
ClassFilter constantVariablesFilter = new ClassFilter(PsiVariable.class) {
|
|
@Override
|
|
public boolean isAcceptable(Object element, PsiElement context) {
|
|
PsiVariable variable;
|
|
if (element instanceof PsiField field) {
|
|
if (!field.hasModifierProperty(PsiModifier.FINAL) || !field.hasModifierProperty(PsiModifier.STATIC) ||
|
|
!JavaResolveUtil.isAccessible(field, field.getContainingClass(), field.getModifierList(), context, null, null)) {
|
|
return false;
|
|
}
|
|
|
|
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(field.getType());
|
|
if (aClass != null && aClass.isEnum()) {
|
|
return false;
|
|
}
|
|
variable = field;
|
|
}
|
|
else if (element instanceof PsiLocalVariable local) {
|
|
if (!local.hasModifierProperty(PsiModifier.FINAL)) {
|
|
return false;
|
|
}
|
|
variable = local;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
return TypeConversionUtil.isAssignable(selectorType, variable.getType());
|
|
}
|
|
};
|
|
|
|
if (!HighlightingFeature.PATTERNS_IN_SWITCH.isAvailable(position) ||
|
|
isPrimitive(selectorType) || TypeUtils.isJavaLangString(selectorType)) {
|
|
ClassFilter classFilter = new ClassFilter(PsiClass.class) {
|
|
@Override
|
|
public boolean isAcceptable(Object element, PsiElement context) {
|
|
// Accept only classes with inner classes or with suitable fields
|
|
if (!(element instanceof PsiClass psiClass)) return false;
|
|
for (PsiClass aClass : psiClass.getInnerClasses()) {
|
|
if (JavaResolveUtil.isAccessible(aClass, psiClass, aClass.getModifierList(), context, null, null)) {
|
|
return true;
|
|
}
|
|
}
|
|
return ContainerUtil.exists(psiClass.getAllFields(), field -> constantVariablesFilter.isAcceptable(field, context));
|
|
}
|
|
};
|
|
return new OrFilter(classFilter, constantVariablesFilter);
|
|
}
|
|
|
|
ClassFilter enumInheritorsFilter = new ClassFilter(PsiField.class) {
|
|
@Override
|
|
public boolean isAcceptable(Object element, PsiElement context) {
|
|
return element instanceof PsiEnumConstant enumConstant && TypeConversionUtil.areTypesConvertible(enumConstant.getType(), selectorType);
|
|
}
|
|
};
|
|
|
|
if (TypeUtils.isJavaLangObject(selectorType)) {
|
|
return new OrFilter(new ClassFilter(PsiClass.class), constantVariablesFilter, enumInheritorsFilter);
|
|
}
|
|
|
|
ClassFilter inheritorsFilter = new ClassFilter(PsiClass.class) {
|
|
@Override
|
|
public boolean isAcceptable(Object element, PsiElement context) {
|
|
return element instanceof PsiClass psiClass &&
|
|
TypeConversionUtil.areTypesConvertible(TypeUtils.getType(psiClass), selectorType);
|
|
}
|
|
};
|
|
|
|
if (TypeUtils.isJavaLangString(selectorType)) {
|
|
return new OrFilter(constantVariablesFilter, inheritorsFilter);
|
|
}
|
|
|
|
return selectorType instanceof PsiClassType
|
|
? new OrFilter(inheritorsFilter, enumInheritorsFilter)
|
|
: TrueFilter.INSTANCE;
|
|
}
|
|
|
|
@Contract(pure = true)
|
|
private static boolean isPrimitive(@NotNull PsiType type) {
|
|
if (type instanceof PsiPrimitiveType) return true;
|
|
if (!(type instanceof PsiClassType)) return false;
|
|
|
|
PsiClass aClass = ((PsiClassType)type).resolve();
|
|
Collection<String> boxedPrimitiveTypes = JvmPrimitiveTypeKind.getBoxedFqns();
|
|
return aClass != null && boxedPrimitiveTypes.contains(aClass.getQualifiedName());
|
|
}
|
|
|
|
@NotNull
|
|
static AndFilter createPermitsListFilter() {
|
|
return new AndFilter(ElementClassFilter.CLASS, new NotFilter(new AssignableFromContextFilter() {
|
|
@Override
|
|
protected boolean checkInheritance(@NotNull PsiClass curClass, @NotNull PsiClass candidate) {
|
|
String qualifiedName = curClass.getQualifiedName();
|
|
return qualifiedName != null && (qualifiedName.equals(candidate.getQualifiedName()) || curClass.isInheritor(candidate, true));
|
|
}
|
|
}));
|
|
}
|
|
|
|
private static boolean isInsideAnnotationName(PsiElement position) {
|
|
PsiAnnotation anno = PsiTreeUtil.getParentOfType(position, PsiAnnotation.class, true, PsiMember.class);
|
|
return anno != null && PsiTreeUtil.isAncestor(anno.getNameReferenceElement(), position, true);
|
|
}
|
|
|
|
private static ElementFilter createAnnotationFilter() {
|
|
return new OrFilter(
|
|
ElementClassFilter.CLASS,
|
|
ElementClassFilter.PACKAGE,
|
|
new AndFilter(new ClassFilter(PsiField.class), new ModifierFilter(PsiModifier.STATIC, PsiModifier.FINAL)));
|
|
}
|
|
|
|
public static ElementFilter applyScopeFilter(ElementFilter filter, PsiElement position) {
|
|
Module module = ModuleUtilCore.findModuleForPsiElement(position);
|
|
return module != null ? new AndFilter(filter, new SearchScopeFilter(module.getModuleScope())) : filter;
|
|
}
|
|
|
|
@Override
|
|
public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet _result) {
|
|
PsiElement position = parameters.getPosition();
|
|
if (!isInJavaContext(position)) {
|
|
return;
|
|
}
|
|
|
|
if (AFTER_NUMBER_LITERAL.accepts(position) ||
|
|
UNEXPECTED_REFERENCE_AFTER_DOT.accepts(position) ||
|
|
AFTER_ENUM_CONSTANT.accepts(position)) {
|
|
_result.stopHere();
|
|
return;
|
|
}
|
|
|
|
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
|
|
|
|
CompletionResultSet result = JavaCompletionSorting.addJavaSorting(parameters, _result);
|
|
JavaCompletionSession session = new JavaCompletionSession(result);
|
|
|
|
PrefixMatcher matcher = result.getPrefixMatcher();
|
|
PsiElement parent = position.getParent();
|
|
|
|
if (new JavaKeywordCompletion(parameters, session, smart).addWildcardExtendsSuper(result, position)) {
|
|
result.stopHere();
|
|
return;
|
|
}
|
|
|
|
boolean mayCompleteReference = true;
|
|
|
|
if (position instanceof PsiIdentifier) {
|
|
addIdentifierVariants(parameters, position, result, session, matcher);
|
|
|
|
Set<ExpectedTypeInfo> expectedInfos = ContainerUtil.newHashSet(JavaSmartCompletionContributor.getExpectedTypes(parameters));
|
|
boolean shouldAddExpressionVariants = shouldAddExpressionVariants(parameters);
|
|
|
|
boolean hasTypeMatchingSuggestions =
|
|
shouldAddExpressionVariants && addExpectedTypeMembers(parameters, false, expectedInfos,
|
|
item -> session.registerBatchItems(Collections.singleton(item)));
|
|
|
|
if (!smart) {
|
|
PsiAnnotation anno = findAnnotationWhoseAttributeIsCompleted(position);
|
|
if (anno != null) {
|
|
PsiClass annoClass = anno.resolveAnnotationType();
|
|
mayCompleteReference = mayCompleteValueExpression(position, annoClass);
|
|
if (annoClass != null) {
|
|
completeAnnotationAttributeName(result, position, anno, annoClass);
|
|
JavaKeywordCompletion.addPrimitiveTypes(result, position, session);
|
|
}
|
|
}
|
|
}
|
|
|
|
PsiReference ref = position.getContainingFile().findReferenceAt(parameters.getOffset());
|
|
if (ref instanceof PsiLabelReference) {
|
|
session.registerBatchItems(processLabelReference((PsiLabelReference)ref));
|
|
result.stopHere();
|
|
}
|
|
|
|
List<LookupElement> refSuggestions = Collections.emptyList();
|
|
if (parent instanceof PsiJavaCodeReferenceElement parentRef && mayCompleteReference) {
|
|
if (IN_PERMITS_LIST.accepts(parent) && parameters.getInvocationCount() <= 1 && !parentRef.isQualified()) {
|
|
refSuggestions = completePermitsListReference(parameters, parentRef, matcher);
|
|
}
|
|
else {
|
|
if (parameters.getInvocationCount() >= 2 && Registry.is("java.completion.methods.use.tags")) {
|
|
MethodTags.TagMatcher tagMatcher = new MethodTags.TagMatcher(matcher);
|
|
List<LookupElement> proposedElements = completeReference(parameters, parentRef, session, expectedInfos, tagMatcher::prefixMatches);
|
|
refSuggestions = ContainerUtil.map(proposedElements, element -> {
|
|
LookupElement withTags = MethodTags.wrapLookupWithTags(element, matcher::prefixMatches, matcher.getPrefix(), parameters.getCompletionType());
|
|
return withTags != null ? withTags : element;
|
|
});
|
|
}
|
|
else {
|
|
refSuggestions = completeReference(parameters, parentRef, session, expectedInfos, matcher::prefixMatches);
|
|
}
|
|
}
|
|
List<LookupElement> filtered = filterReferenceSuggestions(parameters, expectedInfos, refSuggestions);
|
|
hasTypeMatchingSuggestions |= ContainerUtil.exists(filtered, item ->
|
|
ReferenceExpressionCompletionContributor.matchesExpectedType(item, expectedInfos));
|
|
session.registerBatchItems(filtered);
|
|
result.stopHere();
|
|
}
|
|
|
|
session.flushBatchItems();
|
|
|
|
if (smart) {
|
|
hasTypeMatchingSuggestions |= smartCompleteExpression(parameters, result, expectedInfos);
|
|
smartCompleteNonExpression(parameters, result);
|
|
} else {
|
|
if (!JavaKeywordCompletion.AFTER_DOT.accepts(position)) {
|
|
for (ExpectedTypeInfo info : expectedInfos) {
|
|
ClassLiteralGetter.addCompletions(new JavaSmartCompletionParameters(parameters, info), result, matcher);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((!hasTypeMatchingSuggestions || parameters.getInvocationCount() >= 2) &&
|
|
parent instanceof PsiJavaCodeReferenceElement &&
|
|
!expectedInfos.isEmpty() &&
|
|
JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(position)) {
|
|
List<LookupElement> base = ContainerUtil.concat(
|
|
refSuggestions,
|
|
completeReference(parameters, (PsiJavaCodeReferenceElement)parent, session, expectedInfos, s -> !matcher.prefixMatches(s)));
|
|
SlowerTypeConversions.addChainedSuggestions(parameters, result, expectedInfos, base);
|
|
}
|
|
|
|
if (smart && parameters.getInvocationCount() > 1 && shouldAddExpressionVariants) {
|
|
addExpectedTypeMembers(parameters, true, expectedInfos, result);
|
|
}
|
|
}
|
|
|
|
if (!smart && psiElement().inside(PsiLiteralExpression.class).accepts(position)) {
|
|
Set<String> usedWords = new HashSet<>();
|
|
result.runRemainingContributors(parameters, cr -> {
|
|
usedWords.add(cr.getLookupElement().getLookupString());
|
|
result.passResult(cr);
|
|
});
|
|
|
|
PsiReference reference = position.getContainingFile().findReferenceAt(parameters.getOffset());
|
|
if (reference == null || reference.isSoft()) {
|
|
WordCompletionContributor.addWordCompletionVariants(result, parameters, usedWords);
|
|
}
|
|
}
|
|
|
|
if (!smart && position instanceof PsiIdentifier) {
|
|
JavaGenerateMemberCompletionContributor.fillCompletionVariants(parameters, result);
|
|
}
|
|
|
|
if (!smart && mayCompleteReference) {
|
|
addAllClasses(parameters, result, session);
|
|
}
|
|
|
|
if (position instanceof PsiIdentifier) {
|
|
FunctionalExpressionCompletionProvider.addFunctionalVariants(parameters, true, result.getPrefixMatcher(), result);
|
|
}
|
|
|
|
if (position instanceof PsiIdentifier &&
|
|
!smart &&
|
|
parent instanceof PsiReferenceExpression &&
|
|
!((PsiReferenceExpression)parent).isQualified() &&
|
|
parameters.isExtendedCompletion() &&
|
|
StringUtil.isNotEmpty(matcher.getPrefix())) {
|
|
new JavaStaticMemberProcessor(parameters).processStaticMethodsGlobally(matcher, result);
|
|
}
|
|
|
|
if (!smart && parent instanceof PsiJavaModuleReferenceElement) {
|
|
addModuleReferences(parent, parameters.getOriginalFile(), result);
|
|
}
|
|
}
|
|
|
|
@VisibleForTesting
|
|
public static boolean mayCompleteValueExpression(@NotNull PsiElement position, @Nullable PsiClass annoClass) {
|
|
return psiElement().afterLeaf("(").accepts(position) && annoClass != null && annoClass.findMethodsByName("value", false).length > 0;
|
|
}
|
|
|
|
private static List<LookupElement> filterReferenceSuggestions(CompletionParameters parameters,
|
|
Set<ExpectedTypeInfo> expectedInfos,
|
|
List<LookupElement> refSuggestions) {
|
|
if (parameters.getCompletionType() == CompletionType.SMART) {
|
|
return ReferenceExpressionCompletionContributor.smartCompleteReference(refSuggestions, expectedInfos);
|
|
}
|
|
return refSuggestions;
|
|
}
|
|
|
|
private static void smartCompleteNonExpression(CompletionParameters parameters, CompletionResultSet result) {
|
|
PsiElement position = parameters.getPosition();
|
|
PsiElement parent = position.getParent();
|
|
if (!SmartCastProvider.inCastContext(parameters) && parent instanceof PsiJavaCodeReferenceElement) {
|
|
JavaSmartCompletionContributor.addClassReferenceSuggestions(parameters, result, position, (PsiJavaCodeReferenceElement)parent);
|
|
}
|
|
if (InstanceofTypeProvider.AFTER_INSTANCEOF.accepts(position)) {
|
|
InstanceofTypeProvider.addCompletions(parameters, result);
|
|
}
|
|
if (ExpectedAnnotationsProvider.ANNOTATION_ATTRIBUTE_VALUE.accepts(position)) {
|
|
ExpectedAnnotationsProvider.addCompletions(position, result);
|
|
}
|
|
if (CatchTypeProvider.CATCH_CLAUSE_TYPE.accepts(position)) {
|
|
CatchTypeProvider.addCompletions(parameters, result);
|
|
}
|
|
}
|
|
|
|
private static boolean smartCompleteExpression(CompletionParameters parameters,
|
|
CompletionResultSet result,
|
|
Set<? extends ExpectedTypeInfo> infos) {
|
|
PsiElement position = parameters.getPosition();
|
|
if (SmartCastProvider.inCastContext(parameters) ||
|
|
!JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(position) ||
|
|
!(position.getParent() instanceof PsiJavaCodeReferenceElement)) {
|
|
return false;
|
|
}
|
|
|
|
boolean[] hadItems = new boolean[1];
|
|
for (ExpectedTypeInfo info : new ObjectOpenCustomHashSet<>(infos, JavaSmartCompletionContributor.EXPECTED_TYPE_INFO_STRATEGY)) {
|
|
BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(parameters, info), lookupElement -> {
|
|
PsiType psiType = JavaCompletionUtil.getLookupElementType(lookupElement);
|
|
if (psiType != null && info.getType().isAssignableFrom(psiType)) {
|
|
hadItems[0] = true;
|
|
result.addElement(JavaSmartCompletionContributor.decorate(lookupElement, infos));
|
|
}
|
|
}, result.getPrefixMatcher());
|
|
}
|
|
return hadItems[0];
|
|
}
|
|
|
|
@Nullable
|
|
private static PsiAnnotation findAnnotationWhoseAttributeIsCompleted(@NotNull PsiElement position) {
|
|
return ANNOTATION_ATTRIBUTE_NAME.accepts(position) && !JavaKeywordCompletion.isAfterPrimitiveOrArrayType(position)
|
|
? Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiAnnotation.class))
|
|
: null;
|
|
}
|
|
|
|
private static void addIdentifierVariants(@NotNull CompletionParameters parameters,
|
|
PsiElement position,
|
|
CompletionResultSet result,
|
|
JavaCompletionSession session, PrefixMatcher matcher) {
|
|
session.registerBatchItems(getFastIdentifierVariants(parameters, position, matcher, position.getParent(), session));
|
|
|
|
if (JavaSmartCompletionContributor.AFTER_NEW.accepts(position)) {
|
|
session.flushBatchItems();
|
|
|
|
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
|
|
ConstructorInsertHandler handler = smart
|
|
? ConstructorInsertHandler.SMART_INSTANCE
|
|
: ConstructorInsertHandler.BASIC_INSTANCE;
|
|
ExpectedTypeInfo[] types = JavaSmartCompletionContributor.getExpectedTypes(parameters);
|
|
new JavaInheritorsGetter(handler).generateVariants(parameters, matcher, types, lookupElement -> {
|
|
if ((smart || !isSuggestedByKeywordCompletion(lookupElement)) && result.getPrefixMatcher().prefixMatches(lookupElement)) {
|
|
session.registerClassFrom(lookupElement);
|
|
result.addElement(smart
|
|
? JavaSmartCompletionContributor.decorate(lookupElement, Arrays.asList(types))
|
|
: AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElement));
|
|
}
|
|
});
|
|
}
|
|
|
|
suggestSmartCast(parameters, session, false, result);
|
|
}
|
|
|
|
private static boolean isSuggestedByKeywordCompletion(LookupElement lookupElement) {
|
|
if (lookupElement instanceof PsiTypeLookupItem) {
|
|
PsiType type = ((PsiTypeLookupItem)lookupElement).getType();
|
|
return type instanceof PsiArrayType && ((PsiArrayType)type).getComponentType() instanceof PsiPrimitiveType;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static void suggestSmartCast(CompletionParameters parameters, JavaCompletionSession session, boolean quick, Consumer<? super LookupElement> result) {
|
|
if (SmartCastProvider.shouldSuggestCast(parameters)) {
|
|
session.flushBatchItems();
|
|
SmartCastProvider.addCastVariants(parameters, session.getMatcher(), element -> {
|
|
registerClassFromTypeElement(element, session);
|
|
result.consume(PrioritizedLookupElement.withPriority(element, 1));
|
|
}, quick);
|
|
}
|
|
}
|
|
|
|
private static List<LookupElement> getFastIdentifierVariants(@NotNull CompletionParameters parameters,
|
|
PsiElement position,
|
|
PrefixMatcher matcher,
|
|
PsiElement parent,
|
|
@NotNull JavaCompletionSession session) {
|
|
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
|
|
|
|
List<LookupElement> items = new ArrayList<>();
|
|
if (TypeArgumentCompletionProvider.IN_TYPE_ARGS.accepts(position)) {
|
|
new TypeArgumentCompletionProvider(smart, session).addTypeArgumentVariants(parameters, items::add, matcher);
|
|
}
|
|
|
|
FunctionalExpressionCompletionProvider.addFunctionalVariants(parameters, false, matcher, items::add);
|
|
|
|
if (MethodReturnTypeProvider.IN_METHOD_RETURN_TYPE.accepts(position)) {
|
|
MethodReturnTypeProvider.addProbableReturnTypes(position, element -> {
|
|
registerClassFromTypeElement(element, session);
|
|
items.add(element);
|
|
});
|
|
}
|
|
|
|
suggestSmartCast(parameters, session, true, items::add);
|
|
|
|
if (parent instanceof PsiReferenceExpression && !(parent instanceof PsiMethodReferenceExpression)) {
|
|
List<ExpectedTypeInfo> expected = Arrays.asList(ExpectedTypesProvider.getExpectedTypes((PsiExpression)parent, true));
|
|
StreamConversion.addCollectConversion((PsiReferenceExpression)parent, expected,
|
|
lookupElement -> items.add(JavaSmartCompletionContributor.decorate(lookupElement, expected)));
|
|
if (!smart) {
|
|
items.addAll(StreamConversion.addToStreamConversion((PsiReferenceExpression)parent, parameters));
|
|
}
|
|
items.addAll(ArgumentSuggester.suggestArgument((PsiReferenceExpression)parent, smart ? expected : Collections.emptyList()));
|
|
}
|
|
|
|
if (IMPORT_REFERENCE.accepts(position)) {
|
|
items.add(LookupElementBuilder.create("*"));
|
|
}
|
|
|
|
if (findAnnotationWhoseAttributeIsCompleted(position) == null) {
|
|
items.addAll(new JavaKeywordCompletion(parameters, session, smart).getResults());
|
|
}
|
|
|
|
addExpressionVariants(parameters, position, items::add);
|
|
|
|
return items;
|
|
}
|
|
|
|
private static void registerClassFromTypeElement(LookupElement element, JavaCompletionSession session) {
|
|
PsiType type = Objects.requireNonNull(element.as(PsiTypeLookupItem.CLASS_CONDITION_KEY)).getType();
|
|
if (type instanceof PsiPrimitiveType) {
|
|
session.registerKeyword(type.getCanonicalText(false));
|
|
}
|
|
else if (type instanceof PsiClassType && ((PsiClassType)type).getParameterCount() == 0) {
|
|
PsiClass aClass = ((PsiClassType)type).resolve();
|
|
if (aClass != null) {
|
|
session.registerClass(aClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean shouldAddExpressionVariants(CompletionParameters parameters) {
|
|
return JavaSmartCompletionContributor.INSIDE_EXPRESSION.accepts(parameters.getPosition()) &&
|
|
!JavaKeywordCompletion.AFTER_DOT.accepts(parameters.getPosition()) &&
|
|
!SmartCastProvider.inCastContext(parameters);
|
|
}
|
|
|
|
private static void addExpressionVariants(@NotNull CompletionParameters parameters, PsiElement position, Consumer<? super LookupElement> result) {
|
|
if (shouldAddExpressionVariants(parameters)) {
|
|
if (SameSignatureCallParametersProvider.IN_CALL_ARGUMENT.accepts(position)) {
|
|
new SameSignatureCallParametersProvider().addSignatureItems(position, result);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static boolean isInJavaContext(PsiElement position) {
|
|
return PsiUtilCore.findLanguageFromElement(position).isKindOf(JavaLanguage.INSTANCE);
|
|
}
|
|
|
|
public static void addAllClasses(CompletionParameters parameters, CompletionResultSet result, JavaCompletionSession session) {
|
|
if (!isClassNamePossible(parameters) || !mayStartClassName(result)) {
|
|
return;
|
|
}
|
|
|
|
if (parameters.getInvocationCount() >= 2) {
|
|
JavaNoVariantsDelegator.suggestNonImportedClasses(parameters, result, session);
|
|
}
|
|
else {
|
|
advertiseSecondCompletion(parameters.getPosition().getProject(), result);
|
|
}
|
|
}
|
|
|
|
public static void advertiseSecondCompletion(Project project, CompletionResultSet result) {
|
|
if (FeatureUsageTracker.getInstance().isToBeAdvertisedInLookup(CodeCompletionFeatures.SECOND_BASIC_COMPLETION, project)) {
|
|
result.addLookupAdvertisement(JavaBundle.message("press.0.to.see.non.imported.classes",
|
|
KeymapUtil.getFirstKeyboardShortcutText(IdeActions.ACTION_CODE_COMPLETION)));
|
|
}
|
|
}
|
|
|
|
private static @NotNull List<LookupElement> completeReference(@NotNull CompletionParameters parameters,
|
|
PsiJavaCodeReferenceElement ref,
|
|
JavaCompletionSession session,
|
|
Set<? extends ExpectedTypeInfo> expectedTypes,
|
|
Condition<? super String> nameCondition) {
|
|
PsiElement position = parameters.getPosition();
|
|
if (TOP_LEVEL_VAR_IN_MODULE.accepts(position)) return Collections.emptyList();
|
|
ElementFilter filter = getReferenceFilter(position);
|
|
if (filter == null) return Collections.emptyList();
|
|
if (parameters.getInvocationCount() <= 1 && JavaClassNameCompletionContributor.AFTER_NEW.accepts(position)) {
|
|
filter = new AndFilter(filter, new ElementFilter() {
|
|
@Override
|
|
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
|
|
return !JavaPsiClassReferenceElement.isInaccessibleConstructorSuggestion(position, ObjectUtils.tryCast(element, PsiClass.class));
|
|
}
|
|
|
|
@Override
|
|
public boolean isClassAcceptable(Class<?> hintClass) {
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
|
|
if (smart) {
|
|
if (JavaSmartCompletionContributor.INSIDE_TYPECAST_EXPRESSION.accepts(position) || SmartCastProvider.inCastContext(parameters)) {
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
ElementFilter smartRestriction = ReferenceExpressionCompletionContributor.getReferenceFilter(position, false);
|
|
if (smartRestriction != TrueFilter.INSTANCE) {
|
|
filter = new AndFilter(filter, smartRestriction);
|
|
}
|
|
}
|
|
|
|
boolean inSwitchLabel = IN_SWITCH_LABEL.accepts(position);
|
|
TailType forcedTail = getTailType(smart, inSwitchLabel, position);
|
|
|
|
List<LookupElement> items = new ArrayList<>();
|
|
if (INSIDE_CONSTRUCTOR.accepts(position) &&
|
|
(parameters.getInvocationCount() <= 1 || CheckInitialized.isInsideConstructorCall(position))) {
|
|
filter = new AndFilter(filter, new CheckInitialized(position));
|
|
}
|
|
PsiFile originalFile = parameters.getOriginalFile();
|
|
|
|
boolean first = parameters.getInvocationCount() <= 1;
|
|
JavaCompletionProcessor.Options options =
|
|
JavaCompletionProcessor.Options.DEFAULT_OPTIONS
|
|
.withCheckAccess(first)
|
|
.withFilterStaticAfterInstance(first)
|
|
.withShowInstanceInStaticContext(!first && !smart);
|
|
|
|
for (LookupElement element : JavaCompletionUtil.processJavaReference(position,
|
|
ref,
|
|
new ElementExtractorFilter(filter),
|
|
options,
|
|
nameCondition, parameters)) {
|
|
if (session.alreadyProcessed(element)) {
|
|
continue;
|
|
}
|
|
|
|
LookupItem<?> item = element.as(LookupItem.CLASS_CONDITION_KEY);
|
|
|
|
if (forcedTail != null && !(element instanceof JavaPsiClassReferenceElement)) {
|
|
element = new TailTypeDecorator<>(element) {
|
|
@Override
|
|
protected TailType computeTailType(InsertionContext context) {
|
|
if (context.getCompletionChar() == ':' && forcedTail == JavaTailTypes.CASE_ARROW) {
|
|
return TailTypes.caseColonType();
|
|
}
|
|
return forcedTail;
|
|
}
|
|
};
|
|
}
|
|
|
|
if (inSwitchLabel && !smart) {
|
|
element = new IndentingDecorator(element);
|
|
}
|
|
if (originalFile instanceof PsiJavaCodeReferenceCodeFragment &&
|
|
!((PsiJavaCodeReferenceCodeFragment)originalFile).isClassesAccepted() && item != null) {
|
|
item.setTailType(TailTypes.noneType());
|
|
}
|
|
if (item instanceof JavaMethodCallElement call) {
|
|
PsiMethod method = call.getObject();
|
|
if (method.getTypeParameters().length > 0) {
|
|
PsiType returned = TypeConversionUtil.erasure(method.getReturnType());
|
|
ExpectedTypeInfo matchingExpectation = returned == null ? null : ContainerUtil.find(expectedTypes, info ->
|
|
info.getDefaultType().isAssignableFrom(returned) ||
|
|
AssignableFromFilter.isAcceptable(method, position, info.getDefaultType(), call.getSubstitutor()));
|
|
if (matchingExpectation != null) {
|
|
call.setInferenceSubstitutorFromExpectedType(position, matchingExpectation.getDefaultType());
|
|
}
|
|
}
|
|
}
|
|
items.add(element);
|
|
|
|
ContainerUtil.addIfNotNull(items, ArrayMemberAccess.accessFirstElement(position, element));
|
|
}
|
|
|
|
if (parameters.getInvocationCount() > 0) {
|
|
items.addAll(getInnerScopeVariables(parameters, position));
|
|
}
|
|
|
|
if (ref.getQualifier() instanceof PsiExpression qualifierExpression &&
|
|
parameters.getInvocationCount() <= 2 &&
|
|
AdvancedSettings.getBoolean("java.completion.qualifier.as.argument")) {
|
|
JavaQualifierAsArgumentStaticMembersProcessor processor =
|
|
new JavaQualifierAsArgumentStaticMembersProcessor(parameters, qualifierExpression);
|
|
|
|
items = ContainerUtil.map(items, lookupItem -> {
|
|
JavaMethodCallElement javaMethodCallElement = lookupItem.as(JavaMethodCallElement.class);
|
|
if (javaMethodCallElement != null) {
|
|
PsiMethod psiMethod = javaMethodCallElement.getObject();
|
|
Ref<LookupElement> staticref = new Ref<>();
|
|
processor.processStaticMember(staticItem -> staticref.set(staticItem), psiMethod, new HashSet<>());
|
|
if (!staticref.isNull()) {
|
|
return staticref.get();
|
|
}
|
|
}
|
|
return lookupItem;
|
|
});
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
@Nullable
|
|
private static TailType getTailType(boolean smart, boolean inSwitchLabel, PsiElement position) {
|
|
if (!smart && inSwitchLabel) {
|
|
if (position instanceof PsiClass) {
|
|
return TailTypes.insertSpaceType();
|
|
}
|
|
return JavaTailTypes.forSwitchLabel(Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)));
|
|
}
|
|
if (!smart && shouldInsertSemicolon(position)) {
|
|
return TailTypes.semicolonType();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static Collection<LookupElement> getInnerScopeVariables(CompletionParameters parameters, PsiElement position) {
|
|
PsiElement container = BringVariableIntoScopeFix.getContainer(position);
|
|
if (container == null) return Collections.emptyList();
|
|
Map<String, Optional<PsiLocalVariable>> variableMap =
|
|
EntryStream.ofTree(container, (depth, element) -> depth > 2 ? null : StreamEx.of(element.getChildren()))
|
|
.values()
|
|
.select(PsiCodeBlock.class)
|
|
.flatArray(PsiCodeBlock::getStatements)
|
|
.select(PsiDeclarationStatement.class)
|
|
.flatArray(PsiDeclarationStatement::getDeclaredElements)
|
|
.select(PsiLocalVariable.class)
|
|
.remove(var -> PsiTreeUtil.isAncestor(var, position, true))
|
|
.toMap(PsiLocalVariable::getName, Optional::of, (v1, v2) -> Optional.empty());
|
|
PsiResolveHelper helper = JavaPsiFacade.getInstance(parameters.getOriginalFile().getProject()).getResolveHelper();
|
|
variableMap.values().removeAll(Collections.singleton(Optional.<PsiLocalVariable>empty()));
|
|
variableMap.keySet().removeIf(name -> helper.resolveReferencedVariable(name, position) != null);
|
|
int offset = position.getTextRange().getStartOffset();
|
|
variableMap.values().removeIf(v -> v.orElseThrow().getTextRange().getStartOffset() > offset);
|
|
if (variableMap.isEmpty()) return Collections.emptyList();
|
|
return ContainerUtil.map(variableMap.values(), optVar -> {
|
|
assert optVar.isPresent();
|
|
PsiLocalVariable variable = optVar.get();
|
|
String place = getPlace(variable);
|
|
return new VariableLookupItem(variable, JavaBundle.message("completion.inner.scope.tail.text", place)).setPriority(-1);
|
|
});
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
private static String getPlace(PsiLocalVariable variable) {
|
|
String place = JavaBundle.message("completion.inner.scope");
|
|
PsiCodeBlock block = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
|
|
PsiElement statement = block == null ? null : block.getParent();
|
|
if (statement instanceof PsiTryStatement) {
|
|
place = ((PsiTryStatement)statement).getFinallyBlock() == block ? PsiKeyword.TRY + "-" + PsiKeyword.FINALLY : PsiKeyword.TRY;
|
|
}
|
|
else if (statement instanceof PsiCatchSection) {
|
|
place = PsiKeyword.CATCH;
|
|
}
|
|
else if (statement instanceof PsiSynchronizedStatement) {
|
|
place = PsiKeyword.SYNCHRONIZED;
|
|
}
|
|
else if (statement instanceof PsiBlockStatement) {
|
|
PsiElement parent = statement.getParent();
|
|
if (parent instanceof PsiWhileStatement) {
|
|
place = PsiKeyword.WHILE;
|
|
}
|
|
else if (parent instanceof PsiIfStatement) {
|
|
place = ((PsiIfStatement)parent).getThenBranch() == statement ? PsiKeyword.IF + "-then" : PsiKeyword.IF + "-" + PsiKeyword.ELSE;
|
|
}
|
|
}
|
|
return place;
|
|
}
|
|
|
|
private static @NotNull List<LookupElement> completePermitsListReference(@NotNull CompletionParameters parameters,
|
|
@NotNull PsiJavaCodeReferenceElement referenceElement,
|
|
@NotNull PrefixMatcher prefixMatcher) {
|
|
List<LookupElement> lookupElements = new SmartList<>();
|
|
PsiJavaFile psiJavaFile = ObjectUtils.tryCast(referenceElement.getContainingFile(), PsiJavaFile.class);
|
|
if (psiJavaFile == null) return lookupElements;
|
|
PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(psiJavaFile.getOriginalElement());
|
|
if (javaModule == null) {
|
|
String packageName = psiJavaFile.getPackageName();
|
|
PsiPackage psiPackage = JavaPsiFacade.getInstance(psiJavaFile.getProject()).findPackage(packageName);
|
|
if (psiPackage == null) return lookupElements;
|
|
for (PsiClass psiClass : psiPackage.getClasses(referenceElement.getResolveScope())) {
|
|
CompletionElement completionElement = new CompletionElement(psiClass, PsiSubstitutor.EMPTY);
|
|
JavaCompletionUtil.createLookupElements(completionElement, referenceElement).forEach(lookupElements::add);
|
|
}
|
|
}
|
|
else {
|
|
JavaClassNameCompletionContributor.addAllClasses(parameters, true, prefixMatcher, lookupElements::add);
|
|
}
|
|
return lookupElements;
|
|
}
|
|
|
|
static boolean shouldInsertSemicolon(PsiElement position) {
|
|
return position.getParent() instanceof PsiMethodReferenceExpression &&
|
|
LambdaHighlightingUtil.insertSemicolon(position.getParent().getParent());
|
|
}
|
|
|
|
private static List<LookupElement> processLabelReference(PsiLabelReference reference) {
|
|
return ContainerUtil.map(reference.getVariants(), s -> TailTypeDecorator.withTail(LookupElementBuilder.create(s),
|
|
TailTypes.semicolonType()));
|
|
}
|
|
|
|
static boolean isClassNamePossible(CompletionParameters parameters) {
|
|
boolean isSecondCompletion = parameters.getInvocationCount() >= 2;
|
|
|
|
PsiElement position = parameters.getPosition();
|
|
if (JavaKeywordCompletion.isInstanceofPlace(position) ||
|
|
JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN.accepts(position) ||
|
|
AFTER_ENUM_CONSTANT.accepts(position)) {
|
|
return false;
|
|
}
|
|
|
|
PsiElement parent = position.getParent();
|
|
if (!(parent instanceof PsiJavaCodeReferenceElement)) return isSecondCompletion;
|
|
if (((PsiJavaCodeReferenceElement)parent).getQualifier() != null) return isSecondCompletion;
|
|
|
|
if (parent instanceof PsiJavaCodeReferenceElementImpl &&
|
|
((PsiJavaCodeReferenceElementImpl)parent).getKindEnum(parent.getContainingFile()) == PsiJavaCodeReferenceElementImpl.Kind.PACKAGE_NAME_KIND) {
|
|
return false;
|
|
}
|
|
|
|
if (IN_SWITCH_LABEL.accepts(position)) {
|
|
return HighlightingFeature.PATTERNS_IN_SWITCH.isAvailable(parent);
|
|
}
|
|
|
|
if (psiElement().inside(PsiImportStatement.class).accepts(parent)) {
|
|
return isSecondCompletion;
|
|
}
|
|
|
|
PsiElement grand = parent.getParent();
|
|
if (grand instanceof PsiAnonymousClass) {
|
|
grand = grand.getParent();
|
|
}
|
|
if (grand instanceof PsiNewExpression && ((PsiNewExpression)grand).getQualifier() != null) {
|
|
return false;
|
|
}
|
|
|
|
return !JavaKeywordCompletion.isAfterPrimitiveOrArrayType(position);
|
|
}
|
|
|
|
public static boolean mayStartClassName(CompletionResultSet result) {
|
|
return InternalCompletionSettings.getInstance().mayStartClassNameCompletion(result);
|
|
}
|
|
|
|
private static void completeAnnotationAttributeName(@NotNull CompletionResultSet result,
|
|
@NotNull PsiElement position,
|
|
@NotNull PsiAnnotation anno,
|
|
@NotNull PsiClass annoClass) {
|
|
PsiNameValuePair[] existingPairs = anno.getParameterList().getAttributes();
|
|
|
|
methods: for (PsiMethod method : annoClass.getMethods()) {
|
|
if (!(method instanceof PsiAnnotationMethod)) continue;
|
|
|
|
String attrName = method.getName();
|
|
for (PsiNameValuePair existingAttr : existingPairs) {
|
|
if (PsiTreeUtil.isAncestor(existingAttr, position, false)) break;
|
|
if (Objects.equals(existingAttr.getName(), attrName) ||
|
|
PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.equals(attrName) && existingAttr.getName() == null) continue methods;
|
|
}
|
|
|
|
PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod)method).getDefaultValue();
|
|
String defText = defaultValue == null ? null : defaultValue.getText();
|
|
if (PsiKeyword.TRUE.equals(defText) || PsiKeyword.FALSE.equals(defText)) {
|
|
result.addElement(createAnnotationAttributeElement(method,
|
|
PsiKeyword.TRUE.equals(defText) ? PsiKeyword.FALSE : PsiKeyword.TRUE,
|
|
position));
|
|
result.addElement(PrioritizedLookupElement.withPriority(createAnnotationAttributeElement(method, defText, position)
|
|
.withTailText(" (default)", true), -1));
|
|
} else {
|
|
LookupElementBuilder element = createAnnotationAttributeElement(method, null, position);
|
|
if (defText != null) {
|
|
element = element.withTailText(" default " + defText, true);
|
|
}
|
|
result.addElement(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
private static LookupElementBuilder createAnnotationAttributeElement(@NotNull PsiMethod annoMethod,
|
|
@Nullable String value,
|
|
@NotNull PsiElement position) {
|
|
CommonCodeStyleSettings styleSettings = CodeStyle.getLanguageSettings(annoMethod.getContainingFile());
|
|
String space = ReferenceExpressionCompletionContributor.getSpace(styleSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS);
|
|
String lookupString = annoMethod.getName() + (value == null ? "" : space + "=" + space + value);
|
|
return LookupElementBuilder.create(annoMethod, lookupString).withIcon(annoMethod.getIcon(0))
|
|
.withStrikeoutness(JavaDeprecationUtils.isDeprecated(annoMethod, position))
|
|
.withInsertHandler((context, item) -> {
|
|
Editor editor = context.getEditor();
|
|
if (value == null) {
|
|
EqTailType.INSTANCE.processTail(editor, editor.getCaretModel().getOffset());
|
|
}
|
|
context.setAddCompletionChar(false);
|
|
|
|
context.commitDocument();
|
|
PsiAnnotationParameterList paramList =
|
|
PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiAnnotationParameterList.class, false);
|
|
Document document = context.getDocument();
|
|
if (paramList != null && paramList.getAttributes().length > 0 && paramList.getAttributes()[0].getName() == null) {
|
|
int valueOffset = paramList.getAttributes()[0].getTextRange().getStartOffset();
|
|
document.insertString(valueOffset, PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME);
|
|
EqTailType.INSTANCE.processTail(editor, valueOffset + PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.length());
|
|
}
|
|
int offset = editor.getCaretModel().getOffset();
|
|
CharSequence sequence = document.getCharsSequence();
|
|
if (hasAttributeNameAt(sequence, offset)) {
|
|
document.insertString(offset, styleSettings.SPACE_AFTER_COMMA ? ", " : ",");
|
|
}
|
|
});
|
|
}
|
|
|
|
private static boolean hasAttributeNameAt(@NotNull CharSequence sequence, int offset) {
|
|
int length = sequence.length();
|
|
if (length <= offset) return false;
|
|
char nextChar = sequence.charAt(offset);
|
|
if (!StringUtil.isJavaIdentifierStart(nextChar)) return false;
|
|
while (offset < length - 1 && StringUtil.isJavaIdentifierPart(sequence.charAt(offset + 1))) {
|
|
offset++;
|
|
}
|
|
while (offset < length - 1 && StringUtil.isWhiteSpace(sequence.charAt(offset + 1))) {
|
|
offset++;
|
|
}
|
|
return offset < length - 1 && sequence.charAt(offset + 1) == '=';
|
|
}
|
|
|
|
@Override
|
|
public String advertise(@NotNull CompletionParameters parameters) {
|
|
if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null;
|
|
|
|
if (parameters.getCompletionType() == CompletionType.BASIC && parameters.getInvocationCount() > 0) {
|
|
PsiElement position = parameters.getPosition();
|
|
if (psiElement().withParent(psiReferenceExpression().withFirstChild(psiReferenceExpression().referencing(psiClass()))).accepts(position)) {
|
|
if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.GLOBAL_MEMBER_NAME)) {
|
|
String shortcut = KeymapUtil.getFirstKeyboardShortcutText(IdeActions.ACTION_CODE_COMPLETION);
|
|
if (StringUtil.isNotEmpty(shortcut)) {
|
|
return JavaBundle.message("pressing.0.twice.without.a.class.qualifier", shortcut);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parameters.getCompletionType() != CompletionType.SMART && shouldSuggestSmartCompletion(parameters.getPosition())) {
|
|
if (CompletionUtil.shouldShowFeature(parameters, CodeCompletionFeatures.EDITING_COMPLETION_SMARTTYPE_GENERAL)) {
|
|
String shortcut = KeymapUtil.getFirstKeyboardShortcutText(IdeActions.ACTION_SMART_TYPE_COMPLETION);
|
|
if (StringUtil.isNotEmpty(shortcut)) {
|
|
return JavaBundle.message("completion.smart.hint", shortcut);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parameters.getCompletionType() == CompletionType.SMART && parameters.getInvocationCount() == 1) {
|
|
PsiType[] psiTypes = ExpectedTypesGetter.getExpectedTypes(parameters.getPosition(), true);
|
|
if (psiTypes.length > 0) {
|
|
if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_TOAR)) {
|
|
String shortcut = KeymapUtil.getFirstKeyboardShortcutText(IdeActions.ACTION_SMART_TYPE_COMPLETION);
|
|
if (StringUtil.isNotEmpty(shortcut)) {
|
|
for (PsiType psiType : psiTypes) {
|
|
PsiType type = PsiUtil.extractIterableTypeParameter(psiType, false);
|
|
if (type != null) {
|
|
return JavaBundle.message("completion.smart.aslist.hint", shortcut, type.getPresentableText());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_ASLIST)) {
|
|
String shortcut = KeymapUtil.getFirstKeyboardShortcutText(IdeActions.ACTION_SMART_TYPE_COMPLETION);
|
|
if (StringUtil.isNotEmpty(shortcut)) {
|
|
for (PsiType psiType : psiTypes) {
|
|
if (psiType instanceof PsiArrayType) {
|
|
PsiType componentType = ((PsiArrayType)psiType).getComponentType();
|
|
if (!(componentType instanceof PsiPrimitiveType)) {
|
|
return JavaBundle.message("completion.smart.toar.hint", shortcut, componentType.getPresentableText());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CompletionUtil.shouldShowFeature(parameters, JavaCompletionFeatures.SECOND_SMART_COMPLETION_CHAIN)) {
|
|
String shortcut = KeymapUtil.getFirstKeyboardShortcutText(IdeActions.ACTION_SMART_TYPE_COMPLETION);
|
|
if (StringUtil.isNotEmpty(shortcut)) {
|
|
return JavaBundle.message("completion.smart.chain.hint", shortcut);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public @NlsContexts.HintText String handleEmptyLookup(@NotNull CompletionParameters parameters, Editor editor) {
|
|
if (!(parameters.getOriginalFile() instanceof PsiJavaFile)) return null;
|
|
|
|
String ad = advertise(parameters);
|
|
String suffix = ad == null ? "" : "; " + StringUtil.decapitalize(ad);
|
|
if (parameters.getCompletionType() == CompletionType.SMART) {
|
|
PsiExpression expression = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true);
|
|
if (expression instanceof PsiLiteralExpression) {
|
|
return LangBundle.message("completion.no.suggestions") + suffix;
|
|
}
|
|
|
|
if (expression instanceof PsiInstanceOfExpression instanceOfExpression &&
|
|
PsiTreeUtil.isAncestor(instanceOfExpression.getCheckType(), parameters.getPosition(), false)) {
|
|
return LangBundle.message("completion.no.suggestions") + suffix;
|
|
}
|
|
|
|
Set<PsiType> expectedTypes = JavaCompletionUtil.getExpectedTypes(parameters);
|
|
if (expectedTypes != null) {
|
|
PsiType type = expectedTypes.size() == 1 ? expectedTypes.iterator().next() : null;
|
|
if (type != null) {
|
|
PsiType deepComponentType = type.getDeepComponentType();
|
|
String expectedType = type.getPresentableText();
|
|
if (expectedType.contains(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) {
|
|
return null;
|
|
}
|
|
|
|
if (deepComponentType instanceof PsiClassType) {
|
|
if (((PsiClassType)deepComponentType).resolve() != null) {
|
|
return JavaBundle.message("completion.no.suggestions.of.type", expectedType) + suffix;
|
|
}
|
|
return JavaBundle.message("completion.unknown.type", expectedType) + suffix;
|
|
}
|
|
if (!PsiTypes.nullType().equals(type)) {
|
|
return JavaBundle.message("completion.no.suggestions.of.type", expectedType) + suffix;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return LangBundle.message("completion.no.suggestions") + suffix;
|
|
}
|
|
|
|
@Override
|
|
public boolean invokeAutoPopup(@NotNull PsiElement position, char typeChar) {
|
|
return typeChar == ':' && JavaTokenType.COLON == PsiUtilCore.getElementType(position);
|
|
}
|
|
|
|
private static boolean shouldSuggestSmartCompletion(PsiElement element) {
|
|
if (shouldSuggestClassNameCompletion(element)) return false;
|
|
|
|
PsiElement parent = element.getParent();
|
|
if (parent instanceof PsiReferenceExpression && ((PsiReferenceExpression)parent).getQualifier() != null) return false;
|
|
if (parent instanceof PsiReferenceExpression && parent.getParent() instanceof PsiReferenceExpression) return true;
|
|
|
|
return ExpectedTypesGetter.getExpectedTypes(element, false).length > 0;
|
|
}
|
|
|
|
private static boolean shouldSuggestClassNameCompletion(PsiElement element) {
|
|
if (element == null) return false;
|
|
PsiElement parent = element.getParent();
|
|
if (parent == null) return false;
|
|
return parent.getParent() instanceof PsiTypeElement || parent.getParent() instanceof PsiExpressionStatement ||
|
|
parent.getParent() instanceof PsiReferenceList;
|
|
}
|
|
|
|
@Override
|
|
public void beforeCompletion(@NotNull CompletionInitializationContext context) {
|
|
PsiFile file = context.getFile();
|
|
|
|
if (file instanceof PsiJavaFile) {
|
|
String dummyIdentifier = customizeDummyIdentifier(context, file);
|
|
if (dummyIdentifier != null) {
|
|
context.setDummyIdentifier(dummyIdentifier);
|
|
}
|
|
|
|
PsiElement element = file.findElementAt(context.getStartOffset());
|
|
if (file.getName().equals(PsiJavaModule.MODULE_INFO_FILE)) {
|
|
if (element instanceof PsiWhiteSpace &&
|
|
element.textContains('\n') &&
|
|
element.getTextRange().getStartOffset() == context.getStartOffset()) {
|
|
context.setReplacementOffset(context.getStartOffset());
|
|
}
|
|
}
|
|
|
|
PsiLiteralExpression literal =
|
|
PsiTreeUtil.findElementOfClassAtOffset(file, context.getStartOffset(), PsiLiteralExpression.class, false);
|
|
if (literal != null) {
|
|
TextRange range = literal.getTextRange();
|
|
if (range.getStartOffset() == context.getStartOffset()) {
|
|
context.setReplacementOffset(range.getEndOffset());
|
|
}
|
|
}
|
|
|
|
PsiJavaCodeReferenceElement ref = getAnnotationNameIfInside(element);
|
|
if (ref != null) {
|
|
context.setReplacementOffset(ref.getTextRange().getEndOffset());
|
|
}
|
|
}
|
|
|
|
if (context.getCompletionType() == CompletionType.SMART) {
|
|
JavaSmartCompletionContributor.beforeSmartCompletion(context);
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
static PsiJavaCodeReferenceElement getAnnotationNameIfInside(@Nullable PsiElement position) {
|
|
PsiAnnotation anno = PsiTreeUtil.getParentOfType(position, PsiAnnotation.class);
|
|
PsiJavaCodeReferenceElement ref = anno == null ? null : anno.getNameReferenceElement();
|
|
return ref != null && PsiTreeUtil.isAncestor(ref, position, false) ? ref : null;
|
|
}
|
|
|
|
@Nullable
|
|
private static String customizeDummyIdentifier(@NotNull CompletionInitializationContext context, PsiFile file) {
|
|
if (context.getCompletionType() != CompletionType.BASIC) return null;
|
|
|
|
int offset = context.getStartOffset();
|
|
if (PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, PsiReferenceParameterList.class, false) != null) {
|
|
return CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED;
|
|
}
|
|
|
|
if (semicolonNeeded(file, offset)) {
|
|
return CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED + ";";
|
|
}
|
|
|
|
PsiElement leaf = file.findElementAt(offset);
|
|
if (leaf instanceof PsiIdentifier || leaf instanceof PsiKeyword) {
|
|
return CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public static boolean semicolonNeeded(PsiFile file, int startOffset) {
|
|
PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiJavaCodeReferenceElement.class, false);
|
|
if (ref != null && !(ref instanceof PsiReferenceExpression)) {
|
|
if (ref.getParent() instanceof PsiTypeElement) {
|
|
return true;
|
|
}
|
|
}
|
|
PsiElement at = file.findElementAt(startOffset);
|
|
if (psiElement(PsiIdentifier.class).withParent(psiParameter()).accepts(at)) {
|
|
return true;
|
|
}
|
|
|
|
if (PsiUtilCore.getElementType(at) == JavaTokenType.IDENTIFIER) {
|
|
at = PsiTreeUtil.nextLeaf(at);
|
|
}
|
|
|
|
at = skipWhitespacesAndComments(at);
|
|
|
|
if (PsiUtilCore.getElementType(at) == JavaTokenType.LPARENTH &&
|
|
PsiTreeUtil.getParentOfType(ref, PsiExpression.class, PsiClass.class) == null &&
|
|
PsiTreeUtil.getParentOfType(at, PsiImplicitClass.class) == null) { // TODO check before it that there is record
|
|
// looks like a method declaration, e.g. StringBui<caret>methodName() inside a class
|
|
return true;
|
|
}
|
|
|
|
if (PsiUtilCore.getElementType(at) == JavaTokenType.COLON &&
|
|
PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiConditionalExpression.class, false) == null) {
|
|
return true;
|
|
}
|
|
|
|
at = skipWhitespacesAndComments(at);
|
|
|
|
if (PsiUtilCore.getElementType(at) != JavaTokenType.IDENTIFIER) {
|
|
return false;
|
|
}
|
|
|
|
at = PsiTreeUtil.nextLeaf(at);
|
|
at = skipWhitespacesAndComments(at);
|
|
|
|
// <caret> foo = something, we don't want the reference to be treated as a type
|
|
return at != null && at.getNode().getElementType() == JavaTokenType.EQ;
|
|
}
|
|
|
|
@Nullable
|
|
private static PsiElement skipWhitespacesAndComments(@Nullable PsiElement at) {
|
|
PsiElement nextLeaf = at;
|
|
while (nextLeaf != null && (nextLeaf instanceof PsiWhiteSpace ||
|
|
nextLeaf instanceof PsiComment ||
|
|
nextLeaf instanceof PsiErrorElement ||
|
|
nextLeaf.getTextLength() == 0)) {
|
|
nextLeaf = PsiTreeUtil.nextLeaf(nextLeaf, true);
|
|
}
|
|
return nextLeaf;
|
|
}
|
|
|
|
private static boolean addExpectedTypeMembers(CompletionParameters parameters,
|
|
boolean searchInheritors,
|
|
Collection<? extends ExpectedTypeInfo> types,
|
|
Consumer<? super LookupElement> result) {
|
|
boolean[] added = new boolean[1];
|
|
boolean smart = parameters.getCompletionType() == CompletionType.SMART;
|
|
if (smart || parameters.getInvocationCount() <= 1) { // on second basic completion, StaticMemberProcessor will suggest those
|
|
Consumer<LookupElement> consumer = e -> {
|
|
added[0] = true;
|
|
result.consume(smart ? JavaSmartCompletionContributor.decorate(e, types) : e);
|
|
};
|
|
for (ExpectedTypeInfo info : types) {
|
|
new JavaMembersGetter(info.getType(), parameters).addMembers(searchInheritors, consumer);
|
|
if (!info.getType().equals(info.getDefaultType())) {
|
|
new JavaMembersGetter(info.getDefaultType(), parameters).addMembers(searchInheritors, consumer);
|
|
}
|
|
}
|
|
}
|
|
return added[0];
|
|
}
|
|
|
|
private static void addModuleReferences(PsiElement moduleRef, PsiFile originalFile, CompletionResultSet result) {
|
|
PsiElement statement = moduleRef.getParent();
|
|
boolean requires;
|
|
if ((requires = statement instanceof PsiRequiresStatement) || statement instanceof PsiPackageAccessibilityStatement) {
|
|
PsiElement parent = statement.getParent();
|
|
if (parent != null) {
|
|
Project project = moduleRef.getProject();
|
|
Set<String> filter = new HashSet<>();
|
|
filter.add(((PsiJavaModule)parent).getName());
|
|
|
|
JavaModuleNameIndex index = JavaModuleNameIndex.getInstance();
|
|
GlobalSearchScope scope = ProjectScope.getAllScope(project);
|
|
for (String name : index.getAllKeys(project)) {
|
|
if (!index.getModules(name, project, scope).isEmpty() && filter.add(name)) {
|
|
LookupElement lookup = LookupElementBuilder.create(name).withIcon(AllIcons.Nodes.JavaModule);
|
|
if (requires) lookup = TailTypeDecorator.withTail(lookup, TailTypes.semicolonType());
|
|
result.addElement(lookup);
|
|
}
|
|
}
|
|
|
|
if (requires) {
|
|
Module module = ModuleUtilCore.findModuleForFile(originalFile);
|
|
if (module != null) {
|
|
scope = ProjectScope.getAllScope(project);
|
|
Set<String> shadowedNames = new HashSet<>();
|
|
for (String name : JavaSourceModuleNameIndex.getAllKeys(project)) {
|
|
Collection<VirtualFile> manifests = JavaSourceModuleNameIndex.getFilesByKey(name, scope);
|
|
if (!manifests.isEmpty()) {
|
|
shadowedNames.add(name);
|
|
for (VirtualFile manifest : manifests) {
|
|
VirtualFile jarRoot = manifest.getParent().getParent();
|
|
if (jarRoot.getFileSystem() instanceof JarFileSystem) {
|
|
shadowedNames.add(LightJavaModule.moduleName(jarRoot.getNameWithoutExtension()));
|
|
}
|
|
}
|
|
addAutoModuleReference(name, parent, filter, result);
|
|
}
|
|
}
|
|
VirtualFile[] roots = ModuleRootManager.getInstance(module).orderEntries().withoutSdk().librariesOnly().getClassesRoots();
|
|
scope = GlobalSearchScope.filesScope(project, Arrays.asList(roots));
|
|
for (String name : JavaAutoModuleNameIndex.getAllKeys(project)) {
|
|
if (shadowedNames.contains(name)) {
|
|
continue;
|
|
}
|
|
if (!JavaAutoModuleNameIndex.getFilesByKey(name, scope).isEmpty()) {
|
|
addAutoModuleReference(name, parent, filter, result);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void addAutoModuleReference(String name, PsiElement parent, Set<? super String> filter, CompletionResultSet result) {
|
|
if (PsiNameHelper.isValidModuleName(name, parent) && filter.add(name)) {
|
|
LookupElement lookup = LookupElementBuilder.create(name).withIcon(AllIcons.FileTypes.Archive);
|
|
lookup = TailTypeDecorator.withTail(lookup, TailTypes.semicolonType());
|
|
lookup = PrioritizedLookupElement.withPriority(lookup, -1);
|
|
result.addElement(lookup);
|
|
}
|
|
}
|
|
|
|
static class IndentingDecorator extends LookupElementDecorator<LookupElement> {
|
|
IndentingDecorator(LookupElement delegate) {
|
|
super(delegate);
|
|
}
|
|
|
|
@Override
|
|
public void handleInsert(@NotNull InsertionContext context) {
|
|
super.handleInsert(context);
|
|
Project project = context.getProject();
|
|
Document document = context.getDocument();
|
|
int lineStartOffset = DocumentUtil.getLineStartOffset(context.getStartOffset(), document);
|
|
PsiDocumentManager.getInstance(project).commitDocument(document);
|
|
CodeStyleManager.getInstance(project).adjustLineIndent(context.getFile(), lineStartOffset);
|
|
}
|
|
}
|
|
|
|
private static class SearchScopeFilter implements ElementFilter {
|
|
private final GlobalSearchScope myScope;
|
|
|
|
SearchScopeFilter(GlobalSearchScope scope) {
|
|
myScope = scope;
|
|
}
|
|
|
|
@Override
|
|
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
|
|
if (element instanceof PsiPackage) {
|
|
return ((PsiDirectoryContainer)element).getDirectories(myScope).length > 0;
|
|
}
|
|
else if (element instanceof PsiElement) {
|
|
PsiFile psiFile = ((PsiElement)element).getContainingFile();
|
|
if (psiFile != null) {
|
|
VirtualFile file = psiFile.getVirtualFile();
|
|
return file != null && myScope.contains(file);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean isClassAcceptable(Class<?> hintClass) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static class EnumStaticFieldsFilter implements ElementFilter {
|
|
private final PsiClass myEnumClass;
|
|
|
|
private EnumStaticFieldsFilter(PsiClass enumClass) { myEnumClass = enumClass;}
|
|
|
|
@Override
|
|
public boolean isAcceptable(Object element, @Nullable PsiElement context) {
|
|
return !(element instanceof PsiField) || !GenericsHighlightUtil.isRestrictedStaticEnumField((PsiField)element, myEnumClass);
|
|
}
|
|
|
|
@Override
|
|
public boolean isClassAcceptable(Class<?> hintClass) {
|
|
return true;
|
|
}
|
|
}
|
|
} |