24d7a09942df19b11b837d751cca5451dcd1001c
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / actions / GotoClassAction.java
1 /*
2  * Copyright 2000-2014 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.intellij.ide.actions;
18
19 import com.intellij.codeInsight.navigation.NavigationUtil;
20 import com.intellij.featureStatistics.FeatureUsageTracker;
21 import com.intellij.ide.structureView.StructureView;
22 import com.intellij.ide.structureView.StructureViewBuilder;
23 import com.intellij.ide.structureView.StructureViewTreeElement;
24 import com.intellij.ide.util.EditSourceUtil;
25 import com.intellij.ide.util.gotoByName.*;
26 import com.intellij.ide.util.treeView.smartTree.TreeElement;
27 import com.intellij.lang.Language;
28 import com.intellij.lang.LanguageStructureViewBuilder;
29 import com.intellij.lang.PsiStructureViewFactory;
30 import com.intellij.navigation.AnonymousElementProvider;
31 import com.intellij.navigation.ChooseByNameRegistry;
32 import com.intellij.navigation.ItemPresentation;
33 import com.intellij.navigation.NavigationItem;
34 import com.intellij.openapi.actionSystem.*;
35 import com.intellij.openapi.application.AccessToken;
36 import com.intellij.openapi.application.ReadAction;
37 import com.intellij.openapi.extensions.Extensions;
38 import com.intellij.openapi.fileEditor.FileEditor;
39 import com.intellij.openapi.fileEditor.FileEditorManager;
40 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
41 import com.intellij.openapi.project.DumbAware;
42 import com.intellij.openapi.project.DumbService;
43 import com.intellij.openapi.project.Project;
44 import com.intellij.openapi.ui.playback.commands.ActionCommand;
45 import com.intellij.openapi.util.Disposer;
46 import com.intellij.openapi.vfs.VirtualFile;
47 import com.intellij.pom.Navigatable;
48 import com.intellij.psi.PsiDocumentManager;
49 import com.intellij.psi.PsiElement;
50 import com.intellij.psi.codeStyle.MinusculeMatcher;
51 import com.intellij.psi.codeStyle.NameUtil;
52 import com.intellij.psi.util.PsiUtilCore;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 import java.util.ArrayList;
57 import java.util.List;
58
59 public class GotoClassAction extends GotoActionBase implements DumbAware {
60   @Override
61   public void actionPerformed(@NotNull final AnActionEvent e) {
62     final Project project = e.getData(CommonDataKeys.PROJECT);
63     assert project != null;
64     if (!DumbService.getInstance(project).isDumb()) {
65       super.actionPerformed(e);
66     }
67     else {
68       DumbService.getInstance(project)
69         .showDumbModeNotification("Goto Class action is not available until indices are built, using Goto File instead");
70       ActionManager.getInstance()
71         .tryToExecute(ActionManager.getInstance().getAction(GotoFileAction.ID), ActionCommand.getInputEvent(GotoFileAction.ID),
72                       e.getData(PlatformDataKeys.CONTEXT_COMPONENT), e.getPlace(), true);
73     }
74   }
75
76   @Override
77   public void gotoActionPerformed(AnActionEvent e) {
78     final Project project = e.getData(CommonDataKeys.PROJECT);
79     assert project != null;
80
81     PsiDocumentManager.getInstance(project).commitAllDocuments();
82
83     FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.popup.class");
84     final GotoClassModel2 model = new GotoClassModel2(project);
85     showNavigationPopup(e, model, new GotoActionCallback<Language>() {
86       @Override
87       protected ChooseByNameFilter<Language> createFilter(@NotNull ChooseByNamePopup popup) {
88         return new ChooseByNameLanguageFilter(popup, model, GotoClassSymbolConfiguration.getInstance(project), project);
89       }
90
91       @Override
92       public void elementChosen(ChooseByNamePopup popup, Object element) {
93         AccessToken token = ReadAction.start();
94         try {
95           if (element instanceof PsiElement) {
96             final PsiElement psiElement = getElement(((PsiElement)element), popup);
97             final VirtualFile file = PsiUtilCore.getVirtualFile(psiElement);
98             if (popup.getLinePosition() != -1 && file != null) {
99               Navigatable n = new OpenFileDescriptor(project, file, popup.getLinePosition(), popup.getColumnPosition()).setUseCurrentWindow(
100                 popup.isOpenInCurrentWindowRequested());
101               if (n.canNavigate()) {
102                 n.navigate(true);
103                 return;
104               }
105             }
106             if (psiElement != null && file != null && popup.getMemberPattern() != null) {
107               NavigationUtil.activateFileWithPsiElement(psiElement, !popup.isOpenInCurrentWindowRequested());
108               Navigatable member = findMember(popup.getMemberPattern(), psiElement, file);
109               if (member != null) {
110                 member.navigate(true);
111               }
112             }
113
114             NavigationUtil.activateFileWithPsiElement(psiElement, !popup.isOpenInCurrentWindowRequested());
115           }
116           else {
117             EditSourceUtil.navigate(((NavigationItem)element), true, popup.isOpenInCurrentWindowRequested());
118           }
119         }
120         finally {
121           token.finish();
122         }
123       }
124     }, "Classes matching pattern", true);
125   }
126
127   @Nullable private static Navigatable findMember(String pattern, PsiElement psiElement, VirtualFile file) {
128     final PsiStructureViewFactory factory = LanguageStructureViewBuilder.INSTANCE.forLanguage(psiElement.getLanguage());
129     final StructureViewBuilder builder = factory == null ? null : factory.getStructureViewBuilder(psiElement.getContainingFile());
130     final FileEditor[] editors = FileEditorManager.getInstance(psiElement.getProject()).getEditors(file);
131     if (builder == null || editors.length == 0) {
132       return null;
133     }
134
135     final StructureView view = builder.createStructureView(editors[0], psiElement.getProject());
136     try {
137       final StructureViewTreeElement element = findElement(view.getTreeModel().getRoot(), psiElement, 4);
138       if (element == null) {
139         return null;
140       }
141
142       final MinusculeMatcher matcher = new MinusculeMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE);
143       int max = Integer.MIN_VALUE;
144       Object target = null;
145       for (TreeElement treeElement : element.getChildren()) {
146         if (treeElement instanceof StructureViewTreeElement) {
147           final ItemPresentation presentation = treeElement.getPresentation();
148           String presentableText = presentation == null ? null : presentation.getPresentableText();
149           if (presentableText != null) {
150             final int degree = matcher.matchingDegree(presentableText);
151             if (degree > max) {
152               max = degree;
153               target = ((StructureViewTreeElement)treeElement).getValue();
154             }
155           }
156         }
157       }
158       return target instanceof Navigatable ? (Navigatable)target : null;
159     }
160     finally {
161       Disposer.dispose(view);
162     }
163   }
164
165   @Nullable
166   private static StructureViewTreeElement findElement(StructureViewTreeElement node, PsiElement element, int hopes) {
167     final Object value = node.getValue();
168     if (value instanceof PsiElement) {
169       if (((PsiElement)value).isEquivalentTo(element)) return node;
170       if (hopes != 0) {
171         for (TreeElement child : node.getChildren()) {
172           if (child instanceof StructureViewTreeElement) {
173             final StructureViewTreeElement e = findElement((StructureViewTreeElement)child, element, hopes - 1);
174             if (e != null) {
175               return e;
176             }
177           }
178         }
179       }
180     }
181     return null;
182   }
183
184   private static PsiElement getElement(@NotNull PsiElement element, ChooseByNamePopup popup) {
185     final String path = popup.getPathToAnonymous();
186     if (path != null) {
187       final String[] classes = path.split("\\$");
188       List<Integer> indexes = new ArrayList<Integer>();
189       for (String cls : classes) {
190         if (cls.isEmpty()) continue;
191         try {
192           indexes.add(Integer.parseInt(cls) - 1);
193         }
194         catch (Exception e) {
195           return element;
196         }
197       }
198       PsiElement current = element;
199       for (int index : indexes) {
200         final PsiElement[] anonymousClasses = getAnonymousClasses(current);
201         if (anonymousClasses.length > index) {
202           current = anonymousClasses[index];
203         }
204         else {
205           return current;
206         }
207       }
208       return current;
209     }
210     return element;
211   }
212
213   @NotNull
214   private static PsiElement[] getAnonymousClasses(@NotNull PsiElement element) {
215     for (AnonymousElementProvider provider : Extensions.getExtensions(AnonymousElementProvider.EP_NAME)) {
216       final PsiElement[] elements = provider.getAnonymousElements(element);
217       if (elements.length > 0) {
218         return elements;
219       }
220     }
221     return PsiElement.EMPTY_ARRAY;
222   }
223
224   @Override
225   protected boolean hasContributors(DataContext dataContext) {
226     return ChooseByNameRegistry.getInstance().getClassModelContributors().length > 0;
227   }
228 }