java completion: fix "{} ()" when choosing anonymous class constructor with parameters

This commit is contained in:
peter
2016-10-19 09:23:15 +02:00
parent 0ac4972e92
commit 2824ee87e4
11 changed files with 200 additions and 43 deletions
@@ -79,13 +79,7 @@ public class ConstructorInsertHandler implements InsertHandler<LookupElementDeco
final PsiExpression enclosing = PsiTreeUtil.getContextOfType(position, PsiExpression.class, true);
final PsiAnonymousClass anonymousClass = PsiTreeUtil.getParentOfType(position, PsiAnonymousClass.class);
final boolean inAnonymous = anonymousClass != null && anonymousClass.getParent() == enclosing;
boolean fillTypeArgs = false;
if (delegate instanceof PsiTypeLookupItem) {
fillTypeArgs = !isRawTypeExpected(context, (PsiTypeLookupItem)delegate) &&
psiClass.getTypeParameters().length > 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<LookupElementDeco
final int offset = context.getTailOffset();
document.insertString(offset, " {}");
editor.getCaretModel().moveToOffset(offset + 2);
OffsetKey insideBraces = context.trackOffset(offset + 2, true);
final PsiFile file = context.getFile();
PsiDocumentManager.getInstance(file.getProject()).commitDocument(document);
reformatEnclosingExpressionListAtOffset(file, offset);
if (fillTypeArgs && JavaCompletionUtil.promptTypeArgs(context, context.getOffset(insideRef))) return;
if (promptTypeOrConstructorArgs(context, delegate, context.getOffset(insideRef))) return;
editor.getCaretModel().moveToOffset(context.getOffset(insideBraces));
context.setLaterRunnable(generateAnonymousBody(editor, file));
}
else {
@@ -147,12 +142,32 @@ public class ConstructorInsertHandler implements InsertHandler<LookupElementDeco
if (mySmart) {
FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.AFTER_NEW);
}
if (fillTypeArgs) {
JavaCompletionUtil.promptTypeArgs(context, context.getOffset(insideRef));
}
promptTypeOrConstructorArgs(context, delegate, context.getOffset(insideRef));
}
}
private static boolean promptTypeOrConstructorArgs(InsertionContext context, LookupElement delegate, int refOffset) {
if (shouldFillTypeArgs(context, delegate) && JavaCompletionUtil.promptTypeArgs(context, refOffset)) {
return true;
}
PsiMethod constructor = JavaConstructorCallElement.extractCalledConstructor(delegate);
return constructor != null && JavaMethodCallElement.startArgumentLiveTemplate(context, constructor);
}
private static boolean shouldFillTypeArgs(InsertionContext context, LookupElement delegate) {
if (!(delegate instanceof PsiTypeLookupItem) ||
isRawTypeExpected(context, (PsiTypeLookupItem)delegate) ||
!((PsiClass)delegate.getObject()).hasTypeParameters()) {
return false;
}
PsiElement position = SmartCompletionDecorator.getPosition(context, delegate);
return position != null &&
((PsiTypeLookupItem)delegate).calcGenerics(position, context).isEmpty() &&
context.getCompletionChar() != '(';
}
private static void reformatEnclosingExpressionListAtOffset(@NotNull PsiFile file, int offset) {
final PsiElement elementAtOffset = PsiUtilCore.getElementAtOffset(file, offset);
PsiExpressionList listToReformat = getEnclosingExpressionList(elementAtOffset.getParent());
@@ -195,7 +210,7 @@ public class ConstructorInsertHandler implements InsertHandler<LookupElementDeco
LookupElement delegate,
final PsiClass psiClass,
final boolean forAnonymous) {
if (context.getCompletionChar() == '[' || JavaConstructorCallElement.isWrapped(delegate)) {
if (context.getCompletionChar() == '[') {
return false;
}
@@ -18,6 +18,7 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInsight.lookup.TypedLookupItem;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
@@ -28,6 +29,7 @@ import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
@@ -36,16 +38,17 @@ import java.util.function.Supplier;
/**
* @author peter
*/
public class JavaConstructorCallElement extends JavaMethodCallElement {
public class JavaConstructorCallElement extends LookupElementDecorator<LookupElement> implements TypedLookupItem {
private static final Key<JavaConstructorCallElement> 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<PsiClassType> 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<PsiMethod> 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;
}
}
@@ -180,10 +180,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> 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<PsiMethod> implements Type
qualifyMethodCall(file, startOffset, document);
}
public static final Key<JavaMethodCallElement> ARGUMENT_TEMPLATE_ACTIVE = Key.create("ARGUMENT_TEMPLATE_ACTIVE");
public static final Key<PsiMethod> 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<PsiMethod> 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<PsiMethod> 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) {
@@ -0,0 +1,8 @@
abstract class Foo{
public Foo(int x) {
}
{
Foo f = new F<caret>
}
}
@@ -0,0 +1,8 @@
abstract class Foo{
public Foo(int x) {
}
{
Foo f = new Foo(<selection>x</selection><caret>) {}
}
}
@@ -0,0 +1,8 @@
class Foo{
Foo(int arg) {
}
{
Foo f = new F<caret>
}
}
@@ -0,0 +1,8 @@
class Foo{
Foo(int arg) {
}
{
Foo f = new Foo(<selection>arg</selection><caret>)
}
}
@@ -0,0 +1,5 @@
class Foo{
{
Foo f = new F<caret>
}
}
@@ -0,0 +1,5 @@
class Foo{
{
Foo f = new Foo()<caret>
}
}
@@ -0,0 +1,12 @@
class Foo{
Foo(int arg) {
}
Foo(boolean arg) {
}
Foo() {
}
{
Foo f = new F<caret>
}
}
@@ -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
}
}