From 2824ee87e4da8f2e15dcc31ba2e731872122d2eb Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 19 Oct 2016 09:23:15 +0200 Subject: [PATCH] java completion: fix "{} ()" when choosing anonymous class constructor with parameters --- .../completion/ConstructorInsertHandler.java | 39 +++++++---- .../JavaConstructorCallElement.java | 51 +++++++++----- .../completion/JavaMethodCallElement.java | 30 ++++---- .../AnonymousNonDefaultConstructor.java | 8 +++ .../AnonymousNonDefaultConstructor_after.java | 8 +++ .../signature/NonDefaultConstructor.java | 8 +++ .../NonDefaultConstructor_after.java | 8 +++ .../signature/OnlyDefaultConstructor.java | 5 ++ .../OnlyDefaultConstructor_after.java | 5 ++ .../signature/SeveralConstructors.java | 12 ++++ .../completion/SignatureCompletionTest.groovy | 69 +++++++++++++++++++ 11 files changed, 200 insertions(+), 43 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor.java create mode 100644 java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor.java create mode 100644 java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor.java create mode 100644 java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/signature/SeveralConstructors.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/completion/SignatureCompletionTest.groovy diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java b/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java index d31afee0a899..a57029729610 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java @@ -79,13 +79,7 @@ public class ConstructorInsertHandler implements InsertHandler 0 && - ((PsiTypeLookupItem)delegate).calcGenerics(position, context).isEmpty() && - context.getCompletionChar() != '('; - if (context.getDocument().getTextLength() > context.getTailOffset() && context.getDocument().getCharsSequence().charAt(context.getTailOffset()) == '<') { PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getTailOffset(), PsiJavaCodeReferenceElement.class, false); @@ -124,14 +118,15 @@ public class ConstructorInsertHandler implements InsertHandler implements TypedLookupItem { private static final Key WRAPPING_CONSTRUCTOR_CALL = Key.create("WRAPPING_CONSTRUCTOR_CALL"); - @NotNull private final LookupElement myClassItem; + @NotNull private final PsiMethod myConstructor; @NotNull private final PsiClassType myType; + @NotNull private final PsiSubstitutor mySubstitutor; - private JavaConstructorCallElement(@NotNull LookupElement classItem, @NotNull PsiMethod constructor, @NotNull Supplier type) { - super(constructor); - myClassItem = classItem; - myType = type.get(); - setQualifierSubstitutor(myType.resolveGenerics().getSubstitutor()); + private JavaConstructorCallElement(@NotNull LookupElement classItem, @NotNull PsiMethod constructor, @NotNull PsiClassType type) { + super(classItem); + myConstructor = constructor; + myType = type; + mySubstitutor = myType.resolveGenerics().getSubstitutor(); markClassItemWrapped(classItem); } @@ -59,28 +62,38 @@ public class JavaConstructorCallElement extends JavaMethodCallElement { } } + @NotNull + @Override + public PsiMethod getObject() { + return myConstructor; + } + + @Override + public boolean equals(Object o) { + return this == o || super.equals(o) && myConstructor.equals(((JavaConstructorCallElement)o).myConstructor); + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + myConstructor.hashCode(); + } + @NotNull @Override public PsiType getType() { return myType; } - @Override - public void handleInsert(InsertionContext context) { - myClassItem.handleInsert(context); - super.handleInsert(context); - } - @Override public void renderElement(LookupElementPresentation presentation) { - myClassItem.renderElement(presentation); + super.renderElement(presentation); String tailText = StringUtil.notNullize(presentation.getTailText()); int genericsEnd = tailText.lastIndexOf('>') + 1; presentation.clearTail(); presentation.appendTailText(tailText.substring(0, genericsEnd), false); - presentation.appendTailText(MemberLookupHelper.getMethodParameterString(getObject(), getSubstitutor()), false); + presentation.appendTailText(MemberLookupHelper.getMethodParameterString(myConstructor, mySubstitutor), false); presentation.appendTailText(tailText.substring(genericsEnd), true); } @@ -94,7 +107,7 @@ public class JavaConstructorCallElement extends JavaMethodCallElement { if (Registry.is("java.completion.show.constructors") && isConstructorCallPlace(position)) { List constructors = ContainerUtil.filter(psiClass.getConstructors(), c -> shouldSuggestConstructor(psiClass, position, c)); if (!constructors.isEmpty()) { - return ContainerUtil.map(constructors, c -> new JavaConstructorCallElement(classItem, c, type)); + return ContainerUtil.map(constructors, c -> new JavaConstructorCallElement(classItem, c, type.get())); } } return Collections.singletonList(classItem); @@ -117,8 +130,10 @@ public class JavaConstructorCallElement extends JavaMethodCallElement { }); } - static boolean isWrapped(LookupElement element) { - return element.getUserData(WRAPPING_CONSTRUCTOR_CALL) != null; + @Nullable + static PsiMethod extractCalledConstructor(@NotNull LookupElement element) { + JavaConstructorCallElement callItem = element.getUserData(WRAPPING_CONSTRUCTOR_CALL); + return callItem != null ? callItem.getObject() : null; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java index 065527c55642..21a494ed31f8 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java @@ -180,10 +180,7 @@ public class JavaMethodCallElement extends LookupItem implements Type } } - context.commitDocument(); - if (hasParams && context.getCompletionChar() != Lookup.COMPLETE_STATEMENT_SELECT_CHAR && Registry.is("java.completion.argument.live.template")) { - startArgumentLiveTemplate(context, method); - } + startArgumentLiveTemplate(context, method); } private void importOrQualify(Document document, PsiFile file, PsiMethod method, int startOffset) { @@ -198,7 +195,7 @@ public class JavaMethodCallElement extends LookupItem implements Type qualifyMethodCall(file, startOffset, document); } - public static final Key ARGUMENT_TEMPLATE_ACTIVE = Key.create("ARGUMENT_TEMPLATE_ACTIVE"); + public static final Key ARGUMENT_TEMPLATE_ACTIVE = Key.create("ARGUMENT_TEMPLATE_ACTIVE"); @NotNull private static Template createArgTemplate(PsiMethod method, int caretOffset, @@ -226,19 +223,25 @@ public class JavaMethodCallElement extends LookupItem implements Type return template; } - private void startArgumentLiveTemplate(InsertionContext context, PsiMethod method) { - Editor editor = context.getEditor(); + public static boolean startArgumentLiveTemplate(InsertionContext context, PsiMethod method) { + if (method.getParameterList().getParametersCount() == 0 || + context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR || + !Registry.is("java.completion.argument.live.template")) { + return false; + } - PsiCallExpression call = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiCallExpression.class, false); + Editor editor = context.getEditor(); + context.commitDocument(); + PsiCall call = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiCall.class, false); PsiExpressionList argList = call == null ? null : call.getArgumentList(); if (argList == null || argList.getExpressions().length > 0) { - return; + return false; } TextRange argRange = argList.getTextRange(); int caretOffset = editor.getCaretModel().getOffset(); if (!argRange.contains(caretOffset)) { - return; + return false; } Template template = createArgTemplate(method, caretOffset, argList, argRange); @@ -247,16 +250,17 @@ public class JavaMethodCallElement extends LookupItem implements Type TemplateManager.getInstance(method.getProject()).startTemplate(editor, template); TemplateState templateState = TemplateManagerImpl.getTemplateState(editor); - if (templateState == null) return; + if (templateState == null) return false; setupNonFilledArgumentRemoving(editor, templateState); - editor.putUserData(ARGUMENT_TEMPLATE_ACTIVE, this); + editor.putUserData(ARGUMENT_TEMPLATE_ACTIVE, method); Disposer.register(templateState, () -> { - if (editor.getUserData(ARGUMENT_TEMPLATE_ACTIVE) == this) { + if (editor.getUserData(ARGUMENT_TEMPLATE_ACTIVE) == method) { editor.putUserData(ARGUMENT_TEMPLATE_ACTIVE, null); } }); + return true; } private static void setupNonFilledArgumentRemoving(final Editor editor, final TemplateState templateState) { diff --git a/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor.java b/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor.java new file mode 100644 index 000000000000..3a09e0ada409 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor.java @@ -0,0 +1,8 @@ +abstract class Foo{ + public Foo(int x) { + } + + { + Foo f = new F + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor_after.java b/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor_after.java new file mode 100644 index 000000000000..49dde1b89a5a --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/signature/AnonymousNonDefaultConstructor_after.java @@ -0,0 +1,8 @@ +abstract class Foo{ + public Foo(int x) { + } + + { + Foo f = new Foo(x) {} + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor.java b/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor.java new file mode 100644 index 000000000000..1d0e66afb20c --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor.java @@ -0,0 +1,8 @@ +class Foo{ + Foo(int arg) { + } + + { + Foo f = new F + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor_after.java b/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor_after.java new file mode 100644 index 000000000000..d45f63ded1db --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/signature/NonDefaultConstructor_after.java @@ -0,0 +1,8 @@ +class Foo{ + Foo(int arg) { + } + + { + Foo f = new Foo(arg) + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor.java b/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor.java new file mode 100644 index 000000000000..adb41ada931b --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor.java @@ -0,0 +1,5 @@ +class Foo{ + { + Foo f = new F + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor_after.java b/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor_after.java new file mode 100644 index 000000000000..e21f30961fc4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/signature/OnlyDefaultConstructor_after.java @@ -0,0 +1,5 @@ +class Foo{ + { + Foo f = new Foo() + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/signature/SeveralConstructors.java b/java/java-tests/testData/codeInsight/completion/signature/SeveralConstructors.java new file mode 100644 index 000000000000..fecb2c4573af --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/signature/SeveralConstructors.java @@ -0,0 +1,12 @@ +class Foo{ + Foo(int arg) { + } + Foo(boolean arg) { + } + Foo() { + } + + { + Foo f = new F + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/SignatureCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/SignatureCompletionTest.groovy new file mode 100644 index 000000000000..c2a504fbf36e --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/SignatureCompletionTest.groovy @@ -0,0 +1,69 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.completion + +import com.intellij.JavaTestUtil +import com.intellij.codeInsight.template.impl.TemplateManagerImpl +import com.intellij.openapi.util.registry.Registry +/** + * @author peter + */ +class SignatureCompletionTest extends LightFixtureCompletionTestCase { + + @Override + protected String getBasePath() { + return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/signature/" + } + + @Override + protected void setUp() throws Exception { + super.setUp() + Registry.get("java.completion.argument.live.template").value = true + Registry.get("java.completion.show.constructors").value = true + TemplateManagerImpl.setTemplateTesting(getProject(), getTestRootDisposable()) + } + + @Override + protected void tearDown() throws Exception { + Registry.get("java.completion.argument.live.template").value = false + Registry.get("java.completion.show.constructors").value = false + super.tearDown() + } + + private checkResult() { + checkResultByFile(getTestName(false) + "_after.java") + } + + private void doFirstItemTest() { + configureByTestName() + myFixture.type('\n') + checkResult() + } + + void testOnlyDefaultConstructor() { doFirstItemTest() } + + void testNonDefaultConstructor() { doFirstItemTest() } + + void testAnonymousNonDefaultConstructor() { doFirstItemTest() } + + void testSeveralConstructors() { + myFixture.configureByFile(getTestName(false) + ".java") + myFixture.complete(CompletionType.SMART) + def items = myFixture.lookup.items + assert items.size() == 3 + } + +}