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