From 30cdd2b4bc88bc6989d845177a8e32337091cb2c Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 16 Oct 2012 15:57:35 +0200 Subject: [PATCH] java class reference: search subtypes only on smart completion, work as usual (and fast) on basic (IDEA-92934) --- ...vaClassReferenceCompletionContributor.java | 27 ++++++-- .../impl/providers/JavaClassReference.java | 52 ++++++-------- .../impl/providers/JavaClassReferenceSet.java | 5 ++ .../intellij/codeInsight/lookup/Lookup.java | 2 + .../codeInsight/lookup/impl/Advertiser.java | 3 + .../codeInsight/lookup/impl/LookupImpl.java | 14 +++- .../fixtures/CodeInsightTestFixture.java | 3 + .../impl/CodeInsightTestFixtureImpl.java | 2 +- ...howAnActionInheritorsOnSmartCompletion.xml | 5 ++ .../codeInsight/ShowPackagesInActionClass.xml | 5 ++ ...st.java => PluginXmlFunctionalTest.groovy} | 69 +++++++++++-------- 11 files changed, 116 insertions(+), 71 deletions(-) create mode 100644 plugins/devkit/testData/codeInsight/ShowAnActionInheritorsOnSmartCompletion.xml create mode 100644 plugins/devkit/testData/codeInsight/ShowPackagesInActionClass.xml rename plugins/devkit/testSources/codeInsight/{PluginXmlFunctionalTest.java => PluginXmlFunctionalTest.groovy} (74%) diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassReferenceCompletionContributor.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassReferenceCompletionContributor.java index fd8b67027027..e7ad8966c1f3 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassReferenceCompletionContributor.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaClassReferenceCompletionContributor.java @@ -15,11 +15,15 @@ */ package com.intellij.codeInsight.completion; +import com.intellij.openapi.actionSystem.IdeActions; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiPackage; import com.intellij.psi.PsiReference; import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReference; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,24 +35,33 @@ public class JavaClassReferenceCompletionContributor extends CompletionContribut public void duringCompletion(@NotNull CompletionInitializationContext context) { JavaClassReference reference = findJavaClassReference(context.getFile(), context.getStartOffset()); if (reference != null && reference.getExtendClassNames() != null) { - final PsiReference[] references = reference.getJavaClassReferenceSet().getReferences(); - final PsiReference last = references[references.length - 1]; - context.setReplacementOffset(last.getRangeInElement().getEndOffset() + last.getElement().getTextRange().getStartOffset()); + JavaClassReferenceSet set = reference.getJavaClassReferenceSet(); + context.setReplacementOffset(set.getRangeInElement().getEndOffset() + set.getElement().getTextRange().getStartOffset()); } } @Override public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet result) { - if (parameters.getCompletionType() == CompletionType.SMART) { - return; - } - PsiElement position = parameters.getPosition(); JavaClassReference reference = findJavaClassReference(position.getContainingFile(), parameters.getOffset()); if (reference == null) { return; } + String[] extendClassNames = reference.getExtendClassNames(); + PsiElement context = reference.getCompletionContext(); + if (extendClassNames != null && context instanceof PsiPackage) { + if (parameters.getCompletionType() == CompletionType.SMART) { + JavaClassReferenceSet set = reference.getJavaClassReferenceSet(); + int setStart = set.getRangeInElement().getStartOffset() + set.getElement().getTextRange().getStartOffset(); + String fullPrefix = parameters.getPosition().getContainingFile().getText().substring(setStart, parameters.getOffset()); + reference.processSubclassVariants((PsiPackage)context, extendClassNames, result.withPrefixMatcher(fullPrefix)); + return; + } + result.addLookupAdvertisement("Press " + getActionShortcut(IdeActions.ACTION_SMART_TYPE_COMPLETION) + " to see inheritors of " + + StringUtil.join(extendClassNames, ", ")); + } + if (parameters.getCompletionType() == CompletionType.CLASS_NAME) { JavaClassNameCompletionContributor.addAllClasses(parameters, result); } diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReference.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReference.java index e73e5c627192..b91cfc74191a 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReference.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReference.java @@ -15,6 +15,7 @@ */ package com.intellij.psi.impl.source.resolve.reference.impl.providers; +import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.completion.JavaLookupElementBuilder; import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor; import com.intellij.codeInsight.daemon.QuickFixProvider; @@ -23,6 +24,7 @@ import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix; import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixActionRegistrarImpl; import com.intellij.codeInsight.daemon.quickFix.CreateClassOrPackageFix; +import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.LocalQuickFixProvider; import com.intellij.lang.java.JavaLanguage; @@ -55,7 +57,10 @@ import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; /** * @author peter @@ -225,15 +230,8 @@ public class JavaClassReference extends GenericReference implements PsiJavaRefer @Override @NotNull public Object[] getVariants() { - PsiElement context = getContext(); - if (context == null) { - context = JavaPsiFacade.getInstance(getElement().getProject()).findPackage(""); - } + PsiElement context = getCompletionContext(); if (context instanceof PsiPackage) { - final String[] extendClasses = getExtendClassNames(); - if (extendClasses != null) { - return getSubclassVariants((PsiPackage)context, extendClasses); - } return processPackage((PsiPackage)context); } if (context instanceof PsiClass) { @@ -257,6 +255,12 @@ public class JavaClassReference extends GenericReference implements PsiJavaRefer return ArrayUtil.EMPTY_OBJECT_ARRAY; } + @NotNull + public PsiElement getCompletionContext() { + PsiElement context = getContext(); + return context == null ? JavaPsiFacade.getInstance(getElement().getProject()).findPackage("") : context; + } + public String[] getExtendClassNames() { return JavaClassReferenceProvider.EXTEND_CLASS_NAMES.getValue(getOptions()); } @@ -508,8 +512,7 @@ public class JavaClassReference extends GenericReference implements PsiJavaRefer } @NotNull - private Object[] getSubclassVariants(@NotNull PsiPackage context, @NotNull String[] extendClasses) { - HashSet lookups = new HashSet(); + public void processSubclassVariants(@NotNull PsiPackage context, @NotNull String[] extendClasses, CompletionResultSet result) { GlobalSearchScope packageScope = PackageScope.packageScope(context, true); GlobalSearchScope scope = myJavaClassReferenceSet.getProvider().getScope(getElement().getProject()); if (scope != null) { @@ -529,35 +532,22 @@ public class JavaClassReference extends GenericReference implements PsiJavaRefer // add itself if (packageScope.contains(extendClass.getContainingFile().getVirtualFile())) { if (isClassAccepted(extendClass, classKind, instantiatable, concrete, notInterface, notEnum)) { - ContainerUtil.addIfNotNull(createSubclassLookupValue(context, extendClass), lookups); + result.addElement(createSubclassLookupValue(extendClass, extendClassName)); } } for (final PsiClass clazz : ClassInheritorsSearch.search(extendClass, packageScope, true)) { - if (isClassAccepted(clazz, classKind, instantiatable, concrete, notInterface, notEnum)) { - ContainerUtil.addIfNotNull(createSubclassLookupValue(context, clazz), lookups); + String qname = clazz.getQualifiedName(); + if (qname != null && isClassAccepted(clazz, classKind, instantiatable, concrete, notInterface, notEnum)) { + result.addElement(createSubclassLookupValue(clazz, qname)); } } } } - return lookups.toArray(); } - @Nullable - private static Object createSubclassLookupValue(@NotNull final PsiPackage context, @NotNull final PsiClass clazz) { - final String qname = clazz.getQualifiedName(); - if (qname == null) return null; - - String name = qname; - final String pack = context.getQualifiedName(); - if (pack.length() > 0) { - if (name.startsWith(pack)) { - name = name.substring(pack.length() + 1); - } - else { - return null; - } - } - return JavaLookupElementBuilder.forClass(clazz, name, true).withLookupString(qname).withLookupString(clazz.getName()); + @NotNull + private static LookupElementBuilder createSubclassLookupValue(@NotNull final PsiClass clazz, @NotNull String qname) { + return JavaLookupElementBuilder.forClass(clazz, qname, true).withPresentableText(StringUtil.getShortName(qname)); } @Override diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReferenceSet.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReferenceSet.java index 0b212aa49494..c184775ad11b 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReferenceSet.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/JavaClassReferenceSet.java @@ -59,6 +59,11 @@ public class JavaClassReferenceSet { return myProvider; } + public TextRange getRangeInElement() { + PsiReference[] references = getReferences(); + return new TextRange(references[0].getRangeInElement().getStartOffset(), references[references.length - 1].getRangeInElement().getEndOffset()); + } + private void reparse(String str, PsiElement element, final boolean isStaticImport, JavaClassReferenceSet context) { myElement = element; myContext = context; diff --git a/platform/lang-api/src/com/intellij/codeInsight/lookup/Lookup.java b/platform/lang-api/src/com/intellij/codeInsight/lookup/Lookup.java index b41fb9330f53..10f124ab0b7f 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/lookup/Lookup.java +++ b/platform/lang-api/src/com/intellij/codeInsight/lookup/Lookup.java @@ -69,4 +69,6 @@ public interface Lookup { PrefixMatcher itemMatcher(@NotNull LookupElement item); boolean isSelectionTouched(); + + List getAdvertisements(); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/Advertiser.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/Advertiser.java index 78222057f2fd..e35986dfafdd 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/Advertiser.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/Advertiser.java @@ -138,4 +138,7 @@ public class Advertiser { return myComponent; } + public List getAdvertisements() { + return myTexts; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java index 72ef454026b1..35a119bbee76 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupImpl.java @@ -1158,6 +1158,11 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable return mySelectionTouched; } + @Override + public List getAdvertisements() { + return myAdComponent.getAdvertisements(); + } + @Override public void hide(){ hideLookup(true); @@ -1290,7 +1295,7 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable } public void addAdvertisement(@NotNull final String text) { - ApplicationManager.getApplication().invokeLater(new Runnable() { + Runnable runnable = new Runnable() { @Override public void run() { if (!myDisposed) { @@ -1301,7 +1306,12 @@ public class LookupImpl extends LightweightHint implements LookupEx, Disposable } } } - }, myModalityState); + }; + if (ApplicationManager.getApplication().isDispatchThread()) { + runnable.run(); + } else { + ApplicationManager.getApplication().invokeLater(runnable, myModalityState); + } } public boolean isLookupDisposed() { diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java index 3c2c00c87705..506e478d2b24 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java @@ -19,6 +19,7 @@ package com.intellij.testFramework.fixtures; import com.intellij.codeInsight.completion.CompletionType; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInsight.lookup.Lookup; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInspection.InspectionProfileEntry; import com.intellij.codeInspection.InspectionToolProvider; @@ -361,6 +362,8 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture { void finishLookup(); + Lookup getLookup(); + @NotNull PsiElement getElementAtCaret(); diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index 933eb4e26ee9..50ea02ec1ad9 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -1879,7 +1879,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig } } - private LookupImpl getLookup() { + public LookupImpl getLookup() { return (LookupImpl)LookupManager.getActiveLookup(myEditor); } diff --git a/plugins/devkit/testData/codeInsight/ShowAnActionInheritorsOnSmartCompletion.xml b/plugins/devkit/testData/codeInsight/ShowAnActionInheritorsOnSmartCompletion.xml new file mode 100644 index 000000000000..e201819aa60c --- /dev/null +++ b/plugins/devkit/testData/codeInsight/ShowAnActionInheritorsOnSmartCompletion.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testData/codeInsight/ShowPackagesInActionClass.xml b/plugins/devkit/testData/codeInsight/ShowPackagesInActionClass.xml new file mode 100644 index 000000000000..e201819aa60c --- /dev/null +++ b/plugins/devkit/testData/codeInsight/ShowPackagesInActionClass.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/plugins/devkit/testSources/codeInsight/PluginXmlFunctionalTest.java b/plugins/devkit/testSources/codeInsight/PluginXmlFunctionalTest.groovy similarity index 74% rename from plugins/devkit/testSources/codeInsight/PluginXmlFunctionalTest.java rename to plugins/devkit/testSources/codeInsight/PluginXmlFunctionalTest.groovy index 050f604ce17e..9c331f1749cf 100644 --- a/plugins/devkit/testSources/codeInsight/PluginXmlFunctionalTest.java +++ b/plugins/devkit/testSources/codeInsight/PluginXmlFunctionalTest.groovy @@ -13,28 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.idea.devkit.codeInsight; - -import com.intellij.codeInsight.TargetElementUtilBase; -import com.intellij.codeInspection.LocalInspectionTool; -import com.intellij.openapi.application.PluginPathManager; -import com.intellij.openapi.application.Result; -import com.intellij.openapi.command.WriteCommandAction; -import com.intellij.psi.ElementDescriptionUtil; -import com.intellij.psi.PsiElement; -import com.intellij.testFramework.PsiTestUtil; -import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory; -import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; -import com.intellij.testFramework.fixtures.TempDirTestFixture; -import com.intellij.usageView.UsageViewNodeTextLocation; -import com.intellij.usageView.UsageViewTypeLocation; -import com.intellij.util.xml.DeprecatedClassUsageInspection; -import org.jetbrains.idea.devkit.inspections.*; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; - +package org.jetbrains.idea.devkit.codeInsight +import com.intellij.codeInsight.TargetElementUtilBase +import com.intellij.codeInsight.completion.CompletionType +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PluginPathManager +import com.intellij.psi.ElementDescriptionUtil +import com.intellij.psi.PsiElement +import com.intellij.testFramework.PsiTestUtil +import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase +import com.intellij.testFramework.fixtures.TempDirTestFixture +import com.intellij.usageView.UsageViewNodeTextLocation +import com.intellij.usageView.UsageViewTypeLocation +import com.intellij.util.xml.DeprecatedClassUsageInspection +import org.jetbrains.idea.devkit.inspections.* /** * @author peter */ @@ -124,14 +118,8 @@ public class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase { } private void addPluginXml(final String root, final String text) throws IOException { - myTempDirFixture.createFile(root + - "/META-INF/plugin.xml", text); - new WriteCommandAction(getProject()) { - @Override - protected void run(Result result) throws Throwable { - PsiTestUtil.addSourceContentToRoots(myModule, myTempDirFixture.getFile(root)); - } - }.execute(); + myTempDirFixture.createFile(root + "/META-INF/plugin.xml", text); + ApplicationManager.application.runWriteAction { PsiTestUtil.addSourceContentToRoots(myModule, myTempDirFixture.getFile(root)) } } public void testNoWordCompletionInClassPlaces() throws Throwable { @@ -152,6 +140,27 @@ public class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase { myFixture.checkResultByFile(getTestName(false) + "_after.xml"); } + public void testShowPackagesInActionClass() { + myFixture.addClass("package com.intellij.openapi.actionSystem; public class AnAction { }"); + myFixture.addClass("package foo.bar; public class BarAction extends com.intellij.openapi.actionSystem.AnAction { }"); + myFixture.addClass("package foo.goo; public class GooAction extends com.intellij.openapi.actionSystem.AnAction { }"); + myFixture.configureByFile(getTestName(false) + ".xml"); + myFixture.completeBasic(); + assert myFixture.lookupElementStrings == ['bar', 'goo'] + assert myFixture.lookup.advertisements.find { it.contains('to see inheritors of com.intellij.openapi.actionSystem.AnAction') } + } + + public void testShowAnActionInheritorsOnSmartCompletion() { + myFixture.addClass("package com.intellij.openapi.actionSystem; public class AnAction { }"); + myFixture.addClass("package foo.bar; public class BarAction extends com.intellij.openapi.actionSystem.AnAction { }"); + myFixture.addClass("package foo.goo; public class GooAction extends com.intellij.openapi.actionSystem.AnAction { }"); + myFixture.addClass("package another.goo; public class AnotherAction extends com.intellij.openapi.actionSystem.AnAction { }"); + myFixture.configureByFile(getTestName(false) + ".xml"); + myFixture.complete(CompletionType.SMART); + assert myFixture.lookupElementStrings == ['foo.bar.BarAction', 'foo.goo.GooAction'] + assert !myFixture.lookup.advertisements.find { it.contains('to see inheritors of com.intellij.openapi.actionSystem.AnAction') } + } + public void testDeprecatedExtensionAttribute() { myFixture.enableInspections(DeprecatedClassUsageInspection.class); myFixture.testHighlighting("deprecatedExtensionAttribute.xml", "MyExtBean.java");