mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
Code completion updates for enhanced switches (IDEA-202340)
- Support inside switch expression - Support rule-based labels - Support break inside expression - Suggest 'switch' keyword in expressions - Insert ':' or '->' depending on the switch type
This commit is contained in:
@@ -570,13 +570,13 @@ public class ExpectedTypesProvider {
|
||||
getExpectedArgumentsTypesForNewExpression((PsiNewExpression)parent.getParent(), list);
|
||||
}
|
||||
else if (parent instanceof PsiSwitchLabelStatementBase) {
|
||||
PsiSwitchStatement switchStatement = ((PsiSwitchLabelStatementBase)parent).getEnclosingSwitchStatement();
|
||||
if (switchStatement != null) {
|
||||
PsiExpression expression = switchStatement.getExpression();
|
||||
PsiSwitchBlock switchBlock = ((PsiSwitchLabelStatementBase)parent).getEnclosingSwitchBlock();
|
||||
if (switchBlock != null) {
|
||||
PsiExpression expression = switchBlock.getExpression();
|
||||
if (expression != null) {
|
||||
PsiType type = expression.getType();
|
||||
if (type != null) {
|
||||
myResult.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.CASE_COLON));
|
||||
myResult.add(createInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailTypes.forSwitchLabel(switchBlock)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,17 @@
|
||||
*/
|
||||
package com.intellij.codeInsight;
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.codeInsight.completion.simple.BracesTailType;
|
||||
import com.intellij.codeInsight.completion.simple.ParenthesesTailType;
|
||||
import com.intellij.codeInsight.completion.simple.RParenthTailType;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiSwitchBlock;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.siyeh.ig.psiutils.SwitchUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class TailTypes {
|
||||
public static final TailType CALL_RPARENTH = new RParenthTailType(){
|
||||
@@ -124,11 +130,38 @@ public class TailTypes {
|
||||
return styleSettings.SPACE_WITHIN_IF_PARENTHESES;
|
||||
}
|
||||
};
|
||||
private static final String ARROW = " -> ";
|
||||
public static final TailType CASE_ARROW = new TailType() {
|
||||
@Override
|
||||
public int processTail(Editor editor, int tailOffset) {
|
||||
Document document = editor.getDocument();
|
||||
document.insertString(tailOffset, ARROW);
|
||||
return moveCaret(editor, tailOffset, ARROW.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(@NotNull InsertionContext context) {
|
||||
Document document = context.getDocument();
|
||||
int offset = context.getTailOffset();
|
||||
int length = document.getTextLength();
|
||||
int endOffset = offset + ARROW.length();
|
||||
if (endOffset > length) return true;
|
||||
return !document.getText(new TextRange(offset, endOffset)).equals(ARROW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CASE_ARROW";
|
||||
}
|
||||
};
|
||||
private static final TailType BRACES = new BracesTailType();
|
||||
public static final TailType FINALLY_LBRACE = BRACES;
|
||||
public static final TailType TRY_LBRACE = BRACES;
|
||||
public static final TailType DO_LBRACE = BRACES;
|
||||
|
||||
public static TailType forSwitchLabel(@NotNull PsiSwitchBlock block) {
|
||||
return SwitchUtils.isRuleFormatSwitch(block) ? CASE_ARROW : TailType.CASE_COLON;
|
||||
}
|
||||
|
||||
|
||||
private TailTypes() {}
|
||||
|
||||
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.completion;
|
||||
import com.intellij.codeInsight.ExpectedTypeInfo;
|
||||
import com.intellij.codeInsight.ExpectedTypesProvider;
|
||||
import com.intellij.codeInsight.TailType;
|
||||
import com.intellij.codeInsight.TailTypes;
|
||||
import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFix;
|
||||
import com.intellij.codeInsight.lookup.*;
|
||||
@@ -81,13 +82,13 @@ public class JavaCompletionContributor extends CompletionContributor {
|
||||
psiElement().afterLeaf("(").withParent(psiReferenceExpression().withParent(NAME_VALUE_PAIR)));
|
||||
|
||||
public static final ElementPattern<PsiElement> IN_SWITCH_LABEL =
|
||||
psiElement().withSuperParent(2, psiElement(PsiExpressionList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2, PsiSwitchStatement.class)));
|
||||
psiElement().withSuperParent(2, psiElement(PsiExpressionList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2, PsiSwitchBlock.class)));
|
||||
private static final ElementPattern IN_ENUM_SWITCH_LABEL =
|
||||
psiElement().withSuperParent(2, psiElement(PsiExpressionList.class).withParent(psiElement(PsiSwitchLabelStatementBase.class).withSuperParent(2,
|
||||
psiElement(PsiSwitchStatement.class).with(new PatternCondition<PsiSwitchStatement>("enumExpressionType") {
|
||||
psiElement(PsiSwitchBlock.class).with(new PatternCondition<PsiSwitchBlock>("enumExpressionType") {
|
||||
@Override
|
||||
public boolean accepts(@NotNull PsiSwitchStatement psiSwitchStatement, ProcessingContext context) {
|
||||
PsiExpression expression = psiSwitchStatement.getExpression();
|
||||
public boolean accepts(@NotNull PsiSwitchBlock psiSwitchBlock, ProcessingContext context) {
|
||||
PsiExpression expression = psiSwitchBlock.getExpression();
|
||||
if (expression == null) return false;
|
||||
PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
|
||||
return aClass != null && aClass.isEnum();
|
||||
@@ -426,7 +427,13 @@ public class JavaCompletionContributor extends CompletionContributor {
|
||||
MultiMap<CompletionResultSet, LookupElement> items = MultiMap.create();
|
||||
final PsiElement position = parameters.getPosition();
|
||||
final boolean first = parameters.getInvocationCount() <= 1;
|
||||
final boolean isSwitchLabel = IN_ENUM_SWITCH_LABEL.accepts(position);
|
||||
final TailType switchLabelTail;
|
||||
if (IN_ENUM_SWITCH_LABEL.accepts(position)) {
|
||||
PsiSwitchBlock block = Objects.requireNonNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class));
|
||||
switchLabelTail = TailTypes.forSwitchLabel(block);
|
||||
} else {
|
||||
switchLabelTail = null;
|
||||
}
|
||||
final boolean isAfterNew = JavaClassNameCompletionContributor.AFTER_NEW.accepts(position);
|
||||
final boolean pkgContext = JavaCompletionUtil.inSomePackage(position);
|
||||
final PsiType[] expectedTypes = ExpectedTypesGetter.getExpectedTypes(parameters.getPosition(), true);
|
||||
@@ -453,8 +460,8 @@ public class JavaCompletionContributor extends CompletionContributor {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSwitchLabel) {
|
||||
items.putValue(result1, new IndentingDecorator(TailTypeDecorator.withTail(element, TailType.createSimpleTailType(':'))));
|
||||
if (switchLabelTail != null) {
|
||||
items.putValue(result1, new IndentingDecorator(TailTypeDecorator.withTail(element, switchLabelTail)));
|
||||
}
|
||||
else {
|
||||
final LookupItem item = element.as(LookupItem.CLASS_CONDITION_KEY);
|
||||
|
||||
@@ -318,45 +318,46 @@ public class JavaKeywordCompletion {
|
||||
}
|
||||
|
||||
private void addCaseDefault() {
|
||||
if (getSwitchFromLabelPosition(myPosition) != null) {
|
||||
PsiSwitchBlock switchBlock = getSwitchFromLabelPosition(myPosition);
|
||||
if (switchBlock != null) {
|
||||
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.CASE), TailType.INSERT_SPACE));
|
||||
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailType.CASE_COLON));
|
||||
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.DEFAULT), TailTypes.forSwitchLabel(switchBlock)));
|
||||
}
|
||||
}
|
||||
|
||||
private static PsiSwitchStatement getSwitchFromLabelPosition(PsiElement position) {
|
||||
private static PsiSwitchBlock 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) {
|
||||
return ObjectUtils.tryCast(statement.getParent().getParent(), PsiSwitchStatement.class);
|
||||
if (!(statement instanceof PsiSwitchLabelStatementBase) && statement.getParent() instanceof PsiCodeBlock) {
|
||||
return ObjectUtils.tryCast(statement.getParent().getParent(), PsiSwitchBlock.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void addEnumCases() {
|
||||
PsiSwitchStatement switchStatement = getSwitchFromLabelPosition(myPosition);
|
||||
PsiExpression expression = switchStatement == null ? null : switchStatement.getExpression();
|
||||
PsiSwitchBlock switchBlock = getSwitchFromLabelPosition(myPosition);
|
||||
PsiExpression expression = switchBlock == null ? null : switchBlock.getExpression();
|
||||
PsiClass switchType = expression == null ? null : PsiUtil.resolveClassInClassTypeOnly(expression.getType());
|
||||
if (switchType == null || !switchType.isEnum()) return;
|
||||
|
||||
Set<PsiField> used = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(switchStatement);
|
||||
Set<PsiField> used = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(switchBlock);
|
||||
TailType tailType = TailTypes.forSwitchLabel(switchBlock);
|
||||
for (PsiField field : switchType.getAllFields()) {
|
||||
String name = field.getName();
|
||||
if (!(field instanceof PsiEnumConstant) || used.contains(CompletionUtil.getOriginalOrSelf(field)) || name == null) {
|
||||
continue;
|
||||
}
|
||||
String prefix = "case ";
|
||||
String suffix = name + ":";
|
||||
LookupElementBuilder caseConst = LookupElementBuilder
|
||||
.create(field, prefix + suffix)
|
||||
.create(field, prefix + name)
|
||||
.bold()
|
||||
.withPresentableText(prefix)
|
||||
.withTailText(suffix)
|
||||
.withTailText(name)
|
||||
.withLookupString(name);
|
||||
myResults.add(new JavaCompletionContributor.IndentingDecorator(caseConst));
|
||||
myResults.add(new JavaCompletionContributor.IndentingDecorator(TailTypeDecorator.withTail(caseConst, tailType)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,6 +431,9 @@ public class JavaKeywordCompletion {
|
||||
if (PsiTreeUtil.getParentOfType(myPosition, PsiAnnotation.class) == null) {
|
||||
if (!statementPosition) {
|
||||
addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.NEW), TailType.INSERT_SPACE));
|
||||
if (PsiUtil.getLanguageLevel(myPosition.getContainingFile()).isAtLeast(LanguageLevel.JDK_12_PREVIEW)) {
|
||||
addKeyword(new OverridableSpace(createKeyword(PsiKeyword.SWITCH), TailTypes.SWITCH_LPARENTH));
|
||||
}
|
||||
}
|
||||
addKeyword(createKeyword(PsiKeyword.NULL));
|
||||
}
|
||||
@@ -762,6 +766,8 @@ public class JavaKeywordCompletion {
|
||||
}
|
||||
if (psiElement().inside(PsiSwitchStatement.class).accepts(myPosition)) {
|
||||
addKeyword(br);
|
||||
} else if (psiElement().inside(PsiSwitchExpression.class).accepts(myPosition)) {
|
||||
addKeyword(TailTypeDecorator.withTail(createKeyword(PsiKeyword.BREAK), TailType.INSERT_SPACE));
|
||||
}
|
||||
|
||||
for (PsiLabeledStatement labeled : psiApi().parents(myPosition).takeWhile(notInstanceOf(PsiMember.class)).filter(PsiLabeledStatement.class)) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.intellij.util.Consumer;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.SwitchUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -170,24 +171,20 @@ public class ReferenceExpressionCompletionContributor {
|
||||
@NotNull
|
||||
public static Set<PsiField> findConstantsUsedInSwitch(@Nullable PsiElement position) {
|
||||
return JavaCompletionContributor.IN_SWITCH_LABEL.accepts(position)
|
||||
? findConstantsUsedInSwitch(ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(position, PsiSwitchStatement.class)))
|
||||
? findConstantsUsedInSwitch(ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(position, PsiSwitchBlock.class)))
|
||||
: Collections.emptySet();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Set<PsiField> findConstantsUsedInSwitch(@NotNull PsiSwitchStatement sw) {
|
||||
public static Set<PsiField> findConstantsUsedInSwitch(@NotNull PsiSwitchBlock sw) {
|
||||
final PsiCodeBlock body = sw.getBody();
|
||||
if (body == null) return Collections.emptySet();
|
||||
|
||||
Set<PsiField> used = ContainerUtil.newLinkedHashSet();
|
||||
for (PsiStatement statement : body.getStatements()) {
|
||||
if (statement instanceof PsiSwitchLabelStatement) {
|
||||
final PsiExpression value = ((PsiSwitchLabelStatement)statement).getCaseValue();
|
||||
if (value instanceof PsiReferenceExpression) {
|
||||
final PsiElement target = ((PsiReferenceExpression)value).resolve();
|
||||
if (target instanceof PsiField) {
|
||||
used.add(CompletionUtil.getOriginalOrSelf((PsiField)target));
|
||||
}
|
||||
if (statement instanceof PsiSwitchLabelStatementBase) {
|
||||
for (PsiEnumConstant constant : SwitchUtils.findEnumConstants((PsiSwitchLabelStatementBase)statement)) {
|
||||
used.add(CompletionUtil.getOriginalOrSelf(constant));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.intellij.psi.filters.getters;
|
||||
|
||||
import com.intellij.codeInsight.TailType;
|
||||
import com.intellij.codeInsight.TailTypes;
|
||||
import com.intellij.codeInsight.completion.*;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.TailTypeDecorator;
|
||||
@@ -26,6 +26,7 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -69,7 +70,9 @@ public class JavaMembersGetter extends MembersGetter {
|
||||
}
|
||||
|
||||
private void addConstantsFromReferencedClassesInSwitch(final Consumer<? super LookupElement> results) {
|
||||
final Set<PsiField> fields = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(myPlace);
|
||||
if (!JavaCompletionContributor.IN_SWITCH_LABEL.accepts(myPlace)) return;
|
||||
PsiSwitchBlock block = ObjectUtils.assertNotNull(PsiTreeUtil.getParentOfType(myPlace, PsiSwitchBlock.class));
|
||||
final Set<PsiField> fields = ReferenceExpressionCompletionContributor.findConstantsUsedInSwitch(block);
|
||||
final Set<PsiClass> classes = new HashSet<>();
|
||||
for (PsiField field : fields) {
|
||||
ContainerUtil.addIfNotNull(classes, field.getContainingClass());
|
||||
@@ -78,7 +81,7 @@ public class JavaMembersGetter extends MembersGetter {
|
||||
processMembers(element -> {
|
||||
//noinspection SuspiciousMethodCalls
|
||||
if (!fields.contains(element.getObject())) {
|
||||
results.consume(TailTypeDecorator.withTail(element, TailType.CASE_COLON));
|
||||
results.consume(TailTypeDecorator.withTail(element, TailTypes.forSwitchLabel(block)));
|
||||
}
|
||||
}, aClass, true, false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
public class ConstConfig {
|
||||
int test(int x) {
|
||||
return switch (x) {
|
||||
case 1:
|
||||
if (Math.random() > 0.5) br<caret>
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
public class ConstConfig {
|
||||
int test(int x) {
|
||||
return switch (x) {
|
||||
case 1:
|
||||
if (Math.random() > 0.5) break <caret>
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstConfig {
|
||||
{
|
||||
switch (a) {
|
||||
case Foooo -> {}
|
||||
def<caret>
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstConfig {
|
||||
{
|
||||
switch (a) {
|
||||
case Foooo -> {}
|
||||
default -> <caret>
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
public class ConstConfig {
|
||||
enum X {FOO, BAR, BAZ}
|
||||
|
||||
void test(X x) {
|
||||
switch (x) {
|
||||
case BAR -> {}
|
||||
case BAZ -> {}
|
||||
case FO<caret>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
public class ConstConfig {
|
||||
enum X {FOO, BAR, BAZ}
|
||||
|
||||
void test(X x) {
|
||||
switch (x) {
|
||||
case BAR -> {}
|
||||
case BAZ -> {}
|
||||
case FOO -> <caret>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstConfig {
|
||||
enum X {FOO, BAR, BAZ}
|
||||
|
||||
void test(X x) {
|
||||
switch (x) {
|
||||
case BAR, FO<caret>: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstConfig {
|
||||
enum X {FOO, BAR, BAZ}
|
||||
|
||||
void test(X x) {
|
||||
switch (x) {
|
||||
case BAR, FOO:<caret> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstConfig {
|
||||
enum X {FOO, BAR, BAZ}
|
||||
|
||||
void test(X x) {
|
||||
switch (x) {
|
||||
case BAR, FO<caret> -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstConfig {
|
||||
enum X {FOO, BAR, BAZ}
|
||||
|
||||
void test(X x) {
|
||||
switch (x) {
|
||||
case BAR, FOO<caret> -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class ConstConfig {
|
||||
int test(int x) {
|
||||
return sw<caret>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class ConstConfig {
|
||||
int test(int x) {
|
||||
return switch (<caret>)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInsight.completion
|
||||
|
||||
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import groovy.transform.CompileStatic
|
||||
import org.jetbrains.annotations.NotNull
|
||||
|
||||
@CompileStatic
|
||||
class Normal12CompletionTest extends NormalCompletionTestCase {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return JAVA_12
|
||||
}
|
||||
|
||||
void testDefaultInRuleSwitch() throws Throwable { doTest() }
|
||||
void testLabelInRuleSwitch() throws Throwable { doTest() }
|
||||
void testSecondLabelInOldSwitch() throws Throwable { doTest() }
|
||||
void testSecondLabelInRuleSwitch() throws Throwable { doTest() }
|
||||
void testSwitchExpressionStart() throws Throwable { doTest() }
|
||||
void testBreakInSwitchExpression() throws Throwable { doTest() }
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public class SwitchUtils {
|
||||
* @return true if given switch block has a rule-based format; false if it has conventional label-based format (like 'case 0:')
|
||||
* If switch body has no labels yet and language level permits, rule-based format is assumed.
|
||||
*/
|
||||
public static boolean isRuleFormatSwitch(PsiSwitchBlock block) {
|
||||
public static boolean isRuleFormatSwitch(@NotNull PsiSwitchBlock block) {
|
||||
if (PsiUtil.getLanguageLevel(block).isLessThan(LanguageLevel.JDK_12_PREVIEW)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user