diff --git a/java-language-api/src/main/java/com/intellij/java/language/patterns/PsiJavaPatterns.java b/java-language-api/src/main/java/com/intellij/java/language/patterns/PsiJavaPatterns.java index a957f68312..0d658db1d6 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/patterns/PsiJavaPatterns.java +++ b/java-language-api/src/main/java/com/intellij/java/language/patterns/PsiJavaPatterns.java @@ -16,6 +16,7 @@ package com.intellij.java.language.patterns; import com.intellij.java.language.psi.*; +import consulo.annotation.access.RequiredReadAction; import consulo.language.pattern.ElementPattern; import consulo.language.pattern.IElementTypePattern; import consulo.language.pattern.InitialPatternCondition; @@ -28,6 +29,7 @@ import consulo.language.pattern.VirtualFilePattern; import org.jspecify.annotations.Nullable; + import java.util.Collections; import java.util.List; @@ -35,155 +37,160 @@ * @author peter */ public class PsiJavaPatterns extends StandardPatterns { + public static IElementTypePattern elementType() { + return PlatformPatterns.elementType(); + } + + public static VirtualFilePattern virtualFile() { + return PlatformPatterns.virtualFile(); + } + + public static PsiJavaElementPattern.Capture psiJavaElement() { + return new PsiJavaElementPattern.Capture<>(PsiElement.class); + } + + public static PsiJavaElementPattern.Capture psiJavaElement(IElementType type) { + return psiJavaElement().withElementType(type); + } + + public static PsiJavaElementPattern.Capture psiJavaElement(Class aClass) { + return new PsiJavaElementPattern.Capture<>(aClass); + } + + @SafeVarargs + public static PsiJavaElementPattern.Capture psiJavaElement(final Class... classAlternatives) { + return new PsiJavaElementPattern.Capture<>(new InitialPatternCondition<>(PsiElement.class) { + @Override + @RequiredReadAction + public boolean accepts(@Nullable Object o, ProcessingContext context) { + for (Class classAlternative : classAlternatives) { + if (classAlternative.isInstance(o)) { + return true; + } + } + return false; + } + }); + } + + public static PsiJavaElementPattern.Capture literalExpression() { + return literalExpression(null); + } + + public static PsiJavaElementPattern.Capture psiLiteral() { + return psiLiteral(null); + } + + public static PsiJavaElementPattern.Capture psiLiteral(@Nullable final ElementPattern value) { + return new PsiJavaElementPattern.Capture<>(new InitialPatternConditionPlus<>(PsiLiteral.class) { + @Override + public boolean accepts(@Nullable Object o, ProcessingContext context) { + return o instanceof PsiLiteral literal && (value == null || value.accepts(literal.getValue(), context)); + } + + @Override + public List> getPatterns() { + return Collections.>singletonList(value); + } + }); + } + + public static PsiJavaElementPattern.Capture psiNewExpression(final String... fqns) { + return new PsiJavaElementPattern.Capture<>(new InitialPatternCondition<>(PsiNewExpression.class) { + @Override + public boolean accepts(@Nullable Object o, ProcessingContext context) { + if (o instanceof PsiNewExpression newExpr) { + PsiJavaCodeReferenceElement reference = newExpr.getClassOrAnonymousClassReference(); + if (reference != null) { + for (String fqn : fqns) { + if (fqn.equals(reference.getQualifiedName())) { + return true; + } + } + } + } + return false; + } + }); + } + + public static PsiJavaElementPattern.Capture literalExpression(@Nullable final ElementPattern value) { + return new PsiJavaElementPattern.Capture<>(new InitialPatternConditionPlus<>(PsiLiteralExpression.class) { + @Override + public boolean accepts(@Nullable Object o, ProcessingContext context) { + return o instanceof PsiLiteralExpression literal && (value == null || value.accepts(literal.getValue(), context)); + } - public static IElementTypePattern elementType() { - return PlatformPatterns.elementType(); - } - - public static VirtualFilePattern virtualFile() { - return PlatformPatterns.virtualFile(); - } - - public static PsiJavaElementPattern.Capture psiJavaElement() { - return new PsiJavaElementPattern.Capture<>(PsiElement.class); - } - - public static PsiJavaElementPattern.Capture psiJavaElement(IElementType type) { - return psiJavaElement().withElementType(type); - } - - public static PsiJavaElementPattern.Capture psiJavaElement(final Class aClass) { - return new PsiJavaElementPattern.Capture<>(aClass); - } - - @SafeVarargs - public static PsiJavaElementPattern.Capture psiJavaElement(final Class... classAlternatives) { - return new PsiJavaElementPattern.Capture<>(new InitialPatternCondition<>(PsiElement.class) { - @Override - public boolean accepts(@Nullable Object o, ProcessingContext context) { - for (Class classAlternative : classAlternatives) { - if (classAlternative.isInstance(o)) { - return true; - } - } - return false; - } - }); - } - - public static PsiJavaElementPattern.Capture literalExpression() { - return literalExpression(null); - } - - public static PsiJavaElementPattern.Capture psiLiteral() { - return psiLiteral(null); - } - - public static PsiJavaElementPattern.Capture psiLiteral(@Nullable final ElementPattern value) { - return new PsiJavaElementPattern.Capture<>(new InitialPatternConditionPlus<>(PsiLiteral.class) { - public boolean accepts(@Nullable final Object o, final ProcessingContext context) { - return o instanceof PsiLiteral && (value == null || value.accepts(((PsiLiteral)o).getValue(), context)); - } - - @Override - public List> getPatterns() { - return Collections.>singletonList(value); - } - }); - } - - public static PsiJavaElementPattern.Capture psiNewExpression(final String... fqns) { - return new PsiJavaElementPattern.Capture<>(new InitialPatternCondition<>(PsiNewExpression.class) { - public boolean accepts(@Nullable final Object o, final ProcessingContext context) { - if(o instanceof PsiNewExpression) { - PsiJavaCodeReferenceElement reference = ((PsiNewExpression)o).getClassOrAnonymousClassReference(); - if (reference != null) { - for (String fqn : fqns) { - if( fqn.equals(reference.getQualifiedName())) return true; + @Override + public List> getPatterns() { + return Collections.>singletonList(value); + } + }); + } + + public static PsiMemberPattern.Capture psiMember() { + return new PsiMemberPattern.Capture(); + } + + public static PsiMethodPattern psiMethod() { + return new PsiMethodPattern(); + } + + public static PsiParameterPattern psiParameter() { + return new PsiParameterPattern(); + } + + public static PsiModifierListOwnerPattern.Capture psiModifierListOwner() { + return new PsiModifierListOwnerPattern.Capture<>(new InitialPatternCondition<>(PsiModifierListOwner.class) { + @Override + @RequiredReadAction + public boolean accepts(@Nullable Object o, ProcessingContext context) { + return o instanceof PsiModifierListOwner; } - } - } - return false; - } - }); - } - - public static PsiJavaElementPattern.Capture literalExpression(@Nullable final ElementPattern value) { - return new PsiJavaElementPattern.Capture<>(new InitialPatternConditionPlus<>(PsiLiteralExpression.class) { - public boolean accepts(@Nullable final Object o, final ProcessingContext context) { - return o instanceof PsiLiteralExpression && (value == null || value.accepts(((PsiLiteralExpression)o).getValue(), context)); - } - - @Override - public List> getPatterns() { - return Collections.>singletonList(value); - } - }); - } - - public static PsiMemberPattern.Capture psiMember() { - return new PsiMemberPattern.Capture(); - } - - public static PsiMethodPattern psiMethod() { - return new PsiMethodPattern(); - } - - public static PsiParameterPattern psiParameter() { - return new PsiParameterPattern(); - } - - public static PsiModifierListOwnerPattern.Capture psiModifierListOwner() { - return new PsiModifierListOwnerPattern.Capture<>(new InitialPatternCondition<>(PsiModifierListOwner.class) { - @Override - public boolean accepts(@Nullable Object o, ProcessingContext context) { - return o instanceof PsiModifierListOwner; - } - }); - } - - - public static PsiFieldPattern psiField() { - return new PsiFieldPattern(); - } - - public static PsiClassPattern psiClass() { - return new PsiClassPattern(); - } - - public static PsiAnnotationPattern psiAnnotation() { - return new PsiAnnotationPattern(); - } - - public static PsiNameValuePairPattern psiNameValuePair() { - return new PsiNameValuePairPattern(); - } - - public static PsiTypePattern psiType() { - return new PsiTypePattern(); - } - - public static PsiExpressionPattern.Capture psiExpression() { - return new PsiExpressionPattern.Capture<>(PsiExpression.class); - } - - public static PsiBinaryExpressionPattern psiBinaryExpression() { - return new PsiBinaryExpressionPattern(); - } - - public static PsiTypeCastExpressionPattern psiTypeCastExpression() { - return new PsiTypeCastExpressionPattern(); - } - - public static PsiJavaElementPattern.Capture psiReferenceExpression() { - return psiJavaElement(PsiReferenceExpression.class); - } - - public static PsiStatementPattern.Capture psiExpressionStatement() { - return new PsiStatementPattern.Capture<>(PsiExpressionStatement.class); - } - - public static PsiStatementPattern.Capture psiReturnStatement() { - return new PsiStatementPattern.Capture<>(PsiReturnStatement.class); - } + }); + } + + public static PsiFieldPattern psiField() { + return new PsiFieldPattern(); + } + + public static PsiClassPattern psiClass() { + return new PsiClassPattern(); + } + + public static PsiAnnotationPattern psiAnnotation() { + return new PsiAnnotationPattern(); + } + + public static PsiNameValuePairPattern psiNameValuePair() { + return new PsiNameValuePairPattern(); + } + + public static PsiTypePattern psiType() { + return new PsiTypePattern(); + } + + public static PsiExpressionPattern.Capture psiExpression() { + return new PsiExpressionPattern.Capture<>(PsiExpression.class); + } + + public static PsiBinaryExpressionPattern psiBinaryExpression() { + return new PsiBinaryExpressionPattern(); + } + + public static PsiTypeCastExpressionPattern psiTypeCastExpression() { + return new PsiTypeCastExpressionPattern(); + } + + public static PsiJavaElementPattern.Capture psiReferenceExpression() { + return psiJavaElement(PsiReferenceExpression.class); + } + + public static PsiStatementPattern.Capture psiExpressionStatement() { + return new PsiStatementPattern.Capture<>(PsiExpressionStatement.class); + } + + public static PsiStatementPattern.Capture psiReturnStatement() { + return new PsiStatementPattern.Capture<>(PsiReturnStatement.class); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaKeywordCompletion.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaKeywordCompletion.java index 8660107e01..20a49ff2aa 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaKeywordCompletion.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/completion/JavaKeywordCompletion.java @@ -38,6 +38,7 @@ import consulo.language.editor.completion.CompletionUtilCore; import consulo.language.editor.completion.lookup.*; import consulo.language.pattern.ElementPattern; +import consulo.language.pattern.PsiElementPattern; import consulo.language.psi.*; import consulo.language.psi.filter.AndFilter; import consulo.language.psi.filter.ClassFilter; @@ -48,7 +49,6 @@ import consulo.util.collection.ContainerUtil; import consulo.util.lang.ObjectUtil; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Conditions; import org.jspecify.annotations.Nullable; import java.util.ArrayList; @@ -63,1021 +63,1115 @@ import static consulo.util.lang.function.Conditions.notInstanceOf; public class JavaKeywordCompletion { - public static final ElementPattern AFTER_DOT = psiElement().afterLeaf("."); - - static final ElementPattern VARIABLE_AFTER_FINAL = - psiElement().afterLeaf(PsiKeyword.FINAL).inside(PsiDeclarationStatement.class); - - private static final ElementPattern INSIDE_PARAMETER_LIST = - psiElement().withParent(psiElement(PsiJavaCodeReferenceElement.class).insideStarting(psiElement().withTreeParent - (psiElement(PsiParameterList.class).andNot( - psiElement( - PsiAnnotationParameterList.class))))); - - private static final AndFilter START_OF_CODE_FRAGMENT = - new AndFilter(new ScopeFilter(new AndFilter(new ClassFilter(JavaCodeFragment.class), - new ClassFilter(PsiExpressionCodeFragment.class, - false), - new ClassFilter(PsiJavaCodeReferenceCodeFragment.class, false), - new ClassFilter(PsiTypeCodeFragment.class, false))), new StartElementFilter()); - private static final ElementPattern INSIDE_RECORD_HEADER = - psiElement().withParent( - psiElement(PsiJavaCodeReferenceElement.class).insideStarting( - or( - psiElement().withTreeParent( - psiElement(PsiRecordComponent.class)), - psiElement().withTreeParent( - psiElement(PsiRecordHeader.class) - ) - ) - )); - - static final ElementPattern START_SWITCH = - psiElement().afterLeaf(psiElement().withText("{").withParents(PsiCodeBlock.class, PsiSwitchStatement.class)); - - private static final ElementPattern SUPER_OR_THIS_PATTERN = - and(JavaSmartCompletionContributor.INSIDE_EXPRESSION, - not(psiElement().afterLeaf(PsiKeyword.CASE)), - not(psiElement() - .afterLeaf(psiElement().withText(".") - .afterLeaf(PsiKeyword.THIS, - PsiKeyword.SUPER))), - not(psiElement().inside(PsiAnnotation.class)), - not(START_SWITCH), - not - (JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN)); - - static final Set PRIMITIVE_TYPES = - Set.of(PsiKeyword.SHORT, PsiKeyword.BOOLEAN, PsiKeyword.DOUBLE, PsiKeyword.LONG, PsiKeyword.INT, PsiKeyword.FLOAT, PsiKeyword - .CHAR, PsiKeyword.BYTE); - - static final ElementPattern START_FOR = psiElement().afterLeaf(psiElement().withText("(").afterLeaf("for")) - .withParents(PsiJavaCodeReferenceElement.class, PsiExpressionStatement - .class, PsiForStatement.class); - private static final ElementPattern CLASS_REFERENCE = - psiElement().withParent(psiReferenceExpression().referencing(psiClass().andNot(psiElement(PsiTypeParameter.class)))); - - private static final ElementPattern EXPR_KEYWORDS = - and(psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent(not(or(psiElement(PsiTypeCastExpression.class), - psiElement(PsiSwitchLabelStatement.class), - psiElement(PsiExpressionStatement.class), - psiElement(PsiPrefixExpression.class))))), - not(psiElement().afterLeaf("."))); - - static boolean isEndOfBlock(PsiElement element) { - PsiElement prev = prevSignificantLeaf(element); - if (prev == null) { - PsiFile file = element.getContainingFile(); - return !(file instanceof PsiCodeFragment) || isStatementCodeFragment(file); - } + public static final ElementPattern AFTER_DOT = psiElement().afterLeaf("."); + + static final ElementPattern VARIABLE_AFTER_FINAL = + psiElement().afterLeaf(PsiKeyword.FINAL).inside(PsiDeclarationStatement.class); + + private static final ElementPattern INSIDE_PARAMETER_LIST = + psiElement().withParent(psiElement(PsiJavaCodeReferenceElement.class).insideStarting(psiElement().withTreeParent + (psiElement(PsiParameterList.class).andNot(psiElement(PsiAnnotationParameterList.class))))); + + private static final AndFilter START_OF_CODE_FRAGMENT = + new AndFilter( + new ScopeFilter(new AndFilter( + new ClassFilter(JavaCodeFragment.class), + new ClassFilter(PsiExpressionCodeFragment.class, false), + new ClassFilter(PsiJavaCodeReferenceCodeFragment.class, false), + new ClassFilter(PsiTypeCodeFragment.class, false) + )), + new StartElementFilter() + ); + private static final ElementPattern INSIDE_RECORD_HEADER = + psiElement().withParent( + psiElement(PsiJavaCodeReferenceElement.class).insideStarting( + or( + psiElement().withTreeParent(psiElement(PsiRecordComponent.class)), + psiElement().withTreeParent(psiElement(PsiRecordHeader.class)) + ) + )); + + static final ElementPattern START_SWITCH = + psiElement().afterLeaf(psiElement().withText("{").withParents(PsiCodeBlock.class, PsiSwitchStatement.class)); + + private static final ElementPattern SUPER_OR_THIS_PATTERN = + and( + JavaSmartCompletionContributor.INSIDE_EXPRESSION, + not(psiElement().afterLeaf(PsiKeyword.CASE)), + not(psiElement().afterLeaf( + psiElement().withText(".").afterLeaf(PsiKeyword.THIS, PsiKeyword.SUPER) + )), + not(psiElement().inside(PsiAnnotation.class)), + not(START_SWITCH), + not(JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN) + ); + + static final Set PRIMITIVE_TYPES = + Set.of( + PsiKeyword.SHORT, + PsiKeyword.BOOLEAN, + PsiKeyword.DOUBLE, + PsiKeyword.LONG, + PsiKeyword.INT, + PsiKeyword.FLOAT, + PsiKeyword.CHAR, + PsiKeyword.BYTE + ); + + static final ElementPattern START_FOR = psiElement().afterLeaf(psiElement().withText("(").afterLeaf("for")) + .withParents(PsiJavaCodeReferenceElement.class, PsiExpressionStatement.class, PsiForStatement.class); + private static final ElementPattern CLASS_REFERENCE = + psiElement().withParent(psiReferenceExpression().referencing(psiClass().andNot(psiElement(PsiTypeParameter.class)))); + + private static final ElementPattern EXPR_KEYWORDS = + and( + psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent(not(or( + psiElement(PsiTypeCastExpression.class), + psiElement(PsiSwitchLabelStatement.class), + psiElement(PsiExpressionStatement.class), + psiElement(PsiPrefixExpression.class) + )))), + not(psiElement().afterLeaf(".")) + ); + + @RequiredReadAction + static boolean isEndOfBlock(PsiElement element) { + PsiElement prev = prevSignificantLeaf(element); + if (prev == null) { + PsiFile file = element.getContainingFile(); + return !(file instanceof PsiCodeFragment) || isStatementCodeFragment(file); + } + + if (psiElement().inside(psiAnnotation()).accepts(prev)) { + return false; + } - if (psiElement().inside(psiAnnotation()).accepts(prev)) return false; + if (prev instanceof OuterLanguageElement) { + return true; + } + if (psiElement().withText(string().oneOf("{", "}", ";", ":", "else")).accepts(prev)) { + return true; + } + if (prev.textMatches(")")) { + PsiElement parent = prev.getParent(); + if (parent instanceof PsiParameterList) { + return PsiTreeUtil.getParentOfType(PsiTreeUtil.prevVisibleLeaf(element), PsiDocComment.class) != null; + } - if (prev instanceof OuterLanguageElement) return true; - if (psiElement().withText(string().oneOf("{", "}", ";", ":", "else")).accepts(prev)) return true; - if (prev.textMatches(")")) { - PsiElement parent = prev.getParent(); - if (parent instanceof PsiParameterList) { - return PsiTreeUtil.getParentOfType(PsiTreeUtil.prevVisibleLeaf(element), PsiDocComment.class) != null; - } + return !(parent instanceof PsiExpressionList + || parent instanceof PsiTypeCastExpression + || parent instanceof PsiRecordHeader); + } - return !(parent instanceof PsiExpressionList || parent instanceof PsiTypeCastExpression - || parent instanceof PsiRecordHeader); + return false; } - return false; - } - - private static boolean isStatementCodeFragment(PsiFile file) { - return file instanceof JavaCodeFragment && - !(file instanceof PsiExpressionCodeFragment || - file instanceof PsiJavaCodeReferenceCodeFragment || - file instanceof PsiTypeCodeFragment); - } - - private final CompletionParameters myParameters; - private final JavaCompletionSession mySession; - private final PsiElement myPosition; - private final String myPrefix; - private final List myResults = new ArrayList<>(); - @Nullable - private PsiElement myPrevLeaf; - - JavaKeywordCompletion(CompletionParameters parameters, JavaCompletionSession session) { - myParameters = parameters; - mySession = session; - myPrefix = session.getMatcher().getPrefix(); - myPosition = parameters.getPosition(); - myPrevLeaf = PsiTreeUtil.prevVisibleLeaf(myPosition); - - addKeywords(); - addEnumCases(); - } - - private void addKeyword(LookupElement element) { - if (element.getLookupString().startsWith(myPrefix)) { - myResults.add(element); + private static boolean isStatementCodeFragment(PsiFile file) { + return file instanceof JavaCodeFragment && + !(file instanceof PsiExpressionCodeFragment || + file instanceof PsiJavaCodeReferenceCodeFragment || + file instanceof PsiTypeCodeFragment); } - } - private static PsiElement prevSignificantLeaf(PsiElement position) { - return FilterPositionUtil.searchNonSpaceNonCommentBack(position); - } + private final CompletionParameters myParameters; + private final JavaCompletionSession mySession; + private final PsiElement myPosition; + private final String myPrefix; + private final List myResults = new ArrayList<>(); + @Nullable + private PsiElement myPrevLeaf; - List getResults() { - return myResults; - } + @RequiredReadAction + JavaKeywordCompletion(CompletionParameters parameters, JavaCompletionSession session) { + myParameters = parameters; + mySession = session; + myPrefix = session.getMatcher().getPrefix(); + myPosition = parameters.getPosition(); + myPrevLeaf = PsiTreeUtil.prevVisibleLeaf(myPosition); - public static boolean isInsideParameterList(PsiElement position) { - PsiElement prev = PsiTreeUtil.prevVisibleLeaf(position); - PsiModifierList modifierList = PsiTreeUtil.getParentOfType(prev, PsiModifierList.class); - if (modifierList != null) { - if (PsiTreeUtil.isAncestor(modifierList, position, false)) { - return false; - } - PsiElement parent = modifierList.getParent(); - return parent instanceof PsiParameterList || parent instanceof PsiParameter && parent.getParent() instanceof PsiParameterList; - } - return INSIDE_PARAMETER_LIST.accepts(position); - } - - private static TailType getReturnTail(PsiElement position) { - PsiElement scope = position; - while (true) { - if (scope instanceof PsiFile || scope instanceof PsiClassInitializer) { - return TailType.NONE; - } - - if (scope instanceof PsiMethod) { - PsiMethod method = (PsiMethod)scope; - if (method.isConstructor() || PsiType.VOID.equals(method.getReturnType())) { - return TailType.SEMICOLON; - } - - return TailType.HUMBLE_SPACE_BEFORE_WORD; - } - if (scope instanceof PsiLambdaExpression) { - PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(((PsiLambdaExpression)scope)); - if (PsiType.VOID.equals(returnType)) { - return TailType.SEMICOLON; - } - return TailType.HUMBLE_SPACE_BEFORE_WORD; - } - scope = scope.getParent(); + addKeywords(); + addEnumCases(); } - } - - private void addStatementKeywords() { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.SWITCH), TailTypes.SWITCH_LPARENTH)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.WHILE), TailTypes.WHILE_LPARENTH)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DO), TailTypes.DO_LBRACE)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.FOR), TailTypes.FOR_LPARENTH)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IF), TailTypes.IF_LPARENTH)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.TRY), TailTypes.TRY_LBRACE)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.THROW), TailType.INSERT_SPACE)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.NEW), TailType.INSERT_SPACE)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.SYNCHRONIZED), TailTypes.SYNCHRONIZED_LPARENTH)); - - if (PsiUtil.getLanguageLevel(myPosition).isAtLeast(LanguageLevel.JDK_1_4)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ASSERT), TailType.INSERT_SPACE)); + + private void addKeyword(LookupElement element) { + if (element.getLookupString().startsWith(myPrefix)) { + myResults.add(element); + } } - TailType returnTail = getReturnTail(myPosition); - LookupElement ret = createKeyword(PsiKeyword.RETURN); - if (returnTail != TailType.NONE) { - ret = new OverridableSpace(ret, returnTail); + private static PsiElement prevSignificantLeaf(PsiElement position) { + return FilterPositionUtil.searchNonSpaceNonCommentBack(position); } - addKeyword(ret); - if (psiElement().withText(";").withSuperParent(2, PsiIfStatement.class).accepts(myPrevLeaf) || psiElement().withText("}") - .withSuperParent(3, - PsiIfStatement.class) - .accepts(myPrevLeaf)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ELSE), TailType.HUMBLE_SPACE_BEFORE_WORD)); + List getResults() { + return myResults; } - if (psiElement().withText("}") - .withParent(psiElement(PsiCodeBlock.class).withParent(or(psiElement(PsiTryStatement.class), - psiElement(PsiCatchSection.class)))) - .accepts(myPrevLeaf)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CATCH), TailTypes.CATCH_LPARENTH)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.FINALLY), TailTypes.FINALLY_LBRACE)); + @RequiredReadAction + public static boolean isInsideParameterList(PsiElement position) { + PsiElement prev = PsiTreeUtil.prevVisibleLeaf(position); + PsiModifierList modifierList = PsiTreeUtil.getParentOfType(prev, PsiModifierList.class); + if (modifierList != null) { + if (PsiTreeUtil.isAncestor(modifierList, position, false)) { + return false; + } + PsiElement parent = modifierList.getParent(); + return parent instanceof PsiParameterList + || parent instanceof PsiParameter param && param.getParent() instanceof PsiParameterList; + } + return INSIDE_PARAMETER_LIST.accepts(position); } - } + private static TailType getReturnTail(PsiElement position) { + PsiElement scope = position; + while (true) { + if (scope instanceof PsiFile || scope instanceof PsiClassInitializer) { + return TailType.NONE; + } + + if (scope instanceof PsiMethod method) { + if (method.isConstructor() || PsiType.VOID.equals(method.getReturnType())) { + return TailType.SEMICOLON; + } - void addKeywords() { - if (PsiTreeUtil.getNonStrictParentOfType(myPosition, PsiLiteralExpression.class, PsiComment.class) != null) { - return; + return TailType.HUMBLE_SPACE_BEFORE_WORD; + } + if (scope instanceof PsiLambdaExpression lambda) { + PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(lambda); + if (PsiType.VOID.equals(returnType)) { + return TailType.SEMICOLON; + } + return TailType.HUMBLE_SPACE_BEFORE_WORD; + } + scope = scope.getParent(); + } } - addFinal(); + @RequiredReadAction + private void addStatementKeywords() { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.SWITCH), TailTypes.SWITCH_LPARENTH)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.WHILE), TailTypes.WHILE_LPARENTH)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DO), TailTypes.DO_LBRACE)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.FOR), TailTypes.FOR_LPARENTH)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IF), TailTypes.IF_LPARENTH)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.TRY), TailTypes.TRY_LBRACE)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.THROW), TailType.INSERT_SPACE)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.NEW), TailType.INSERT_SPACE)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.SYNCHRONIZED), TailTypes.SYNCHRONIZED_LPARENTH)); + + if (PsiUtil.getLanguageLevel(myPosition).isAtLeast(LanguageLevel.JDK_1_4)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ASSERT), TailType.INSERT_SPACE)); + } + + TailType returnTail = getReturnTail(myPosition); + LookupElement ret = createKeyword(PsiKeyword.RETURN); + if (returnTail != TailType.NONE) { + ret = new OverridableSpace(ret, returnTail); + } + addKeyword(ret); - boolean statementPosition = isStatementPosition(myPosition); - if (statementPosition) { - addCaseDefault(); - if (START_SWITCH.accepts(myPosition)) { - return; - } + if (psiElement().withText(";").withSuperParent(2, PsiIfStatement.class).accepts(myPrevLeaf) + || psiElement().withText("}").withSuperParent(3, PsiIfStatement.class).accepts(myPrevLeaf)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ELSE), TailType.HUMBLE_SPACE_BEFORE_WORD)); + } + + if (psiElement().withText("}") + .withParent(psiElement(PsiCodeBlock.class).withParent(or( + psiElement(PsiTryStatement.class), + psiElement(PsiCatchSection.class) + ))) + .accepts(myPrevLeaf)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CATCH), TailTypes.CATCH_LPARENTH)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.FINALLY), TailTypes.FINALLY_LBRACE)); + } - addBreakContinue(); - addStatementKeywords(); } - addThisSuper(); + @RequiredReadAction + void addKeywords() { + if (PsiTreeUtil.getNonStrictParentOfType(myPosition, PsiLiteralExpression.class, PsiComment.class) != null) { + return; + } + + addFinal(); + + boolean statementPosition = isStatementPosition(myPosition); + if (statementPosition) { + addCaseDefault(); + if (START_SWITCH.accepts(myPosition)) { + return; + } + + addBreakContinue(); + addStatementKeywords(); + } - addExpressionKeywords(statementPosition); + addThisSuper(); - addFileHeaderKeywords(); + addExpressionKeywords(statementPosition); - addInstanceof(); + addFileHeaderKeywords(); - addClassKeywords(); + addInstanceof(); - addMethodHeaderKeywords(); + addClassKeywords(); - addPrimitiveTypes(this::addKeyword, myPosition, mySession); + addMethodHeaderKeywords(); - addVar(); + addPrimitiveTypes(this::addKeyword, myPosition, mySession); - addClassLiteral(); + addVar(); - addUnfinishedMethodTypeParameters(); + addClassLiteral(); - addExtendsImplements(); - } + addUnfinishedMethodTypeParameters(); - private void addVar() { - if (isVarAllowed()) { - addKeyword(createKeyword(PsiKeyword.VAR)); + addExtendsImplements(); } - } - @RequiredReadAction - private boolean isVarAllowed() { - if (PsiUtil.isAvailable(JavaFeature.VAR_LAMBDA_PARAMETER, myPosition) && isLambdaParameterType()) { - return true; + @RequiredReadAction + private void addVar() { + if (isVarAllowed()) { + addKeyword(createKeyword(PsiKeyword.VAR)); + } } - if (!PsiUtil.isAvailable(JavaFeature.LVTI, myPosition)) return false; + @RequiredReadAction + private boolean isVarAllowed() { + if (PsiUtil.isAvailable(JavaFeature.VAR_LAMBDA_PARAMETER, myPosition) && isLambdaParameterType()) { + return true; + } - if (isAtCatchOrResourceVariableStart(myPosition) && PsiTreeUtil.getParentOfType(myPosition, PsiCatchSection.class) == null) { - return true; - } + if (!PsiUtil.isAvailable(JavaFeature.LVTI, myPosition)) { + return false; + } - return isVariableTypePosition(myPosition) && - PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class, PsiLambdaExpression.class) != null; - } + if (isAtCatchOrResourceVariableStart(myPosition) && PsiTreeUtil.getParentOfType(myPosition, PsiCatchSection.class) == null) { + return true; + } - private static boolean isVariableTypePosition(PsiElement position) { - PsiElement parent = position.getParent(); - if (parent instanceof PsiJavaCodeReferenceElement && parent.getParent() instanceof PsiTypeElement && - parent.getParent().getParent() instanceof PsiDeclarationStatement) { - return true; + return isVariableTypePosition(myPosition) && + PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class, PsiLambdaExpression.class) != null; } - return START_FOR.accepts(position) || - isInsideParameterList(position) || - INSIDE_RECORD_HEADER.accepts(position) || - VARIABLE_AFTER_FINAL.accepts(position) || - isStatementPosition(position); - } - - private boolean isLambdaParameterType() { - PsiElement position = myParameters.getOriginalPosition(); - PsiParameterList paramList = PsiTreeUtil.getParentOfType(position, PsiParameterList.class); - if (paramList != null && paramList.getParent() instanceof PsiLambdaExpression) { - PsiParameter param = PsiTreeUtil.getParentOfType(position, PsiParameter.class); - PsiTypeElement type = param == null ? null : param.getTypeElement(); - return type == null || PsiTreeUtil.isAncestor(type, position, false); + + @RequiredReadAction + private static boolean isVariableTypePosition(PsiElement position) { + if (position.getParent() instanceof PsiJavaCodeReferenceElement codeRef + && codeRef.getParent() instanceof PsiTypeElement typeElem + && typeElem.getParent() instanceof PsiDeclarationStatement) { + return true; + } + return START_FOR.accepts(position) + || isInsideParameterList(position) + || INSIDE_RECORD_HEADER.accepts(position) + || VARIABLE_AFTER_FINAL.accepts(position) + || isStatementPosition(position); + } + + private boolean isLambdaParameterType() { + PsiElement position = myParameters.getOriginalPosition(); + PsiParameterList paramList = PsiTreeUtil.getParentOfType(position, PsiParameterList.class); + if (paramList != null && paramList.getParent() instanceof PsiLambdaExpression) { + PsiParameter param = PsiTreeUtil.getParentOfType(position, PsiParameter.class); + PsiTypeElement type = param == null ? null : param.getTypeElement(); + return type == null || PsiTreeUtil.isAncestor(type, position, false); + } + return false; } - return false; - } - - static boolean addWildcardExtendsSuper(CompletionResultSet result, PsiElement position) { - if (JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN.accepts(position)) { - for (String keyword : List.of(PsiKeyword.EXTENDS, PsiKeyword.SUPER)) { - if (keyword.startsWith(result.getPrefixMatcher().getPrefix())) { - result.addElement(new OverridableSpace(BasicExpressionCompletionContributor.createKeywordLookupItem(position, keyword), - TailType.HUMBLE_SPACE_BEFORE_WORD)); - } - } - return true; + + static boolean addWildcardExtendsSuper(CompletionResultSet result, PsiElement position) { + if (JavaMemberNameCompletionContributor.INSIDE_TYPE_PARAMS_PATTERN.accepts(position)) { + for (String keyword : List.of(PsiKeyword.EXTENDS, PsiKeyword.SUPER)) { + if (keyword.startsWith(result.getPrefixMatcher().getPrefix())) { + result.addElement(new OverridableSpace( + BasicExpressionCompletionContributor.createKeywordLookupItem(position, keyword), + TailType.HUMBLE_SPACE_BEFORE_WORD + )); + } + } + return true; + } + return false; } - return false; - } - - private void addMethodHeaderKeywords() { - if (psiElement().withText(")").withParents(PsiParameterList.class, PsiMethod.class).accepts(myPrevLeaf)) { - assert myPrevLeaf != null; - if (myPrevLeaf.getParent().getParent() instanceof PsiAnnotationMethod) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailType.HUMBLE_SPACE_BEFORE_WORD)); - } - else { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.THROWS), TailType.HUMBLE_SPACE_BEFORE_WORD)); - } + + @RequiredReadAction + private void addMethodHeaderKeywords() { + if (psiElement().withText(")").withParents(PsiParameterList.class, PsiMethod.class).accepts(myPrevLeaf)) { + assert myPrevLeaf != null; + if (myPrevLeaf.getParent().getParent() instanceof PsiAnnotationMethod) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailType.HUMBLE_SPACE_BEFORE_WORD)); + } + else { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.THROWS), TailType.HUMBLE_SPACE_BEFORE_WORD)); + } + } } - } - private void addCaseDefault() { - if (getSwitchFromLabelPosition(myPosition) != null) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CASE), TailType.INSERT_SPACE)); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailType.CASE_COLON)); + @RequiredReadAction + private void addCaseDefault() { + if (getSwitchFromLabelPosition(myPosition) != null) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CASE), TailType.INSERT_SPACE)); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailType.CASE_COLON)); + } } - } - private static PsiSwitchStatement getSwitchFromLabelPosition(PsiElement position) { - PsiStatement statement = PsiTreeUtil.getParentOfType(position, PsiStatement.class, false, PsiMember.class); - if (statement == null || statement.getTextRange().getStartOffset() != position.getTextRange().getStartOffset()) { - return null; + @RequiredReadAction + private static PsiSwitchStatement getSwitchFromLabelPosition(PsiElement position) { + PsiStatement statement = PsiTreeUtil.getParentOfType(position, PsiStatement.class, false, PsiMember.class); + if (statement == null || statement.getTextRange().getStartOffset() != position.getTextRange().getStartOffset()) { + return null; + } + + if (!(statement instanceof PsiSwitchLabelStatement) && statement.getParent() instanceof PsiCodeBlock codeBlock) { + return ObjectUtil.tryCast(codeBlock.getParent(), PsiSwitchStatement.class); + } + return null; } - if (!(statement instanceof PsiSwitchLabelStatement) && statement.getParent() instanceof PsiCodeBlock) { - return ObjectUtil.tryCast(statement.getParent().getParent(), PsiSwitchStatement.class); + @RequiredReadAction + void addEnumCases() { + PsiSwitchStatement switchStatement = getSwitchFromLabelPosition(myPosition); + PsiExpression expression = switchStatement == null ? null : switchStatement.getExpression(); + PsiClass switchType = expression == null ? null : PsiUtil.resolveClassInClassTypeOnly(expression.getType()); + if (switchType == null || !switchType.isEnum()) { + return; + } + + Set used = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(switchStatement); + for (PsiField field : switchType.getAllFields()) { + String name = field.getName(); + if (!(field instanceof PsiEnumConstant) || used.contains(CompletionUtilCore.getOriginalOrSelf(field)) || name == null) { + continue; + } + String prefix = "case "; + String suffix = name + ":"; + LookupElementBuilder caseConst = LookupElementBuilder.create(field, prefix + suffix) + .bold() + .withPresentableText(prefix) + .withTailText(suffix) + .withLookupString(name); + myResults.add(new JavaCompletionContributor.IndentingDecorator(caseConst)); + } } - return null; - } - - void addEnumCases() { - PsiSwitchStatement switchStatement = getSwitchFromLabelPosition(myPosition); - PsiExpression expression = switchStatement == null ? null : switchStatement.getExpression(); - PsiClass switchType = expression == null ? null : PsiUtil.resolveClassInClassTypeOnly(expression.getType()); - if (switchType == null || !switchType.isEnum()) { - return; + + @RequiredReadAction + private void addFinal() { + PsiStatement statement = PsiTreeUtil.getParentOfType(myPosition, PsiExpressionStatement.class); + if (statement == null) { + statement = PsiTreeUtil.getParentOfType(myPosition, PsiDeclarationStatement.class); + } + if (statement != null && statement.getTextRange().getStartOffset() == myPosition.getTextRange().getStartOffset()) { + if (!psiElement().withSuperParent(2, PsiSwitchStatement.class).afterLeaf("{").accepts(statement)) { + PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(myPrevLeaf, PsiTryStatement.class); + if (tryStatement == null + || tryStatement.getCatchSections().length > 0 + || tryStatement.getFinallyBlock() != null + || tryStatement.getResourceList() != null) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.FINAL), TailType.HUMBLE_SPACE_BEFORE_WORD)); + return; + } + } + } + + if ((isInsideParameterList(myPosition) || isAtResourceVariableStart(myPosition) || isAtCatchVariableStart(myPosition)) + && !psiElement().afterLeaf(PsiKeyword.FINAL).accepts(myPosition) + && !AFTER_DOT.accepts(myPosition)) { + addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.FINAL), TailType.HUMBLE_SPACE_BEFORE_WORD)); + } } - Set used = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(switchStatement); - for (PsiField field : switchType.getAllFields()) { - String name = field.getName(); - if (!(field instanceof PsiEnumConstant) || used.contains(CompletionUtilCore.getOriginalOrSelf(field)) || name == null) { - continue; - } - String prefix = "case "; - String suffix = name + ":"; - LookupElementBuilder caseConst = - LookupElementBuilder.create(field, prefix + suffix).bold().withPresentableText(prefix).withTailText(suffix).withLookupString(name); - myResults.add(new JavaCompletionContributor.IndentingDecorator(caseConst)); + @RequiredReadAction + private void addThisSuper() { + if (SUPER_OR_THIS_PATTERN.accepts(myPosition)) { + boolean afterDot = AFTER_DOT.accepts(myPosition); + boolean insideQualifierClass = isInsideQualifierClass(); + boolean insideInheritorClass = PsiUtil.isLanguageLevel8OrHigher(myPosition) && isInsideInheritorClass(); + if (!afterDot || insideQualifierClass || insideInheritorClass) { + if (!afterDot || insideQualifierClass) { + addKeyword(createKeyword(PsiKeyword.THIS)); + } + + LookupElement superItem = createKeyword(PsiKeyword.SUPER); + if (psiElement().afterLeaf(psiElement().withText("{").withSuperParent(2, psiMethod().constructor(true))) + .accepts(myPosition)) { + PsiMethod method = PsiTreeUtil.getParentOfType(myPosition, PsiMethod.class, false, PsiClass.class); + assert method != null; + final boolean hasParams = superConstructorHasParameters(method); + addKeyword(LookupElementDecorator.withInsertHandler(superItem, new ParenthesesInsertHandler() { + @Override + protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) { + return hasParams; + } + + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + super.handleInsert(context, item); + TailType.insertChar(context.getEditor(), context.getTailOffset(), ';'); + } + })); + return; + } + + addKeyword(superItem); + } + } } - } - private void addFinal() { - PsiStatement statement = PsiTreeUtil.getParentOfType(myPosition, PsiExpressionStatement.class); - if (statement == null) { - statement = PsiTreeUtil.getParentOfType(myPosition, PsiDeclarationStatement.class); + @RequiredReadAction + private void addExpressionKeywords(boolean statementPosition) { + if (psiElement(JavaTokenType.DOUBLE_COLON).accepts(myPrevLeaf)) { + PsiMethodReferenceExpression parent = PsiTreeUtil.getParentOfType(myPosition, PsiMethodReferenceExpression.class); + TailType tail = parent != null && !LambdaHighlightingUtil.insertSemicolon(parent.getParent()) + ? TailType.SEMICOLON + : TailType.NONE; + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.NEW), tail)); + return; + } + + if (isExpressionPosition(myPosition)) { + if (PsiTreeUtil.getParentOfType(myPosition, PsiAnnotation.class) == null) { + if (!statementPosition) { + addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.NEW), TailType.INSERT_SPACE)); + } + addKeyword(createKeyword(PsiKeyword.NULL)); + } + if (mayExpectBoolean(myParameters)) { + addKeyword(createKeyword(PsiKeyword.TRUE)); + addKeyword(createKeyword(PsiKeyword.FALSE)); + } + } } - if (statement != null && statement.getTextRange().getStartOffset() == myPosition.getTextRange().getStartOffset()) { - if (!psiElement().withSuperParent(2, PsiSwitchStatement.class).afterLeaf("{").accepts(statement)) { - PsiTryStatement tryStatement = PsiTreeUtil.getParentOfType(myPrevLeaf, PsiTryStatement.class); - if (tryStatement == null || tryStatement.getCatchSections().length > 0 || tryStatement.getFinallyBlock() != null || tryStatement.getResourceList() != null) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.FINAL), TailType.HUMBLE_SPACE_BEFORE_WORD)); - return; - } - } + + @RequiredReadAction + private void addFileHeaderKeywords() { + PsiFile file = myPosition.getContainingFile(); + assert file != null; + + if (!(file instanceof PsiExpressionCodeFragment) + && !(file instanceof PsiJavaCodeReferenceCodeFragment) + && !(file instanceof PsiTypeCodeFragment)) { + if (myPrevLeaf == null) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.PACKAGE), JavaTailTypes.humbleSpaceBeforeWordType())); + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IMPORT), JavaTailTypes.humbleSpaceBeforeWordType())); + } + else if (psiElement().inside(psiAnnotation().withParents(PsiModifierList.class, PsiFile.class)).accepts(myPrevLeaf) + && PsiJavaPackage.PACKAGE_INFO_FILE.equals(file.getName())) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.PACKAGE), JavaTailTypes.humbleSpaceBeforeWordType())); + } + else if (isEndOfBlock(myPosition) && PsiTreeUtil.getParentOfType(myPosition, PsiMember.class) == null) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IMPORT), JavaTailTypes.humbleSpaceBeforeWordType())); + } + } + + if (PsiUtil.isAvailable(JavaFeature.STATIC_IMPORTS, file) && myPrevLeaf != null && myPrevLeaf.textMatches(PsiKeyword.IMPORT)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.STATIC), JavaTailTypes.humbleSpaceBeforeWordType())); + } } - if ((isInsideParameterList(myPosition) || isAtResourceVariableStart(myPosition) || isAtCatchVariableStart(myPosition)) && !psiElement().afterLeaf( - PsiKeyword.FINAL).accepts(myPosition) && - !AFTER_DOT.accepts(myPosition)) { - addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.FINAL), TailType.HUMBLE_SPACE_BEFORE_WORD)); + @RequiredReadAction + private void addInstanceof() { + if (isInstanceofPlace(myPosition)) { + addKeyword(LookupElementDecorator.withInsertHandler( + createKeyword(PsiKeyword.INSTANCEOF), + (context, item) -> { + TailType tailType = TailType.HUMBLE_SPACE_BEFORE_WORD; + if (tailType.isApplicable(context)) { + tailType.processTail(context.getEditor(), context.getTailOffset()); + } + + if ('!' == context.getCompletionChar()) { + context.setAddCompletionChar(false); + context.commitDocument(); + PsiInstanceOfExpression expr = PsiTreeUtil.findElementOfClassAtOffset( + context.getFile(), + context.getStartOffset(), + PsiInstanceOfExpression.class, + false + ); + if (expr != null) { + String space = + CompletionStyleUtil.getCodeStyleSettings(context).SPACE_WITHIN_PARENTHESES ? " " : ""; + context.getDocument() + .insertString(expr.getTextRange().getStartOffset(), "!(" + space); + context.getDocument().insertString(context.getTailOffset(), space + ")"); + } + } + } + )); + } } - } - - private void addThisSuper() { - if (SUPER_OR_THIS_PATTERN.accepts(myPosition)) { - boolean afterDot = AFTER_DOT.accepts(myPosition); - boolean insideQualifierClass = isInsideQualifierClass(); - boolean insideInheritorClass = PsiUtil.isLanguageLevel8OrHigher(myPosition) && isInsideInheritorClass(); - if (!afterDot || insideQualifierClass || insideInheritorClass) { - if (!afterDot || insideQualifierClass) { - addKeyword(createKeyword(PsiKeyword.THIS)); - } - - LookupElement superItem = createKeyword(PsiKeyword.SUPER); - if (psiElement().afterLeaf(psiElement().withText("{").withSuperParent(2, psiMethod().constructor(true))).accepts(myPosition)) { - PsiMethod method = PsiTreeUtil.getParentOfType(myPosition, PsiMethod.class, false, PsiClass.class); - assert method != null; - final boolean hasParams = superConstructorHasParameters(method); - addKeyword(LookupElementDecorator.withInsertHandler(superItem, new ParenthesesInsertHandler() { - @Override - protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) { - return hasParams; + @RequiredReadAction + private void addClassKeywords() { + if (isSuitableForClass(myPosition)) { + for (String s : ModifierChooser.getKeywords(myPosition)) { + addKeyword(new OverridableSpace(createKeyword(s), JavaTailTypes.humbleSpaceBeforeWordType())); } - @Override - public void handleInsert(InsertionContext context, LookupElement item) { - super.handleInsert(context, item); - TailType.insertChar(context.getEditor(), context.getTailOffset(), ';'); + if (psiElement().insideStarting(psiElement(PsiLocalVariable.class, PsiExpressionStatement.class)).accepts(myPosition)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), JavaTailTypes.humbleSpaceBeforeWordType())); + addKeyword(new OverridableSpace( + LookupElementBuilder.create("abstract class").bold(), + JavaTailTypes.humbleSpaceBeforeWordType() + )); + if (PsiUtil.isAvailable(JavaFeature.RECORDS, myPosition)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.RECORD), JavaTailTypes.humbleSpaceBeforeWordType())); + } + if (PsiUtil.isAvailable(JavaFeature.LOCAL_ENUMS, myPosition)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ENUM), JavaTailTypes.humbleSpaceBeforeWordType())); + } + if (PsiUtil.isAvailable(JavaFeature.LOCAL_INTERFACES, myPosition)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.INTERFACE), JavaTailTypes.humbleSpaceBeforeWordType())); + } + } + if (PsiTreeUtil.getParentOfType(myPosition, PsiExpression.class, true, PsiMember.class) == null + && PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class) == null) { + List keywords = new ArrayList<>(); + keywords.add(PsiKeyword.CLASS); + keywords.add(PsiKeyword.INTERFACE); + if (PsiUtil.isAvailable(JavaFeature.RECORDS, myPosition)) { + keywords.add(PsiKeyword.RECORD); + } + if (PsiUtil.isAvailable(JavaFeature.ENUMS, myPosition)) { + keywords.add(PsiKeyword.ENUM); + } + String className = recommendClassName(); + for (String keyword : keywords) { + if (className == null) { + addKeyword(new OverridableSpace(createKeyword(keyword), JavaTailTypes.humbleSpaceBeforeWordType())); + } + else { + addKeyword(createTypeDeclaration(keyword, className)); + } + } } - })); - return; } - addKeyword(superItem); - } - } - } - - private void addExpressionKeywords(boolean statementPosition) { - if (psiElement(JavaTokenType.DOUBLE_COLON).accepts(myPrevLeaf)) { - PsiMethodReferenceExpression parent = PsiTreeUtil.getParentOfType(myPosition, PsiMethodReferenceExpression.class); - TailType tail = parent != null && !LambdaHighlightingUtil.insertSemicolon(parent.getParent()) ? TailType.SEMICOLON : TailType.NONE; - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.NEW), tail)); - return; + if (psiElement().withText("@").andNot(psiElement().inside(PsiParameterList.class)).andNot(psiElement().inside(psiNameValuePair())) + .accepts(myPrevLeaf)) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.INTERFACE), JavaTailTypes.humbleSpaceBeforeWordType())); + } } - if (isExpressionPosition(myPosition)) { - if (PsiTreeUtil.getParentOfType(myPosition, PsiAnnotation.class) == null) { - if (!statementPosition) { - addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.NEW), TailType.INSERT_SPACE)); - } - addKeyword(createKeyword(PsiKeyword.NULL)); - } - if (mayExpectBoolean(myParameters)) { - addKeyword(createKeyword(PsiKeyword.TRUE)); - addKeyword(createKeyword(PsiKeyword.FALSE)); - } - } - } - - private void addFileHeaderKeywords() { - PsiFile file = myPosition.getContainingFile(); - assert file != null; - - if (!(file instanceof PsiExpressionCodeFragment) && - !(file instanceof PsiJavaCodeReferenceCodeFragment) && - !(file instanceof PsiTypeCodeFragment)) { - if (myPrevLeaf == null) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.PACKAGE), JavaTailTypes.humbleSpaceBeforeWordType())); - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IMPORT), JavaTailTypes.humbleSpaceBeforeWordType())); - } - else if (psiElement().inside(psiAnnotation().withParents(PsiModifierList.class, PsiFile.class)).accepts(myPrevLeaf) - && PsiJavaPackage.PACKAGE_INFO_FILE.equals(file.getName())) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.PACKAGE), JavaTailTypes.humbleSpaceBeforeWordType())); - } - else if (isEndOfBlock(myPosition) && PsiTreeUtil.getParentOfType(myPosition, PsiMember.class) == null) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IMPORT), JavaTailTypes.humbleSpaceBeforeWordType())); - } + @RequiredReadAction + private LookupElement createTypeDeclaration(String keyword, String className) { + PsiElement nextElement = PsiTreeUtil.skipWhitespacesAndCommentsForward(PsiTreeUtil.nextLeaf(myPosition)); + IElementType nextToken; + if (nextElement instanceof PsiJavaToken t) { + nextToken = t.getTokenType(); + } + else if (nextElement instanceof PsiParameterList l && l.getFirstChild() instanceof PsiJavaToken t) { + nextToken = t.getTokenType(); + } + else if (nextElement instanceof PsiCodeBlock b && b.getFirstChild() instanceof PsiJavaToken t) { + nextToken = t.getTokenType(); + } + else { + nextToken = null; + } + LookupElement element = LookupElementBuilder.create(keyword + " " + className).withPresentableText(keyword).bold() + .withTailText(" " + className, false) + .withIcon(CreateClassKind.valueOf(keyword.toUpperCase(Locale.ROOT)).getKindIcon()) + .withInsertHandler((context, item) -> { + Document document = context.getDocument(); + int offset = context.getTailOffset(); + String suffix = " "; + if (keyword.equals(PsiKeyword.RECORD)) { + if (JavaTokenType.LPARENTH.equals(nextToken)) { + suffix = ""; + } + else if (JavaTokenType.LBRACE.equals(nextToken)) { + suffix = "() "; + } + else { + suffix = "() {\n}"; + } + } + else if (!JavaTokenType.LBRACE.equals(nextToken)) { + suffix = " {\n}"; + } + if (offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == ' ') { + suffix = suffix.trim(); + } + document.insertString(offset, suffix); + context.getEditor().getCaretModel().moveToOffset(offset + 1); + }); + return element; } - if (PsiUtil.isAvailable(JavaFeature.STATIC_IMPORTS, file) && myPrevLeaf != null && myPrevLeaf.textMatches(PsiKeyword.IMPORT)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.STATIC), JavaTailTypes.humbleSpaceBeforeWordType())); - } - } - - private void addInstanceof() { - if (isInstanceofPlace(myPosition)) { - addKeyword(LookupElementDecorator.withInsertHandler(createKeyword(PsiKeyword.INSTANCEOF), (context, item) -> { - TailType tailType = TailType.HUMBLE_SPACE_BEFORE_WORD; - if (tailType.isApplicable(context)) { - tailType.processTail(context.getEditor(), context.getTailOffset()); - } - - if ('!' == context.getCompletionChar()) { - context.setAddCompletionChar(false); - context.commitDocument(); - PsiInstanceOfExpression expr = PsiTreeUtil.findElementOfClassAtOffset( - context.getFile(), - context.getStartOffset(), - PsiInstanceOfExpression.class, - false); - if (expr != null) { - String space = - CompletionStyleUtil.getCodeStyleSettings(context).SPACE_WITHIN_PARENTHESES ? " " : ""; - context.getDocument() - .insertString(expr.getTextRange().getStartOffset(), "!(" + space); - context.getDocument().insertString(context.getTailOffset(), space + ")"); - } - } - })); - } - } - - @RequiredReadAction - private void addClassKeywords() { - if (isSuitableForClass(myPosition)) { - for (String s : ModifierChooser.getKeywords(myPosition)) { - addKeyword(new OverridableSpace(createKeyword(s), JavaTailTypes.humbleSpaceBeforeWordType())); - } - - if (psiElement().insideStarting(psiElement(PsiLocalVariable.class, PsiExpressionStatement.class)).accepts(myPosition)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CLASS), JavaTailTypes.humbleSpaceBeforeWordType())); - addKeyword(new OverridableSpace(LookupElementBuilder.create("abstract class").bold(), JavaTailTypes.humbleSpaceBeforeWordType())); - if (PsiUtil.isAvailable(JavaFeature.RECORDS, myPosition)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.RECORD), JavaTailTypes.humbleSpaceBeforeWordType())); - } - if (PsiUtil.isAvailable(JavaFeature.LOCAL_ENUMS, myPosition)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.ENUM), JavaTailTypes.humbleSpaceBeforeWordType())); - } - if (PsiUtil.isAvailable(JavaFeature.LOCAL_INTERFACES, myPosition)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.INTERFACE), JavaTailTypes.humbleSpaceBeforeWordType())); - } - } - if (PsiTreeUtil.getParentOfType(myPosition, PsiExpression.class, true, PsiMember.class) == null && - PsiTreeUtil.getParentOfType(myPosition, PsiCodeBlock.class, true, PsiMember.class) == null) { - List keywords = new ArrayList<>(); - keywords.add(PsiKeyword.CLASS); - keywords.add(PsiKeyword.INTERFACE); - if (PsiUtil.isAvailable(JavaFeature.RECORDS, myPosition)) { - keywords.add(PsiKeyword.RECORD); - } - if (PsiUtil.isAvailable(JavaFeature.ENUMS, myPosition)) { - keywords.add(PsiKeyword.ENUM); - } - String className = recommendClassName(); - for (String keyword : keywords) { - if (className == null) { - addKeyword(new OverridableSpace(createKeyword(keyword), JavaTailTypes.humbleSpaceBeforeWordType())); - } - else { - addKeyword(createTypeDeclaration(keyword, className)); - } - } - } - } + @Nullable + @RequiredReadAction + private String recommendClassName() { + if (myPrevLeaf == null) { + return null; + } + if (!myPrevLeaf.textMatches(PsiKeyword.PUBLIC) || !(myPrevLeaf.getParent() instanceof PsiModifierList)) { + return null; + } - if (psiElement().withText("@").andNot(psiElement().inside(PsiParameterList.class)).andNot(psiElement().inside(psiNameValuePair())) - .accepts(myPrevLeaf)) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.INTERFACE), JavaTailTypes.humbleSpaceBeforeWordType())); - } - } - - @RequiredReadAction - private LookupElement createTypeDeclaration(String keyword, String className) { - LookupElement element; - PsiElement nextElement = PsiTreeUtil.skipWhitespacesAndCommentsForward(PsiTreeUtil.nextLeaf(myPosition)); - IElementType nextToken; - if (nextElement instanceof PsiJavaToken) { - nextToken = ((PsiJavaToken)nextElement).getTokenType(); - } - else { - if (nextElement instanceof PsiParameterList l && l.getFirstChild() instanceof PsiJavaToken t) { - nextToken = t.getTokenType(); - } - else if (nextElement instanceof PsiCodeBlock b && b.getFirstChild() instanceof PsiJavaToken t) { - nextToken = t.getTokenType(); - } - else { - nextToken = null; - } - } - element = LookupElementBuilder.create(keyword + " " + className).withPresentableText(keyword).bold() - .withTailText(" " + className, false) - .withIcon(CreateClassKind.valueOf(keyword.toUpperCase(Locale.ROOT)).getKindIcon()) - .withInsertHandler((context, item) -> { - Document document = context.getDocument(); - int offset = context.getTailOffset(); - String suffix = " "; - if (keyword.equals(PsiKeyword.RECORD)) { - if (JavaTokenType.LPARENTH.equals(nextToken)) { - suffix = ""; - } - else if (JavaTokenType.LBRACE.equals(nextToken)) { - suffix = "() "; - } - else { - suffix = "() {\n}"; - } - } - else if (!JavaTokenType.LBRACE.equals(nextToken)) { - suffix = " {\n}"; - } - if (offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == ' ') { - suffix = suffix.trim(); - } - document.insertString(offset, suffix); - context.getEditor().getCaretModel().moveToOffset(offset + 1); - }); - return element; - } - - @Nullable - @RequiredReadAction - private String recommendClassName() { - if (myPrevLeaf == null) return null; - if (!myPrevLeaf.textMatches(PsiKeyword.PUBLIC) || !(myPrevLeaf.getParent() instanceof PsiModifierList)) return null; - - if (nextIsIdentifier(myPosition)) return null; - - PsiJavaFile file = getFileForDeclaration(myPrevLeaf); - if (file == null) return null; - String name = file.getName(); - if (!StringUtil.endsWithIgnoreCase(name, JavaFileType.DOT_DEFAULT_EXTENSION)) return null; - String candidate = name.substring(0, name.length() - JavaFileType.DOT_DEFAULT_EXTENSION.length()); - if (StringUtil.isJavaIdentifier(candidate) && !ContainerUtil.exists(file.getClasses(), c -> candidate.equals(c.getName()))) { - return candidate; - } - return null; - } - - private static boolean nextIsIdentifier(PsiElement position) { - PsiElement nextLeaf = PsiTreeUtil.nextLeaf(position); - if (nextLeaf == null) return false; - PsiElement parent = nextLeaf.getParent(); - if (!(parent instanceof PsiJavaCodeReferenceElement)) return false; - PsiElement grandParent = parent.getParent(); - if (!(grandParent instanceof PsiTypeElement)) return false; - return PsiTreeUtil.skipWhitespacesAndCommentsForward(grandParent) instanceof PsiIdentifier; - } - - @Nullable - private static PsiJavaFile getFileForDeclaration(PsiElement elementBeforeName) { - PsiElement parent = elementBeforeName.getParent(); - if (parent == null) return null; - PsiElement grandParent = parent.getParent(); - if (grandParent == null) return null; - if (grandParent instanceof PsiJavaFile f) { - return f; - } - PsiElement grandGrandParent = grandParent.getParent(); - if (grandGrandParent == null) return null; - return ObjectUtil.tryCast(grandGrandParent.getParent(), PsiJavaFile.class); - } - - private void addClassLiteral() { - if (isAfterTypeDot(myPosition)) { - addKeyword(createKeyword(PsiKeyword.CLASS)); + if (nextIsIdentifier(myPosition)) { + return null; + } + + PsiJavaFile file = getFileForDeclaration(myPrevLeaf); + if (file == null) { + return null; + } + String name = file.getName(); + if (!StringUtil.endsWithIgnoreCase(name, JavaFileType.DOT_DEFAULT_EXTENSION)) { + return null; + } + String candidate = name.substring(0, name.length() - JavaFileType.DOT_DEFAULT_EXTENSION.length()); + if (StringUtil.isJavaIdentifier(candidate) && !ContainerUtil.exists(file.getClasses(), c -> candidate.equals(c.getName()))) { + return candidate; + } + return null; } - } - private void addExtendsImplements() { - if (myPrevLeaf == null || - !(myPrevLeaf instanceof PsiIdentifier || myPrevLeaf.textMatches(">") || myPrevLeaf.textMatches(")"))) { - return; + @RequiredReadAction + private static boolean nextIsIdentifier(PsiElement position) { + PsiElement nextLeaf = PsiTreeUtil.nextLeaf(position); + return nextLeaf != null + && nextLeaf.getParent() instanceof PsiJavaCodeReferenceElement codeRef + && codeRef.getParent() instanceof PsiTypeElement typeElem + && PsiTreeUtil.skipWhitespacesAndCommentsForward(typeElem) instanceof PsiIdentifier; } - PsiClass psiClass = null; - PsiElement prevParent = myPrevLeaf.getParent(); - if (myPrevLeaf instanceof PsiIdentifier && prevParent instanceof PsiClass) { - psiClass = (PsiClass)prevParent; + @Nullable + private static PsiJavaFile getFileForDeclaration(PsiElement elementBeforeName) { + PsiElement parent = elementBeforeName.getParent(); + if (parent == null) { + return null; + } + PsiElement grandParent = parent.getParent(); + if (grandParent == null) { + return null; + } + if (grandParent instanceof PsiJavaFile f) { + return f; + } + PsiElement grandGrandParent = grandParent.getParent(); + if (grandGrandParent == null) { + return null; + } + return ObjectUtil.tryCast(grandGrandParent.getParent(), PsiJavaFile.class); } - else { - PsiReferenceList referenceList = PsiTreeUtil.getParentOfType(myPrevLeaf, PsiReferenceList.class); - if (referenceList != null && referenceList.getParent() instanceof PsiClass) { - psiClass = (PsiClass)referenceList.getParent(); - } - else if ((prevParent instanceof PsiTypeParameterList || prevParent instanceof PsiRecordHeader) - && prevParent.getParent() instanceof PsiClass) { - psiClass = (PsiClass)prevParent.getParent(); - } + + @RequiredReadAction + private void addClassLiteral() { + if (isAfterTypeDot(myPosition)) { + addKeyword(createKeyword(PsiKeyword.CLASS)); + } } - if (psiClass != null) { - if (!psiClass.isEnum() && !psiClass.isRecord()) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.EXTENDS), JavaTailTypes.humbleSpaceBeforeWordType())); - if (PsiUtil.isAvailable(JavaFeature.SEALED_CLASSES, psiClass)) { - PsiModifierList modifiers = psiClass.getModifierList(); - if (myParameters.getInvocationCount() > 1 || - (modifiers != null && - !modifiers.hasExplicitModifier(PsiModifier.FINAL) && - !modifiers.hasExplicitModifier(PsiModifier.NON_SEALED))) { - InsertHandler handler = (context, item) -> { - PsiClass aClass = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiClass.class, false); - if (aClass != null) { - PsiModifierList modifierList = aClass.getModifierList(); - if (modifierList != null) { - modifierList.setModifierProperty(PsiModifier.SEALED, true); + @RequiredReadAction + private void addExtendsImplements() { + if (myPrevLeaf == null || + !(myPrevLeaf instanceof PsiIdentifier || myPrevLeaf.textMatches(">") || myPrevLeaf.textMatches(")"))) { + return; + } + + PsiClass psiClass = null; + PsiElement prevParent = myPrevLeaf.getParent(); + if (myPrevLeaf instanceof PsiIdentifier && prevParent instanceof PsiClass c) { + psiClass = c; + } + else { + PsiReferenceList referenceList = PsiTreeUtil.getParentOfType(myPrevLeaf, PsiReferenceList.class); + if (referenceList != null && referenceList.getParent() instanceof PsiClass c) { + psiClass = c; + } + else if ((prevParent instanceof PsiTypeParameterList || prevParent instanceof PsiRecordHeader) + && prevParent.getParent() instanceof PsiClass c) { + psiClass = c; + } + } + + if (psiClass != null) { + if (!psiClass.isEnum() && !psiClass.isRecord()) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.EXTENDS), JavaTailTypes.humbleSpaceBeforeWordType())); + if (PsiUtil.isAvailable(JavaFeature.SEALED_CLASSES, psiClass)) { + PsiModifierList modifiers = psiClass.getModifierList(); + if (myParameters.getInvocationCount() > 1 || + (modifiers != null && + !modifiers.hasExplicitModifier(PsiModifier.FINAL) && + !modifiers.hasExplicitModifier(PsiModifier.NON_SEALED))) { + InsertHandler handler = (context, item) -> { + PsiClass aClass = PsiTreeUtil.findElementOfClassAtOffset( + context.getFile(), + context.getStartOffset(), + PsiClass.class, + false + ); + if (aClass != null) { + PsiModifierList modifierList = aClass.getModifierList(); + if (modifierList != null) { + modifierList.setModifierProperty(PsiModifier.SEALED, true); + } + } + }; + LookupElement element = new OverridableSpace( + LookupElementDecorator.withInsertHandler(createKeyword(PsiKeyword.PERMITS), handler), + JavaTailTypes.humbleSpaceBeforeWordType() + ); + addKeyword(element); + } } - } - }; - LookupElement element = - new OverridableSpace(LookupElementDecorator.withInsertHandler(createKeyword(PsiKeyword.PERMITS), handler), - JavaTailTypes.humbleSpaceBeforeWordType()); - addKeyword(element); - } - } - } - if (!psiClass.isInterface()) { - addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IMPLEMENTS), JavaTailTypes.humbleSpaceBeforeWordType())); - } - } - } - - private static boolean mayExpectBoolean(CompletionParameters parameters) { - for (ExpectedTypeInfo info : JavaSmartCompletionContributor.getExpectedTypes(parameters)) { - PsiType type = info.getType(); - if (type instanceof PsiClassType || PsiType.BOOLEAN.equals(type)) { - return true; - } - } - return false; - } - - private static boolean isExpressionPosition(PsiElement position) { - return EXPR_KEYWORDS.accepts(position) || psiElement().insideStarting(psiElement(PsiClassObjectAccessExpression.class)) - .accepts(position); - } - - public static boolean isInstanceofPlace(PsiElement position) { - PsiElement prev = PsiTreeUtil.prevVisibleLeaf(position); - if (prev == null) { - return false; + } + if (!psiClass.isInterface()) { + addKeyword(new OverridableSpace(createKeyword(PsiKeyword.IMPLEMENTS), JavaTailTypes.humbleSpaceBeforeWordType())); + } + } } - PsiElement expr = PsiTreeUtil.getParentOfType(prev, PsiExpression.class); - if (expr != null && expr.getTextRange().getEndOffset() == prev.getTextRange().getEndOffset()) { - return true; + private static boolean mayExpectBoolean(CompletionParameters parameters) { + for (ExpectedTypeInfo info : JavaSmartCompletionContributor.getExpectedTypes(parameters)) { + PsiType type = info.getType(); + if (type instanceof PsiClassType || PsiType.BOOLEAN.equals(type)) { + return true; + } + } + return false; } - if (position instanceof PsiIdentifier && position.getParent() instanceof PsiLocalVariable) { - PsiType type = ((PsiLocalVariable)position.getParent()).getType(); - if (type instanceof PsiClassType && ((PsiClassType)type).resolve() == null) { - return true; - } + @RequiredReadAction + private static boolean isExpressionPosition(PsiElement position) { + return EXPR_KEYWORDS.accepts(position) + || psiElement().insideStarting(psiElement(PsiClassObjectAccessExpression.class)).accepts(position); } - return false; - } + @RequiredReadAction + public static boolean isInstanceofPlace(PsiElement position) { + PsiElement prev = PsiTreeUtil.prevVisibleLeaf(position); + if (prev == null) { + return false; + } - public static boolean isSuitableForClass(PsiElement position) { - if (psiElement().afterLeaf("@").accepts(position) || - PsiTreeUtil.getNonStrictParentOfType(position, PsiLiteralExpression.class, PsiComment.class, PsiExpressionCodeFragment.class) != - null) { - return false; - } + PsiElement expr = PsiTreeUtil.getParentOfType(prev, PsiExpression.class); + if (expr != null && expr.getTextRange().getEndOffset() == prev.getTextRange().getEndOffset()) { + return true; + } - PsiElement prev = prevSignificantLeaf(position); - if (prev == null) { - return true; - } - if (psiElement().withoutText(".").inside( - psiElement(PsiModifierList.class).withParent( - not(psiElement(PsiParameter.class)).andNot(psiElement(PsiParameterList.class)))).accepts(prev) && - (!psiElement().inside(PsiAnnotationParameterList.class).accepts(prev) || prev.textMatches(")"))) { - return true; - } + return position instanceof PsiIdentifier + && position.getParent() instanceof PsiLocalVariable localVar + && localVar.getType() instanceof PsiClassType classType + && classType.resolve() == null; + } + + @RequiredReadAction + public static boolean isSuitableForClass(PsiElement position) { + if (psiElement().afterLeaf("@").accepts(position) + || PsiTreeUtil.getNonStrictParentOfType( + position, + PsiLiteralExpression.class, + PsiComment.class, + PsiExpressionCodeFragment.class + ) != null) { + return false; + } + + PsiElement prev = prevSignificantLeaf(position); + if (prev == null) { + return true; + } + if (psiElement().withoutText(".").inside( + psiElement(PsiModifierList.class).withParent( + not(psiElement(PsiParameter.class)).andNot(psiElement(PsiParameterList.class)))).accepts(prev) && + (!psiElement().inside(PsiAnnotationParameterList.class).accepts(prev) || prev.textMatches(")"))) { + return true; + } - if (psiElement().withParents(PsiErrorElement.class, PsiFile.class).accepts(position)) { - return true; + if (psiElement().withParents(PsiErrorElement.class, PsiFile.class).accepts(position)) { + return true; + } + + return isEndOfBlock(position); + } + + @RequiredReadAction + private void addUnfinishedMethodTypeParameters() { + ProcessingContext context = new ProcessingContext(); + PsiElementPattern.Capture capture = psiElement().inside( + psiElement(PsiTypeElement.class).afterLeaf( + psiElement().withText(">").withParent( + psiElement(PsiTypeParameterList.class).withParent(PsiErrorElement.class).save("typeParameterList") + ) + ) + ); + if (capture.accepts(myPosition, context)) { + PsiTypeParameterList list = (PsiTypeParameterList) context.get("typeParameterList"); + PsiElement current = list.getParent().getParent(); + if (current instanceof PsiField field) { + current = field.getParent(); + } + if (current instanceof PsiClass) { + for (PsiTypeParameter typeParameter : list.getTypeParameters()) { + addKeyword(new JavaPsiClassReferenceElement(typeParameter)); + } + } + } } - return isEndOfBlock(position); - } - - private void addUnfinishedMethodTypeParameters() { - ProcessingContext context = new ProcessingContext(); - if (psiElement().inside(psiElement(PsiTypeElement.class).afterLeaf(psiElement().withText(">") - .withParent(psiElement(PsiTypeParameterList.class).withParent( - PsiErrorElement.class).save - ("typeParameterList")))) - .accepts(myPosition, context)) { - PsiTypeParameterList list = (PsiTypeParameterList)context.get("typeParameterList"); - PsiElement current = list.getParent().getParent(); - if (current instanceof PsiField) { - current = current.getParent(); - } - if (current instanceof PsiClass) { - for (PsiTypeParameter typeParameter : list.getTypeParameters()) { - addKeyword(new JavaPsiClassReferenceElement(typeParameter)); - } - } + @RequiredReadAction + static boolean isAfterPrimitiveOrArrayType(PsiElement element) { + return psiElement().withParent(psiReferenceExpression().withFirstChild( + psiElement(PsiClassObjectAccessExpression.class).withLastChild(not(psiElement().withText(PsiKeyword.CLASS)))) + ).accepts(element); } - } - static boolean isAfterPrimitiveOrArrayType(PsiElement element) { - return psiElement().withParent(psiReferenceExpression().withFirstChild(psiElement(PsiClassObjectAccessExpression.class).withLastChild( - not(psiElement().withText(PsiKeyword.CLASS))))).accepts - (element); - } + @RequiredReadAction + static boolean isAfterTypeDot(PsiElement position) { + if (isInsideParameterList(position) || position.getContainingFile() instanceof PsiJavaCodeReferenceCodeFragment) { + return false; + } - static boolean isAfterTypeDot(PsiElement position) { - if (isInsideParameterList(position) || position.getContainingFile() instanceof PsiJavaCodeReferenceCodeFragment) { - return false; + return psiElement().afterLeaf(psiElement().withText(".").afterLeaf(CLASS_REFERENCE)).accepts(position) + || isAfterPrimitiveOrArrayType(position); } - return psiElement().afterLeaf(psiElement().withText(".").afterLeaf(CLASS_REFERENCE)).accepts(position) || isAfterPrimitiveOrArrayType( - position); - } + @RequiredReadAction + static void addPrimitiveTypes(Consumer result, PsiElement position, JavaCompletionSession session) { + if (AFTER_DOT.accepts(position) + || psiElement().inside(psiAnnotation()).accepts(position) && !expectsClassLiteral(position)) { + return; + } - static void addPrimitiveTypes(Consumer result, PsiElement position, JavaCompletionSession session) { - if (AFTER_DOT.accepts(position) || - psiElement().inside(psiAnnotation()).accepts(position) && !expectsClassLiteral(position)) { - return; - } + boolean afterNew = JavaSmartCompletionContributor.AFTER_NEW.accepts(position) + && !psiElement().afterLeaf(psiElement().afterLeaf(".")).accepts(position); + if (afterNew) { + Set expected = ContainerUtil.map2Set( + JavaSmartCompletionContributor.getExpectedTypes(position, false), + ExpectedTypeInfo::getDefaultType + ); + boolean addAll = expected.isEmpty() + || ContainerUtil.exists( + expected, + t -> t.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || t.equalsToText(CommonClassNames.JAVA_IO_SERIALIZABLE) + ); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(position.getProject()); + for (String primitiveType : PRIMITIVE_TYPES) { + PsiType array = factory.createTypeFromText(primitiveType + "[]", null); + if (addAll || expected.contains(array)) { + result.accept(PsiTypeLookupItem.createLookupItem(array, null)); + } + } + return; + } - boolean afterNew = JavaSmartCompletionContributor.AFTER_NEW.accepts(position) && - !psiElement().afterLeaf(psiElement().afterLeaf(".")).accepts(position); - if (afterNew) { - Set expected = ContainerUtil.map2Set(JavaSmartCompletionContributor.getExpectedTypes(position, false), - ExpectedTypeInfo::getDefaultType); - boolean addAll = expected.isEmpty() || ContainerUtil.exists(expected, t -> - t.equalsToText(CommonClassNames.JAVA_LANG_OBJECT) || t.equalsToText(CommonClassNames.JAVA_IO_SERIALIZABLE)); - PsiElementFactory factory = JavaPsiFacade.getElementFactory(position.getProject()); - for (String primitiveType : PRIMITIVE_TYPES) { - PsiType array = factory.createTypeFromText(primitiveType + "[]", null); - if (addAll || expected.contains(array)) { - result.accept(PsiTypeLookupItem.createLookupItem(array, null)); - } - } - return; + boolean inCast = psiElement() + .afterLeaf(psiElement().withText("(").withParent(psiElement(PsiParenthesizedExpression.class, PsiTypeCastExpression.class))) + .accepts(position); + + boolean typeFragment = position.getContainingFile() instanceof PsiTypeCodeFragment && PsiTreeUtil.prevVisibleLeaf(position) == null; + boolean declaration = isDeclarationStart(position); + boolean expressionPosition = isExpressionPosition(position); + boolean inGenerics = PsiTreeUtil.getParentOfType(position, PsiReferenceParameterList.class) != null; + if (isVariableTypePosition(position) + || inGenerics + || inCast + || declaration + || typeFragment + || expressionPosition) { + for (String primitiveType : PRIMITIVE_TYPES) { + if (!session.isKeywordAlreadyProcessed(primitiveType)) { + result.accept(BasicExpressionCompletionContributor.createKeywordLookupItem(position, primitiveType)); + } + } + if (expressionPosition && !session.isKeywordAlreadyProcessed(PsiKeyword.VOID)) { + result.accept(BasicExpressionCompletionContributor.createKeywordLookupItem(position, PsiKeyword.VOID)); + } + } + if (declaration) { + LookupElement item = BasicExpressionCompletionContributor.createKeywordLookupItem(position, PsiKeyword.VOID); + result.accept(new OverridableSpace(item, JavaTailTypes.humbleSpaceBeforeWordType())); + } + else if (typeFragment && ((PsiTypeCodeFragment) position.getContainingFile()).isVoidValid()) { + result.accept(BasicExpressionCompletionContributor.createKeywordLookupItem(position, PsiKeyword.VOID)); + } } - boolean inCast = psiElement() - .afterLeaf(psiElement().withText("(").withParent(psiElement(PsiParenthesizedExpression.class, PsiTypeCastExpression.class))) - .accepts(position); - - boolean typeFragment = position.getContainingFile() instanceof PsiTypeCodeFragment && PsiTreeUtil.prevVisibleLeaf(position) == null; - boolean declaration = isDeclarationStart(position); - boolean expressionPosition = isExpressionPosition(position); - boolean inGenerics = PsiTreeUtil.getParentOfType(position, PsiReferenceParameterList.class) != null; - if (isVariableTypePosition(position) || - inGenerics || - inCast || - declaration || - typeFragment || - expressionPosition) { - for (String primitiveType : PRIMITIVE_TYPES) { - if (!session.isKeywordAlreadyProcessed(primitiveType)) { - result.accept(BasicExpressionCompletionContributor.createKeywordLookupItem(position, primitiveType)); - } - } - if (expressionPosition && !session.isKeywordAlreadyProcessed(PsiKeyword.VOID)) { - result.accept(BasicExpressionCompletionContributor.createKeywordLookupItem(position, PsiKeyword.VOID)); - } + private static boolean expectsClassLiteral(PsiElement position) { + return ContainerUtil.find( + JavaSmartCompletionContributor.getExpectedTypes(position, false), + info -> InheritanceUtil.isInheritor(info.getType(), CommonClassNames.JAVA_LANG_CLASS) + ) != null; } - if (declaration) { - LookupElement item = BasicExpressionCompletionContributor.createKeywordLookupItem(position, PsiKeyword.VOID); - result.accept(new OverridableSpace(item, JavaTailTypes.humbleSpaceBeforeWordType())); + + @RequiredReadAction + private static boolean isAtResourceVariableStart(PsiElement position) { + return psiElement().insideStarting(psiElement(PsiTypeElement.class).withParent(PsiResourceList.class)).accepts(position); } - else if (typeFragment && ((PsiTypeCodeFragment)position.getContainingFile()).isVoidValid()) { - result.accept(BasicExpressionCompletionContributor.createKeywordLookupItem(position, PsiKeyword.VOID)); + + @RequiredReadAction + private static boolean isAtCatchVariableStart(PsiElement position) { + return psiElement().insideStarting(psiElement(PsiTypeElement.class).withParent(PsiCatchSection.class)).accepts(position); } - } - private static boolean expectsClassLiteral(PsiElement position) { - return ContainerUtil.find(JavaSmartCompletionContributor.getExpectedTypes(position, false), - info -> InheritanceUtil.isInheritor(info.getType(), CommonClassNames.JAVA_LANG_CLASS)) != null; - } + @RequiredReadAction + static boolean isDeclarationStart(PsiElement position) { + if (psiElement().afterLeaf("@", ".").accepts(position)) { + return false; + } - private static boolean isAtResourceVariableStart(PsiElement position) { - return psiElement().insideStarting(psiElement(PsiTypeElement.class).withParent(PsiResourceList.class)).accepts(position); - } + PsiElement parent = position.getParent(); + if (parent instanceof PsiJavaCodeReferenceElement codeRef && codeRef.getParent() instanceof PsiTypeElement typeElem) { + PsiElement typeHolder = psiApi().parents(typeElem).skipWhile(PsiTypeElement.class::isInstance).first(); + return typeHolder instanceof PsiMember || typeHolder instanceof PsiClassLevelDeclarationStatement; + } - private static boolean isAtCatchVariableStart(PsiElement position) { - return psiElement().insideStarting(psiElement(PsiTypeElement.class).withParent(PsiCatchSection.class)).accepts(position); - } + return false; + } - static boolean isDeclarationStart(PsiElement position) { - if (psiElement().afterLeaf("@", ".").accepts(position)) return false; + @RequiredReadAction + private static boolean isAtCatchOrResourceVariableStart(PsiElement position) { + PsiElement type = PsiTreeUtil.getParentOfType(position, PsiTypeElement.class); + if (type != null && type.getTextRange().getStartOffset() == position.getTextRange().getStartOffset()) { + PsiElement parent = type.getParent(); + if (parent instanceof PsiVariable variable) { + parent = variable.getParent(); + } + return parent instanceof PsiCatchSection || parent instanceof PsiResourceList; + } + return psiElement().insideStarting(psiElement(PsiResourceExpression.class)).accepts(position); + } + + @RequiredReadAction + private void addBreakContinue() { + PsiLoopStatement loop = + PsiTreeUtil.getParentOfType(myPosition, PsiLoopStatement.class, true, PsiLambdaExpression.class, PsiMember.class); + + LookupElement br = createKeyword(PsiKeyword.BREAK); + LookupElement cont = createKeyword(PsiKeyword.CONTINUE); + TailType tailType; + if (psiElement().insideSequence( + true, + psiElement(PsiLabeledStatement.class), + or(psiElement(PsiFile.class), psiElement(PsiMethod.class), psiElement(PsiClassInitializer.class)) + ).accepts(myPosition)) { + tailType = TailType.HUMBLE_SPACE_BEFORE_WORD; + } + else { + tailType = TailType.SEMICOLON; + } + br = TailTypeDecorator.withTail(br, tailType); + cont = TailTypeDecorator.withTail(cont, tailType); - PsiElement parent = position.getParent(); - if (parent instanceof PsiJavaCodeReferenceElement && parent.getParent() instanceof PsiTypeElement) { - PsiElement typeHolder = psiApi().parents(parent.getParent()).skipWhile(Conditions.instanceOf(PsiTypeElement.class)).first(); - return typeHolder instanceof PsiMember || typeHolder instanceof PsiClassLevelDeclarationStatement; - } + if (loop != null && PsiTreeUtil.isAncestor(loop.getBody(), myPosition, false)) { + addKeyword(br); + addKeyword(cont); + } + if (psiElement().inside(PsiSwitchStatement.class).accepts(myPosition)) { + addKeyword(br); + } - return false; - } - - private static boolean isAtCatchOrResourceVariableStart(PsiElement position) { - PsiElement type = PsiTreeUtil.getParentOfType(position, PsiTypeElement.class); - if (type != null && type.getTextRange().getStartOffset() == position.getTextRange().getStartOffset()) { - PsiElement parent = type.getParent(); - if (parent instanceof PsiVariable) { - parent = parent.getParent(); - } - return parent instanceof PsiCatchSection || parent instanceof PsiResourceList; - } - return psiElement().insideStarting(psiElement(PsiResourceExpression.class)).accepts(position); - } - - private void addBreakContinue() { - PsiLoopStatement loop = - PsiTreeUtil.getParentOfType(myPosition, PsiLoopStatement.class, true, PsiLambdaExpression.class, PsiMember.class); - - LookupElement br = createKeyword(PsiKeyword.BREAK); - LookupElement cont = createKeyword(PsiKeyword.CONTINUE); - TailType tailType; - if (psiElement().insideSequence(true, - psiElement(PsiLabeledStatement.class), - or(psiElement(PsiFile.class), psiElement(PsiMethod.class), psiElement(PsiClassInitializer.class))) - .accepts - (myPosition)) { - tailType = TailType.HUMBLE_SPACE_BEFORE_WORD; - } - else { - tailType = TailType.SEMICOLON; + for (PsiLabeledStatement labeled : psiApi().parents(myPosition) + .takeWhile(notInstanceOf(PsiMember.class)) + .filter(PsiLabeledStatement.class)) { + addKeyword(TailTypeDecorator.withTail(LookupElementBuilder.create("break " + labeled.getName()).bold(), TailType.SEMICOLON)); + } } - br = TailTypeDecorator.withTail(br, tailType); - cont = TailTypeDecorator.withTail(cont, tailType); - if (loop != null && PsiTreeUtil.isAncestor(loop.getBody(), myPosition, false)) { - addKeyword(br); - addKeyword(cont); - } - if (psiElement().inside(PsiSwitchStatement.class).accepts(myPosition)) { - addKeyword(br); - } + @RequiredReadAction + private static boolean isStatementPosition(PsiElement position) { + if (psiElement() + .withSuperParent(2, PsiConditionalExpression.class) + .andNot(psiElement().insideStarting(psiElement(PsiConditionalExpression.class))) + .accepts(position)) { + return false; + } - for (PsiLabeledStatement labeled : psiApi().parents(myPosition) - .takeWhile(notInstanceOf(PsiMember.class)) - .filter(PsiLabeledStatement.class)) { - addKeyword(TailTypeDecorator.withTail(LookupElementBuilder.create("break " + labeled.getName()).bold(), TailType.SEMICOLON)); - } - } - - private static boolean isStatementPosition(PsiElement position) { - if (psiElement() - .withSuperParent(2, PsiConditionalExpression.class) - .andNot(psiElement().insideStarting(psiElement(PsiConditionalExpression.class))) - .accepts(position)) { - return false; + if (isEndOfBlock(position) + && PsiTreeUtil.getParentOfType(position, PsiCodeBlock.class, true, PsiMember.class) != null) { + return !isForLoopMachinery(position); + } + + if (psiElement().withParents(PsiReferenceExpression.class, PsiExpressionStatement.class, PsiIfStatement.class) + .andNot(psiElement().afterLeaf(".")).accepts(position)) { + PsiElement stmt = position.getParent().getParent(); + PsiIfStatement ifStatement = (PsiIfStatement) stmt.getParent(); + return ifStatement.getElseBranch() == stmt || ifStatement.getThenBranch() == stmt; + } + + return false; } - if (isEndOfBlock(position) && - PsiTreeUtil.getParentOfType(position, PsiCodeBlock.class, true, PsiMember.class) != null) { - return !isForLoopMachinery(position); + private static boolean isForLoopMachinery(PsiElement position) { + PsiStatement statement = PsiTreeUtil.getParentOfType(position, PsiStatement.class); + if (statement == null) { + return false; + } + + return statement instanceof PsiForStatement + || statement.getParent() instanceof PsiForStatement forStmt && statement != forStmt.getBody(); } - if (psiElement().withParents(PsiReferenceExpression.class, PsiExpressionStatement.class, PsiIfStatement.class).andNot( - psiElement().afterLeaf(".")).accepts(position)) { - PsiElement stmt = position.getParent().getParent(); - PsiIfStatement ifStatement = (PsiIfStatement)stmt.getParent(); - return ifStatement.getElseBranch() == stmt || ifStatement.getThenBranch() == stmt; + private LookupElement createKeyword(String keyword) { + return BasicExpressionCompletionContributor.createKeywordLookupItem(myPosition, keyword); } - return false; - } - - private static boolean isForLoopMachinery(PsiElement position) { - PsiStatement statement = PsiTreeUtil.getParentOfType(position, PsiStatement.class); - if (statement == null) return false; - - return statement instanceof PsiForStatement || - statement.getParent() instanceof PsiForStatement && statement != ((PsiForStatement)statement.getParent()).getBody(); - } - - private LookupElement createKeyword(String keyword) { - return BasicExpressionCompletionContributor.createKeywordLookupItem(myPosition, keyword); - } - - private boolean isInsideQualifierClass() { - if (myPosition.getParent() instanceof PsiJavaCodeReferenceElement) { - PsiElement qualifier = ((PsiJavaCodeReferenceElement)myPosition.getParent()).getQualifier(); - if (qualifier instanceof PsiJavaCodeReferenceElement) { - PsiElement qualifierClass = ((PsiJavaCodeReferenceElement)qualifier).resolve(); - if (qualifierClass instanceof PsiClass) { - PsiElement parent = myPosition; - PsiManager psiManager = myPosition.getManager(); - while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) { - if (psiManager.areElementsEquivalent(parent, qualifierClass)) { - return true; + @RequiredReadAction + private boolean isInsideQualifierClass() { + if (myPosition.getParent() instanceof PsiJavaCodeReferenceElement codeRef + && codeRef.getQualifier() instanceof PsiJavaCodeReferenceElement qualifier + && qualifier.resolve() instanceof PsiClass qualifierClass) { + PsiElement parent = myPosition; + PsiManager psiManager = myPosition.getManager(); + while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) { + if (psiManager.areElementsEquivalent(parent, qualifierClass)) { + return true; + } } - } } - } + return false; } - return false; - } - - private boolean isInsideInheritorClass() { - if (myPosition.getParent() instanceof PsiJavaCodeReferenceElement) { - PsiElement qualifier = ((PsiJavaCodeReferenceElement)myPosition.getParent()).getQualifier(); - if (qualifier instanceof PsiJavaCodeReferenceElement) { - PsiElement qualifierClass = ((PsiJavaCodeReferenceElement)qualifier).resolve(); - if (qualifierClass instanceof PsiClass && ((PsiClass)qualifierClass).isInterface()) { - PsiElement parent = myPosition; - while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) { - if (PsiUtil.getEnclosingStaticElement(myPosition, - (PsiClass)parent) == null && ((PsiClass)parent).isInheritor((PsiClass)qualifierClass, - true)) { - return true; + + @RequiredReadAction + private boolean isInsideInheritorClass() { + if (myPosition.getParent() instanceof PsiJavaCodeReferenceElement codeRef + && codeRef.getQualifier() instanceof PsiJavaCodeReferenceElement qualifier + && qualifier.resolve() instanceof PsiClass qualifierClass + && qualifierClass.isInterface()) { + PsiElement parent = myPosition; + while ((parent = PsiTreeUtil.getParentOfType(parent, PsiClass.class, true)) != null) { + PsiClass parentClass = (PsiClass) parent; + if (PsiUtil.getEnclosingStaticElement(myPosition, parentClass) == null + && parentClass.isInheritor(qualifierClass, true)) { + return true; + } } - } } - } + return false; } - return false; - } - private static boolean superConstructorHasParameters(PsiMethod method) { - PsiClass psiClass = method.getContainingClass(); - if (psiClass == null) { - return false; - } + private static boolean superConstructorHasParameters(PsiMethod method) { + PsiClass psiClass = method.getContainingClass(); + if (psiClass == null) { + return false; + } - PsiClass superClass = psiClass.getSuperClass(); - if (superClass != null) { - for (PsiMethod psiMethod : superClass.getConstructors()) { - PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper(); - if (resolveHelper.isAccessible(psiMethod, method, null) && psiMethod.getParameterList().getParameters().length > 0) { - return true; + PsiClass superClass = psiClass.getSuperClass(); + if (superClass != null) { + for (PsiMethod psiMethod : superClass.getConstructors()) { + PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(method.getProject()).getResolveHelper(); + if (resolveHelper.isAccessible(psiMethod, method, null) && psiMethod.getParameterList().getParameters().length > 0) { + return true; + } + } } - } + return false; } - return false; - } - public static class OverridableSpace extends TailTypeDecorator { - private final TailType myTail; + public static class OverridableSpace extends TailTypeDecorator { + private final TailType myTail; - public OverridableSpace(LookupElement keyword, TailType tail) { - super(keyword); - myTail = tail; - } + public OverridableSpace(LookupElement keyword, TailType tail) { + super(keyword); + myTail = tail; + } - @Override - protected TailType computeTailType(InsertionContext context) { - return context.shouldAddCompletionChar() ? TailType.NONE : myTail; + @Override + protected TailType computeTailType(InsertionContext context) { + return context.shouldAddCompletionChar() ? TailType.NONE : myTail; + } } - } }