From 939d116ee4ca45599e3fc3b76afc6ebd7baa0181 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Wed, 16 Oct 2013 11:18:22 +0200 Subject: [PATCH] IDEA-25908 (Add Find Usages for imports) --- .../HighlightImportedElementsHandler.java | 306 ++++++++++++++++++ ...ghlightImportedElementsHandlerFactory.java | 46 +++ .../HighlightImport.java | 13 + .../HighlightStaticImport.java | 10 + .../HighlightUsagesHandlerTest.groovy | 14 + .../HighlightUsagesHandlerBase.java | 4 +- .../src/messages/CodeInsightBundle.properties | 2 + resources/src/META-INF/IdeaPlugin.xml | 1 + 8 files changed, 394 insertions(+), 2 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandler.java create mode 100644 java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandlerFactory.java create mode 100644 java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightImport.java create mode 100644 java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightStaticImport.java diff --git a/java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandler.java b/java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandler.java new file mode 100644 index 000000000000..b51094596c4f --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandler.java @@ -0,0 +1,306 @@ +/* + * 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.highlighting; + +import com.intellij.codeInsight.CodeInsightBundle; +import com.intellij.ide.util.NavigationItemListCellRenderer; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.PopupChooserBuilder; +import com.intellij.psi.*; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.ui.ListSpeedSearch; +import com.intellij.ui.components.JBList; +import com.intellij.util.Consumer; +import com.intellij.util.Function; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.io.Serializable; +import java.util.*; + +/** + * @author Bas Leijdekkers + */ +public class HighlightImportedElementsHandler extends HighlightUsagesHandlerBase { + + private final PsiElement myTarget; + private final PsiImportStatementBase myImportStatement; + private final boolean myImportStatic; + private Map> myClassReferenceListMap = null; + + public HighlightImportedElementsHandler(Editor editor, PsiFile file, PsiElement target, PsiImportStatementBase importStatement) { + super(editor, file); + myTarget = target; + myImportStatement = importStatement; + myImportStatic = myImportStatement instanceof PsiImportStaticStatement; + } + + @Override + public List getTargets() { + final PsiJavaCodeReferenceElement importReference = myImportStatement.getImportReference(); + if (importReference == null) { + return Collections.emptyList(); + } + final PsiJavaFile javaFile = PsiTreeUtil.getParentOfType(importReference, PsiJavaFile.class); + if (javaFile == null) { + return Collections.emptyList(); + } + final JavaResolveResult[] resolveResults = importReference.multiResolve(false); + if (resolveResults.length == 0) { + return Collections.emptyList(); + } + final PsiElement[] importedElements = new PsiElement[resolveResults.length]; + for (int i = 0; i < resolveResults.length; i++) { + final JavaResolveResult resolveResult = resolveResults[i]; + importedElements[i] = resolveResult.getElement(); + } + final ReferenceCollector collector = new ReferenceCollector(importedElements, myImportStatement.isOnDemand(), myImportStatic); + javaFile.accept(collector); + myClassReferenceListMap = collector.getClassReferenceListMap(); + if (myClassReferenceListMap.isEmpty()) { + return Collections.emptyList(); + } + return new ArrayList(myClassReferenceListMap.keySet()); + } + + @Override + protected void selectTargets(final List targets, final Consumer> selectionConsumer) { + if (targets.isEmpty()) { + selectionConsumer.consume(Collections.emptyList()); + return; + } + if (targets.size() == 1) { + selectionConsumer.consume(Collections.singletonList(targets.get(0))); + return; + } + if (ApplicationManager.getApplication().isUnitTestMode()) { + selectionConsumer.consume(targets); + return; + } + Collections.sort(targets, new PsiMemberComparator()); + final List model = new ArrayList(); + model.add(CodeInsightBundle.message("highlight.thrown.exceptions.chooser.all.entry")); + model.addAll(targets); + final JList list = new JBList(model); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + final ListCellRenderer renderer = new NavigationItemListCellRenderer(); + list.setCellRenderer(renderer); + final PopupChooserBuilder builder = new PopupChooserBuilder(list); + builder.setFilteringEnabled(new Function() { + @Override + public String fun(Object o) { + if (o instanceof PsiMember) { + final PsiMember member = (PsiMember)o; + return member.getName(); + } + return o.toString(); + } + }); + if (myImportStatic) { + builder.setTitle(CodeInsightBundle.message("highlight.imported.members.chooser.title")); + } else { + builder.setTitle(CodeInsightBundle.message("highlight.imported.classes.chooser.title")); + } + builder.setItemChoosenCallback(new Runnable() { + @Override + public void run() { + final int index= list.getSelectedIndex(); + if (index == 0) { + selectionConsumer.consume(targets); + } + else { + selectionConsumer.consume(Collections.singletonList(targets.get(index - 1))); + } + } + }); + final JBPopup popup = builder.createPopup(); + popup.showInBestPositionFor(myEditor); + } + + @Override + public void computeUsages(List targets) { + if (targets.isEmpty()) { + buildStatusText("import", 0); + return; + } + if (myClassReferenceListMap == null) { + return; + } + addOccurrence(myTarget); + for (PsiMember target : targets) { + final List elements = myClassReferenceListMap.get(target); + for (PsiElement element : elements) { + addOccurrence(element); + } + } + buildStatusText("import", myReadUsages.size() - 1 /* exclude target */); + } + + static class ReferenceCollector extends JavaRecursiveElementVisitor { + + private final Map> classReferenceListMap = new HashMap>(); + private final PsiElement[] myImportTargets; + private final boolean myOnDemand; + private final boolean myImportStatic; + + ReferenceCollector(@NotNull PsiElement[] importTargets, boolean onDemand, boolean importStatic) { + this.myImportTargets = importTargets; + this.myOnDemand = onDemand; + this.myImportStatic = importStatic; + } + + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + if (!myImportStatic && reference.getText().equals(reference.getQualifiedName())) { + return; + } + PsiElement parent = reference.getParent(); + if (parent instanceof PsiImportStatementBase) { + return; + } + while (parent instanceof PsiJavaCodeReferenceElement) { + parent = parent.getParent(); + if (parent instanceof PsiImportStatementBase) { + return; + } + } + if (myImportStatic) { + checkStaticImportReference(reference); + } + else { + checkImportReference(reference); + } + } + + private void checkStaticImportReference(PsiJavaCodeReferenceElement reference) { + if (reference.isQualified()) { + return; + } + final PsiElement target = reference.resolve(); + if (!(target instanceof PsiMethod) && !(target instanceof PsiClass) && !(target instanceof PsiField)) { + return; + } + final PsiMember member = (PsiMember)target; + for (PsiElement importTarget : myImportTargets) { + if (importTarget instanceof PsiMethod) { + if (member.equals(importTarget)) { + addReference(member, reference); + } + } + else if (importTarget instanceof PsiClass) { + final PsiClass importClass = (PsiClass)importTarget; + if (myOnDemand) { + final PsiClass containingClass = member.getContainingClass(); + if (InheritanceUtil.isInheritorOrSelf(importClass, containingClass, true)) { + addReference(member, reference); + } + } + else { + if (importTarget.equals(member)) { + addReference(member, reference); + } + } + } + } + } + + private void checkImportReference(PsiJavaCodeReferenceElement reference) { + final PsiElement element = reference.resolve(); + if (!(element instanceof PsiClass)) { + return; + } + final PsiClass referencedClass = (PsiClass)element; + for (PsiElement importTarget : myImportTargets) { + if (importTarget instanceof PsiPackage) { + if (referencedClass.getContainingClass() != null) { + return; + } + final PsiFile file = referencedClass.getContainingFile(); + if (!(file instanceof PsiJavaFile)) { + return; + } + final PsiJavaFile javaFile = (PsiJavaFile)file; + final PsiPackage aPackage = (PsiPackage)importTarget; + final String packageName = aPackage.getQualifiedName(); + final String filePackage = javaFile.getPackageName(); + if (filePackage.equals(packageName)) { + addReference(referencedClass, reference); + } + } + else if (importTarget instanceof PsiClass) { + final PsiClass aClass = (PsiClass)importTarget; + final String name = aClass.getQualifiedName(); + if (name == null) { + return; + } + if (!myOnDemand) { + if (name.equals(referencedClass.getQualifiedName())) { + addReference(referencedClass, reference); + } + } + else { + final PsiClass containingClass = referencedClass.getContainingClass(); + if (containingClass == null) { + return; + } + if (name.equals(containingClass.getQualifiedName())) { + addReference(referencedClass, reference); + } + } + } + } + } + + private void addReference(PsiMember referencedMember, PsiJavaCodeReferenceElement reference) { + List referenceList = classReferenceListMap.get(referencedMember); + if (referenceList == null) { + referenceList = new ArrayList(); + classReferenceListMap.put(referencedMember, referenceList); + } + referenceList.add(reference.getReferenceNameElement()); + } + + public Map> getClassReferenceListMap() { + return classReferenceListMap; + } + } + + static class PsiMemberComparator implements Comparator { + + public int compare(PsiMember member1, PsiMember member2) { + final String name1 = member1.getName(); + if (name1 == null) { + return -1; + } + final String name2 = member2.getName(); + if (name2 == null) { + return 1; + } + final int i = name1.compareTo(name2); + if (i != 0) { + return i; + } + final PsiJavaFile file1 = (PsiJavaFile)member1.getContainingFile(); + final PsiJavaFile file2 = (PsiJavaFile)member2.getContainingFile(); + final String packageName1 = file1.getPackageName(); + final String packageName2 = file2.getPackageName(); + return packageName1.compareTo(packageName2); + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandlerFactory.java b/java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandlerFactory.java new file mode 100644 index 000000000000..def7501d93e5 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/highlighting/HighlightImportedElementsHandlerFactory.java @@ -0,0 +1,46 @@ +/* + * 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.highlighting; + +import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.*; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class HighlightImportedElementsHandlerFactory implements HighlightUsagesHandlerFactory { + + @Nullable + @Override + public HighlightUsagesHandlerBase createHighlightUsagesHandler(Editor editor, PsiFile file) { + final int offset = TargetElementUtilBase.adjustOffset(file, editor.getDocument(), editor.getCaretModel().getOffset()); + final PsiElement target = file.findElementAt(offset); + if (!(target instanceof PsiKeyword) || !PsiKeyword.IMPORT.equals(target.getText())) { + return null; + } + final PsiElement parent = target.getParent(); + if (!(parent instanceof PsiImportStatementBase)) { + return null; + } + final PsiElement grand = parent.getParent(); + if (!(grand instanceof PsiImportList)) { + return null; + } + return new HighlightImportedElementsHandler(editor, file, target, (PsiImportStatementBase) parent); + } +} diff --git a/java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightImport.java b/java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightImport.java new file mode 100644 index 000000000000..c3fca3a6109d --- /dev/null +++ b/java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightImport.java @@ -0,0 +1,13 @@ +import java.util.List; + +class HighlightImport { + + void f(List l) { + List m; + java.util.List n; + } + + List g() { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightStaticImport.java b/java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightStaticImport.java new file mode 100644 index 000000000000..02842f42e8f5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/highlightUsagesHandler/HighlightStaticImport.java @@ -0,0 +1,10 @@ +import static java.lang.Math.*; + +class HighlightStaticImport { + + void g() { + abs(-1.0); + abs(1L); + pow(1.0, 2.0); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerTest.groovy index 7fc39a366819..885b336ba0ad 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerTest.groovy @@ -15,6 +15,20 @@ public class HighlightUsagesHandlerTest extends LightCodeInsightFixtureTestCase HighlightUsagesHandler.invoke(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile()); } + public void testHighlightImport() throws Exception { + configureFile(); + ctrlShiftF7(); + assertRangeText("import", "List", "List", "List", "List", "List"); + checkUnselect(); + } + + public void testHighlightStaticImport() throws Exception { + configureFile(); + ctrlShiftF7(); + assertRangeText("import", "abs", "abs", "pow"); + checkUnselect(); + } + public void testSimpleThrows() throws Exception { configureFile(); ctrlShiftF7(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerBase.java b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerBase.java index f1921e0de783..a55e721c9d95 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/HighlightUsagesHandlerBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * 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. @@ -92,7 +92,7 @@ public abstract class HighlightUsagesHandlerBase { HighlightUsagesHandler.getShortcutText()); } else { - myStatusText = CodeInsightBundle.message(elementName != null ? + myHintText = CodeInsightBundle.message(elementName != null ? "status.bar.highlighted.usages.not.found.message" : "status.bar.highlighted.usages.not.found.no.target.message", elementName); } diff --git a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties index 14bc906b1277..e1bbe4d131b3 100644 --- a/platform/platform-resources-en/src/messages/CodeInsightBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeInsightBundle.properties @@ -489,3 +489,5 @@ rename.public.class.text=Rename class ''{0}'' to ''{1}'' rename.named.element.text=Rename ''{0}'' to ''{1}'' dialog.edit.template.checkbox.html.text=HTML Text dialog.edit.template.checkbox.xsl.text=XSL Text +highlight.imported.classes.chooser.title=Choose Imported Classes to Highlight +highlight.imported.members.chooser.title=Choose Imported Members to Highlight diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 6591dd0570b5..369ac61c3c75 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -1143,6 +1143,7 @@ +