From 53a305b847c90024e45b089d9b81645aa95fb207 Mon Sep 17 00:00:00 2001 From: peter Date: Fri, 1 Feb 2013 16:34:38 +0100 Subject: [PATCH] IDEA-99186 Ctrl+Space help when implementing/overriding methods --- .../completion/JavaCompletionSorting.java | 4 +- .../JavaOverrideCompletionContributor.java | 123 ++++++++++++++++++ .../generation/OverrideImplementUtil.java | 44 +++++-- .../normal/ImplementViaCompletion.java | 7 + .../normal/ImplementViaCompletion_after.java | 10 ++ .../completion/NormalCompletionTest.groovy | 15 +++ resources/src/META-INF/IdeaPlugin.xml | 2 + 7 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/JavaOverrideCompletionContributor.java create mode 100644 java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion.java create mode 100644 java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion_after.java diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionSorting.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionSorting.java index 63d55511f488..6e73bec5a290 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionSorting.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaCompletionSorting.java @@ -132,7 +132,9 @@ public class JavaCompletionSorting { public Comparable weigh(@NotNull LookupElement element) { final Object o = element.getObject(); if (o instanceof PsiKeyword) return -3; - if (!(o instanceof PsiMember)) return 0; + if (!(o instanceof PsiMember) || element.getUserData(JavaOverrideCompletionContributor.OVERRIDE_ELEMENT) != null) { + return 0; + } if (((PsiMember)o).hasModifierProperty(PsiModifier.STATIC) && !hasNonVoid(infos)) { if (o instanceof PsiMethod) return -5; diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaOverrideCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaOverrideCompletionContributor.java new file mode 100644 index 000000000000..d71de30567c2 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaOverrideCompletionContributor.java @@ -0,0 +1,123 @@ +/* + * Copyright 2000-2013 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.codeInsight.generation.GenerateMembersUtil; +import com.intellij.codeInsight.generation.OverrideImplementExploreUtil; +import com.intellij.codeInsight.generation.OverrideImplementUtil; +import com.intellij.codeInsight.generation.PsiGenerationInfo; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.util.Key; +import com.intellij.psi.*; +import com.intellij.psi.infos.CandidateInfo; +import com.intellij.psi.util.PsiFormatUtil; +import com.intellij.psi.util.PsiFormatUtilBase; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.ui.RowIcon; +import com.intellij.util.Consumer; + +import java.util.List; + +import static com.intellij.patterns.PlatformPatterns.psiElement; + +/** + * @author peter + */ +public class JavaOverrideCompletionContributor extends CompletionContributor { + static final Key OVERRIDE_ELEMENT = Key.create("OVERRIDE_ELEMENT"); + + @Override + public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet _result) { + if (parameters.getCompletionType() != CompletionType.BASIC && parameters.getCompletionType() != CompletionType.SMART) { + return; + } + + PsiElement position = parameters.getPosition(); + if (psiElement(PsiIdentifier.class).withParents(PsiJavaCodeReferenceElement.class, PsiTypeElement.class, PsiClass.class). + andNot(JavaCompletionData.AFTER_DOT). + andNot(psiElement().afterLeaf(psiElement().inside(PsiModifierList.class))).accepts(position)) { + final PsiClass parent = CompletionUtil.getOriginalElement((PsiClass)position.getParent().getParent().getParent()); + if (parent != null) { + CompletionResultSet result = JavaCompletionSorting.addJavaSorting(parameters, _result); + addSuperSignatureElements(parent, true, result); + addSuperSignatureElements(parent, false, result); + } + } + + } + + private static void addSuperSignatureElements(final PsiClass parent, boolean implemented, CompletionResultSet result) { + for (CandidateInfo candidate : OverrideImplementExploreUtil.getMethodsToOverrideImplement(parent, implemented)) { + PsiMethod baseMethod = (PsiMethod)candidate.getElement(); + if (!baseMethod.isConstructor()) { + PsiClass baseClass = baseMethod.getContainingClass(); + List prototypes = OverrideImplementUtil.overrideOrImplementMethod(parent, baseMethod, candidate.getSubstitutor(), new Consumer() { + @Override + public void consume(PsiMethod method) { + OverrideImplementUtil.deleteDocComment(method); + } + }); + if (!prototypes.isEmpty() && baseClass != null) { + result.addElement(createOverridingLookupElement(parent, implemented, baseMethod, baseClass, prototypes.get(0))); + } + } + } + } + + private static LookupElementBuilder createOverridingLookupElement(final PsiClass parent, + boolean implemented, + final PsiMethod baseMethod, + PsiClass baseClass, final PsiMethod prototype) { + PsiIdentifier nameIdentifier = prototype.getNameIdentifier(); + assert nameIdentifier != null; + + String signature = prototype.getModifierList().getText(); + if (!signature.isEmpty()) { + signature += " "; + } + PsiType returnType = prototype.getReturnType(); + signature += TypeConversionUtil.erasure(returnType).getPresentableText() + " " + prototype.getName(); + + String parameters = PsiFormatUtil.formatMethod(prototype, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_PARAMETERS, PsiFormatUtilBase.SHOW_NAME); + + InsertHandler insertHandler = new InsertHandler() { + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + context.getDocument().deleteString(context.getStartOffset(), context.getTailOffset()); + context.commitDocument(); + + List prototypes = OverrideImplementUtil.overrideOrImplementMethod(parent, baseMethod, false); + List> infos = OverrideImplementUtil.convert2GenerationInfos(prototypes); + List> newInfos = GenerateMembersUtil.insertMembersAtOffset(context.getFile(), context.getStartOffset(), infos); + if (!newInfos.isEmpty()) { + newInfos.get(0).positionCaret(context.getEditor(), true); + } + } + }; + + RowIcon icon = new RowIcon(2); + icon.setIcon(baseMethod.getIcon(0), 0); + icon.setIcon(implemented ? AllIcons.Gutter.ImplementingMethod : AllIcons.Gutter.OverridingMethod, 1); + + LookupElementBuilder element = LookupElementBuilder.create(baseMethod, signature).withLookupString(prototype.getName()). + withLookupString(signature).withInsertHandler(insertHandler). + appendTailText(parameters, false).appendTailText(" {...}", true).withTypeText(baseClass.getName()).withIcon(icon); + element.putUserData(OVERRIDE_ELEMENT, true); + return element; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java index 115545a38828..8a2fa775a35a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/OverrideImplementUtil.java @@ -61,6 +61,7 @@ import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.*; import com.intellij.util.ArrayUtil; +import com.intellij.util.Consumer; import com.intellij.util.Function; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; @@ -95,7 +96,7 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { * @return list of method prototypes */ @NotNull - public static Collection overrideOrImplementMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc) throws IncorrectOperationException { + public static List overrideOrImplementMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc) throws IncorrectOperationException { final PsiClass containingClass = method.getContainingClass(); LOG.assertTrue(containingClass != null); PsiSubstitutor substitutor = aClass.isInheritor(containingClass, true) @@ -126,11 +127,18 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { } @NotNull - private static Collection overrideOrImplementMethod(PsiClass aClass, + private static List overrideOrImplementMethod(PsiClass aClass, PsiMethod method, PsiSubstitutor substitutor, boolean toCopyJavaDoc, boolean insertOverrideIfPossible) throws IncorrectOperationException { + return overrideOrImplementMethod(aClass, method, substitutor, createDefaultDecorator(aClass, method, toCopyJavaDoc, insertOverrideIfPossible)); + } + + public static List overrideOrImplementMethod(PsiClass aClass, + PsiMethod method, + PsiSubstitutor substitutor, + Consumer decorator) throws IncorrectOperationException { if (!method.isValid() || !substitutor.isValid()) return Collections.emptyList(); List results = new ArrayList(); @@ -141,13 +149,14 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { } else { for (PsiMethod prototype : prototypes) { - results.add(decorateMethod(aClass, method, toCopyJavaDoc, insertOverrideIfPossible, prototype)); + decorator.consume(prototype); + results.add(prototype); } } } if (results.isEmpty()) { PsiMethod method1 = GenerateMembersUtil.substituteGenericMethod(method, substitutor, aClass); - + PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); PsiMethod result = (PsiMethod)factory.createClass("Dummy").add(method1); if (PsiUtil.isAnnotationMethod(result)) { @@ -161,7 +170,8 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { defaultValue.getParent().deleteChildRange(defaultKeyword, defaultValue); } } - results.add(decorateMethod(aClass, method, toCopyJavaDoc, insertOverrideIfPossible, result)); + decorator.consume(result); + results.add(result); } for (Iterator iterator = results.iterator(); iterator.hasNext();) { @@ -173,6 +183,18 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { return results; } + public static Consumer createDefaultDecorator(final PsiClass aClass, + final PsiMethod method, + final boolean toCopyJavaDoc, + final boolean insertOverrideIfPossible) { + return new Consumer() { + @Override + public void consume(PsiMethod result) { + decorateMethod(aClass, method, toCopyJavaDoc, insertOverrideIfPossible, result); + } + }; + } + private static PsiMethod decorateMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc, @@ -182,10 +204,7 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { PsiUtil.setModifierProperty(result, PsiModifier.NATIVE, false); if (!toCopyJavaDoc){ - PsiDocComment comment = result.getDocComment(); - if (comment != null){ - comment.delete(); - } + deleteDocComment(result); } //method type params are not allowed when overriding from raw type @@ -231,6 +250,13 @@ public class OverrideImplementUtil extends OverrideImplementExploreUtil { return result; } + public static void deleteDocComment(PsiMethod result) { + PsiDocComment comment = result.getDocComment(); + if (comment != null){ + comment.delete(); + } + } + public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden) { annotateOnOverrideImplement(method, targetClass, overridden, CodeStyleSettingsManager.getSettings(method.getProject()).INSERT_OVERRIDE_ANNOTATION); diff --git a/java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion.java b/java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion.java new file mode 100644 index 000000000000..85d7845803d8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion.java @@ -0,0 +1,7 @@ +interface Foo { + void run(T t, int myInt); +} + +public class A implements Foo { + p +} diff --git a/java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion_after.java b/java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion_after.java new file mode 100644 index 000000000000..a13b20e89807 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/normal/ImplementViaCompletion_after.java @@ -0,0 +1,10 @@ +interface Foo { + void run(T t, int myInt); +} + +public class A implements Foo { + @Override + public void run(String s, int myInt) { + //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy index ba50bdbe788c..0c7e3f1ce4bf 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionTest.groovy @@ -1316,5 +1316,20 @@ class XInternalError {} assert lookup.items.size() == 1 } + public void testImplementViaCompletion() { + configure() + myFixture.assertPreferredCompletionItems 0, 'private', 'protected', 'public', 'public void run' + def item = lookup.items[3] + + def p = LookupElementPresentation.renderElement(item) + assert p.itemText == 'public void run' + assert p.tailText == '(s, myInt) {...}' + assert p.typeText == 'Foo' + + lookup.currentItem = item + myFixture.type('\n') + checkResult() + } + } diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 99fc80a5159c..debd34b9ffad 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -254,6 +254,8 @@ +