mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-13 21:55:01 +07:00
java completion: fix "{} ()" when choosing anonymous class constructor with parameters
This commit is contained in:
+27
-12
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+33
-18
@@ -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) {
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
abstract class Foo{
|
||||
public Foo(int x) {
|
||||
}
|
||||
|
||||
{
|
||||
Foo f = new F<caret>
|
||||
}
|
||||
}
|
||||
+8
@@ -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>
|
||||
}
|
||||
}
|
||||
+8
@@ -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>
|
||||
}
|
||||
}
|
||||
+5
@@ -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>
|
||||
}
|
||||
}
|
||||
+69
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user