From 2461a8751cb815745ef226cbed7c6f61bac2cc45 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 9 May 2017 11:58:44 +0200 Subject: [PATCH] IDEA-163986 Code complete second default array argument in annotation --- .../editorActions/JavaTypedHandler.java | 58 +++++++++++++++++++ ...tionArgumentWhenArrayIsExpected_after.java | 4 ++ ...ionArgumentWhenArrayIsExpected_before.java | 4 ++ .../intellij/codeInsight/JavaTypingTest.java | 4 ++ 4 files changed, 70 insertions(+) create mode 100644 java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_after.java create mode 100644 java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_before.java diff --git a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java index b2e8b67ad3de..ed4270b027dc 100644 --- a/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/editorActions/JavaTypedHandler.java @@ -37,9 +37,12 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * @author yole */ @@ -181,9 +184,64 @@ public class JavaTypedHandler extends TypedHandlerDelegate { return Result.STOP; } } + else if (c == ',' && handleAnnotationParameter(project, editor, file)) { + return Result.STOP; + } return Result.CONTINUE; } + private static boolean handleAnnotationParameter(Project project, @NotNull Editor editor, @NotNull PsiFile file) { + int caret = editor.getCaretModel().getOffset(); + if (mightBeInsideDefaultAnnotationAttribute(editor, caret - 2)) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + PsiAnnotation anno = PsiTreeUtil.findElementOfClassAtOffset(file, caret, PsiAnnotation.class, false); + PsiNameValuePair attr = anno == null ? null : getTheOnlyDefaultAttribute(anno); + if (attr != null && hasDefaultArrayMethod(anno) && !(attr.getValue() instanceof PsiArrayInitializerMemberValue)) { + editor.getDocument().insertString(caret, "}"); + editor.getDocument().insertString(attr.getTextRange().getStartOffset(), "{"); + return true; + } + } + return false; + } + + @Nullable private static PsiNameValuePair getTheOnlyDefaultAttribute(@NotNull PsiAnnotation anno) { + List attributes = ContainerUtil.findAll(anno.getParameterList().getAttributes(), a -> !a.getTextRange().isEmpty()); + return attributes.size() == 1 && attributes.get(0).getNameIdentifier() == null ? attributes.get(0) : null; + } + + private static boolean hasDefaultArrayMethod(@NotNull PsiAnnotation anno) { + PsiJavaCodeReferenceElement nameRef = anno.getNameReferenceElement(); + PsiElement annoClass = nameRef == null ? null : nameRef.resolve(); + if (annoClass instanceof PsiClass) { + PsiMethod[] methods = ((PsiClass)annoClass).getMethods(); + return methods.length == 1 && PsiUtil.isAnnotationMethod(methods[0]) && + PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME.equals(methods[0].getName()) && + methods[0].getReturnType() instanceof PsiArrayType; + } + return false; + } + + private static boolean mightBeInsideDefaultAnnotationAttribute(@NotNull Editor editor, int offset) { + HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(offset); + int parenCount = 0; + while (!iterator.atEnd()) { + IElementType tokenType = iterator.getTokenType(); + if (tokenType == JavaTokenType.AT) { + return true; + } + if (tokenType == JavaTokenType.RPARENTH || tokenType == JavaTokenType.LBRACE || + tokenType == JavaTokenType.EQ || tokenType == JavaTokenType.SEMICOLON || tokenType == JavaTokenType.COMMA) { + return false; + } + if (tokenType == JavaTokenType.LPARENTH && ++parenCount > 1) { + return false; + } + iterator.retreat(); + } + return false; + } + private static boolean handleSemicolon(Editor editor, FileType fileType) { if (fileType != StdFileTypes.JAVA) return false; int offset = editor.getCaretModel().getOffset(); diff --git a/java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_after.java b/java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_after.java new file mode 100644 index 000000000000..84aa7fc72441 --- /dev/null +++ b/java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_after.java @@ -0,0 +1,4 @@ +public @interface Category { + Class[] value(); +} +@Category({Foo.class,}) \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_before.java b/java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_before.java new file mode 100644 index 000000000000..10dddff77eef --- /dev/null +++ b/java/java-tests/testData/codeInsight/typing/commaAfterDefaultAnnotationArgumentWhenArrayIsExpected_before.java @@ -0,0 +1,4 @@ +public @interface Category { + Class[] value(); +} +@Category(Foo.class) \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/JavaTypingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/JavaTypingTest.java index f60db32cf61e..ce802a62f413 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/JavaTypingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/JavaTypingTest.java @@ -110,6 +110,10 @@ public class JavaTypingTest extends LightPlatformCodeInsightFixtureTestCase { doTest(';'); } + public void testCommaAfterDefaultAnnotationArgumentWhenArrayIsExpected() { + doTest(','); + } + private void doTest(char c) { myFixture.configureByFile(getTestName(true) + "_before.java"); myFixture.type(c);