mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-214795 [java-completion] Some reference completion (partial classes, variables, fields, packages)
GitOrigin-RevId: 2f8f143e509eb686bc542ededd61aabe559214cf
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b19d370b89
commit
2c9ab44f07
@@ -1195,6 +1195,7 @@
|
||||
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.LabelReferenceItemProvider"/>
|
||||
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.AnnotationAttributeItemProvider"/>
|
||||
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.GenerateMemberItemProvider"/>
|
||||
<modcompletion.completionItemProvider language="JAVA" implementationClass="com.intellij.codeInsight.completion.modcompletion.ReferenceItemProvider"/>
|
||||
|
||||
<completion.skip implementation="com.intellij.codeInsight.completion.AbstractExpectedTypeSkipper" id="skipAbstract"/>
|
||||
<completion.skip implementation="com.intellij.codeInsight.completion.DeprecatedSkipper" id="skipDeprecated"/>
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -20,16 +21,17 @@ import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
class CheckInitialized implements ElementFilter {
|
||||
@ApiStatus.Internal
|
||||
public class CheckInitialized implements ElementFilter {
|
||||
private final Set<PsiField> myNonInitializedFields;
|
||||
private final boolean myInsideConstructorCall;
|
||||
|
||||
CheckInitialized(@NotNull PsiElement position) {
|
||||
public CheckInitialized(@NotNull PsiElement position) {
|
||||
myNonInitializedFields = getNonInitializedFields(position);
|
||||
myInsideConstructorCall = isInsideConstructorCall(position);
|
||||
}
|
||||
|
||||
static boolean isInsideConstructorCall(@NotNull PsiElement position) {
|
||||
public static boolean isInsideConstructorCall(@NotNull PsiElement position) {
|
||||
return JavaPsiConstructorUtil.isConstructorCall(PsiTreeUtil.getParentOfType(position, PsiMethodCallExpression.class)) &&
|
||||
!JavaKeywordCompletion.AFTER_DOT.accepts(position);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.intellij.codeInsight.guess.GuessManager;
|
||||
import com.intellij.codeInsight.lookup.*;
|
||||
import com.intellij.java.codeserver.core.JavaPsiModuleUtil;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.modcompletion.ModCompletionItemProvider;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
@@ -85,7 +86,7 @@ public final class JavaCompletionUtil {
|
||||
|
||||
public static final Key<Boolean> SUPER_METHOD_PARAMETERS = Key.create("SUPER_METHOD_PARAMETERS");
|
||||
|
||||
public static @Nullable Set<PsiType> getExpectedTypes(@NotNull CompletionParameters parameters) {
|
||||
public static @Nullable Set<PsiType> getExpectedTypes(@NotNull BaseCompletionParameters parameters) {
|
||||
PsiExpression expr = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true);
|
||||
if (expr != null) {
|
||||
Set<PsiType> set = new HashSet<>();
|
||||
@@ -328,48 +329,45 @@ public final class JavaCompletionUtil {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static boolean shouldCast(@NotNull LookupElement item,
|
||||
@NotNull PsiTypeLookupItem castTypeItem,
|
||||
@Nullable PsiType plainQualifier,
|
||||
@NotNull JavaCompletionProcessor processor,
|
||||
@NotNull Set<? extends PsiType> expectedTypes) {
|
||||
PsiType castType = castTypeItem.getType();
|
||||
public static boolean shouldCast(@Nullable PsiType plainQualifier,
|
||||
@NotNull JavaCompletionProcessor processor,
|
||||
@NotNull Set<? extends PsiType> expectedTypes,
|
||||
PsiType castType,
|
||||
@NotNull PsiMember member) {
|
||||
if (plainQualifier != null) {
|
||||
Object o = item.getObject();
|
||||
if (o instanceof PsiMethod) {
|
||||
if (plainQualifier instanceof PsiClassType && castType instanceof PsiClassType) {
|
||||
PsiMethod method = (PsiMethod)o;
|
||||
PsiClassType.ClassResolveResult plainResult = ((PsiClassType)plainQualifier).resolveGenerics();
|
||||
PsiClass plainClass = plainResult.getElement();
|
||||
HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature();
|
||||
PsiMethod plainMethod = plainClass == null ? null :
|
||||
StreamEx.ofTree(signature, s -> StreamEx.of(s.getSuperSignatures()))
|
||||
.map(sig -> MethodSignatureUtil.findMethodBySignature(plainClass, sig, true))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst().orElse(null);
|
||||
if (plainMethod != null) {
|
||||
PsiClassType.ClassResolveResult castResult = ((PsiClassType)castType).resolveGenerics();
|
||||
PsiClass castClass = castResult.getElement();
|
||||
if (member instanceof PsiMethod method &&
|
||||
plainQualifier instanceof PsiClassType classQualifier &&
|
||||
castType instanceof PsiClassType classCastType) {
|
||||
PsiClassType.ClassResolveResult plainResult = classQualifier.resolveGenerics();
|
||||
PsiClass plainClass = plainResult.getElement();
|
||||
HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature();
|
||||
PsiMethod plainMethod = plainClass == null ? null :
|
||||
StreamEx.ofTree(signature, s -> StreamEx.of(s.getSuperSignatures()))
|
||||
.map(sig -> MethodSignatureUtil.findMethodBySignature(plainClass, sig, true))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst().orElse(null);
|
||||
if (plainMethod != null) {
|
||||
PsiClassType.ClassResolveResult castResult = classCastType.resolveGenerics();
|
||||
PsiClass castClass = castResult.getElement();
|
||||
|
||||
if (castClass == null || !castClass.isInheritor(plainClass, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!processor.isAccessible(plainMethod)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PsiSubstitutor castSub = TypeConversionUtil.getSuperClassSubstitutor(plainClass, (PsiClassType)castType);
|
||||
PsiType typeAfterCast = toRaw(castSub.substitute(method.getReturnType()));
|
||||
PsiType typeDeclared = toRaw(plainResult.getSubstitutor().substitute(plainMethod.getReturnType()));
|
||||
return typeAfterCast != null && typeDeclared != null &&
|
||||
!typeAfterCast.equals(typeDeclared) &&
|
||||
ContainerUtil.exists(expectedTypes, et -> et.isAssignableFrom(typeAfterCast) && !et.isAssignableFrom(typeDeclared));
|
||||
if (castClass == null || !castClass.isInheritor(plainClass, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!processor.isAccessible(plainMethod)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PsiSubstitutor castSub = TypeConversionUtil.getSuperClassSubstitutor(plainClass, classCastType);
|
||||
PsiType typeAfterCast = toRaw(castSub.substitute(method.getReturnType()));
|
||||
PsiType typeDeclared = toRaw(plainResult.getSubstitutor().substitute(plainMethod.getReturnType()));
|
||||
return typeAfterCast != null && typeDeclared != null &&
|
||||
!typeAfterCast.equals(typeDeclared) &&
|
||||
ContainerUtil.exists(expectedTypes, et -> et.isAssignableFrom(typeAfterCast) && !et.isAssignableFrom(typeDeclared));
|
||||
}
|
||||
}
|
||||
|
||||
return containsMember(castType, o, true) && !containsMember(plainQualifier, o, true);
|
||||
return containsMember(castType, member, true) && !containsMember(plainQualifier, member, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -420,7 +418,12 @@ public final class JavaCompletionUtil {
|
||||
@Nullable PsiType plainQualifier,
|
||||
JavaCompletionProcessor processor,
|
||||
Set<? extends PsiType> expectedTypes) {
|
||||
return ContainerUtil.find(castTypeItems, c -> shouldCast(item, c, plainQualifier, processor, expectedTypes));
|
||||
PsiMember member = ObjectUtils.tryCast(item.getObject(), PsiMember.class);
|
||||
if (member == null) return null;
|
||||
return ContainerUtil.find(castTypeItems, c -> {
|
||||
PsiType castType = c.getType();
|
||||
return shouldCast(plainQualifier, processor, expectedTypes, castType, member);
|
||||
});
|
||||
}
|
||||
|
||||
private static @Nullable PsiType toRaw(@Nullable PsiType type) {
|
||||
@@ -536,7 +539,7 @@ public final class JavaCompletionUtil {
|
||||
return Collections.singletonList(JavaLookupElementBuilder.forMethod((PsiMethod)completion, PsiSubstitutor.EMPTY));
|
||||
}
|
||||
|
||||
if (completion instanceof PsiClass) {
|
||||
if (completion instanceof PsiClass && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
|
||||
List<JavaPsiClassReferenceElement> classItems = JavaClassNameCompletionContributor.createClassLookupItems(
|
||||
CompletionUtil.getOriginalOrSelf((PsiClass)completion),
|
||||
JavaClassNameCompletionContributor.AFTER_NEW.accepts(reference),
|
||||
@@ -548,7 +551,7 @@ public final class JavaCompletionUtil {
|
||||
|
||||
PsiSubstitutor substitutor = completionElement.getSubstitutor();
|
||||
if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
|
||||
if (completion instanceof PsiClass) {
|
||||
if (completion instanceof PsiClass && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
|
||||
JavaPsiClassReferenceElement classItem =
|
||||
JavaClassNameCompletionContributor.createClassLookupItem((PsiClass)completion, true).setSubstitutor(substitutor);
|
||||
return JavaConstructorCallElement.wrap(classItem, reference.getElement());
|
||||
@@ -563,14 +566,14 @@ public final class JavaCompletionUtil {
|
||||
item.setForcedQualifier(completionElement.getQualifierText());
|
||||
return Collections.singletonList(item);
|
||||
}
|
||||
if (completion instanceof PsiVariable) {
|
||||
if (completion instanceof PsiVariable && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
|
||||
if (completion instanceof PsiEnumConstant enumConstant &&
|
||||
PsiTreeUtil.isAncestor(enumConstant.getArgumentList(), reference.getElement(), true)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(new VariableLookupItem((PsiVariable)completion).setSubstitutor(substitutor).qualifyIfNeeded(reference, null));
|
||||
}
|
||||
if (completion instanceof PsiPackage) {
|
||||
if (completion instanceof PsiPackage && !ModCompletionItemProvider.modCommandCompletionEnabled()) {
|
||||
return Collections.singletonList(new PackageLookupItem((PsiPackage)completion, reference.getElement()));
|
||||
}
|
||||
|
||||
@@ -762,9 +765,13 @@ public final class JavaCompletionUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static boolean inSomePackage(@NotNull PsiElement context) {
|
||||
/**
|
||||
* @param context element from Java file
|
||||
* @return true if the element is located not in the unnamed package
|
||||
*/
|
||||
public static boolean inSomePackage(@NotNull PsiElement context) {
|
||||
PsiFile contextFile = context.getContainingFile();
|
||||
return contextFile instanceof PsiClassOwner && StringUtil.isNotEmpty(((PsiClassOwner)contextFile).getPackageName());
|
||||
return contextFile instanceof PsiClassOwner owner && StringUtil.isNotEmpty(owner.getPackageName());
|
||||
}
|
||||
|
||||
static boolean isSourceLevelAccessible(@NotNull PsiElement context,
|
||||
@@ -773,7 +780,7 @@ public final class JavaCompletionUtil {
|
||||
return isSourceLevelAccessible(context, psiClass, pkgContext, psiClass.getContainingClass());
|
||||
}
|
||||
|
||||
private static boolean isSourceLevelAccessible(PsiElement context,
|
||||
public static boolean isSourceLevelAccessible(PsiElement context,
|
||||
@NotNull PsiClass psiClass,
|
||||
boolean pkgContext,
|
||||
@Nullable PsiClass qualifierClass) {
|
||||
|
||||
+6
-1
@@ -211,7 +211,12 @@ public class JavaPsiClassReferenceElement extends LookupItem<Object> implements
|
||||
return CodeStyle.getLanguageSettings(element.getContainingFile(), JavaLanguage.INSTANCE).SPACE_AFTER_COMMA;
|
||||
}
|
||||
|
||||
static boolean isInaccessibleConstructorSuggestion(@NotNull PsiElement position, @Nullable PsiClass cls) {
|
||||
/**
|
||||
* @param position position to test
|
||||
* @param cls class where constructor is located
|
||||
* @return true if there's no accessible constructor of a given class in a given position.
|
||||
*/
|
||||
public static boolean isInaccessibleConstructorSuggestion(@NotNull PsiElement position, @Nullable PsiClass cls) {
|
||||
if (cls == null || cls.hasModifierProperty(PsiModifier.ABSTRACT)) return false;
|
||||
PsiMethod[] constructors = cls.getConstructors();
|
||||
if (constructors.length > 0) {
|
||||
|
||||
+4
@@ -134,6 +134,10 @@ public final class JavaSmartCompletionContributor {
|
||||
}
|
||||
|
||||
public static ExpectedTypeInfo @NotNull [] getExpectedTypes(final CompletionParameters parameters) {
|
||||
return getExpectedTypes((BaseCompletionParameters)parameters);
|
||||
}
|
||||
|
||||
public static ExpectedTypeInfo @NotNull [] getExpectedTypes(final BaseCompletionParameters parameters) {
|
||||
return getExpectedTypes(parameters.getPosition(), parameters.getCompletionType() == CompletionType.SMART);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.codeInsight.completion;
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElementPresentation;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
@@ -87,7 +88,7 @@ public final class MemberLookupHelper {
|
||||
|
||||
presentation.appendTailText(params, false);
|
||||
if (myShouldImport && !constructor && StringUtil.isNotEmpty(className)) {
|
||||
presentation.appendTailText(" in " + className + location, true);
|
||||
presentation.appendTailText(JavaBundle.message("member.in.class", className) + location, true);
|
||||
} else {
|
||||
presentation.appendTailText(location, true);
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import static com.intellij.patterns.PsiJavaPatterns.psiElement;
|
||||
public final class ReferenceExpressionCompletionContributor {
|
||||
private static final Logger LOG = Logger.getInstance(ReferenceExpressionCompletionContributor.class);
|
||||
|
||||
static @NotNull ElementFilter getReferenceFilter(PsiElement element, boolean allowRecursion) {
|
||||
public static @NotNull ElementFilter getReferenceFilter(PsiElement element, boolean allowRecursion) {
|
||||
//throw foo
|
||||
if (psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent(PsiThrowStatement.class)).accepts(element)) {
|
||||
return TrueFilter.INSTANCE;
|
||||
|
||||
+55
@@ -3,11 +3,13 @@ package com.intellij.codeInsight.completion.modcompletion;
|
||||
|
||||
import com.intellij.application.options.CodeStyle;
|
||||
import com.intellij.codeInsight.completion.AllClassesGetter;
|
||||
import com.intellij.codeInsight.completion.JavaCompletionUtil;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcompletion.ModCompletionItemPresentation;
|
||||
import com.intellij.modcompletion.PsiUpdateCompletionItem;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Iconable;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
@@ -15,7 +17,12 @@ import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.MarkupText;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
|
||||
import com.intellij.psi.impl.source.codeStyle.ImportHelper;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.javadoc.PsiDocTag;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.siyeh.ig.psiutils.JavaDeprecationUtils;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
@@ -57,9 +64,57 @@ public final class ClassReferenceCompletionItem extends PsiUpdateCompletionItem<
|
||||
|
||||
@Override
|
||||
public void update(ActionContext actionContext, InsertionContext insertionContext, ModPsiUpdater updater) {
|
||||
if (!contextObject().isValid()) return;
|
||||
if (processInImport(updater)) return;
|
||||
if (processJavaDoc(actionContext.offset(), updater)) return;
|
||||
AllClassesGetter.tryShorten(updater.getPsiFile(), updater, contextObject());
|
||||
}
|
||||
|
||||
private boolean processJavaDoc(int startOffset, ModPsiUpdater updater) {
|
||||
PsiFile file = updater.getPsiFile();
|
||||
int offset = updater.getCaretOffset();
|
||||
PsiElement position = file.findElementAt(offset - 1);
|
||||
PsiJavaCodeReferenceElement ref = position != null && position.getParent() instanceof PsiJavaCodeReferenceElement codeRef ?
|
||||
codeRef : null;
|
||||
String qname = myQualifiedName;
|
||||
if (qname != null && PsiTreeUtil.getParentOfType(position, PsiDocComment.class, false) != null &&
|
||||
(ref == null || !ref.isQualified()) &&
|
||||
shouldInsertFqnInJavadoc(file)) {
|
||||
updater.getDocument().replaceString(startOffset, offset, qname);
|
||||
return true;
|
||||
}
|
||||
|
||||
return ref != null && PsiTreeUtil.getParentOfType(position, PsiDocTag.class) != null && ref.isReferenceTo(contextObject());
|
||||
}
|
||||
|
||||
private boolean shouldInsertFqnInJavadoc(PsiFile file) {
|
||||
return switch (JavaCodeStyleSettings.getInstance(file).CLASS_NAMES_IN_JAVADOC) {
|
||||
case JavaCodeStyleSettings.FULLY_QUALIFY_NAMES_ALWAYS -> true;
|
||||
case JavaCodeStyleSettings.FULLY_QUALIFY_NAMES_IF_NOT_IMPORTED -> file instanceof PsiJavaFile javaFile && myQualifiedName != null &&
|
||||
!ImportHelper.isAlreadyImported(javaFile, myQualifiedName);
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean processInImport(ModPsiUpdater updater) {
|
||||
int offset = updater.getCaretOffset();
|
||||
PsiFile file = updater.getPsiFile();
|
||||
PsiImportStatementBase importStatement = PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, PsiImportStatementBase.class, false);
|
||||
if (importStatement == null) return false;
|
||||
PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, PsiJavaCodeReferenceElement.class, false);
|
||||
Document document = updater.getDocument();
|
||||
if (myQualifiedName != null && (ref == null || !myQualifiedName.equals(ref.getCanonicalText()))) {
|
||||
int start = JavaCompletionUtil.findQualifiedNameStart(offset, document);
|
||||
document.replaceString(start, offset, myQualifiedName);
|
||||
}
|
||||
if (importStatement instanceof PsiImportStaticStatement) {
|
||||
//context.setAddCompletionChar(false);
|
||||
document.insertString(offset, ".");
|
||||
updater.moveCaretTo(offset + 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModCompletionItemPresentation presentation() {
|
||||
String name = getName();
|
||||
|
||||
+394
-1
@@ -1,16 +1,409 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion.modcompletion;
|
||||
|
||||
import com.intellij.application.options.CodeStyle;
|
||||
import com.intellij.codeInsight.ExpectedTypeInfo;
|
||||
import com.intellij.codeInsight.JavaTailTypes;
|
||||
import com.intellij.codeInsight.ModNavigatorTailType;
|
||||
import com.intellij.codeInsight.completion.*;
|
||||
import com.intellij.codeInsight.completion.scope.CompletionElement;
|
||||
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
|
||||
import com.intellij.codeInsight.guess.GuessManager;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.java.syntax.parser.JavaKeywords;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import com.intellij.modcommand.ModCommandExecutor;
|
||||
import com.intellij.modcompletion.ModCompletionItem;
|
||||
import com.intellij.modcompletion.ModCompletionItemPresentation;
|
||||
import com.intellij.modcompletion.ModCompletionItemProvider;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Iconable;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.MarkupText;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.patterns.ElementPattern;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.filters.AndFilter;
|
||||
import com.intellij.psi.filters.ElementExtractorFilter;
|
||||
import com.intellij.psi.filters.ElementFilter;
|
||||
import com.intellij.psi.filters.TrueFilter;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.ui.IconManager;
|
||||
import com.intellij.ui.PlatformIcons;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.JBIterable;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.intellij.patterns.PlatformPatterns.psiElement;
|
||||
import static com.intellij.patterns.PsiJavaPatterns.psiMethod;
|
||||
import static com.intellij.patterns.PsiJavaPatterns.virtualFile;
|
||||
|
||||
@NotNullByDefault
|
||||
final class ReferenceItemProvider implements ModCompletionItemProvider {
|
||||
|
||||
private static final ElementPattern<PsiElement> TOP_LEVEL_VAR_IN_MODULE = psiElement().withSuperParent(3, PsiJavaFile.class)
|
||||
.inVirtualFile(virtualFile().withName("module-info.java"));
|
||||
private static final ElementPattern<PsiElement> INSIDE_TYPECAST_EXPRESSION = psiElement().withParent(
|
||||
psiElement(PsiReferenceExpression.class).afterLeaf(
|
||||
psiElement().withText(")").withParent(PsiTypeCastExpression.class)));
|
||||
private static final ElementPattern<PsiElement> INSIDE_CONSTRUCTOR = psiElement().inside(psiMethod().constructor(true));
|
||||
private static final ElementPattern<PsiElement> AFTER_NEW = psiElement().afterLeaf(psiElement().withText(JavaKeywords.NEW));
|
||||
|
||||
@Override
|
||||
public void provideItems(CompletionContext context, Consumer<ModCompletionItem> sink) {
|
||||
PsiElement position = context.getPosition();
|
||||
if (!(position.getParent() instanceof PsiJavaCodeReferenceElement ref)) return;
|
||||
if (TOP_LEVEL_VAR_IN_MODULE.accepts(position) ||
|
||||
JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN.accepts(position)) {
|
||||
return;
|
||||
}
|
||||
Set<ExpectedTypeInfo> expectedTypes = ContainerUtil.newHashSet(
|
||||
JavaSmartCompletionContributor.getExpectedTypes(position, context.isSmart()));
|
||||
ElementFilter filter = JavaCompletionContributor.getReferenceFilter(position);
|
||||
if (filter == null) return;
|
||||
if (context.invocationCount() <= 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 = context.isSmart();
|
||||
if (smart) {
|
||||
if (INSIDE_TYPECAST_EXPRESSION.accepts(position) || inCastContext(context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ElementFilter smartRestriction = ReferenceExpressionCompletionContributor.getReferenceFilter(position, false);
|
||||
if (smartRestriction != TrueFilter.INSTANCE) {
|
||||
filter = new AndFilter(filter, smartRestriction);
|
||||
}
|
||||
}
|
||||
|
||||
boolean inSwitchLabel = JavaCompletionContributor.IN_SWITCH_LABEL.accepts(position);
|
||||
ModNavigatorTailType forcedTail = getTailType(smart, inSwitchLabel, position);
|
||||
|
||||
if (INSIDE_CONSTRUCTOR.accepts(position) &&
|
||||
(context.invocationCount() <= 1 || CheckInitialized.isInsideConstructorCall(position))) {
|
||||
filter = new AndFilter(filter, new CheckInitialized(position));
|
||||
}
|
||||
PsiFile originalFile = context.getOriginalFile();
|
||||
|
||||
boolean first = context.invocationCount() <= 1;
|
||||
boolean instantiableOnly = AFTER_NEW.accepts(position);
|
||||
JavaCompletionProcessor.Options options =
|
||||
JavaCompletionProcessor.Options.DEFAULT_OPTIONS
|
||||
.withCheckAccess(first)
|
||||
.withFilterStaticAfterInstance(first)
|
||||
.withInstantiableOnly(instantiableOnly)
|
||||
.withShowInstanceInStaticContext(!first && !smart);
|
||||
|
||||
processJavaReference(position, ref, new ElementExtractorFilter(filter),
|
||||
options, context, element -> {
|
||||
if (forcedTail != null && element instanceof CommonCompletionItem commonItem) {
|
||||
element = commonItem.withTail(forcedTail);
|
||||
}
|
||||
|
||||
if (inSwitchLabel && !smart && element instanceof CommonCompletionItem commonItem) {
|
||||
element = commonItem.adjustIndent();
|
||||
}
|
||||
//if (originalFile instanceof PsiJavaCodeReferenceCodeFragment fragment &&
|
||||
// !fragment.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());
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
sink.accept(element);
|
||||
|
||||
CommonCompletionItem firstArrayElement = accessFirstElement(element);
|
||||
if (firstArrayElement != null) {
|
||||
sink.accept(firstArrayElement);
|
||||
}
|
||||
});
|
||||
|
||||
//if (context.invocationCount() > 0) {
|
||||
// items.addAll(getInnerScopeVariables(parameters, position));
|
||||
//}
|
||||
}
|
||||
|
||||
private static @Nullable CommonCompletionItem accessFirstElement(ModCompletionItem item) {
|
||||
if (item.contextObject() instanceof PsiLocalVariable variable) {
|
||||
final PsiType type = variable.getType();
|
||||
final PsiExpression expression = variable.getInitializer();
|
||||
if (type instanceof PsiArrayType arrayType && expression instanceof PsiNewExpression newExpression) {
|
||||
final PsiExpression[] dimensions = newExpression.getArrayDimensions();
|
||||
if (dimensions.length == 1 && "1".equals(dimensions[0].getText()) && newExpression.getArrayInitializer() == null) {
|
||||
final String text = variable.getName() + "[0]";
|
||||
return new CommonCompletionItem(text)
|
||||
.withPresentation(new ModCompletionItemPresentation(MarkupText.plainText(text))
|
||||
.withMainIcon(variable.getIcon(Iconable.ICON_FLAG_VISIBILITY))
|
||||
.withDetailText(JavaModCompletionUtils.typeMarkup(arrayType.getComponentType())));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean inCastContext(CompletionContext context) {
|
||||
PsiElement position = context.getPosition();
|
||||
PsiElement parent = getParenthesisOwner(position);
|
||||
if (parent instanceof PsiTypeCastExpression) return true;
|
||||
if (parent instanceof PsiParenthesizedExpression) {
|
||||
return context.getOffset() == position.getTextRange().getStartOffset();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement getParenthesisOwner(PsiElement position) {
|
||||
PsiElement lParen = PsiTreeUtil.prevVisibleLeaf(position);
|
||||
return lParen == null || !lParen.textMatches("(") ? null : lParen.getParent();
|
||||
}
|
||||
|
||||
private static @Nullable ModNavigatorTailType getTailType(boolean smart, boolean inSwitchLabel, PsiElement position) {
|
||||
if (!smart && inSwitchLabel) {
|
||||
if (position instanceof PsiClass) {
|
||||
return ModNavigatorTailType.insertSpaceType();
|
||||
}
|
||||
return JavaTailTypes.forSwitchLabel(Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)));
|
||||
}
|
||||
if (!smart && shouldInsertSemicolon(position)) {
|
||||
return ModNavigatorTailType.semicolonType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean shouldInsertSemicolon(PsiElement position) {
|
||||
return position.getParent() instanceof PsiMethodReferenceExpression &&
|
||||
JavaFrontendCompletionUtil.insertSemicolon(position.getParent().getParent());
|
||||
}
|
||||
|
||||
private static List<PsiType> getQualifierCastTypes(PsiJavaReference javaReference, int invocationCount) {
|
||||
if (javaReference instanceof PsiReferenceExpression refExpr) {
|
||||
PsiExpression qualifier = refExpr.getQualifierExpression();
|
||||
if (qualifier != null) {
|
||||
Project project = qualifier.getProject();
|
||||
return GuessManager.getInstance(project).getControlFlowExpressionTypeConjuncts(qualifier, invocationCount > 1);
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private static void processJavaReference(PsiElement element,
|
||||
PsiJavaCodeReferenceElement javaReference,
|
||||
ElementFilter elementFilter,
|
||||
JavaCompletionProcessor.Options options,
|
||||
CompletionContext parameters,
|
||||
Consumer<ModCompletionItem> sink) {
|
||||
Condition<String> nameCondition = s -> true;
|
||||
JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, options, nameCondition);
|
||||
PsiType plainQualifier = processor.getQualifierType();
|
||||
|
||||
List<PsiType> runtimeQualifiers = getQualifierCastTypes(javaReference, parameters.invocationCount());
|
||||
if (!runtimeQualifiers.isEmpty()) {
|
||||
PsiType[] conjuncts = JBIterable.of(plainQualifier).append(runtimeQualifiers).toArray(PsiType.EMPTY_ARRAY);
|
||||
PsiType composite = PsiIntersectionType.createIntersection(false, conjuncts);
|
||||
PsiElement ctx = JavaCompletionUtil.createContextWithXxxVariable(element, composite);
|
||||
javaReference = (PsiReferenceExpression)JavaPsiFacade.getElementFactory(parameters.getProject())
|
||||
.createExpressionFromText("xxx.xxx", ctx);
|
||||
processor.setQualifierType(composite);
|
||||
}
|
||||
|
||||
javaReference.processVariants(processor);
|
||||
|
||||
List<PsiTypeCompletionItem> castItems = ContainerUtil.map(runtimeQualifiers, q -> new PsiTypeCompletionItem(q, q.getPresentableText()));
|
||||
|
||||
boolean pkgContext = JavaCompletionUtil.inSomePackage(element);
|
||||
|
||||
PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(plainQualifier);
|
||||
boolean honorExcludes = qualifierClass == null || !JavaCompletionUtil.isInExcludedPackage(qualifierClass, false);
|
||||
|
||||
Set<PsiType> expectedTypes = ObjectUtils.coalesce(JavaCompletionUtil.getExpectedTypes(parameters), Collections.emptySet());
|
||||
|
||||
//Set<PsiMember> mentioned = new HashSet<>();
|
||||
//JavaCompletionUtil.JavaLookupElementHighlighter highlighter = getHighlighterForPlace(element, parameters.getOriginalFile().getVirtualFile());
|
||||
for (CompletionElement completionElement : processor.getResults()) {
|
||||
for (ModCompletionItem item : createLookupElements(completionElement, javaReference)) {
|
||||
//item.putUserData(QUALIFIER_TYPE_ATTR, plainQualifier);
|
||||
Object o = item.contextObject();
|
||||
if (o instanceof PsiClass psiClass) {
|
||||
PsiClass specifiedQualifierClass = javaReference.isQualified() ? qualifierClass : (psiClass).getContainingClass();
|
||||
if (!JavaCompletionUtil.isSourceLevelAccessible(element, psiClass, pkgContext, specifiedQualifierClass)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (o instanceof PsiMember member) {
|
||||
if (honorExcludes && JavaCompletionUtil.isInExcludedPackage(member, true)) {
|
||||
continue;
|
||||
}
|
||||
//mentioned.add(CompletionUtil.getOriginalOrSelf(member));
|
||||
}
|
||||
PsiTypeCompletionItem qualifierCast = null;
|
||||
PsiMember member = ObjectUtils.tryCast(item.contextObject(), PsiMember.class);
|
||||
if (member != null) {
|
||||
qualifierCast = ContainerUtil.find(castItems, c -> {
|
||||
PsiType castType = c.getType();
|
||||
return JavaCompletionUtil.shouldCast(plainQualifier, processor, expectedTypes, castType, member);
|
||||
});
|
||||
}
|
||||
// TODO: support VariableCompletionItem, etc.
|
||||
if (qualifierCast != null && item instanceof CommonCompletionItem cci) item = castQualifier(cci, qualifierCast);
|
||||
sink.accept(item);
|
||||
//set.add(highlighter.highlightIfNeeded(qualifierCast != null ? qualifierCast.getType() : plainQualifier, item, o));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//PsiElement refQualifier = javaReference.getQualifier();
|
||||
//if (refQualifier == null && PsiTreeUtil.getParentOfType(element, PsiPackageStatement.class, PsiImportStatementBase.class) == null) {
|
||||
// StaticMemberProcessor memberProcessor = new JavaStaticMemberProcessor(parameters);
|
||||
// memberProcessor.processMembersOfRegisteredClasses(nameCondition, (member, psiClass) -> {
|
||||
// if (!mentioned.contains(member) && processor.satisfies(member, ResolveState.initial())) {
|
||||
// ContainerUtil.addIfNotNull(set, memberProcessor.createLookupElement(member, psiClass, true));
|
||||
// }
|
||||
// });
|
||||
//}
|
||||
//else if (refQualifier instanceof PsiSuperExpression && ((PsiSuperExpression)refQualifier).getQualifier() == null) {
|
||||
// set.addAll(SuperCalls.suggestQualifyingSuperCalls(element, javaReference, elementFilter, options, nameCondition));
|
||||
//}
|
||||
}
|
||||
|
||||
private static @Unmodifiable List<ModCompletionItem> createLookupElements(CompletionElement completionElement, PsiJavaReference reference) {
|
||||
Object completion = completionElement.getElement();
|
||||
assert !(completion instanceof LookupElement);
|
||||
|
||||
if (reference instanceof PsiJavaCodeReferenceElement ref) {
|
||||
if (completion instanceof PsiMethod method &&
|
||||
ref.getParent() instanceof PsiImportStaticStatement) {
|
||||
String parameters = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY,
|
||||
PsiFormatUtilBase.SHOW_PARAMETERS,
|
||||
PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_TYPE);
|
||||
ModCompletionItemPresentation presentation =
|
||||
new ModCompletionItemPresentation(
|
||||
MarkupText.plainText(method.getName()).concat(parameters, MarkupText.Kind.GRAYED))
|
||||
.withMainIcon(method.getIcon(Iconable.ICON_FLAG_VISIBILITY))
|
||||
.withDetailText(JavaModCompletionUtils.typeMarkup(method.getReturnType()));
|
||||
return List.of(new CommonCompletionItem(method.getName())
|
||||
.withObject(method)
|
||||
.withPresentation(presentation));
|
||||
}
|
||||
|
||||
//if (completion instanceof PsiClass) {
|
||||
// List<JavaPsiClassReferenceElement> classItems = JavaClassNameCompletionContributor.createClassLookupItems(
|
||||
// CompletionUtil.getOriginalOrSelf((PsiClass)completion),
|
||||
// JavaClassNameCompletionContributor.AFTER_NEW.accepts(reference),
|
||||
// JavaClassNameInsertHandler.JAVA_CLASS_INSERT_HANDLER,
|
||||
// Conditions.alwaysTrue());
|
||||
// return JBIterable.from(classItems).flatMap(i -> JavaConstructorCallElement.wrap(i, reference.getElement()));
|
||||
//}
|
||||
}
|
||||
|
||||
PsiSubstitutor substitutor = completionElement.getSubstitutor();
|
||||
if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
|
||||
if (completion instanceof PsiClass cls) {
|
||||
return List.of(new ClassReferenceCompletionItem(cls).withSubstitutor(substitutor));
|
||||
//return JavaConstructorCallElement.wrap(classItem, reference.getElement());
|
||||
}
|
||||
//if (completion instanceof PsiMethod) {
|
||||
// if (reference instanceof PsiMethodReferenceExpression) {
|
||||
// return Collections.singleton((LookupElement)new JavaMethodReferenceElement(
|
||||
// (PsiMethod)completion, (PsiMethodReferenceExpression)reference, completionElement.getMethodRefType()));
|
||||
// }
|
||||
//
|
||||
// JavaMethodCallElement item = new JavaMethodCallElement((PsiMethod)completion).setQualifierSubstitutor(substitutor);
|
||||
// item.setForcedQualifier(completionElement.getQualifierText());
|
||||
// return Collections.singletonList(item);
|
||||
//}
|
||||
if (completion instanceof PsiVariable var) {
|
||||
if (completion instanceof PsiEnumConstant enumConstant &&
|
||||
PsiTreeUtil.isAncestor(enumConstant.getArgumentList(), reference.getElement(), true)) {
|
||||
return List.of();
|
||||
}
|
||||
return List.of(new VariableCompletionItem(var).withSubstitutor(substitutor).qualifyIfNeeded(reference, null));
|
||||
}
|
||||
if (completion instanceof PsiPackage pkg) {
|
||||
PsiElement context = reference.getElement();
|
||||
PsiFile file = context.getContainingFile();
|
||||
boolean inExportsOpens = context.getParent() instanceof PsiPackageAccessibilityStatement;
|
||||
boolean addDot = !inExportsOpens && (!(file instanceof PsiJavaCodeReferenceCodeFragment ref) || ref.isClassesAccepted());
|
||||
return List.of(new CommonCompletionItem(StringUtil.notNullize(pkg.getName()))
|
||||
.withObject(pkg)
|
||||
.withTail(addDot ? ModNavigatorTailType.dotType() : ModNavigatorTailType.noneType())
|
||||
.withPresentation(new ModCompletionItemPresentation(MarkupText.plainText(pkg.getName()+(addDot?".":"")))
|
||||
.withMainIcon(IconManager.getInstance().getPlatformIcon(PlatformIcons.Package))));
|
||||
}
|
||||
//
|
||||
//return Collections.singletonList(LookupItemUtil.objectToLookupItem(completion));
|
||||
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private static CommonCompletionItem castQualifier(CommonCompletionItem item, PsiTypeCompletionItem castTypeItem) {
|
||||
return item.withAdditionalUpdater((completionStart, updater) -> {
|
||||
Document document = updater.getDocument();
|
||||
PsiDocumentManager.getInstance(updater.getProject()).commitDocument(document);
|
||||
PsiFile file = updater.getPsiFile();
|
||||
PsiJavaCodeReferenceElement ref =
|
||||
PsiTreeUtil.findElementOfClassAtOffset(file, completionStart, PsiJavaCodeReferenceElement.class, false);
|
||||
if (ref != null) {
|
||||
PsiElement qualifier = ref.getQualifier();
|
||||
if (qualifier != null) {
|
||||
Language lang = PsiUtilCore.getLanguageAtOffset(file, updater.getCaretOffset());
|
||||
CommonCodeStyleSettings settings = CodeStyle.getLanguageSettings(file, lang);
|
||||
|
||||
String parenSpace = settings.SPACE_WITHIN_PARENTHESES ? " " : "";
|
||||
document.insertString(qualifier.getTextRange().getEndOffset(), parenSpace + ")");
|
||||
|
||||
String spaceWithin = settings.SPACE_WITHIN_CAST_PARENTHESES ? " " : "";
|
||||
String prefix = "(" + parenSpace + "(" + spaceWithin;
|
||||
String spaceAfter = settings.SPACE_AFTER_TYPE_CAST ? " " : "";
|
||||
int exprStart = qualifier.getTextRange().getStartOffset();
|
||||
document.insertString(exprStart, prefix + spaceWithin + ")" + spaceAfter);
|
||||
|
||||
ActionContext ctx = new ActionContext(updater.getProject(), updater.getPsiFile(),
|
||||
updater.getCaretOffset(), TextRange.from(updater.getCaretOffset(), 0),
|
||||
null);
|
||||
ModCommand command = castTypeItem.perform(ctx, ModCompletionItem.DEFAULT_INSERTION_CONTEXT);
|
||||
ModCommandExecutor.getInstance().executeForFileCopy(command, updater.getPsiFile());
|
||||
}
|
||||
}
|
||||
});
|
||||
//
|
||||
// @Override
|
||||
// public void renderElement(@NotNull LookupElementPresentation presentation) {
|
||||
// super.renderElement(presentation);
|
||||
//
|
||||
// presentation.appendTailText(" on " + castTypeItem.getType().getPresentableText(), true);
|
||||
// }
|
||||
//};
|
||||
}
|
||||
}
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion.modcompletion;
|
||||
|
||||
import com.intellij.codeInsight.completion.JavaCompletionUtil;
|
||||
import com.intellij.codeInsight.completion.MemberLookupHelper;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.BringVariableIntoScopeFix;
|
||||
import com.intellij.codeInsight.lookup.VariableLookupItem;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.modcommand.ActionContext;
|
||||
import com.intellij.modcommand.ModCommandExecutor;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcompletion.ModCompletionItemPresentation;
|
||||
import com.intellij.modcompletion.PsiUpdateCompletionItem;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.NlsSafe;
|
||||
import com.intellij.openapi.util.text.MarkupText;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.pom.java.JavaFeature;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
||||
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
|
||||
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.JavaDeprecationUtils;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNullByDefault;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@NotNullByDefault
|
||||
final class VariableCompletionItem extends PsiUpdateCompletionItem<PsiVariable> {
|
||||
private final @Nullable MemberLookupHelper myHelper;
|
||||
private final boolean myNegatable;
|
||||
@NlsSafe private final @Nullable String myTailText;
|
||||
private final PsiSubstitutor mySubstitutor;
|
||||
private final @Nullable PsiClass myQualifierClass;
|
||||
|
||||
VariableCompletionItem(PsiVariable var) {
|
||||
this(var, null, null, PsiSubstitutor.EMPTY, null);
|
||||
}
|
||||
|
||||
VariableCompletionItem(PsiField field, boolean shouldImport) {
|
||||
this(field, new MemberLookupHelper(field, field.getContainingClass(), shouldImport, false), null, PsiSubstitutor.EMPTY, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param var variable to lookup
|
||||
* @param tailText specific tail text to insert (initializer text is used if not specified)
|
||||
*/
|
||||
VariableCompletionItem(PsiVariable var, @Nls String tailText) {
|
||||
this(var, null, tailText, PsiSubstitutor.EMPTY, null);
|
||||
}
|
||||
|
||||
private VariableCompletionItem(PsiVariable var,
|
||||
@Nullable MemberLookupHelper helper,
|
||||
@Nullable @Nls String tailText,
|
||||
PsiSubstitutor substitutor,
|
||||
@Nullable PsiClass qualifierClass) {
|
||||
super(Objects.requireNonNull(var.getName()), var);
|
||||
myHelper = helper;
|
||||
myTailText = tailText == null ? VariableLookupItem.getInitializerText(var) : tailText;
|
||||
myNegatable = TypeConversionUtil.isBooleanType(var.getType());
|
||||
mySubstitutor = substitutor;
|
||||
myQualifierClass = qualifierClass;
|
||||
}
|
||||
|
||||
public VariableCompletionItem withSubstitutor(PsiSubstitutor substitutor) {
|
||||
return new VariableCompletionItem(contextObject(), myHelper, myTailText, substitutor, myQualifierClass);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public VariableCompletionItem qualifyIfNeeded(@Nullable PsiReference position, @Nullable PsiClass origClass) {
|
||||
PsiVariable var = contextObject();
|
||||
if (var instanceof PsiField field && !shouldImport() && VariableLookupItem.shouldQualify(field, position)) {
|
||||
PsiClass aClass = field.getContainingClass();
|
||||
if (aClass != null && origClass != null &&
|
||||
!JavaResolveUtil.isAccessible(aClass, aClass.getContainingClass(), aClass.getModifierList(), position.getElement(), null, null) &&
|
||||
JavaResolveUtil.isAccessible(origClass, origClass.getContainingClass(), origClass.getModifierList(), position.getElement(), null,
|
||||
null) &&
|
||||
var.isEquivalentTo(origClass.findFieldByName(var.getName(), true))) {
|
||||
aClass = origClass;
|
||||
}
|
||||
if (aClass != null && aClass.getName() != null) {
|
||||
return new VariableCompletionItem(var, myHelper, myTailText, mySubstitutor, aClass);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private boolean shouldImport() {
|
||||
return myHelper != null && myHelper.willBeImported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<@NlsSafe String> additionalLookupStrings() {
|
||||
if (myQualifierClass != null) {
|
||||
String infix = contextObject().hasModifierProperty(PsiModifier.STATIC) ? "." : ".this.";
|
||||
String suffix = infix + contextObject().getName();
|
||||
return ContainerUtil.map2Set(JavaCompletionUtil.getAllLookupStrings(myQualifierClass),
|
||||
s -> s + suffix);
|
||||
}
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
private @Nullable String getForcedQualifier() {
|
||||
if (myQualifierClass == null) return null;
|
||||
String className = myQualifierClass.getName();
|
||||
if (className == null) return null;
|
||||
return contextObject().hasModifierProperty(PsiModifier.STATIC) ? className + "." : className + ".this.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ActionContext actionContext, InsertionContext insertionContext, ModPsiUpdater updater) {
|
||||
PsiVariable variable = contextObject();
|
||||
Document document = updater.getDocument();
|
||||
PsiFile file = updater.getPsiFile();
|
||||
Project project = updater.getProject();
|
||||
|
||||
if (variable instanceof PsiField field) {
|
||||
if (shouldImport()) {
|
||||
RangeMarker toDelete = JavaCompletionUtil.insertTemporary(updater.getCaretOffset(), document, " ");
|
||||
PsiDocumentManager.getInstance(project).commitDocument(document);
|
||||
PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(
|
||||
file, actionContext.offset(), PsiReferenceExpression.class, false);
|
||||
if (ref != null) {
|
||||
PsiClass containingClass = field.getContainingClass();
|
||||
if (!ref.isQualified() && containingClass != null) {
|
||||
ref.bindToElementViaStaticImport(containingClass);
|
||||
PostprocessReformattingAspect.getInstance(project).doPostponedFormatting();
|
||||
}
|
||||
}
|
||||
if (toDelete != null && toDelete.isValid()) {
|
||||
document.deleteString(toDelete.getStartOffset(), toDelete.getEndOffset());
|
||||
}
|
||||
PsiDocumentManager.getInstance(project).commitDocument(document);
|
||||
}
|
||||
else if (VariableLookupItem.shouldQualify(field, file.findReferenceAt(updater.getCaretOffset() - 1))) {
|
||||
qualifyFieldReference(actionContext.offset(), updater, field);
|
||||
}
|
||||
}
|
||||
|
||||
PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(file, updater.getCaretOffset() - 1, PsiReferenceExpression.class, false);
|
||||
if (ref != null) {
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(ref);
|
||||
}
|
||||
|
||||
ref = PsiTreeUtil.findElementOfClassAtOffset(file, updater.getCaretOffset() - 1, PsiReferenceExpression.class, false);
|
||||
PsiElement target = ref == null ? null : ref.resolve();
|
||||
if (PsiUtil.isJvmLocalVariable(target)) {
|
||||
makeFinalIfNeeded(ref, (PsiVariable)target);
|
||||
}
|
||||
if (target == null && ref != null &&
|
||||
JavaPsiFacade.getInstance(project).getResolveHelper()
|
||||
.resolveReferencedVariable(Objects.requireNonNull(variable.getName()), ref) == null) {
|
||||
BringVariableIntoScopeFix fix = BringVariableIntoScopeFix.fromReference(ref);
|
||||
if (fix != null) {
|
||||
if (fix.getPresentation(actionContext) != null) {
|
||||
ModCommandExecutor.getInstance().executeForFileCopy(fix.perform(actionContext), file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//final char completionChar = insertionContext.insertionCharacter();
|
||||
//if (completionChar == '=') {
|
||||
// //context.setAddCompletionChar(false);
|
||||
// EqTailType.INSTANCE.processTail(context.getEditor(), context.getTailOffset());
|
||||
//}
|
||||
//else if (completionChar == ',' && getAttribute(LookupItem.TAIL_TYPE_ATTR) != TailTypes.unknownType()) {
|
||||
// //context.setAddCompletionChar(false);
|
||||
// CommaTailType.INSTANCE.processTail(context.getEditor(), context.getTailOffset());
|
||||
// AutoPopupController.getInstance(context.getProject()).autoPopupParameterInfo(context.getEditor(), null);
|
||||
//}
|
||||
//else if (completionChar == ':' && getAttribute(LookupItem.TAIL_TYPE_ATTR) != TailTypes.unknownType() && isTernaryCondition(ref)) {
|
||||
// //context.setAddCompletionChar(false);
|
||||
// TailTypes.conditionalExpressionColonType().processTail(context.getEditor(), context.getTailOffset());
|
||||
//}
|
||||
//else if (completionChar == '.') {
|
||||
// AutoPopupController.getInstance(context.getProject()).scheduleAutoPopup(context.getEditor());
|
||||
//}
|
||||
//else if (completionChar == '!' && myNegatable) {
|
||||
// //context.setAddCompletionChar(false);
|
||||
// if (ref != null) {
|
||||
// FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EXCLAMATION_FINISH);
|
||||
// document.insertString(ref.getTextRange().getStartOffset(), "!");
|
||||
// }
|
||||
//}
|
||||
//else if (completionChar == Lookup.REPLACE_SELECT_CHAR) {
|
||||
// removeEmptyCallParentheses(context);
|
||||
//}
|
||||
}
|
||||
|
||||
private void qualifyFieldReference(int startOffset, ModPsiUpdater updater, PsiField field) {
|
||||
Document document = updater.getDocument();
|
||||
PsiDocumentManager.getInstance(updater.getProject()).commitDocument(document);
|
||||
PsiFile file = updater.getPsiFile();
|
||||
final PsiReference reference = file.findReferenceAt(startOffset);
|
||||
if (reference instanceof PsiJavaCodeReferenceElement codeRef && codeRef.isQualified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiClass containingClass = myQualifierClass != null && myQualifierClass.isValid() ? myQualifierClass : field.getContainingClass();
|
||||
if (containingClass != null && containingClass.getName() != null) {
|
||||
document.insertString(startOffset, field.hasModifierProperty(PsiModifier.STATIC) ? "." : ".this.");
|
||||
JavaCompletionUtil.insertClassReference(containingClass, file, startOffset);
|
||||
PsiDocumentManager.getInstance(updater.getProject()).commitDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
public static void makeFinalIfNeeded(PsiElement place, PsiVariable variable) {
|
||||
if (PsiUtil.isAvailable(JavaFeature.EFFECTIVELY_FINAL, place) || JspPsiUtil.isInJspFile(place)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ControlFlowUtil.getScopeEnforcingEffectiveFinality(variable, place) != null &&
|
||||
!ControlFlowUtil.isReassigned(variable, new HashMap<>())) {
|
||||
PsiUtil.setModifierProperty(variable, PsiModifier.FINAL, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModCompletionItemPresentation presentation() {
|
||||
PsiVariable variable = contextObject();
|
||||
String forcedQualifier = getForcedQualifier();
|
||||
boolean qualify = myHelper != null && !myHelper.willBeImported() || forcedQualifier != null;
|
||||
|
||||
String name = Objects.requireNonNull(variable.getName());
|
||||
String locationTail = "";
|
||||
if (variable instanceof PsiField field) {
|
||||
PsiClass containingClass = field.getContainingClass();
|
||||
if (containingClass != null) {
|
||||
String className = containingClass.getName();
|
||||
if (qualify) {
|
||||
name = (forcedQualifier != null ? forcedQualifier : className + ".") + name;
|
||||
}
|
||||
String qname = containingClass.getQualifiedName();
|
||||
String pkg = qname == null ? "" : StringUtil.getPackageName(qname);
|
||||
locationTail = StringUtil.isNotEmpty(pkg) ? " (" + pkg + ")" : "";
|
||||
|
||||
if (shouldImport() && StringUtil.isNotEmpty(className)) {
|
||||
locationTail = JavaBundle.message("member.in.class", className) + locationTail;
|
||||
}
|
||||
}
|
||||
}
|
||||
MarkupText main = MarkupText.plainText(name);
|
||||
if (JavaDeprecationUtils.isDeprecated(variable, null)) {
|
||||
main = main.highlightAll(MarkupText.Kind.STRIKEOUT);
|
||||
}
|
||||
if (myTailText != null) {
|
||||
if (myTailText.startsWith(VariableLookupItem.EQ)) {
|
||||
main = main.concat(" (" + myTailText + ")", MarkupText.Kind.EMPHASIZED);
|
||||
} else {
|
||||
main = main.concat(myTailText, MarkupText.Kind.GRAYED);
|
||||
}
|
||||
}
|
||||
main = main.concat(locationTail, MarkupText.Kind.GRAYED);
|
||||
ModCompletionItemPresentation presentation = new ModCompletionItemPresentation(main)
|
||||
.withMainIcon(variable.getIcon(0));
|
||||
return presentation.withDetailText(JavaModCompletionUtils.typeMarkup(mySubstitutor.substitute(variable.getType())));
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.intellij.psi.impl.source.PsiFieldImpl;
|
||||
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -32,7 +33,8 @@ import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
public class VariableLookupItem extends LookupItem<PsiVariable> implements TypedLookupItem, StaticallyImportable {
|
||||
private static final String EQ = " = ";
|
||||
@ApiStatus.Internal
|
||||
public static final String EQ = " = ";
|
||||
private final @Nullable MemberLookupHelper myHelper;
|
||||
private final String myTailText;
|
||||
private final boolean myNegatable;
|
||||
@@ -60,7 +62,7 @@ public class VariableLookupItem extends LookupItem<PsiVariable> implements Typed
|
||||
super(var, Objects.requireNonNull(var.getName()));
|
||||
myHelper = helper;
|
||||
myTailText = tailText == null ? getInitializerText(var) : tailText;
|
||||
myNegatable = PsiTypes.booleanType().isAssignableFrom(var.getType());
|
||||
myNegatable = TypeConversionUtil.isBooleanType(var.getType());
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@@ -92,7 +94,12 @@ public class VariableLookupItem extends LookupItem<PsiVariable> implements Typed
|
||||
return this;
|
||||
}
|
||||
|
||||
private @Nullable String getInitializerText(PsiVariable var) {
|
||||
/**
|
||||
* @param var variable to get its initializer text
|
||||
* @return the initializer text to display in completion popup; null if not applicable
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public static @Nullable String getInitializerText(PsiVariable var) {
|
||||
if (!var.hasModifierProperty(PsiModifier.FINAL) || !var.hasModifierProperty(PsiModifier.STATIC)) return null;
|
||||
if (PlainDescriptor.hasInitializationHacks(var)) return null;
|
||||
|
||||
@@ -294,9 +301,15 @@ public class VariableLookupItem extends LookupItem<PsiVariable> implements Typed
|
||||
return shouldQualify(field, context.getFile().findReferenceAt(context.getTailOffset() - 1));
|
||||
}
|
||||
|
||||
private static boolean shouldQualify(@NotNull PsiField field, @Nullable PsiReference context) {
|
||||
if ((context instanceof PsiReferenceExpression && !((PsiReferenceExpression)context).isQualified()) ||
|
||||
(context instanceof PsiJavaCodeReferenceElement && !((PsiJavaCodeReferenceElement)context).isQualified())) {
|
||||
/**
|
||||
* @param field field to check
|
||||
* @param context context where it's about to be used
|
||||
* @return whether the field access should be qualified in a given context
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public static boolean shouldQualify(@NotNull PsiField field, @Nullable PsiReference context) {
|
||||
if ((context instanceof PsiReferenceExpression ref && !ref.isQualified()) ||
|
||||
(context instanceof PsiJavaCodeReferenceElement codeRef && !codeRef.isQualified())) {
|
||||
PsiElement element = context.getElement();
|
||||
if (isEnumInSwitch(field, element)) return false;
|
||||
PsiVariable target = JavaPsiFacade.getInstance(element.getProject()).getResolveHelper()
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
package bar;
|
||||
|
||||
public class Outer {
|
||||
private static final String SOME_CONSTANT = "someConstant";
|
||||
public static void main(String[] args) {
|
||||
String someString = SO<caret>;
|
||||
switch(args[0]) {
|
||||
case SO<caret> -> System.out.println(someString);
|
||||
private static final String SOME_CONSTANT = "someConstant";
|
||||
public static void main(String[] args) {
|
||||
String someString = SO<caret>;
|
||||
switch(args[0]) {
|
||||
case SO<caret> -> System.out.println(someString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
package bar;
|
||||
|
||||
public class Outer {
|
||||
private static final String SOME_CONSTANT = "someConstant";
|
||||
public static void main(String[] args) {
|
||||
String someString = SOME_CONSTANT<caret>;
|
||||
switch(args[0]) {
|
||||
case SOME_CONSTANT<caret> -> System.out.println(someString);
|
||||
private static final String SOME_CONSTANT = "someConstant";
|
||||
public static void main(String[] args) {
|
||||
String someString = SOME_CONSTANT<caret>;
|
||||
switch(args[0]) {
|
||||
case SOME_CONSTANT<caret> -> System.out.println(someString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -354,11 +354,11 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
|
||||
checkResultByTestName();
|
||||
}
|
||||
|
||||
private void configureByTestName() {
|
||||
void configureByTestName() {
|
||||
configureByFile(BASE_PATH + getTestName(true) + ".java");
|
||||
}
|
||||
|
||||
private void checkResultByTestName() {
|
||||
void checkResultByTestName() {
|
||||
checkResultByFile(BASE_PATH + getTestName(true) + "_after.java");
|
||||
}
|
||||
|
||||
@@ -380,6 +380,16 @@ public class KeywordCompletionTest extends LightCompletionTestCase {
|
||||
Registry.get("ide.completion.modcommand").setValue(true, getTestRootDisposable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testNoExtraArrowMultiCaret() {
|
||||
configureByTestName();
|
||||
// Intermittently no-variants delegator appears here adding a second completion item
|
||||
// ResourceBundle (as it contains 'SO'), and now no-variants delegators
|
||||
// gets no information that we have no other variants.
|
||||
selectItem(myItems[0]);
|
||||
checkResultByTestName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testInstanceofNegation() {
|
||||
// skip; TODO: support custom completion chars
|
||||
|
||||
@@ -2003,3 +2003,4 @@ todo.item=todo item
|
||||
intention.rename.underscore.name=Rename {0} of type ''{1}''
|
||||
intention.rename.underscore.popup.title=Select Declaration
|
||||
intention.rename.underscore.family.name=Rename unnamed variable
|
||||
member.in.class=\ in {0}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
*a:com.intellij.codeInsight.ModNavigatorTailType
|
||||
- com.intellij.codeInsight.TailType
|
||||
- <init>():V
|
||||
- s:dotType():com.intellij.codeInsight.ModNavigatorTailType
|
||||
- s:humbleSpaceBeforeWordType():com.intellij.codeInsight.ModNavigatorTailType
|
||||
- s:insertSpaceType():com.intellij.codeInsight.ModNavigatorTailType
|
||||
- isApplicable(com.intellij.codeInsight.completion.InsertionContext):Z
|
||||
|
||||
@@ -72,6 +72,15 @@ public abstract class ModNavigatorTailType extends TailType {
|
||||
return (ModNavigatorTailType)TailTypes.spaceType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return tail type that inserts a dot
|
||||
*/
|
||||
@Contract(pure = true)
|
||||
public static @NotNull ModNavigatorTailType dotType() {
|
||||
return (ModNavigatorTailType)TailTypes.dotType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return tail type that insert a space unless there's one at the caret position already, followed by a word or '@'
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user