diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 063b02659f09..282828791470 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -1195,6 +1195,7 @@ + diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/CheckInitialized.java b/java/java-impl/src/com/intellij/codeInsight/completion/CheckInitialized.java index e8dec498bbc4..3603e265e900 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/CheckInitialized.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/CheckInitialized.java @@ -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 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); } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java index ac40998fcd04..b19a44e84cc2 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionUtil.java @@ -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 SUPER_METHOD_PARAMETERS = Key.create("SUPER_METHOD_PARAMETERS"); - public static @Nullable Set getExpectedTypes(@NotNull CompletionParameters parameters) { + public static @Nullable Set getExpectedTypes(@NotNull BaseCompletionParameters parameters) { PsiExpression expr = PsiTreeUtil.getContextOfType(parameters.getPosition(), PsiExpression.class, true); if (expr != null) { Set 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 expectedTypes) { - PsiType castType = castTypeItem.getType(); + public static boolean shouldCast(@Nullable PsiType plainQualifier, + @NotNull JavaCompletionProcessor processor, + @NotNull Set 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 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 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) { diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java index 1d57a266994e..dc8627be44a5 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaPsiClassReferenceElement.java @@ -211,7 +211,12 @@ public class JavaPsiClassReferenceElement extends LookupItem 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) { diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaSmartCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaSmartCompletionContributor.java index 173beb0c28ec..fa1f4048e9af 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaSmartCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaSmartCompletionContributor.java @@ -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); } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/MemberLookupHelper.java b/java/java-impl/src/com/intellij/codeInsight/completion/MemberLookupHelper.java index b708a9d2be6b..612a905234fe 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/MemberLookupHelper.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/MemberLookupHelper.java @@ -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); } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java index d981acd7108e..8cbba97bd1ba 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ReferenceExpressionCompletionContributor.java @@ -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; diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ClassReferenceCompletionItem.java b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ClassReferenceCompletionItem.java index 1fec0cf603d6..cb425329c588 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ClassReferenceCompletionItem.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ClassReferenceCompletionItem.java @@ -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(); diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java index a4c18f2dae4b..5c5a4337a835 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/ReferenceItemProvider.java @@ -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 TOP_LEVEL_VAR_IN_MODULE = psiElement().withSuperParent(3, PsiJavaFile.class) + .inVirtualFile(virtualFile().withName("module-info.java")); + private static final ElementPattern INSIDE_TYPECAST_EXPRESSION = psiElement().withParent( + psiElement(PsiReferenceExpression.class).afterLeaf( + psiElement().withText(")").withParent(PsiTypeCastExpression.class))); + private static final ElementPattern INSIDE_CONSTRUCTOR = psiElement().inside(psiMethod().constructor(true)); + private static final ElementPattern AFTER_NEW = psiElement().afterLeaf(psiElement().withText(JavaKeywords.NEW)); @Override public void provideItems(CompletionContext context, Consumer 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 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 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 sink) { + Condition nameCondition = s -> true; + JavaCompletionProcessor processor = new JavaCompletionProcessor(element, elementFilter, options, nameCondition); + PsiType plainQualifier = processor.getQualifierType(); + + List 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 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 expectedTypes = ObjectUtils.coalesce(JavaCompletionUtil.getExpectedTypes(parameters), Collections.emptySet()); + + //Set 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 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 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); + // } + //}; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/VariableCompletionItem.java b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/VariableCompletionItem.java new file mode 100644 index 000000000000..373ab1406c86 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/modcompletion/VariableCompletionItem.java @@ -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 { + 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()))); + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/lookup/VariableLookupItem.java b/java/java-impl/src/com/intellij/codeInsight/lookup/VariableLookupItem.java index be5537870188..2256b5058e00 100644 --- a/java/java-impl/src/com/intellij/codeInsight/lookup/VariableLookupItem.java +++ b/java/java-impl/src/com/intellij/codeInsight/lookup/VariableLookupItem.java @@ -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 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 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 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 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() diff --git a/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret.java b/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret.java index 45671f788a08..be9fc557f3c1 100644 --- a/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret.java +++ b/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret.java @@ -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; - switch(args[0]) { - case SO -> System.out.println(someString); + private static final String SOME_CONSTANT = "someConstant"; + public static void main(String[] args) { + String someString = SO; + switch(args[0]) { + case SO -> System.out.println(someString); + } } - } } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret_after.java b/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret_after.java index 236f96e5c2c5..0cdf477ed5c1 100644 --- a/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret_after.java +++ b/java/java-tests/testData/codeInsight/completion/keywords/noExtraArrowMultiCaret_after.java @@ -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; - switch(args[0]) { - case SOME_CONSTANT -> System.out.println(someString); + private static final String SOME_CONSTANT = "someConstant"; + public static void main(String[] args) { + String someString = SOME_CONSTANT; + switch(args[0]) { + case SOME_CONSTANT -> System.out.println(someString); + } } - } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java index 0a57ca4e1fad..bcc0f0c11e67 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/KeywordCompletionTest.java @@ -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 diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties index 3f91202f7787..26e8e49fbf47 100644 --- a/java/openapi/resources/messages/JavaBundle.properties +++ b/java/openapi/resources/messages/JavaBundle.properties @@ -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} diff --git a/platform/analysis-api/api-dump-experimental.txt b/platform/analysis-api/api-dump-experimental.txt index 8c7be4d110d1..eca308604f80 100644 --- a/platform/analysis-api/api-dump-experimental.txt +++ b/platform/analysis-api/api-dump-experimental.txt @@ -1,6 +1,7 @@ *a:com.intellij.codeInsight.ModNavigatorTailType - com.intellij.codeInsight.TailType - ():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 diff --git a/platform/analysis-api/src/com/intellij/codeInsight/ModNavigatorTailType.java b/platform/analysis-api/src/com/intellij/codeInsight/ModNavigatorTailType.java index 35d215def7d1..a614b36bd431 100644 --- a/platform/analysis-api/src/com/intellij/codeInsight/ModNavigatorTailType.java +++ b/platform/analysis-api/src/com/intellij/codeInsight/ModNavigatorTailType.java @@ -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 '@' */