├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── scopes │ └── scope_settings.xml ├── vcs.xml ├── encodings.xml ├── modules.xml ├── compiler.xml ├── runConfigurations │ └── Dagger_Plugin.xml ├── misc.xml └── uiDesigner.xml ├── .gitignore ├── dagger-intellij-plugin.jar ├── resources └── icons │ ├── inject.png │ └── provides.png ├── images └── inject-to-provide.gif ├── src └── com │ └── squareup │ └── ideaplugin │ └── dagger │ ├── DaggerProjectHandler.java │ ├── DaggerConstants.java │ ├── handler │ ├── ProvidesToInjectHandler.java │ ├── ConstructorInjectToInjectionPlaceHandler.java │ ├── FieldInjectToProvidesHandler.java │ └── ConstructorInjectToProvidesHandler.java │ ├── CompositeActiveComponent.java │ ├── PickTypeAction.java │ ├── ProvidesLineMarkerProvider.java │ ├── PingEDT.java │ ├── InjectionLineMarkerProvider.java │ ├── Decider.java │ ├── ShowUsagesTableCellRenderer.java │ ├── PsiConsultantImpl.java │ └── ShowUsagesAction.java ├── CONTRIBUTING.md ├── dagger-intellij-plugin.iml ├── README.md ├── META-INF └── plugin.xml └── LICENSE.txt /.idea/.name: -------------------------------------------------------------------------------- 1 | dagger-intellij-plugin -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | target/ 3 | out/ 4 | -------------------------------------------------------------------------------- /dagger-intellij-plugin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/square/dagger-intellij-plugin/HEAD/dagger-intellij-plugin.jar -------------------------------------------------------------------------------- /resources/icons/inject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/square/dagger-intellij-plugin/HEAD/resources/icons/inject.png -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /images/inject-to-provide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/square/dagger-intellij-plugin/HEAD/images/inject-to-provide.gif -------------------------------------------------------------------------------- /resources/icons/provides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/square/dagger-intellij-plugin/HEAD/resources/icons/provides.png -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/DaggerProjectHandler.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger; 2 | 3 | import com.intellij.openapi.components.AbstractProjectComponent; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.psi.PsiManager; 6 | 7 | public class DaggerProjectHandler extends AbstractProjectComponent { 8 | protected DaggerProjectHandler(Project project, PsiManager psiManager) { 9 | super(project); 10 | System.out.println("DaggerProjectHandler initialized"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/DaggerConstants.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger; 2 | 3 | public interface DaggerConstants { 4 | String CLASS_PROVIDES = "dagger.Provides"; 5 | String CLASS_INJECT = "javax.inject.Inject"; 6 | String CLASS_LAZY = "dagger.Lazy"; 7 | String CLASS_PROVIDER = "javax.inject.Provider"; 8 | String CLASS_QUALIFIER = "javax.inject.Qualifier"; 9 | String ATTRIBUTE_TYPE = "type"; 10 | String SET_TYPE = "SET"; 11 | String MAP_TYPE = "MAP"; 12 | 13 | int MAX_USAGES = 100; 14 | } 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | If you would like to contribute code you can do so through GitHub by forking 5 | the repository and sending a pull request. 6 | 7 | When submitting code, please make every effort to follow existing conventions 8 | and style in order to keep the code as readable as possible. 9 | 10 | Before your code can be accepted into the project you must also sign the 11 | [Individual Contributor License Agreement (CLA)][1]. 12 | 13 | 14 | [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1 15 | -------------------------------------------------------------------------------- /dagger-intellij-plugin.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Dagger_Plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Dagger IntelliJ Plugin 2 | ====================== 3 | 4 | [Dagger][1] can provide a class objects without needing to know how they are constructed. 5 | The Dagger IntelliJ plugin helps demystify this behavior and creates visual connections 6 | between a `@Inject` object and the `@Provides` method that creates it. 7 | 8 | ![inject->provide](images/inject-to-provide.gif) 9 | 10 | 11 | Download 12 | -------- 13 | 14 | [Download][2] the plugin jar and select "Install Plugin From Disk" in IntelliJ's plugin preferences. 15 | 16 | 17 | License 18 | ======= 19 | 20 | Copyright 2013 Square, Inc. 21 | 22 | Licensed under the Apache License, Version 2.0 (the "License"); 23 | you may not use this file except in compliance with the License. 24 | You may obtain a copy of the License at 25 | 26 | http://www.apache.org/licenses/LICENSE-2.0 27 | 28 | Unless required by applicable law or agreed to in writing, software 29 | distributed under the License is distributed on an "AS IS" BASIS, 30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | See the License for the specific language governing permissions and 32 | limitations under the License. 33 | 34 | 35 | [1]: http://square.github.io/dagger/ 36 | [2]: https://github.com/square/dagger-intellij-plugin/blob/master/dagger-intellij-plugin.jar?raw=true 37 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/handler/ProvidesToInjectHandler.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger.handler; 2 | 3 | import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; 4 | import com.intellij.psi.PsiClass; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.psi.PsiMethod; 7 | import com.intellij.psi.util.PsiUtilBase; 8 | import com.intellij.ui.awt.RelativePoint; 9 | import com.squareup.ideaplugin.dagger.Decider; 10 | import com.squareup.ideaplugin.dagger.PsiConsultantImpl; 11 | import com.squareup.ideaplugin.dagger.ShowUsagesAction; 12 | import java.awt.event.MouseEvent; 13 | 14 | import static com.squareup.ideaplugin.dagger.DaggerConstants.MAX_USAGES; 15 | 16 | public class ProvidesToInjectHandler implements GutterIconNavigationHandler { 17 | @Override public void navigate(MouseEvent mouseEvent, PsiElement psiElement) { 18 | if (!(psiElement instanceof PsiMethod)) { 19 | throw new IllegalStateException("Called with non-method: " + psiElement); 20 | } 21 | 22 | PsiMethod psiMethod = (PsiMethod) psiElement; 23 | PsiClass psiClass = PsiConsultantImpl.getReturnClassFromMethod(psiMethod, true); 24 | 25 | new ShowUsagesAction(new Decider.ProvidesMethodDecider(psiMethod)).startFindUsages(psiClass, 26 | new RelativePoint(mouseEvent), PsiUtilBase.findEditor(psiClass), MAX_USAGES); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/handler/ConstructorInjectToInjectionPlaceHandler.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger.handler; 2 | 3 | import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; 4 | import com.intellij.psi.PsiClass; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.psi.PsiMethod; 7 | import com.intellij.psi.util.PsiUtilBase; 8 | import com.intellij.ui.awt.RelativePoint; 9 | import com.squareup.ideaplugin.dagger.Decider; 10 | import com.squareup.ideaplugin.dagger.PsiConsultantImpl; 11 | import com.squareup.ideaplugin.dagger.ShowUsagesAction; 12 | import java.awt.event.MouseEvent; 13 | 14 | import static com.squareup.ideaplugin.dagger.DaggerConstants.MAX_USAGES; 15 | 16 | public class ConstructorInjectToInjectionPlaceHandler implements GutterIconNavigationHandler { 17 | @Override public void navigate(MouseEvent mouseEvent, PsiElement psiElement) { 18 | if (!(psiElement instanceof PsiMethod)) { 19 | throw new IllegalStateException("Called with non-method: " + psiElement); 20 | } 21 | 22 | PsiMethod psiMethod = (PsiMethod) psiElement; 23 | PsiClass psiClass = PsiConsultantImpl.getClass(psiElement); 24 | 25 | new ShowUsagesAction(new Decider.ProvidesMethodDecider(psiMethod)).startFindUsages(psiClass, 26 | new RelativePoint(mouseEvent), PsiUtilBase.findEditor(psiClass), MAX_USAGES); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/handler/FieldInjectToProvidesHandler.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger.handler; 2 | 3 | import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; 4 | import com.intellij.psi.PsiClass; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.psi.PsiField; 7 | import com.intellij.psi.util.PsiUtilBase; 8 | import com.intellij.ui.awt.RelativePoint; 9 | import com.squareup.ideaplugin.dagger.Decider; 10 | import com.squareup.ideaplugin.dagger.PsiConsultantImpl; 11 | import com.squareup.ideaplugin.dagger.ShowUsagesAction; 12 | import java.awt.event.MouseEvent; 13 | 14 | import static com.squareup.ideaplugin.dagger.DaggerConstants.MAX_USAGES; 15 | 16 | /** 17 | * Handles linking from field @Inject(ion) to @Provides. 18 | * 19 | * Ensures that a Lazy and Provider resolve to the appropriate 20 | * classes. 21 | */ 22 | public class FieldInjectToProvidesHandler implements GutterIconNavigationHandler { 23 | @Override public void navigate(MouseEvent mouseEvent, PsiElement psiElement) { 24 | if (!(psiElement instanceof PsiField)) { 25 | throw new IllegalStateException("Called with non-field element: " + psiElement); 26 | } 27 | 28 | PsiField psiField = (PsiField) psiElement; 29 | PsiClass injectedClass = PsiConsultantImpl.checkForLazyOrProvider(psiField); 30 | 31 | new ShowUsagesAction(new Decider.FieldInjectDecider(psiField)).startFindUsages(injectedClass, 32 | new RelativePoint(mouseEvent), PsiUtilBase.findEditor(injectedClass), MAX_USAGES); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/CompositeActiveComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2000-2012 JetBrains s.r.o. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.ideaplugin.dagger; 17 | 18 | import com.intellij.ui.ActiveComponent; 19 | import org.jetbrains.annotations.NotNull; 20 | 21 | import javax.swing.*; 22 | import java.awt.*; 23 | 24 | class CompositeActiveComponent implements ActiveComponent { 25 | private final ActiveComponent[] myComponents; 26 | private final JPanel myComponent; 27 | 28 | public CompositeActiveComponent(@NotNull ActiveComponent... components) { 29 | myComponents = components; 30 | 31 | myComponent = new JPanel(new FlowLayout()); 32 | myComponent.setOpaque(false); 33 | for (ActiveComponent component : components) { 34 | myComponent.add(component.getComponent()); 35 | } 36 | } 37 | 38 | @Override 39 | public void setActive(boolean active) { 40 | for (ActiveComponent component : myComponents) { 41 | component.setActive(active); 42 | } 43 | } 44 | 45 | @Override 46 | public JComponent getComponent() { 47 | return myComponent; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/PickTypeAction.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger; 2 | 3 | import com.intellij.openapi.ui.popup.JBPopupFactory; 4 | import com.intellij.openapi.ui.popup.ListPopup; 5 | import com.intellij.openapi.ui.popup.PopupStep; 6 | import com.intellij.openapi.ui.popup.util.BaseListPopupStep; 7 | import com.intellij.psi.PsiClass; 8 | import com.intellij.psi.PsiParameter; 9 | import com.intellij.ui.awt.RelativePoint; 10 | import java.util.Set; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class PickTypeAction { 14 | 15 | public void startPickTypes(RelativePoint relativePoint, PsiParameter[] psiParameters, 16 | final Callback callback) { 17 | if (psiParameters.length == 0) return; 18 | 19 | ListPopup listPopup = JBPopupFactory.getInstance() 20 | .createListPopup(new BaseListPopupStep("Select Type", psiParameters) { 21 | @NotNull @Override public String getTextFor(PsiParameter value) { 22 | StringBuilder builder = new StringBuilder(); 23 | 24 | Set annotations = PsiConsultantImpl.getQualifierAnnotations(value); 25 | for (String annotation : annotations) { 26 | String trimmed = annotation.substring(annotation.lastIndexOf(".") + 1); 27 | builder.append("@").append(trimmed).append(" "); 28 | } 29 | 30 | PsiClass notLazyOrProvider = PsiConsultantImpl.checkForLazyOrProvider(value); 31 | return builder.append(notLazyOrProvider.getName()).toString(); 32 | } 33 | 34 | @Override public PopupStep onChosen(PsiParameter selectedValue, boolean finalChoice) { 35 | callback.onParameterChosen(selectedValue); 36 | return super.onChosen(selectedValue, finalChoice); 37 | } 38 | }); 39 | 40 | listPopup.show(relativePoint); 41 | } 42 | 43 | public interface Callback { 44 | void onParameterChosen(PsiParameter clazz); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.squareup.ideaplugin.dagger 3 | Dagger Plugin 4 | 1.0.5 5 | Square, Inc. 6 | 7 | 10 |
  • @Inject to @Provides
  • 11 |
  • @Provides to all @Injects
  • 12 |
  • Support for Lazy<T> and Provider<T>
  • 13 | 14 | ]]>
    15 | 16 | 21 | 1.0.2: Build using Java 1.6.
    22 | 1.0.1: Add support for Qualifiers.
    23 | 1.0.0: Initial version.
    24 | ]]>
    25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | com.squareup.ideaplugin.dagger.DaggerProjectHandler 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 48 | 49 |
    50 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/ProvidesLineMarkerProvider.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger; 2 | 3 | import com.intellij.codeInsight.daemon.LineMarkerInfo; 4 | import com.intellij.codeInsight.daemon.LineMarkerProvider; 5 | import com.intellij.openapi.util.IconLoader; 6 | import com.intellij.psi.PsiElement; 7 | import com.intellij.psi.PsiMethod; 8 | import com.intellij.psi.PsiTypeElement; 9 | import com.squareup.ideaplugin.dagger.handler.ConstructorInjectToInjectionPlaceHandler; 10 | import com.squareup.ideaplugin.dagger.handler.ProvidesToInjectHandler; 11 | import java.util.Collection; 12 | import java.util.List; 13 | import javax.swing.Icon; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import static com.intellij.codeHighlighting.Pass.UPDATE_ALL; 18 | import static com.intellij.openapi.editor.markup.GutterIconRenderer.Alignment.LEFT; 19 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_INJECT; 20 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_PROVIDES; 21 | import static com.squareup.ideaplugin.dagger.PsiConsultantImpl.hasAnnotation; 22 | 23 | public class ProvidesLineMarkerProvider implements LineMarkerProvider { 24 | private static final Icon ICON = IconLoader.getIcon("/icons/provides.png"); 25 | 26 | /** 27 | * @return a {@link com.intellij.codeInsight.daemon.GutterIconNavigationHandler} if the element 28 | * is a PsiMethod annotated with @Provides. 29 | */ 30 | @Nullable @Override 31 | public LineMarkerInfo getLineMarkerInfo(@NotNull final PsiElement element) { 32 | // Check methods first (includes constructors). 33 | if (element instanceof PsiMethod) { 34 | PsiMethod methodElement = (PsiMethod) element; 35 | 36 | // Does it have an @Provides? 37 | if (hasAnnotation(element, CLASS_PROVIDES)) { 38 | PsiTypeElement returnTypeElement = methodElement.getReturnTypeElement(); 39 | if (returnTypeElement != null) { 40 | return new LineMarkerInfo(element, returnTypeElement.getTextRange(), ICON, 41 | UPDATE_ALL, null, new ProvidesToInjectHandler(), LEFT); 42 | } 43 | } 44 | 45 | // Is it an @Inject-able constructor? 46 | if (methodElement.isConstructor() && hasAnnotation(element, CLASS_INJECT)) { 47 | return new LineMarkerInfo(element, element.getTextRange(), ICON, 48 | UPDATE_ALL, null, new ConstructorInjectToInjectionPlaceHandler(), LEFT); 49 | } 50 | } 51 | 52 | return null; 53 | } 54 | 55 | @Override public void collectSlowLineMarkers(@NotNull List psiElements, 56 | @NotNull Collection lineMarkerInfos) { 57 | // Sure buddy. You ever explain how and we just might. 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/PingEDT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2000-2012 JetBrains s.r.o. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.squareup.ideaplugin.dagger; 17 | 18 | import com.intellij.openapi.util.Condition; 19 | import java.util.concurrent.atomic.AtomicBoolean; 20 | import javax.swing.SwingUtilities; 21 | import org.jetbrains.annotations.NonNls; 22 | import org.jetbrains.annotations.NotNull; 23 | 24 | /** 25 | * Runs activity in the EDT. 26 | * To schedule activity, call {@link #ping()}. It sets the flag telling that the activity should be 27 | * run. Once it has run, the flag is cleared. 28 | * So you can call ping() several times, but the activity will be executed only once. 29 | * If activity took more than {@code maxUnitOfWorkThresholdMs} ms, it will yield till the next 30 | * invokeLater. 31 | */ 32 | class PingEDT { 33 | @SuppressWarnings({ "FieldCanBeLocal", "UnusedDeclaration" }) 34 | private final String myName; 35 | private final Runnable pingAction; 36 | private volatile boolean stopped; 37 | private volatile boolean pinged; 38 | private final Condition myShutUpCondition; 39 | private final int myMaxUnitOfWorkThresholdMs; //-1 means indefinite 40 | 41 | private final AtomicBoolean invokeLaterScheduled = new AtomicBoolean(); 42 | private final Runnable myUpdateRunnable = new Runnable() { 43 | @Override 44 | public void run() { 45 | boolean b = invokeLaterScheduled.compareAndSet(true, false); 46 | assert b; 47 | if (stopped || myShutUpCondition.value(null)) { 48 | stop(); 49 | return; 50 | } 51 | long start = System.currentTimeMillis(); 52 | int processed = 0; 53 | while (true) { 54 | if (processNext()) { 55 | processed++; 56 | } else { 57 | break; 58 | } 59 | long finish = System.currentTimeMillis(); 60 | if (myMaxUnitOfWorkThresholdMs != -1 && finish - start > myMaxUnitOfWorkThresholdMs) break; 61 | } 62 | if (!isEmpty()) { 63 | scheduleUpdate(); 64 | } 65 | } 66 | }; 67 | 68 | public PingEDT(@NotNull @NonNls String name, @NotNull Condition shutUpCondition, 69 | int maxUnitOfWorkThresholdMs, @NotNull Runnable pingAction) { 70 | myName = name; 71 | myShutUpCondition = shutUpCondition; 72 | myMaxUnitOfWorkThresholdMs = maxUnitOfWorkThresholdMs; 73 | this.pingAction = pingAction; 74 | } 75 | 76 | private boolean isEmpty() { 77 | return !pinged; 78 | } 79 | 80 | private boolean processNext() { 81 | pinged = false; 82 | pingAction.run(); 83 | return pinged; 84 | } 85 | 86 | // returns true if invokeLater was called 87 | public boolean ping() { 88 | pinged = true; 89 | return scheduleUpdate(); 90 | } 91 | 92 | // returns true if invokeLater was called 93 | private boolean scheduleUpdate() { 94 | if (!stopped && invokeLaterScheduled.compareAndSet(false, true)) { 95 | SwingUtilities.invokeLater(myUpdateRunnable); 96 | return true; 97 | } 98 | return false; 99 | } 100 | 101 | public void stop() { 102 | stopped = true; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/InjectionLineMarkerProvider.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger; 2 | 3 | import com.intellij.codeInsight.daemon.LineMarkerInfo; 4 | import com.intellij.codeInsight.daemon.LineMarkerProvider; 5 | import com.intellij.openapi.util.IconLoader; 6 | import com.intellij.psi.PsiElement; 7 | import com.intellij.psi.PsiField; 8 | import com.intellij.psi.PsiIdentifier; 9 | import com.intellij.psi.PsiMethod; 10 | import com.intellij.psi.PsiTypeElement; 11 | import com.squareup.ideaplugin.dagger.handler.ConstructorInjectToProvidesHandler; 12 | import com.squareup.ideaplugin.dagger.handler.FieldInjectToProvidesHandler; 13 | import java.util.Collection; 14 | import java.util.List; 15 | import javax.swing.Icon; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import static com.intellij.codeHighlighting.Pass.UPDATE_ALL; 20 | import static com.intellij.openapi.editor.markup.GutterIconRenderer.Alignment.LEFT; 21 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_INJECT; 22 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_PROVIDES; 23 | 24 | public class InjectionLineMarkerProvider implements LineMarkerProvider { 25 | private static final Icon ICON = IconLoader.getIcon("/icons/inject.png"); 26 | 27 | /** 28 | * Check the element. If the element is a PsiMethod, than we want to know if it's a Constructor 29 | * annotated w/ @Inject. 30 | * 31 | * If element is a field, than we only want to see if it is annotated with @Inject. 32 | * 33 | * @return a {@link com.intellij.codeInsight.daemon.GutterIconNavigationHandler} for the 34 | * appropriate type, or null if we don't care about it. 35 | */ 36 | @Nullable @Override 37 | public LineMarkerInfo getLineMarkerInfo(@NotNull final PsiElement element) { 38 | // Check methods first (includes constructors). 39 | if (element instanceof PsiMethod) { 40 | PsiMethod methodElement = (PsiMethod) element; 41 | 42 | // Constructor injection. 43 | if (methodElement.isConstructor() && PsiConsultantImpl.hasAnnotation(element, CLASS_INJECT) && 44 | methodElement.getParameterList().getParametersCount() > 0) { 45 | PsiIdentifier nameIdentifier = methodElement.getNameIdentifier(); 46 | if (nameIdentifier != null) { 47 | return new LineMarkerInfo(element, nameIdentifier.getTextRange(), ICON, 48 | UPDATE_ALL, null, new ConstructorInjectToProvidesHandler(), LEFT); 49 | } 50 | } 51 | 52 | // Method annotated with @Provides and has at least one argument 53 | if (!methodElement.isConstructor() && PsiConsultantImpl.hasAnnotation(element, CLASS_PROVIDES) && 54 | methodElement.getParameterList().getParametersCount() > 0) { 55 | PsiIdentifier nameIdentifier = methodElement.getNameIdentifier(); 56 | if (nameIdentifier != null) { 57 | return new LineMarkerInfo(element, nameIdentifier.getTextRange(), ICON, 58 | UPDATE_ALL, null, new ConstructorInjectToProvidesHandler(), LEFT); 59 | } 60 | } 61 | 62 | // Not a method, is it a Field? 63 | } else if (element instanceof PsiField) { 64 | // Field injection. 65 | PsiField fieldElement = (PsiField) element; 66 | PsiTypeElement typeElement = fieldElement.getTypeElement(); 67 | 68 | if (PsiConsultantImpl.hasAnnotation(element, CLASS_INJECT) && typeElement != null) { 69 | return new LineMarkerInfo(element, typeElement.getTextRange(), ICON, UPDATE_ALL, 70 | null, new FieldInjectToProvidesHandler(), LEFT); 71 | } 72 | } 73 | 74 | return null; 75 | } 76 | 77 | @Override public void collectSlowLineMarkers(@NotNull List psiElements, 78 | @NotNull Collection lineMarkerInfos) { 79 | // Sure buddy. You ever explain how and we just might. 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/handler/ConstructorInjectToProvidesHandler.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger.handler; 2 | 3 | import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; 4 | import com.intellij.pom.Navigatable; 5 | import com.intellij.psi.JavaPsiFacade; 6 | import com.intellij.psi.PsiClass; 7 | import com.intellij.psi.PsiClassType; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiMethod; 10 | import com.intellij.psi.PsiParameter; 11 | import com.intellij.psi.PsiType; 12 | import com.intellij.psi.PsiTypeElement; 13 | import com.intellij.psi.util.PsiUtilBase; 14 | import com.intellij.ui.awt.RelativePoint; 15 | import com.squareup.ideaplugin.dagger.Decider; 16 | import com.squareup.ideaplugin.dagger.PickTypeAction; 17 | import com.squareup.ideaplugin.dagger.PsiConsultantImpl; 18 | import com.squareup.ideaplugin.dagger.ShowUsagesAction; 19 | 20 | import java.awt.event.MouseEvent; 21 | import java.util.List; 22 | import java.util.concurrent.ExecutionException; 23 | 24 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_INJECT; 25 | import static com.squareup.ideaplugin.dagger.DaggerConstants.MAX_USAGES; 26 | 27 | /** 28 | * Handles linking from constructor @Inject(ion) to @Provides. If the Constructor takes multiple 29 | * parameters, a dialog will pop-up asking the user which parameter type they'd like to look at. 30 | * 31 | * Aside from that popup, this is exactly like {@link FieldInjectToProvidesHandler}. 32 | */ 33 | public class ConstructorInjectToProvidesHandler implements GutterIconNavigationHandler { 34 | @Override public void navigate(final MouseEvent mouseEvent, PsiElement psiElement) { 35 | if (!(psiElement instanceof PsiMethod)) { 36 | throw new IllegalStateException("Called with non-method: " + psiElement); 37 | } 38 | 39 | PsiMethod psiMethod = (PsiMethod) psiElement; 40 | PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); 41 | if (parameters.length == 1) { 42 | showUsages(mouseEvent, parameters[0]); 43 | } else { 44 | new PickTypeAction().startPickTypes(new RelativePoint(mouseEvent), parameters, 45 | new PickTypeAction.Callback() { 46 | @Override public void onParameterChosen(PsiParameter selected) { 47 | showUsages(mouseEvent, selected); 48 | } 49 | }); 50 | } 51 | } 52 | 53 | private void showUsages(final MouseEvent mouseEvent, final PsiParameter psiParameter) { 54 | // Check to see if class type of psiParameter has constructor with @Inject. Otherwise, proceed. 55 | if (navigateToConstructorIfProvider(psiParameter)) { 56 | return; 57 | } 58 | 59 | // If psiParameter is Set, check if @Provides(type=SET) T providesT exists. 60 | // Also check map (TODO(radford): Add check for map). 61 | 62 | List paramTypes = PsiConsultantImpl.getTypeParameters(psiParameter); 63 | if (paramTypes.isEmpty()) { 64 | new ShowUsagesAction( 65 | new Decider.ConstructorParameterInjectDecider(psiParameter)).startFindUsages( 66 | PsiConsultantImpl.checkForLazyOrProvider(psiParameter), 67 | new RelativePoint(mouseEvent), 68 | PsiUtilBase.findEditor(psiParameter), MAX_USAGES); 69 | } else { 70 | ShowUsagesAction actions = new ShowUsagesAction( 71 | new Decider.CollectionElementParameterInjectDecider(psiParameter)); 72 | actions.setListener(new ShowUsagesAction.Listener() { 73 | @Override public void onFinished(boolean hasResults) { 74 | if (!hasResults) { 75 | new ShowUsagesAction( 76 | new Decider.ConstructorParameterInjectDecider(psiParameter)).startFindUsages( 77 | PsiConsultantImpl.checkForLazyOrProvider(psiParameter), 78 | new RelativePoint(mouseEvent), 79 | PsiUtilBase.findEditor(psiParameter), MAX_USAGES); 80 | } 81 | } 82 | }); 83 | 84 | actions.startFindUsages(((PsiClassType) paramTypes.get(0)).resolve(), 85 | new RelativePoint(mouseEvent), 86 | PsiUtilBase.findEditor(psiParameter), MAX_USAGES); 87 | } 88 | } 89 | 90 | private boolean navigateToConstructorIfProvider(PsiParameter psiParameter) { 91 | PsiTypeElement declaringTypeElement = psiParameter.getTypeElement(); 92 | PsiClass classElement = JavaPsiFacade.getInstance(psiParameter.getProject()).findClass( 93 | declaringTypeElement.getType().getCanonicalText(), 94 | declaringTypeElement.getResolveScope()); 95 | 96 | if (classElement == null) { 97 | return false; 98 | } 99 | 100 | for (PsiMethod method : classElement.getConstructors()) { 101 | if (PsiConsultantImpl.hasAnnotation(method, CLASS_INJECT) && navigateToElement(method)) { 102 | return true; 103 | } 104 | } 105 | return false; 106 | } 107 | 108 | private boolean navigateToElement(PsiElement element) { 109 | PsiElement navigationElement = element.getNavigationElement(); 110 | if (navigationElement != null && navigationElement instanceof Navigatable && 111 | ((Navigatable) navigationElement).canNavigate()) { 112 | ((Navigatable) navigationElement).navigate(true); 113 | return true; 114 | } 115 | return false; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/Decider.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger; 2 | 3 | import com.intellij.psi.PsiAnnotationMemberValue; 4 | import com.intellij.psi.PsiClass; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.psi.PsiField; 7 | import com.intellij.psi.PsiMethod; 8 | import com.intellij.psi.PsiParameter; 9 | import com.intellij.psi.PsiType; 10 | import com.intellij.usages.Usage; 11 | import com.intellij.usages.UsageInfo2UsageAdapter; 12 | import com.intellij.usages.UsageTarget; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_INJECT; 17 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_PROVIDES; 18 | import static com.squareup.ideaplugin.dagger.DaggerConstants.SET_TYPE; 19 | 20 | public interface Decider { 21 | 22 | boolean shouldShow(UsageTarget target, Usage usage); 23 | 24 | /** Construct with a PsiMethod from a Provider to find where this is injected. */ 25 | public class ProvidesMethodDecider implements Decider { 26 | private final PsiClass returnType; 27 | private final Set qualifierAnnotations; 28 | private final List typeParameters; 29 | 30 | public ProvidesMethodDecider(PsiMethod psiMethod) { 31 | this.returnType = PsiConsultantImpl.getReturnClassFromMethod(psiMethod, true); 32 | this.qualifierAnnotations = PsiConsultantImpl.getQualifierAnnotations(psiMethod); 33 | this.typeParameters = PsiConsultantImpl.getTypeParameters(psiMethod); 34 | } 35 | 36 | @Override public boolean shouldShow(UsageTarget target, Usage usage) { 37 | PsiElement element = ((UsageInfo2UsageAdapter) usage).getElement(); 38 | 39 | PsiField field = PsiConsultantImpl.findField(element); 40 | if (field != null // 41 | && PsiConsultantImpl.hasAnnotation(field, CLASS_INJECT) // 42 | && PsiConsultantImpl.hasQuailifierAnnotations(field, qualifierAnnotations) 43 | && PsiConsultantImpl.hasTypeParameters(field, typeParameters)) { 44 | return true; 45 | } 46 | 47 | PsiMethod method = PsiConsultantImpl.findMethod(element); 48 | if (method != null && (PsiConsultantImpl.hasAnnotation(method, CLASS_INJECT) 49 | || PsiConsultantImpl.hasAnnotation(method, CLASS_PROVIDES))) { 50 | for (PsiParameter parameter : method.getParameterList().getParameters()) { 51 | PsiClass parameterClass = PsiConsultantImpl.checkForLazyOrProvider(parameter); 52 | if (parameterClass.equals(returnType) && PsiConsultantImpl.hasQuailifierAnnotations( 53 | parameter, qualifierAnnotations) 54 | && PsiConsultantImpl.hasTypeParameters(parameter, typeParameters)) { 55 | return true; 56 | } 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | } 63 | 64 | /** 65 | * Construct with a PsiParameter from an @Inject constructor and then use this to ensure the 66 | * usage fits. 67 | */ 68 | public class ConstructorParameterInjectDecider extends IsAProviderDecider { 69 | public ConstructorParameterInjectDecider(PsiParameter psiParameter) { 70 | super(psiParameter); 71 | } 72 | } 73 | 74 | public class CollectionElementParameterInjectDecider extends IsAProviderDecider { 75 | public CollectionElementParameterInjectDecider(PsiElement psiParameter) { 76 | super(psiParameter); 77 | } 78 | 79 | @Override public boolean shouldShow(UsageTarget target, Usage usage) { 80 | PsiElement element = ((UsageInfo2UsageAdapter) usage).getElement(); 81 | PsiMethod psimethod = PsiConsultantImpl.findMethod(element); 82 | 83 | PsiAnnotationMemberValue attribValue = PsiConsultantImpl 84 | .findTypeAttributeOfProvidesAnnotation(psimethod); 85 | 86 | // Is it a @Provides method? 87 | return psimethod != null 88 | // Ensure it has an @Provides. 89 | && PsiConsultantImpl.hasAnnotation(psimethod, CLASS_PROVIDES) 90 | // Check for Qualifier annotations. 91 | && PsiConsultantImpl.hasQuailifierAnnotations(psimethod, qualifierAnnotations) 92 | // Right return type. 93 | && PsiConsultantImpl.getReturnClassFromMethod(psimethod, false) 94 | .getName() 95 | .equals(target.getName()) 96 | // Right type parameters. 97 | && PsiConsultantImpl.hasTypeParameters(psimethod, typeParameters) 98 | // @Provides(type=SET) 99 | && attribValue != null 100 | && attribValue.textMatches(SET_TYPE); 101 | } 102 | } 103 | 104 | /** 105 | * Construct with a PsiField annotated w/ @Inject and then use this to ensure the 106 | * usage fits. 107 | */ 108 | public class FieldInjectDecider extends IsAProviderDecider { 109 | public FieldInjectDecider(PsiField psiField) { 110 | super(psiField); 111 | } 112 | } 113 | 114 | class IsAProviderDecider implements Decider { 115 | protected final Set qualifierAnnotations; 116 | protected final List typeParameters; 117 | 118 | public IsAProviderDecider(PsiElement element) { 119 | this.qualifierAnnotations = PsiConsultantImpl.getQualifierAnnotations(element); 120 | this.typeParameters = PsiConsultantImpl.getTypeParameters(element); 121 | } 122 | 123 | @Override public boolean shouldShow(UsageTarget target, Usage usage) { 124 | PsiElement element = ((UsageInfo2UsageAdapter) usage).getElement(); 125 | 126 | PsiMethod psimethod = PsiConsultantImpl.findMethod(element); 127 | 128 | // For constructors annotated w/ @Inject, this is searched first before committing to the usage search. 129 | 130 | // Is it a @Provides method? 131 | return psimethod != null 132 | // Ensure it has an @Provides. 133 | && PsiConsultantImpl.hasAnnotation(psimethod, CLASS_PROVIDES) 134 | 135 | // Check for Qualifier annotations. 136 | && PsiConsultantImpl.hasQuailifierAnnotations(psimethod, qualifierAnnotations) 137 | 138 | // Right return type. 139 | && PsiConsultantImpl.getReturnClassFromMethod(psimethod, false) 140 | .getName() 141 | .equals(target.getName()) 142 | 143 | // Right type parameters. 144 | && PsiConsultantImpl.hasTypeParameters(psimethod, typeParameters); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/ShowUsagesTableCellRenderer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2000-2009 JetBrains s.r.o. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.squareup.ideaplugin.dagger; 18 | 19 | import com.intellij.openapi.project.Project; 20 | import com.intellij.openapi.vfs.VirtualFile; 21 | import com.intellij.psi.PsiFile; 22 | import com.intellij.psi.PsiManager; 23 | import com.intellij.ui.FileColorManager; 24 | import com.intellij.ui.SimpleColoredComponent; 25 | import com.intellij.ui.SimpleTextAttributes; 26 | import com.intellij.usages.TextChunk; 27 | import com.intellij.usages.Usage; 28 | import com.intellij.usages.UsageGroup; 29 | import com.intellij.usages.UsagePresentation; 30 | import com.intellij.usages.impl.GroupNode; 31 | import com.intellij.usages.impl.UsageNode; 32 | import com.intellij.usages.impl.UsageViewImpl; 33 | import com.intellij.usages.rules.UsageInFile; 34 | import com.intellij.util.ui.EmptyIcon; 35 | import com.intellij.util.ui.UIUtil; 36 | import java.awt.BorderLayout; 37 | import java.awt.Color; 38 | import java.awt.Component; 39 | import java.awt.FlowLayout; 40 | import java.awt.Insets; 41 | import javax.swing.Icon; 42 | import javax.swing.JLabel; 43 | import javax.swing.JPanel; 44 | import javax.swing.JTable; 45 | import javax.swing.SwingConstants; 46 | import javax.swing.table.TableCellRenderer; 47 | import org.jetbrains.annotations.NotNull; 48 | 49 | /** @author cdr */ 50 | class ShowUsagesTableCellRenderer implements TableCellRenderer { 51 | 52 | private final UsageViewImpl myUsageView; 53 | 54 | ShowUsagesTableCellRenderer(@NotNull UsageViewImpl usageView) { 55 | myUsageView = usageView; 56 | } 57 | 58 | @Override 59 | public Component getTableCellRendererComponent(JTable list, Object value, boolean isSelected, 60 | boolean hasFocus, int row, int column) { 61 | UsageNode usageNode = value instanceof UsageNode ? (UsageNode) value : null; 62 | 63 | Usage usage = usageNode == null ? null : usageNode.getUsage(); 64 | 65 | JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); 66 | Color fileBgColor = getBackgroundColor(isSelected, usage); 67 | final Color bg = UIUtil.getListSelectionBackground(); 68 | final Color fg = UIUtil.getListSelectionForeground(); 69 | panel.setBackground(isSelected ? bg : fileBgColor == null ? list.getBackground() : fileBgColor); 70 | panel.setForeground(isSelected ? fg : list.getForeground()); 71 | 72 | if (usage == null || usageNode instanceof ShowUsagesAction.StringNode) { 73 | panel.setLayout(new BorderLayout()); 74 | if (column == 0) { 75 | panel.add( 76 | new JLabel("" + value + "", SwingConstants.CENTER)); 77 | } 78 | return panel; 79 | } 80 | 81 | SimpleColoredComponent textChunks = new SimpleColoredComponent(); 82 | textChunks.setIpad(new Insets(0, 0, 0, 0)); 83 | textChunks.setBorder(null); 84 | 85 | if (column == 0) { 86 | GroupNode parent = (GroupNode) usageNode.getParent(); 87 | appendGroupText(parent, panel, fileBgColor); 88 | if (usage == ShowUsagesAction.MORE_USAGES_SEPARATOR) { 89 | textChunks.append("...<"); 90 | textChunks.append("more usages", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); 91 | textChunks.append(">..."); 92 | } 93 | } else if (usage != ShowUsagesAction.MORE_USAGES_SEPARATOR) { 94 | UsagePresentation presentation = usage.getPresentation(); 95 | TextChunk[] text = presentation.getText(); 96 | 97 | if (column == 1) { 98 | final Icon icon = presentation.getIcon(); 99 | textChunks.setIcon(icon == null ? EmptyIcon.ICON_16 : icon); 100 | if (text.length != 0) { 101 | SimpleTextAttributes attributes = 102 | isSelected ? new SimpleTextAttributes(bg, fg, fg, SimpleTextAttributes.STYLE_ITALIC) 103 | : deriveAttributesWithColor(text[0].getSimpleAttributesIgnoreBackground(), 104 | fileBgColor); 105 | textChunks.append(text[0].getText(), attributes); 106 | } 107 | } else if (column == 2) { 108 | for (int i = 1; i < text.length; i++) { 109 | TextChunk textChunk = text[i]; 110 | final SimpleTextAttributes attrs = textChunk.getSimpleAttributesIgnoreBackground(); 111 | SimpleTextAttributes attributes = 112 | isSelected ? new SimpleTextAttributes(bg, fg, fg, attrs.getStyle()) 113 | : deriveAttributesWithColor(attrs, fileBgColor); 114 | textChunks.append(textChunk.getText(), attributes); 115 | } 116 | } else { 117 | assert false : column; 118 | } 119 | } 120 | panel.add(textChunks); 121 | return panel; 122 | } 123 | 124 | private static SimpleTextAttributes deriveAttributesWithColor(SimpleTextAttributes attributes, 125 | Color fileBgColor) { 126 | if (fileBgColor != null) { 127 | attributes = attributes.derive(-1, null, fileBgColor, null); 128 | } 129 | return attributes; 130 | } 131 | 132 | private Color getBackgroundColor(boolean isSelected, Usage usage) { 133 | Color fileBgColor = null; 134 | if (isSelected) { 135 | fileBgColor = UIUtil.getListSelectionBackground(); 136 | } else { 137 | VirtualFile virtualFile = 138 | usage instanceof UsageInFile ? ((UsageInFile) usage).getFile() : null; 139 | if (virtualFile != null) { 140 | Project project = myUsageView.getProject(); 141 | PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); 142 | if (psiFile != null && psiFile.isValid()) { 143 | final Color color = FileColorManager.getInstance(project).getRendererBackground(psiFile); 144 | if (color != null) fileBgColor = color; 145 | } 146 | } 147 | } 148 | return fileBgColor; 149 | } 150 | 151 | private void appendGroupText(final GroupNode node, JPanel panel, Color fileBgColor) { 152 | UsageGroup group = node == null ? null : node.getGroup(); 153 | if (group == null) return; 154 | GroupNode parentGroup = (GroupNode) node.getParent(); 155 | appendGroupText(parentGroup, panel, fileBgColor); 156 | if (node.canNavigateToSource()) { 157 | SimpleColoredComponent renderer = new SimpleColoredComponent(); 158 | 159 | renderer.setIcon(group.getIcon(false)); 160 | SimpleTextAttributes attributes = 161 | deriveAttributesWithColor(SimpleTextAttributes.REGULAR_ATTRIBUTES, fileBgColor); 162 | renderer.append(group.getText(myUsageView), attributes); 163 | renderer.append(" ", attributes); 164 | renderer.setIpad(new Insets(0, 0, 0, 0)); 165 | renderer.setBorder(null); 166 | panel.add(renderer); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 25 | 26 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 69 | 70 | 77 | 78 | 79 | 97 | 104 | 105 | 106 | 117 | 118 | 131 | 132 | 133 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | localhost 154 | 5050 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/PsiConsultantImpl.java: -------------------------------------------------------------------------------- 1 | package com.squareup.ideaplugin.dagger; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.psi.JavaPsiFacade; 5 | import com.intellij.psi.PsiAnnotation; 6 | import com.intellij.psi.PsiAnnotationMemberValue; 7 | import com.intellij.psi.PsiClass; 8 | import com.intellij.psi.PsiClassType; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.PsiElementFactory; 11 | import com.intellij.psi.PsiField; 12 | import com.intellij.psi.PsiMethod; 13 | import com.intellij.psi.PsiModifierList; 14 | import com.intellij.psi.PsiModifierListOwner; 15 | import com.intellij.psi.PsiParameter; 16 | import com.intellij.psi.PsiType; 17 | import com.intellij.psi.PsiVariable; 18 | import com.intellij.psi.search.GlobalSearchScope; 19 | import java.util.ArrayList; 20 | import java.util.Collection; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Set; 24 | 25 | import static com.squareup.ideaplugin.dagger.DaggerConstants.ATTRIBUTE_TYPE; 26 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_LAZY; 27 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_PROVIDER; 28 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_PROVIDES; 29 | import static com.squareup.ideaplugin.dagger.DaggerConstants.CLASS_QUALIFIER; 30 | import static com.squareup.ideaplugin.dagger.DaggerConstants.MAP_TYPE; 31 | import static com.squareup.ideaplugin.dagger.DaggerConstants.SET_TYPE; 32 | 33 | public class PsiConsultantImpl { 34 | 35 | public static boolean hasAnnotation(PsiElement element, String annotationName) { 36 | return findAnnotation(element, annotationName) != null; 37 | } 38 | 39 | static PsiAnnotation findAnnotation(PsiElement element, String annotationName) { 40 | if (element instanceof PsiModifierListOwner) { 41 | PsiModifierListOwner listOwner = (PsiModifierListOwner) element; 42 | PsiModifierList modifierList = listOwner.getModifierList(); 43 | 44 | if (modifierList != null) { 45 | for (PsiAnnotation psiAnnotation : modifierList.getAnnotations()) { 46 | if (annotationName.equals(psiAnnotation.getQualifiedName())) { 47 | return psiAnnotation; 48 | } 49 | } 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | public static Set getQualifierAnnotations(PsiElement element) { 56 | Set qualifiedClasses = new HashSet(); 57 | 58 | if (element instanceof PsiModifierListOwner) { 59 | PsiModifierListOwner listOwner = (PsiModifierListOwner) element; 60 | PsiModifierList modifierList = listOwner.getModifierList(); 61 | 62 | if (modifierList != null) { 63 | for (PsiAnnotation psiAnnotation : modifierList.getAnnotations()) { 64 | if (psiAnnotation != null && psiAnnotation.getQualifiedName() != null) { 65 | JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(element.getProject()); 66 | PsiClass psiClass = psiFacade.findClass(psiAnnotation.getQualifiedName(), 67 | GlobalSearchScope.projectScope(element.getProject())); 68 | 69 | if (hasAnnotation(psiClass, CLASS_QUALIFIER)) { 70 | qualifiedClasses.add(psiAnnotation.getQualifiedName()); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | return qualifiedClasses; 78 | } 79 | 80 | public static boolean hasQuailifierAnnotations(PsiElement element, Set types) { 81 | Set actualAnnotations = getQualifierAnnotations(element); 82 | return actualAnnotations.equals(types); 83 | } 84 | 85 | public static PsiMethod findMethod(PsiElement element) { 86 | if (element == null) { 87 | return null; 88 | } else if (element instanceof PsiMethod) { 89 | return (PsiMethod) element; 90 | } else { 91 | return findMethod(element.getParent()); 92 | } 93 | } 94 | 95 | public static PsiClass getClass(PsiElement psiElement) { 96 | if (psiElement instanceof PsiVariable) { 97 | PsiVariable variable = (PsiVariable) psiElement; 98 | return getClass(variable.getType()); 99 | } else if (psiElement instanceof PsiMethod) { 100 | return ((PsiMethod) psiElement).getContainingClass(); 101 | } 102 | 103 | return null; 104 | } 105 | 106 | public static PsiClass getClass(PsiType psiType) { 107 | if (psiType instanceof PsiClassType) { 108 | return ((PsiClassType) psiType).resolve(); 109 | } 110 | return null; 111 | } 112 | 113 | public static PsiAnnotationMemberValue findTypeAttributeOfProvidesAnnotation( 114 | PsiElement element ) { 115 | PsiAnnotation annotation = findAnnotation(element, CLASS_PROVIDES); 116 | if (annotation != null) { 117 | return annotation.findAttributeValue(ATTRIBUTE_TYPE); 118 | } 119 | return null; 120 | } 121 | 122 | /** 123 | * Return the appropriate return class for a given method element. 124 | * 125 | * @param psiMethod the method to get the return class from. 126 | * @param expandType set this to true if return types annotated with @Provides(type=?) 127 | * should be expanded to the appropriate collection type. 128 | * @return the appropriate return class for the provided method element. 129 | */ 130 | public static PsiClass getReturnClassFromMethod(PsiMethod psiMethod, boolean expandType) { 131 | if (psiMethod.isConstructor()) { 132 | return psiMethod.getContainingClass(); 133 | } 134 | 135 | PsiClassType returnType = ((PsiClassType) psiMethod.getReturnType()); 136 | if (returnType != null) { 137 | // Check if has @Provides annotation and specified type 138 | if (expandType) { 139 | PsiAnnotationMemberValue attribValue = findTypeAttributeOfProvidesAnnotation(psiMethod); 140 | if (attribValue != null) { 141 | if (attribValue.textMatches(SET_TYPE)) { 142 | String typeName = "java.util.Set<" + returnType.getCanonicalText() + ">"; 143 | returnType = 144 | ((PsiClassType) PsiElementFactory.SERVICE.getInstance(psiMethod.getProject()) 145 | .createTypeFromText(typeName, psiMethod)); 146 | } else if (attribValue.textMatches(MAP_TYPE)) { 147 | // TODO(radford): Supporting map will require fetching the key type and also validating 148 | // the qualifier for the provided key. 149 | // 150 | // String typeName = "java.util.Map"; 151 | // returnType = ((PsiClassType) PsiElementFactory.SERVICE.getInstance(psiMethod.getProject()) 152 | // .createTypeFromText(typeName, psiMethod)); 153 | } 154 | } 155 | } 156 | 157 | return returnType.resolve(); 158 | } 159 | return null; 160 | } 161 | 162 | public static PsiField findField(PsiElement element) { 163 | if (element == null) { 164 | return null; 165 | } else if (element instanceof PsiField) { 166 | return (PsiField) element; 167 | } else { 168 | return findField(element.getParent()); 169 | } 170 | } 171 | 172 | public static PsiClass checkForLazyOrProvider(PsiField psiField) { 173 | PsiClass wrapperClass = PsiConsultantImpl.getClass(psiField); 174 | 175 | PsiType psiFieldType = psiField.getType(); 176 | if (!(psiFieldType instanceof PsiClassType)) { 177 | return wrapperClass; 178 | } 179 | 180 | return getPsiClass(wrapperClass, psiFieldType); 181 | } 182 | 183 | public static PsiClass checkForLazyOrProvider(PsiParameter psiParameter) { 184 | PsiClass wrapperClass = PsiConsultantImpl.getClass(psiParameter); 185 | 186 | PsiType psiParameterType = psiParameter.getType(); 187 | if (!(psiParameterType instanceof PsiClassType)) { 188 | return wrapperClass; 189 | } 190 | 191 | return getPsiClass(wrapperClass, psiParameterType); 192 | } 193 | 194 | private static PsiClass getPsiClass(PsiClass wrapperClass, PsiType psiFieldType) { 195 | PsiClassType psiClassType = (PsiClassType) psiFieldType; 196 | 197 | PsiClassType.ClassResolveResult classResolveResult = psiClassType.resolveGenerics(); 198 | PsiClass outerClass = classResolveResult.getElement(); 199 | 200 | // If Lazy or Provider, extract Foo as the interesting type. 201 | if (PsiConsultantImpl.isLazyOrProvider(outerClass)) { 202 | PsiType genericType = extractFirstTypeParameter(psiClassType); 203 | // Convert genericType to its PsiClass and store in psiClass 204 | wrapperClass = getClass(genericType); 205 | } 206 | 207 | return wrapperClass; 208 | } 209 | 210 | public static boolean hasTypeParameters(PsiElement psiElement, List typeParameters) { 211 | List actualTypeParameters = getTypeParameters(psiElement); 212 | return actualTypeParameters.equals(typeParameters); 213 | } 214 | 215 | public static List getTypeParameters(PsiElement psiElement) { 216 | PsiClassType psiClassType = getPsiClassType(psiElement); 217 | if (psiClassType == null) { 218 | return new ArrayList(); 219 | } 220 | 221 | // Check if @Provides(type=?) pattern (annotation with specified type). 222 | PsiAnnotationMemberValue attribValue = findTypeAttributeOfProvidesAnnotation(psiElement); 223 | if (attribValue != null) { 224 | if (attribValue.textMatches(SET_TYPE)) { 225 | // type = SET. Transform the type parameter to the element type. 226 | ArrayList result = new ArrayList(); 227 | result.add(psiClassType); 228 | return result; 229 | } else if (attribValue.textMatches(MAP_TYPE)) { 230 | // TODO(radford): Need to figure out key type for maps. 231 | // type = SET or type = MAP. Transform the type parameter to the element type. 232 | //ArrayList result = new ArrayList(); 233 | //result.add(psiKeyType): 234 | //result.add(psiClassType); 235 | //return result; 236 | } 237 | } 238 | 239 | if (PsiConsultantImpl.isLazyOrProvider(getClass(psiClassType))) { 240 | psiClassType = extractFirstTypeParameter(psiClassType); 241 | } 242 | 243 | Collection typeParameters = 244 | psiClassType.resolveGenerics().getSubstitutor().getSubstitutionMap().values(); 245 | return new ArrayList(typeParameters); 246 | } 247 | 248 | private static PsiClassType getPsiClassType(PsiElement psiElement) { 249 | if (psiElement instanceof PsiVariable) { 250 | return (PsiClassType) ((PsiVariable) psiElement).getType(); 251 | } else if (psiElement instanceof PsiMethod) { 252 | return (PsiClassType) ((PsiMethod) psiElement).getReturnType(); 253 | } 254 | return null; 255 | } 256 | 257 | private static boolean isLazyOrProvider(PsiClass psiClass) { 258 | if (psiClass == null) { 259 | return false; 260 | } 261 | Project project = psiClass.getProject(); 262 | JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project); 263 | GlobalSearchScope globalSearchScope = GlobalSearchScope.allScope(project); 264 | 265 | PsiClass lazyClass = javaPsiFacade.findClass(CLASS_LAZY, globalSearchScope); 266 | PsiClass providerClass = javaPsiFacade.findClass(CLASS_PROVIDER, globalSearchScope); 267 | 268 | return psiClass.equals(lazyClass) || psiClass.equals(providerClass); 269 | } 270 | 271 | private static PsiClassType extractFirstTypeParameter(PsiClassType psiClassType) { 272 | return (PsiClassType) psiClassType.resolveGenerics().getSubstitutor() 273 | .getSubstitutionMap().values().iterator().next(); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/com/squareup/ideaplugin/dagger/ShowUsagesAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2000-2012 JetBrains s.r.o. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.squareup.ideaplugin.dagger; 18 | 19 | import com.intellij.CommonBundle; 20 | import com.intellij.codeInsight.hint.HintManager; 21 | import com.intellij.codeInsight.hint.HintUtil; 22 | import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction; 23 | import com.intellij.featureStatistics.FeatureUsageTracker; 24 | import com.intellij.find.FindBundle; 25 | import com.intellij.find.FindManager; 26 | import com.intellij.find.actions.FindUsagesInFileAction; 27 | import com.intellij.find.actions.UsageListCellRenderer; 28 | import com.intellij.find.findUsages.AbstractFindUsagesDialog; 29 | import com.intellij.find.findUsages.FindUsagesHandler; 30 | import com.intellij.find.findUsages.FindUsagesManager; 31 | import com.intellij.find.findUsages.FindUsagesOptions; 32 | import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter; 33 | import com.intellij.find.impl.FindManagerImpl; 34 | import com.intellij.icons.AllIcons; 35 | import com.intellij.ide.DataManager; 36 | import com.intellij.ide.util.gotoByName.ModelDiff; 37 | import com.intellij.openapi.Disposable; 38 | import com.intellij.openapi.actionSystem.ActionManager; 39 | import com.intellij.openapi.actionSystem.ActionPlaces; 40 | import com.intellij.openapi.actionSystem.ActionToolbar; 41 | import com.intellij.openapi.actionSystem.AnAction; 42 | import com.intellij.openapi.actionSystem.AnActionEvent; 43 | import com.intellij.openapi.actionSystem.CustomShortcutSet; 44 | import com.intellij.openapi.actionSystem.DataProvider; 45 | import com.intellij.openapi.actionSystem.DefaultActionGroup; 46 | import com.intellij.openapi.actionSystem.IdeActions; 47 | import com.intellij.openapi.actionSystem.KeyboardShortcut; 48 | import com.intellij.openapi.actionSystem.LangDataKeys; 49 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 50 | import com.intellij.openapi.actionSystem.PopupAction; 51 | import com.intellij.openapi.application.ApplicationManager; 52 | import com.intellij.openapi.editor.Editor; 53 | import com.intellij.openapi.fileEditor.FileEditor; 54 | import com.intellij.openapi.fileEditor.FileEditorLocation; 55 | import com.intellij.openapi.fileEditor.TextEditor; 56 | import com.intellij.openapi.keymap.KeymapUtil; 57 | import com.intellij.openapi.progress.ProgressIndicator; 58 | import com.intellij.openapi.project.DumbAwareAction; 59 | import com.intellij.openapi.project.Project; 60 | import com.intellij.openapi.ui.Messages; 61 | import com.intellij.openapi.ui.popup.JBPopup; 62 | import com.intellij.openapi.ui.popup.JBPopupFactory; 63 | import com.intellij.openapi.ui.popup.PopupChooserBuilder; 64 | import com.intellij.openapi.util.Comparing; 65 | import com.intellij.openapi.util.Condition; 66 | import com.intellij.openapi.util.Disposer; 67 | import com.intellij.openapi.vfs.VirtualFile; 68 | import com.intellij.openapi.wm.IdeFocusManager; 69 | import com.intellij.psi.PsiDocumentManager; 70 | import com.intellij.psi.PsiElement; 71 | import com.intellij.psi.search.GlobalSearchScope; 72 | import com.intellij.psi.search.ProjectScope; 73 | import com.intellij.psi.search.PsiElementProcessor; 74 | import com.intellij.psi.search.SearchScope; 75 | import com.intellij.ui.ActiveComponent; 76 | import com.intellij.ui.InplaceButton; 77 | import com.intellij.ui.ScreenUtil; 78 | import com.intellij.ui.SpeedSearchBase; 79 | import com.intellij.ui.SpeedSearchComparator; 80 | import com.intellij.ui.TableScrollingUtil; 81 | import com.intellij.ui.TableUtil; 82 | import com.intellij.ui.awt.RelativePoint; 83 | import com.intellij.ui.popup.AbstractPopup; 84 | import com.intellij.ui.table.JBTable; 85 | import com.intellij.usageView.UsageViewBundle; 86 | import com.intellij.usages.PsiElementUsageTarget; 87 | import com.intellij.usages.Usage; 88 | import com.intellij.usages.UsageInfo2UsageAdapter; 89 | import com.intellij.usages.UsageInfoToUsageConverter; 90 | import com.intellij.usages.UsageTarget; 91 | import com.intellij.usages.UsageToPsiElementProvider; 92 | import com.intellij.usages.UsageView; 93 | import com.intellij.usages.UsageViewManager; 94 | import com.intellij.usages.UsageViewPresentation; 95 | import com.intellij.usages.UsageViewSettings; 96 | import com.intellij.usages.impl.GroupNode; 97 | import com.intellij.usages.impl.NullUsage; 98 | import com.intellij.usages.impl.UsageGroupingRuleProviderImpl; 99 | import com.intellij.usages.impl.UsageNode; 100 | import com.intellij.usages.impl.UsageViewImpl; 101 | import com.intellij.usages.impl.UsageViewManagerImpl; 102 | import com.intellij.usages.impl.UsageViewTreeModelBuilder; 103 | import com.intellij.usages.rules.UsageFilteringRuleProvider; 104 | import com.intellij.util.Alarm; 105 | import com.intellij.util.PlatformIcons; 106 | import com.intellij.util.Processor; 107 | import com.intellij.util.concurrency.FutureResult; 108 | import com.intellij.util.messages.MessageBusConnection; 109 | import com.intellij.util.ui.AsyncProcessIcon; 110 | import com.intellij.util.ui.ColumnInfo; 111 | import com.intellij.util.ui.ListTableModel; 112 | import java.awt.BorderLayout; 113 | import java.awt.Component; 114 | import java.awt.Container; 115 | import java.awt.Dimension; 116 | import java.awt.Point; 117 | import java.awt.Rectangle; 118 | import java.awt.Window; 119 | import java.awt.event.ActionEvent; 120 | import java.awt.event.ActionListener; 121 | import java.util.ArrayList; 122 | import java.util.Collection; 123 | import java.util.Collections; 124 | import java.util.Comparator; 125 | import java.util.LinkedHashSet; 126 | import java.util.List; 127 | import java.util.Set; 128 | import java.util.concurrent.ExecutionException; 129 | import java.util.concurrent.atomic.AtomicBoolean; 130 | import javax.swing.JComponent; 131 | import javax.swing.JLabel; 132 | import javax.swing.JPanel; 133 | import javax.swing.JTable; 134 | import javax.swing.SwingUtilities; 135 | import javax.swing.table.TableColumn; 136 | import org.jetbrains.annotations.NonNls; 137 | import org.jetbrains.annotations.NotNull; 138 | import org.jetbrains.annotations.Nullable; 139 | 140 | public class ShowUsagesAction extends AnAction implements PopupAction { 141 | public interface Listener { 142 | void onFinished(boolean hasResults); 143 | 144 | // NULL listener that does nothing. 145 | Listener NULL = new Listener() { 146 | @Override public void onFinished(boolean hasResults) {} 147 | }; 148 | } 149 | 150 | private final boolean showSettingsDialogBefore; 151 | private static final int USAGES_PAGE_SIZE = 100; 152 | 153 | static final NullUsage MORE_USAGES_SEPARATOR = NullUsage.INSTANCE; 154 | private static final UsageNode MORE_USAGES_SEPARATOR_NODE = UsageViewImpl.NULL_NODE; 155 | 156 | private static final Comparator USAGE_NODE_COMPARATOR = new Comparator() { 157 | @Override 158 | public int compare(UsageNode c1, UsageNode c2) { 159 | if (c1 instanceof StringNode) return 1; 160 | if (c2 instanceof StringNode) return -1; 161 | Usage o1 = c1.getUsage(); 162 | Usage o2 = c2.getUsage(); 163 | if (o1 == MORE_USAGES_SEPARATOR) return 1; 164 | if (o2 == MORE_USAGES_SEPARATOR) return -1; 165 | 166 | VirtualFile v1 = UsageListCellRenderer.getVirtualFile(o1); 167 | VirtualFile v2 = UsageListCellRenderer.getVirtualFile(o2); 168 | String name1 = v1 == null ? null : v1.getName(); 169 | String name2 = v2 == null ? null : v2.getName(); 170 | int i = Comparing.compare(name1, name2); 171 | if (i != 0) return i; 172 | 173 | if (o1 instanceof Comparable && o2 instanceof Comparable) { 174 | return ((Comparable) o1).compareTo(o2); 175 | } 176 | 177 | FileEditorLocation loc1 = o1.getLocation(); 178 | FileEditorLocation loc2 = o2.getLocation(); 179 | return Comparing.compare(loc1, loc2); 180 | } 181 | }; 182 | private static final Runnable HIDE_HINTS_ACTION = new Runnable() { 183 | @Override 184 | public void run() { 185 | hideHints(); 186 | } 187 | }; 188 | @NotNull private final UsageViewSettings myUsageViewSettings; 189 | @Nullable private Runnable mySearchEverywhereRunnable; 190 | private Decider decider; 191 | private Listener listener; 192 | 193 | 194 | // used from plugin.xml 195 | @SuppressWarnings({ "UnusedDeclaration" }) 196 | public ShowUsagesAction(Decider decider) { 197 | this(false); 198 | this.decider = decider; 199 | listener = Listener.NULL; 200 | } 201 | 202 | private ShowUsagesAction(boolean showDialogBefore) { 203 | setInjectedContext(true); 204 | showSettingsDialogBefore = showDialogBefore; 205 | 206 | final UsageViewSettings usageViewSettings = UsageViewSettings.getInstance(); 207 | myUsageViewSettings = new UsageViewSettings(); 208 | myUsageViewSettings.loadState(usageViewSettings); 209 | myUsageViewSettings.GROUP_BY_FILE_STRUCTURE = false; 210 | myUsageViewSettings.GROUP_BY_MODULE = false; 211 | myUsageViewSettings.GROUP_BY_PACKAGE = false; 212 | myUsageViewSettings.GROUP_BY_USAGE_TYPE = false; 213 | myUsageViewSettings.GROUP_BY_SCOPE = false; 214 | } 215 | 216 | public void setListener(Listener listener) { 217 | this.listener = listener; 218 | } 219 | 220 | @Override 221 | public void actionPerformed(AnActionEvent e) { 222 | final Project project = e.getData(PlatformDataKeys.PROJECT); 223 | if (project == null) return; 224 | 225 | Runnable searchEverywhere = mySearchEverywhereRunnable; 226 | mySearchEverywhereRunnable = null; 227 | hideHints(); 228 | 229 | if (searchEverywhere != null) { 230 | searchEverywhere.run(); 231 | return; 232 | } 233 | 234 | final RelativePoint popupPosition = 235 | JBPopupFactory.getInstance().guessBestPopupLocation(e.getDataContext()); 236 | PsiDocumentManager.getInstance(project).commitAllDocuments(); 237 | FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.goto.usages"); 238 | 239 | UsageTarget[] usageTargets = e.getData(UsageView.USAGE_TARGETS_KEY); 240 | final Editor editor = e.getData(PlatformDataKeys.EDITOR); 241 | if (usageTargets == null) { 242 | chooseAmbiguousTargetAndPerform(project, editor, new PsiElementProcessor() { 243 | @Override 244 | public boolean execute(@NotNull final PsiElement element) { 245 | startFindUsages(element, popupPosition, editor, USAGES_PAGE_SIZE); 246 | return false; 247 | } 248 | }); 249 | } else { 250 | PsiElement element = ((PsiElementUsageTarget) usageTargets[0]).getElement(); 251 | if (element != null) { 252 | startFindUsages(element, popupPosition, editor, USAGES_PAGE_SIZE); 253 | } 254 | } 255 | } 256 | 257 | static void chooseAmbiguousTargetAndPerform(@NotNull final Project project, final Editor editor, 258 | @NotNull PsiElementProcessor processor) { 259 | if (editor == null) { 260 | Messages.showMessageDialog(project, FindBundle.message("find.no.usages.at.cursor.error"), 261 | CommonBundle.getErrorTitle(), Messages.getErrorIcon()); 262 | } else { 263 | int offset = editor.getCaretModel().getOffset(); 264 | boolean chosen = GotoDeclarationAction.chooseAmbiguousTarget(editor, offset, processor, 265 | FindBundle.message("find.usages.ambiguous.title", "crap"), null); 266 | if (!chosen) { 267 | ApplicationManager.getApplication().invokeLater(new Runnable() { 268 | @Override 269 | public void run() { 270 | if (editor.isDisposed() || !editor.getComponent().isShowing()) return; 271 | HintManager.getInstance() 272 | .showErrorHint(editor, FindBundle.message("find.no.usages.at.cursor.error")); 273 | } 274 | }, project.getDisposed()); 275 | } 276 | } 277 | } 278 | 279 | private static void hideHints() { 280 | HintManager.getInstance().hideHints(HintManager.HIDE_BY_ANY_KEY, false, false); 281 | } 282 | 283 | public void startFindUsages(@NotNull PsiElement element, @NotNull RelativePoint popupPosition, 284 | Editor editor, int maxUsages) { 285 | Project project = element.getProject(); 286 | FindUsagesManager findUsagesManager = 287 | ((FindManagerImpl) FindManager.getInstance(project)).getFindUsagesManager(); 288 | FindUsagesHandler handler = findUsagesManager.getNewFindUsagesHandler(element, false); 289 | if (handler == null) return; 290 | if (showSettingsDialogBefore) { 291 | showDialogAndFindUsages(handler, popupPosition, editor, maxUsages); 292 | return; 293 | } 294 | showElementUsages(handler, editor, popupPosition, maxUsages, getDefaultOptions(handler)); 295 | } 296 | 297 | @NotNull 298 | private static FindUsagesOptions getDefaultOptions(@NotNull FindUsagesHandler handler) { 299 | FindUsagesOptions options = 300 | handler.getFindUsagesOptions(DataManager.getInstance().getDataContext()); 301 | // by default, scope in FindUsagesOptions is copied from the FindSettings, but we need a default one 302 | options.searchScope = FindUsagesManager.getMaximalScope(handler); 303 | return options; 304 | } 305 | 306 | private void showElementUsages(@NotNull final FindUsagesHandler handler, final Editor editor, 307 | @NotNull final RelativePoint popupPosition, final int maxUsages, 308 | @NotNull final FindUsagesOptions options) { 309 | ApplicationManager.getApplication().assertIsDispatchThread(); 310 | final UsageViewSettings usageViewSettings = UsageViewSettings.getInstance(); 311 | final UsageViewSettings savedGlobalSettings = new UsageViewSettings(); 312 | 313 | savedGlobalSettings.loadState(usageViewSettings); 314 | usageViewSettings.loadState(myUsageViewSettings); 315 | 316 | final Project project = handler.getProject(); 317 | UsageViewManager manager = UsageViewManager.getInstance(project); 318 | FindUsagesManager findUsagesManager = 319 | ((FindManagerImpl) FindManager.getInstance(project)).getFindUsagesManager(); 320 | final UsageViewPresentation presentation = 321 | findUsagesManager.createPresentation(handler, options); 322 | presentation.setDetachedMode(true); 323 | final UsageViewImpl usageView = 324 | (UsageViewImpl) manager.createUsageView(UsageTarget.EMPTY_ARRAY, Usage.EMPTY_ARRAY, 325 | presentation, null); 326 | 327 | Disposer.register(usageView, new Disposable() { 328 | @Override 329 | public void dispose() { 330 | myUsageViewSettings.loadState(usageViewSettings); 331 | usageViewSettings.loadState(savedGlobalSettings); 332 | } 333 | }); 334 | 335 | final List usages = new ArrayList(); 336 | final Set visibleNodes = new LinkedHashSet(); 337 | UsageInfoToUsageConverter.TargetElementsDescriptor descriptor = 338 | new UsageInfoToUsageConverter.TargetElementsDescriptor(handler.getPrimaryElements(), 339 | handler.getSecondaryElements()); 340 | 341 | final MyTable table = new MyTable(); 342 | final AsyncProcessIcon processIcon = new AsyncProcessIcon("xxx"); 343 | boolean hadMoreSeparator = visibleNodes.remove(MORE_USAGES_SEPARATOR_NODE); 344 | if (hadMoreSeparator) { 345 | usages.add(MORE_USAGES_SEPARATOR); 346 | visibleNodes.add(MORE_USAGES_SEPARATOR_NODE); 347 | } 348 | 349 | addUsageNodes(usageView.getRoot(), usageView, new ArrayList()); 350 | 351 | TableScrollingUtil.installActions(table); 352 | 353 | final List data = collectData(usages, visibleNodes, usageView, presentation); 354 | setTableModel(table, usageView, data); 355 | 356 | SpeedSearchBase speedSearch = new MySpeedSearch(table); 357 | speedSearch.setComparator(new SpeedSearchComparator(false)); 358 | 359 | final JBPopup popup = 360 | createUsagePopup(usages, descriptor, visibleNodes, handler, editor, popupPosition, 361 | maxUsages, usageView, options, table, presentation, processIcon, hadMoreSeparator); 362 | 363 | Disposer.register(popup, usageView); 364 | 365 | // show popup only if find usages takes more than 300ms, otherwise it would flicker needlessly 366 | Alarm alarm = new Alarm(usageView); 367 | alarm.addRequest(new Runnable() { 368 | @Override 369 | public void run() { 370 | showPopupIfNeedTo(popup, popupPosition); 371 | } 372 | }, 300); 373 | 374 | final PingEDT pingEDT = new PingEDT("Rebuild popup in EDT", new Condition() { 375 | @Override 376 | public boolean value(Object o) { 377 | return popup.isDisposed(); 378 | } 379 | }, 100, new Runnable() { 380 | @Override 381 | public void run() { 382 | if (popup.isDisposed()) return; 383 | 384 | final List nodes = new ArrayList(); 385 | List copy; 386 | synchronized (usages) { 387 | // open up popup as soon as several usages 've been found 388 | if (!popup.isVisible() && (usages.size() <= 1 || !showPopupIfNeedTo(popup, 389 | popupPosition))) { 390 | return; 391 | } 392 | addUsageNodes(usageView.getRoot(), usageView, nodes); 393 | copy = new ArrayList(usages); 394 | } 395 | 396 | rebuildPopup(usageView, copy, nodes, table, popup, presentation, popupPosition, 397 | !processIcon.isDisposed()); 398 | } 399 | } 400 | ); 401 | 402 | final MessageBusConnection messageBusConnection = project.getMessageBus().connect(usageView); 403 | messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, new Runnable() { 404 | @Override 405 | public void run() { 406 | pingEDT.ping(); 407 | } 408 | }); 409 | 410 | Processor collect = new Processor() { 411 | private UsageTarget myUsageTarget = 412 | new PsiElement2UsageTargetAdapter(handler.getPsiElement()); 413 | 414 | @Override 415 | public boolean process(@NotNull Usage usage) { 416 | synchronized (usages) { 417 | if (visibleNodes.size() >= maxUsages) return false; 418 | if (UsageViewManager.isSelfUsage(usage, new UsageTarget[] { myUsageTarget })) { 419 | return true; 420 | } 421 | 422 | Usage usageToAdd = decider.shouldShow(myUsageTarget, usage) ? usage : null; 423 | if (usageToAdd == null) return true; 424 | 425 | UsageNode node = usageView.doAppendUsage(usageToAdd); 426 | usages.add(usageToAdd); 427 | 428 | if (node != null) { 429 | visibleNodes.add(node); 430 | boolean continueSearch = true; 431 | if (visibleNodes.size() == maxUsages) { 432 | visibleNodes.add(MORE_USAGES_SEPARATOR_NODE); 433 | usages.add(MORE_USAGES_SEPARATOR); 434 | continueSearch = false; 435 | } 436 | pingEDT.ping(); 437 | 438 | return continueSearch; 439 | } 440 | 441 | return true; 442 | } 443 | } 444 | }; 445 | 446 | final ProgressIndicator indicator = 447 | FindUsagesManager.startProcessUsages(handler, handler.getPrimaryElements(), 448 | handler.getSecondaryElements(), collect, options, new Runnable() { 449 | @Override 450 | public void run() { 451 | ApplicationManager.getApplication().invokeLater(new Runnable() { 452 | @Override 453 | public void run() { 454 | Disposer.dispose(processIcon); 455 | Container parent = processIcon.getParent(); 456 | parent.remove(processIcon); 457 | parent.repaint(); 458 | pingEDT.ping(); // repaint title 459 | synchronized (usages) { 460 | if (visibleNodes.isEmpty()) { 461 | if (usages.isEmpty()) { 462 | String text = UsageViewBundle.message("no.usages.found.in", 463 | searchScopePresentableName(options, project)); 464 | showHint(text, editor, popupPosition, handler, maxUsages, options); 465 | popup.cancel(); 466 | } else { 467 | // all usages filtered out 468 | } 469 | } else if (visibleNodes.size() == 1) { 470 | if (usages.size() == 1) { 471 | //the only usage 472 | Usage usage = visibleNodes.iterator().next().getUsage(); 473 | usage.navigate(true); 474 | //String message = UsageViewBundle.message("show.usages.only.usage", searchScopePresentableName(options, project)); 475 | //navigateAndHint(usage, message, handler, popupPosition, maxUsages, options); 476 | popup.cancel(); 477 | } else { 478 | assert usages.size() > 1 : usages; 479 | // usage view can filter usages down to one 480 | Usage visibleUsage = visibleNodes.iterator().next().getUsage(); 481 | if (areAllUsagesInOneLine(visibleUsage, usages)) { 482 | String hint = UsageViewBundle.message("all.usages.are.in.this.line", 483 | usages.size(), searchScopePresentableName(options, project)); 484 | navigateAndHint(visibleUsage, hint, handler, popupPosition, maxUsages, 485 | options); 486 | popup.cancel(); 487 | } 488 | } 489 | } else { 490 | String title = presentation.getTabText(); 491 | boolean shouldShowMoreSeparator = 492 | visibleNodes.contains(MORE_USAGES_SEPARATOR_NODE); 493 | String fullTitle = getFullTitle(usages, title, shouldShowMoreSeparator, 494 | visibleNodes.size() - (shouldShowMoreSeparator ? 1 : 0), false); 495 | ((AbstractPopup) popup).setCaption(fullTitle); 496 | } 497 | listener.onFinished(!visibleNodes.isEmpty()); 498 | } 499 | } 500 | }, project.getDisposed()); 501 | } 502 | } 503 | ); 504 | Disposer.register(popup, new Disposable() { 505 | @Override 506 | public void dispose() { 507 | indicator.cancel(); 508 | } 509 | }); 510 | } 511 | 512 | @NotNull 513 | private static UsageNode createStringNode(@NotNull final Object string) { 514 | return new StringNode(string); 515 | } 516 | 517 | private static class MyModel extends ListTableModel 518 | implements ModelDiff.Model { 519 | private MyModel(@NotNull List data, int cols) { 520 | super(cols(cols), data, 0); 521 | } 522 | 523 | @NotNull 524 | private static ColumnInfo[] cols(int cols) { 525 | ColumnInfo o = new ColumnInfo("") { 526 | @Nullable @Override 527 | public UsageNode valueOf(UsageNode node) { 528 | return node; 529 | } 530 | }; 531 | List> list = Collections.nCopies(cols, o); 532 | return list.toArray(new ColumnInfo[list.size()]); 533 | } 534 | 535 | @Override 536 | public void addToModel(int idx, Object element) { 537 | UsageNode node = 538 | element instanceof UsageNode ? (UsageNode) element : createStringNode(element); 539 | 540 | if (idx < getRowCount()) { 541 | insertRow(idx, node); 542 | } else { 543 | addRow(node); 544 | } 545 | } 546 | 547 | @Override 548 | public void removeRangeFromModel(int start, int end) { 549 | for (int i = end; i >= start; i--) { 550 | removeRow(i); 551 | } 552 | } 553 | } 554 | 555 | private static boolean showPopupIfNeedTo(@NotNull JBPopup popup, 556 | @NotNull RelativePoint popupPosition) { 557 | if (!popup.isDisposed() && !popup.isVisible()) { 558 | popup.show(popupPosition); 559 | return true; 560 | } else { 561 | return false; 562 | } 563 | } 564 | 565 | private void showHint(@NotNull String text, @Nullable final Editor editor, 566 | @NotNull final RelativePoint popupPosition, @NotNull FindUsagesHandler handler, int maxUsages, 567 | @NotNull FindUsagesOptions options) { 568 | JComponent label = 569 | createHintComponent(text, handler, popupPosition, editor, HIDE_HINTS_ACTION, maxUsages, 570 | options); 571 | 572 | HintManager.getInstance().showHint(label, popupPosition, HintManager.HIDE_BY_ANY_KEY | 573 | HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING, 0); 574 | } 575 | 576 | @NotNull 577 | private JComponent createHintComponent(@NotNull String text, 578 | @NotNull final FindUsagesHandler handler, @NotNull final RelativePoint popupPosition, 579 | final Editor editor, @NotNull final Runnable cancelAction, final int maxUsages, 580 | @NotNull final FindUsagesOptions options) { 581 | JComponent label = 582 | HintUtil.createInformationLabel(suggestSecondInvocation(options, handler, text + " ")); 583 | InplaceButton button = 584 | createSettingsButton(handler, popupPosition, editor, maxUsages, cancelAction); 585 | 586 | JPanel panel = new JPanel(new BorderLayout()) { 587 | @Override 588 | public void addNotify() { 589 | mySearchEverywhereRunnable = new Runnable() { 590 | @Override 591 | public void run() { 592 | searchEverywhere(options, handler, editor, popupPosition, maxUsages); 593 | } 594 | }; 595 | super.addNotify(); 596 | } 597 | 598 | @Override 599 | public void removeNotify() { 600 | mySearchEverywhereRunnable = null; 601 | super.removeNotify(); 602 | } 603 | }; 604 | button.setBackground(label.getBackground()); 605 | panel.setBackground(label.getBackground()); 606 | label.setOpaque(false); 607 | label.setBorder(null); 608 | panel.setBorder(HintUtil.createHintBorder()); 609 | panel.add(label, BorderLayout.CENTER); 610 | panel.add(button, BorderLayout.EAST); 611 | return panel; 612 | } 613 | 614 | @NotNull 615 | private InplaceButton createSettingsButton(@NotNull final FindUsagesHandler handler, 616 | @NotNull final RelativePoint popupPosition, final Editor editor, final int maxUsages, 617 | @NotNull final Runnable cancelAction) { 618 | String shortcutText = ""; 619 | KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut(); 620 | if (shortcut != null) { 621 | shortcutText = "(" + KeymapUtil.getShortcutText(shortcut) + ")"; 622 | } 623 | return new InplaceButton("Settings..." + shortcutText, AllIcons.General.Settings, 624 | new ActionListener() { 625 | @Override 626 | public void actionPerformed(ActionEvent e) { 627 | SwingUtilities.invokeLater(new Runnable() { 628 | @Override 629 | public void run() { 630 | showDialogAndFindUsages(handler, popupPosition, editor, maxUsages); 631 | } 632 | }); 633 | cancelAction.run(); 634 | } 635 | } 636 | ); 637 | } 638 | 639 | private void showDialogAndFindUsages(@NotNull FindUsagesHandler handler, 640 | @NotNull RelativePoint popupPosition, Editor editor, int maxUsages) { 641 | AbstractFindUsagesDialog dialog = handler.getFindUsagesDialog(false, false, false); 642 | dialog.show(); 643 | if (dialog.isOK()) { 644 | dialog.calcFindUsagesOptions(); 645 | showElementUsages(handler, editor, popupPosition, maxUsages, getDefaultOptions(handler)); 646 | } 647 | } 648 | 649 | private static String searchScopePresentableName(@NotNull FindUsagesOptions options, 650 | @NotNull Project project) { 651 | return notNullizeScope(options, project).getDisplayName(); 652 | } 653 | 654 | @NotNull 655 | private static SearchScope notNullizeScope(@NotNull FindUsagesOptions options, 656 | @NotNull Project project) { 657 | SearchScope scope = options.searchScope; 658 | if (scope == null) return ProjectScope.getAllScope(project); 659 | return scope; 660 | } 661 | 662 | @NotNull 663 | private JBPopup createUsagePopup(@NotNull final List usages, 664 | @NotNull final UsageInfoToUsageConverter.TargetElementsDescriptor descriptor, 665 | @NotNull Set visibleNodes, @NotNull final FindUsagesHandler handler, 666 | final Editor editor, @NotNull final RelativePoint popupPosition, final int maxUsages, 667 | @NotNull final UsageViewImpl usageView, @NotNull final FindUsagesOptions options, 668 | @NotNull final JTable table, @NotNull final UsageViewPresentation presentation, 669 | @NotNull final AsyncProcessIcon processIcon, boolean hadMoreSeparator) { 670 | table.setRowHeight(PlatformIcons.CLASS_ICON.getIconHeight() + 2); 671 | table.setShowGrid(false); 672 | table.setShowVerticalLines(false); 673 | table.setShowHorizontalLines(false); 674 | table.setTableHeader(null); 675 | table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); 676 | table.setIntercellSpacing(new Dimension(0, 0)); 677 | 678 | PopupChooserBuilder builder = new PopupChooserBuilder(table); 679 | final String title = presentation.getTabText(); 680 | if (title != null) { 681 | String result = getFullTitle(usages, title, hadMoreSeparator, visibleNodes.size() - 1, true); 682 | builder.setTitle(result); 683 | builder.setAdText(getSecondInvocationTitle(options, handler)); 684 | } 685 | 686 | builder.setMovable(true).setResizable(true); 687 | builder.setItemChoosenCallback(new Runnable() { 688 | @Override 689 | public void run() { 690 | int[] selected = table.getSelectedRows(); 691 | for (int i : selected) { 692 | Object value = table.getValueAt(i, 0); 693 | if (value instanceof UsageNode) { 694 | Usage usage = ((UsageNode) value).getUsage(); 695 | if (usage == MORE_USAGES_SEPARATOR) { 696 | appendMoreUsages(editor, popupPosition, handler, maxUsages); 697 | return; 698 | } 699 | navigateAndHint(usage, null, handler, popupPosition, maxUsages, options); 700 | } 701 | } 702 | } 703 | }); 704 | final JBPopup[] popup = new JBPopup[1]; 705 | 706 | KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut(); 707 | if (shortcut != null) { 708 | new DumbAwareAction() { 709 | @Override 710 | public void actionPerformed(AnActionEvent e) { 711 | popup[0].cancel(); 712 | showDialogAndFindUsages(handler, popupPosition, editor, maxUsages); 713 | } 714 | }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table); 715 | } 716 | shortcut = getShowUsagesShortcut(); 717 | if (shortcut != null) { 718 | new DumbAwareAction() { 719 | @Override 720 | public void actionPerformed(AnActionEvent e) { 721 | popup[0].cancel(); 722 | searchEverywhere(options, handler, editor, popupPosition, maxUsages); 723 | } 724 | }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table); 725 | } 726 | 727 | InplaceButton settingsButton = 728 | createSettingsButton(handler, popupPosition, editor, maxUsages, new Runnable() { 729 | @Override 730 | public void run() { 731 | popup[0].cancel(); 732 | } 733 | }); 734 | 735 | ActiveComponent spinningProgress = new ActiveComponent() { 736 | @Override 737 | public void setActive(boolean active) { 738 | } 739 | 740 | @Override 741 | public JComponent getComponent() { 742 | return processIcon; 743 | } 744 | }; 745 | builder.setCommandButton(new CompositeActiveComponent(spinningProgress, settingsButton)); 746 | 747 | DefaultActionGroup toolbar = new DefaultActionGroup(); 748 | usageView.addFilteringActions(toolbar); 749 | 750 | toolbar.add(UsageGroupingRuleProviderImpl.createGroupByFileStructureAction(usageView)); 751 | toolbar.add( 752 | new AnAction("Open Find Usages Toolwindow", "Show all usages in a separate toolwindow", 753 | AllIcons.Toolwindows.ToolWindowFind) { 754 | { 755 | AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_USAGES); 756 | setShortcutSet(action.getShortcutSet()); 757 | } 758 | 759 | @Override 760 | public void actionPerformed(AnActionEvent e) { 761 | hideHints(); 762 | popup[0].cancel(); 763 | FindUsagesManager findUsagesManager = ((FindManagerImpl) FindManager.getInstance( 764 | usageView.getProject())).getFindUsagesManager(); 765 | findUsagesManager.findUsages(handler.getPrimaryElements(), 766 | handler.getSecondaryElements(), handler, options, true); 767 | } 768 | } 769 | ); 770 | 771 | ActionToolbar actionToolbar = ActionManager.getInstance() 772 | .createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, toolbar, true); 773 | actionToolbar.setReservePlaceAutoPopupIcon(false); 774 | final JComponent toolBar = actionToolbar.getComponent(); 775 | toolBar.setOpaque(false); 776 | builder.setSettingButton(toolBar); 777 | 778 | popup[0] = builder.createPopup(); 779 | JComponent content = popup[0].getContent(); 780 | 781 | myWidth = (int) (toolBar.getPreferredSize().getWidth() + new JLabel( 782 | getFullTitle(usages, title, hadMoreSeparator, visibleNodes.size() - 1, 783 | true)).getPreferredSize().getWidth() + settingsButton.getPreferredSize().getWidth()); 784 | myWidth = -1; 785 | for (AnAction action : toolbar.getChildren(null)) { 786 | action.unregisterCustomShortcutSet(usageView.getComponent()); 787 | action.registerCustomShortcutSet(action.getShortcutSet(), content); 788 | } 789 | 790 | return popup[0]; 791 | } 792 | 793 | @NotNull 794 | private static String getFullTitle(@NotNull List usages, @NotNull String title, 795 | boolean hadMoreSeparator, int visibleNodesCount, boolean findUsagesInProgress) { 796 | String s; 797 | if (hadMoreSeparator) { 798 | s = "Some " + title + " " + "(Only " + visibleNodesCount + " usages shown" + ( 799 | findUsagesInProgress ? " so far" : "") + ")"; 800 | } else { 801 | s = title + " (" + UsageViewBundle.message("usages.n", usages.size()) + (findUsagesInProgress 802 | ? " so far" : "") + ")"; 803 | } 804 | return "" + s + ""; 805 | } 806 | 807 | @NotNull 808 | private static String suggestSecondInvocation(@NotNull FindUsagesOptions options, 809 | @NotNull FindUsagesHandler handler, @NotNull String text) { 810 | final String title = getSecondInvocationTitle(options, handler); 811 | 812 | if (title != null) { 813 | text += "
    Press " + title + ""; 814 | } 815 | return "" + text + ""; 816 | } 817 | 818 | @Nullable 819 | private static String getSecondInvocationTitle(@NotNull FindUsagesOptions options, 820 | @NotNull FindUsagesHandler handler) { 821 | if (getShowUsagesShortcut() != null) { 822 | GlobalSearchScope maximalScope = FindUsagesManager.getMaximalScope(handler); 823 | if (!notNullizeScope(options, handler.getProject()).equals(maximalScope)) { 824 | return "Press " 825 | + KeymapUtil.getShortcutText(getShowUsagesShortcut()) 826 | + " again to search in " 827 | + maximalScope.getDisplayName(); 828 | } 829 | } 830 | return null; 831 | } 832 | 833 | private void searchEverywhere(@NotNull FindUsagesOptions options, 834 | @NotNull FindUsagesHandler handler, Editor editor, @NotNull RelativePoint popupPosition, 835 | int maxUsages) { 836 | FindUsagesOptions cloned = options.clone(); 837 | cloned.searchScope = FindUsagesManager.getMaximalScope(handler); 838 | showElementUsages(handler, editor, popupPosition, maxUsages, cloned); 839 | } 840 | 841 | @Nullable 842 | private static KeyboardShortcut getShowUsagesShortcut() { 843 | return ActionManager.getInstance().getKeyboardShortcut("ShowUsages"); 844 | } 845 | 846 | private static int filtered(@NotNull List usages, @NotNull UsageViewImpl usageView) { 847 | int count = 0; 848 | for (Usage usage : usages) { 849 | if (!usageView.isVisible(usage)) count++; 850 | } 851 | return count; 852 | } 853 | 854 | private static int getUsageOffset(@NotNull Usage usage) { 855 | if (!(usage instanceof UsageInfo2UsageAdapter)) return -1; 856 | PsiElement element = ((UsageInfo2UsageAdapter) usage).getElement(); 857 | if (element == null) return -1; 858 | return element.getTextRange().getStartOffset(); 859 | } 860 | 861 | private static boolean areAllUsagesInOneLine(@NotNull Usage visibleUsage, 862 | @NotNull List usages) { 863 | Editor editor = getEditorFor(visibleUsage); 864 | if (editor == null) return false; 865 | int offset = getUsageOffset(visibleUsage); 866 | if (offset == -1) return false; 867 | int lineNumber = editor.getDocument().getLineNumber(offset); 868 | for (Usage other : usages) { 869 | Editor otherEditor = getEditorFor(other); 870 | if (otherEditor != editor) return false; 871 | int otherOffset = getUsageOffset(other); 872 | if (otherOffset == -1) return false; 873 | int otherLine = otherEditor.getDocument().getLineNumber(otherOffset); 874 | if (otherLine != lineNumber) return false; 875 | } 876 | return true; 877 | } 878 | 879 | @NotNull 880 | private static MyModel setTableModel(@NotNull JTable table, @NotNull UsageViewImpl usageView, 881 | @NotNull final List data) { 882 | ApplicationManager.getApplication().assertIsDispatchThread(); 883 | final int columnCount = calcColumnCount(data); 884 | MyModel model = table.getModel() instanceof MyModel ? (MyModel) table.getModel() : null; 885 | if (model == null || model.getColumnCount() != columnCount) { 886 | model = new MyModel(data, columnCount); 887 | table.setModel(model); 888 | 889 | ShowUsagesTableCellRenderer renderer = new ShowUsagesTableCellRenderer(usageView); 890 | for (int i = 0; i < table.getColumnModel().getColumnCount(); i++) { 891 | TableColumn column = table.getColumnModel().getColumn(i); 892 | column.setCellRenderer(renderer); 893 | } 894 | } 895 | return model; 896 | } 897 | 898 | private static int calcColumnCount(@NotNull List data) { 899 | return data.isEmpty() || data.get(0) instanceof StringNode ? 1 : 3; 900 | } 901 | 902 | @NotNull 903 | private static List collectData(@NotNull List usages, 904 | @NotNull Collection visibleNodes, @NotNull UsageViewImpl usageView, 905 | @NotNull UsageViewPresentation presentation) { 906 | @NotNull List data = new ArrayList(); 907 | int filtered = filtered(usages, usageView); 908 | if (filtered != 0) { 909 | data.add(createStringNode(UsageViewBundle.message("usages.were.filtered.out", filtered))); 910 | } 911 | data.addAll(visibleNodes); 912 | if (data.isEmpty()) { 913 | String progressText = UsageViewManagerImpl.getProgressTitle(presentation); 914 | data.add(createStringNode(progressText)); 915 | } 916 | Collections.sort(data, USAGE_NODE_COMPARATOR); 917 | return data; 918 | } 919 | 920 | private static int calcMaxWidth(JTable table) { 921 | int colsNum = table.getColumnModel().getColumnCount(); 922 | 923 | int totalWidth = 0; 924 | for (int col = 0; col < colsNum - 1; col++) { 925 | TableColumn column = table.getColumnModel().getColumn(col); 926 | int preferred = column.getPreferredWidth(); 927 | int width = Math.max(preferred, columnMaxWidth(table, col)); 928 | totalWidth += width; 929 | column.setMinWidth(width); 930 | column.setMaxWidth(width); 931 | column.setWidth(width); 932 | column.setPreferredWidth(width); 933 | } 934 | 935 | totalWidth += columnMaxWidth(table, colsNum - 1); 936 | 937 | return totalWidth; 938 | } 939 | 940 | private static int columnMaxWidth(@NotNull JTable table, int col) { 941 | TableColumn column = table.getColumnModel().getColumn(col); 942 | int width = 0; 943 | for (int row = 0; row < table.getRowCount(); row++) { 944 | Component component = table.prepareRenderer(column.getCellRenderer(), row, col); 945 | 946 | int rendererWidth = component.getPreferredSize().width; 947 | width = Math.max(width, rendererWidth + table.getIntercellSpacing().width); 948 | } 949 | return width; 950 | } 951 | 952 | private int myWidth; 953 | 954 | private void rebuildPopup(@NotNull final UsageViewImpl usageView, 955 | @NotNull final List usages, @NotNull List nodes, 956 | @NotNull final JTable table, @NotNull final JBPopup popup, 957 | @NotNull final UsageViewPresentation presentation, @NotNull final RelativePoint popupPosition, 958 | boolean findUsagesInProgress) { 959 | ApplicationManager.getApplication().assertIsDispatchThread(); 960 | 961 | boolean shouldShowMoreSeparator = usages.contains(MORE_USAGES_SEPARATOR); 962 | if (shouldShowMoreSeparator) { 963 | nodes.add(MORE_USAGES_SEPARATOR_NODE); 964 | } 965 | 966 | String title = presentation.getTabText(); 967 | String fullTitle = getFullTitle(usages, title, shouldShowMoreSeparator, 968 | nodes.size() - (shouldShowMoreSeparator ? 1 : 0), findUsagesInProgress); 969 | 970 | ((AbstractPopup) popup).setCaption(fullTitle); 971 | 972 | List data = collectData(usages, nodes, usageView, presentation); 973 | MyModel tableModel = setTableModel(table, usageView, data); 974 | List existingData = tableModel.getItems(); 975 | 976 | int row = table.getSelectedRow(); 977 | 978 | int newSelection = updateModel(tableModel, existingData, data, row == -1 ? 0 : row); 979 | if (newSelection < 0 || newSelection >= tableModel.getRowCount()) { 980 | TableScrollingUtil.ensureSelectionExists(table); 981 | newSelection = table.getSelectedRow(); 982 | } else { 983 | table.getSelectionModel().setSelectionInterval(newSelection, newSelection); 984 | } 985 | TableScrollingUtil.ensureIndexIsVisible(table, newSelection, 0); 986 | 987 | setSizeAndDimensions(table, popup, popupPosition, data); 988 | } 989 | 990 | // returns new selection 991 | private static int updateModel(@NotNull MyModel tableModel, @NotNull List listOld, 992 | @NotNull List listNew, int oldSelection) { 993 | UsageNode[] oa = listOld.toArray(new UsageNode[listOld.size()]); 994 | UsageNode[] na = listNew.toArray(new UsageNode[listNew.size()]); 995 | List cmds = ModelDiff.createDiffCmds(tableModel, oa, na); 996 | int selection = oldSelection; 997 | if (cmds != null) { 998 | for (ModelDiff.Cmd cmd : cmds) { 999 | selection = cmd.translateSelection(selection); 1000 | cmd.apply(); 1001 | } 1002 | } 1003 | return selection; 1004 | } 1005 | 1006 | private void setSizeAndDimensions(@NotNull JTable table, @NotNull JBPopup popup, 1007 | @NotNull RelativePoint popupPosition, @NotNull List data) { 1008 | JComponent content = popup.getContent(); 1009 | Window window = SwingUtilities.windowForComponent(content); 1010 | Dimension d = window.getSize(); 1011 | 1012 | int width = calcMaxWidth(table); 1013 | width = (int) Math.max(d.getWidth(), width); 1014 | Dimension headerSize = ((AbstractPopup) popup).getHeaderPreferredSize(); 1015 | width = Math.max((int) headerSize.getWidth(), width); 1016 | width = Math.max(myWidth, width); 1017 | 1018 | if (myWidth == -1) myWidth = width; 1019 | int newWidth = Math.max(width, d.width + width - myWidth); 1020 | 1021 | myWidth = newWidth; 1022 | 1023 | int rowsToShow = Math.min(30, data.size()); 1024 | Dimension dimension = new Dimension(newWidth, table.getRowHeight() * rowsToShow); 1025 | Rectangle rectangle = fitToScreen(dimension, popupPosition, table); 1026 | dimension = rectangle.getSize(); 1027 | Point location = window.getLocation(); 1028 | if (!location.equals(rectangle.getLocation())) { 1029 | window.setLocation(rectangle.getLocation()); 1030 | } 1031 | 1032 | if (!data.isEmpty()) { 1033 | TableScrollingUtil.ensureSelectionExists(table); 1034 | } 1035 | table.setSize(dimension); 1036 | //table.setPreferredSize(dimension); 1037 | //table.setMaximumSize(dimension); 1038 | //table.setPreferredScrollableViewportSize(dimension); 1039 | 1040 | Dimension footerSize = ((AbstractPopup) popup).getFooterPreferredSize(); 1041 | 1042 | int newHeight = (int) (dimension.height + headerSize.getHeight() + footerSize.getHeight()) + 4/* invisible borders, margins etc*/; 1043 | Dimension newDim = new Dimension(dimension.width, newHeight); 1044 | window.setSize(newDim); 1045 | window.setMinimumSize(newDim); 1046 | window.setMaximumSize(newDim); 1047 | 1048 | window.validate(); 1049 | window.repaint(); 1050 | table.revalidate(); 1051 | table.repaint(); 1052 | } 1053 | 1054 | private static Rectangle fitToScreen(@NotNull Dimension newDim, 1055 | @NotNull RelativePoint popupPosition, JTable table) { 1056 | Rectangle rectangle = new Rectangle(popupPosition.getScreenPoint(), newDim); 1057 | ScreenUtil.fitToScreen(rectangle); 1058 | if (rectangle.getHeight() != newDim.getHeight()) { 1059 | int newHeight = (int) rectangle.getHeight(); 1060 | int roundedHeight = newHeight - newHeight % table.getRowHeight(); 1061 | rectangle.setSize((int) rectangle.getWidth(), Math.max(roundedHeight, table.getRowHeight())); 1062 | } 1063 | return rectangle; 1064 | } 1065 | 1066 | private void appendMoreUsages(Editor editor, @NotNull RelativePoint popupPosition, 1067 | @NotNull FindUsagesHandler handler, int maxUsages) { 1068 | showElementUsages(handler, editor, popupPosition, maxUsages + USAGES_PAGE_SIZE, 1069 | getDefaultOptions(handler)); 1070 | } 1071 | 1072 | private void addUsageNodes(@NotNull GroupNode root, @NotNull final UsageViewImpl usageView, 1073 | @NotNull List outNodes) { 1074 | for (UsageNode node : root.getUsageNodes()) { 1075 | Usage usage = node.getUsage(); 1076 | if (usageView.isVisible(usage)) { 1077 | node.setParent(root); 1078 | outNodes.add(node); 1079 | } 1080 | } 1081 | for (GroupNode groupNode : root.getSubGroups()) { 1082 | groupNode.setParent(root); 1083 | addUsageNodes(groupNode, usageView, outNodes); 1084 | } 1085 | } 1086 | 1087 | @Override 1088 | public void update(AnActionEvent e) { 1089 | FindUsagesInFileAction.updateFindUsagesAction(e); 1090 | } 1091 | 1092 | private void navigateAndHint(@NotNull Usage usage, @Nullable final String hint, 1093 | @NotNull final FindUsagesHandler handler, @NotNull final RelativePoint popupPosition, 1094 | final int maxUsages, @NotNull final FindUsagesOptions options) { 1095 | usage.navigate(true); 1096 | if (hint == null) return; 1097 | final Editor newEditor = getEditorFor(usage); 1098 | if (newEditor == null) return; 1099 | final Project project = handler.getProject(); 1100 | //opening editor is performing in invokeLater 1101 | IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() { 1102 | @Override 1103 | public void run() { 1104 | newEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() { 1105 | @Override 1106 | public void run() { 1107 | // after new editor created, some editor resizing events are still bubbling. To prevent hiding hint, invokeLater this 1108 | IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() { 1109 | @Override 1110 | public void run() { 1111 | if (newEditor.getComponent().isShowing()) { 1112 | showHint(hint, newEditor, popupPosition, handler, maxUsages, options); 1113 | } 1114 | } 1115 | }); 1116 | } 1117 | }); 1118 | } 1119 | }); 1120 | } 1121 | 1122 | @Nullable 1123 | private static Editor getEditorFor(@NotNull Usage usage) { 1124 | FileEditorLocation location = usage.getLocation(); 1125 | FileEditor newFileEditor = location == null ? null : location.getEditor(); 1126 | return newFileEditor instanceof TextEditor ? ((TextEditor) newFileEditor).getEditor() : null; 1127 | } 1128 | 1129 | private static class MyTable extends JBTable implements DataProvider { 1130 | @Override 1131 | public boolean getScrollableTracksViewportWidth() { 1132 | return true; 1133 | } 1134 | 1135 | @Override 1136 | public Object getData(@NonNls String dataId) { 1137 | if (LangDataKeys.PSI_ELEMENT.is(dataId)) { 1138 | final int[] selected = getSelectedRows(); 1139 | if (selected.length == 1) { 1140 | return getPsiElementForHint(getValueAt(selected[0], 0)); 1141 | } 1142 | } 1143 | return null; 1144 | } 1145 | 1146 | @Nullable 1147 | protected PsiElement getPsiElementForHint(Object selectedValue) { 1148 | if (selectedValue instanceof UsageNode) { 1149 | final Usage usage = ((UsageNode) selectedValue).getUsage(); 1150 | if (usage instanceof UsageInfo2UsageAdapter) { 1151 | final PsiElement element = ((UsageInfo2UsageAdapter) usage).getElement(); 1152 | if (element != null) { 1153 | final PsiElement view = UsageToPsiElementProvider.findAppropriateParentFrom(element); 1154 | return view == null ? element : view; 1155 | } 1156 | } 1157 | } 1158 | return null; 1159 | } 1160 | } 1161 | 1162 | static class StringNode extends UsageNode { 1163 | private final Object myString; 1164 | 1165 | public StringNode(Object string) { 1166 | super(NullUsage.INSTANCE, 1167 | new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY)); 1168 | myString = string; 1169 | } 1170 | 1171 | @Override 1172 | public String toString() { 1173 | return myString.toString(); 1174 | } 1175 | } 1176 | 1177 | private static class MySpeedSearch extends SpeedSearchBase { 1178 | public MySpeedSearch(@NotNull MyTable table) { 1179 | super(table); 1180 | } 1181 | 1182 | @Override 1183 | protected int getSelectedIndex() { 1184 | return getTable().getSelectedRow(); 1185 | } 1186 | 1187 | @Override 1188 | protected int convertIndexToModel(int viewIndex) { 1189 | return getTable().convertRowIndexToModel(viewIndex); 1190 | } 1191 | 1192 | @NotNull @Override 1193 | protected Object[] getAllElements() { 1194 | return ((MyModel) getTable().getModel()).getItems().toArray(); 1195 | } 1196 | 1197 | @Override 1198 | protected String getElementText(@NotNull Object element) { 1199 | if (!(element instanceof UsageNode)) return element.toString(); 1200 | UsageNode node = (UsageNode) element; 1201 | if (node instanceof StringNode) return ""; 1202 | Usage usage = node.getUsage(); 1203 | if (usage == MORE_USAGES_SEPARATOR) return ""; 1204 | GroupNode group = (GroupNode) node.getParent(); 1205 | return usage.getPresentation().getPlainText() + group; 1206 | } 1207 | 1208 | @Override 1209 | protected void selectElement(Object element, String selectedText) { 1210 | List data = ((MyModel) getTable().getModel()).getItems(); 1211 | int i = data.indexOf(element); 1212 | if (i == -1) return; 1213 | final int viewRow = getTable().convertRowIndexToView(i); 1214 | getTable().getSelectionModel().setSelectionInterval(viewRow, viewRow); 1215 | TableUtil.scrollSelectionToVisible(getTable()); 1216 | } 1217 | 1218 | private MyTable getTable() { 1219 | return (MyTable) myComponent; 1220 | } 1221 | } 1222 | } 1223 | --------------------------------------------------------------------------------