ability to show "Current File" always (WEB-20644)
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / search / PredefinedSearchScopeProviderImpl.java
1 /*
2  * Copyright 2000-2016 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.psi.search;
17
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.ide.favoritesTreeView.FavoritesManager;
20 import com.intellij.ide.hierarchy.HierarchyBrowserBase;
21 import com.intellij.ide.projectView.impl.AbstractUrl;
22 import com.intellij.openapi.actionSystem.CommonDataKeys;
23 import com.intellij.openapi.actionSystem.DataContext;
24 import com.intellij.openapi.actionSystem.LangDataKeys;
25 import com.intellij.openapi.application.ApplicationManager;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.editor.SelectionModel;
28 import com.intellij.openapi.fileEditor.FileEditorManager;
29 import com.intellij.openapi.module.*;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.util.Computable;
32 import com.intellij.openapi.util.Condition;
33 import com.intellij.openapi.util.Pair;
34 import com.intellij.openapi.util.TextRange;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.openapi.wm.ToolWindow;
37 import com.intellij.openapi.wm.ToolWindowId;
38 import com.intellij.openapi.wm.ToolWindowManager;
39 import com.intellij.psi.PsiDocumentManager;
40 import com.intellij.psi.PsiElement;
41 import com.intellij.psi.PsiFile;
42 import com.intellij.psi.PsiWhiteSpace;
43 import com.intellij.psi.util.PsiTreeUtil;
44 import com.intellij.psi.util.PsiUtilCore;
45 import com.intellij.ui.content.Content;
46 import com.intellij.ui.content.ContentManager;
47 import com.intellij.usages.Usage;
48 import com.intellij.usages.UsageView;
49 import com.intellij.usages.UsageViewManager;
50 import com.intellij.usages.rules.PsiElementUsage;
51 import com.intellij.util.PlatformUtils;
52 import com.intellij.util.TreeItem;
53 import com.intellij.util.containers.ContainerUtil;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56 import org.jetbrains.jps.model.java.JavaSourceRootType;
57
58 import javax.swing.*;
59 import java.util.*;
60
61 public class PredefinedSearchScopeProviderImpl extends PredefinedSearchScopeProvider {
62   @NotNull
63   @Override
64   public List<SearchScope> getPredefinedScopes(@NotNull final Project project,
65                                                @Nullable final DataContext dataContext,
66                                                boolean suggestSearchInLibs,
67                                                boolean prevSearchFiles,
68                                                boolean currentSelection,
69                                                boolean usageView,
70                                                boolean showEmptyScopes) {
71     Collection<SearchScope> result = ContainerUtil.newLinkedHashSet();
72     result.add(GlobalSearchScope.projectScope(project));
73     if (suggestSearchInLibs) {
74       result.add(GlobalSearchScope.allScope(project));
75     }
76
77     if (ModuleUtil.isSupportedRootType(project, JavaSourceRootType.TEST_SOURCE)) {
78       result.add(GlobalSearchScopesCore.projectProductionScope(project));
79       result.add(GlobalSearchScopesCore.projectTestScope(project));
80     }
81
82     result.add(GlobalSearchScopes.openFilesScope(project));
83
84     final Editor selectedTextEditor = ApplicationManager.getApplication().isDispatchThread()
85                                       ? FileEditorManager.getInstance(project).getSelectedTextEditor()
86                                       : null;
87     final PsiFile psiFile =
88       (selectedTextEditor != null) ? PsiDocumentManager.getInstance(project).getPsiFile(selectedTextEditor.getDocument()) : null;
89     PsiFile currentFile = psiFile;
90
91     if (dataContext != null) {
92       PsiElement dataContextElement = CommonDataKeys.PSI_FILE.getData(dataContext);
93       if (dataContextElement == null) {
94         dataContextElement = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
95       }
96
97       if (dataContextElement == null && psiFile != null) {
98         dataContextElement = psiFile;
99       }
100
101       if (dataContextElement != null) {
102         if (!PlatformUtils.isCidr()) { // TODO: have an API to disable module scopes.
103           Module module = ModuleUtilCore.findModuleForPsiElement(dataContextElement);
104           if (module == null) {
105             module = LangDataKeys.MODULE.getData(dataContext);
106           }
107           if (module != null && !(ModuleType.get(module) instanceof InternalModuleType)) {
108             result.add(module.getModuleScope());
109           }
110         }
111         if (currentFile == null) {
112           currentFile = dataContextElement.getContainingFile();
113         }
114       }
115     }
116
117     if (currentFile != null || showEmptyScopes) {
118       PsiElement[] scope = currentFile != null ? new PsiElement[] {currentFile} : PsiElement.EMPTY_ARRAY;
119       result.add(new LocalSearchScope(scope, IdeBundle.message("scope.current.file")));
120     }
121
122     if (currentSelection && selectedTextEditor != null && psiFile != null) {
123       SelectionModel selectionModel = selectedTextEditor.getSelectionModel();
124       if (selectionModel.hasSelection()) {
125         int start = selectionModel.getSelectionStart();
126         final PsiElement startElement = psiFile.findElementAt(start);
127         if (startElement != null) {
128           int end = selectionModel.getSelectionEnd();
129           final PsiElement endElement = psiFile.findElementAt(end);
130           if (endElement != null) {
131             final PsiElement parent = PsiTreeUtil.findCommonParent(startElement, endElement);
132             if (parent != null) {
133               final List<PsiElement> elements = new ArrayList<PsiElement>();
134               final PsiElement[] children = parent.getChildren();
135               TextRange selection = new TextRange(start, end);
136               for (PsiElement child : children) {
137                 if (!(child instanceof PsiWhiteSpace) &&
138                     child.getContainingFile() != null &&
139                     selection.contains(child.getTextOffset())) {
140                   elements.add(child);
141                 }
142               }
143               if (!elements.isEmpty()) {
144                 SearchScope local = new LocalSearchScope(PsiUtilCore.toPsiElementArray(elements), IdeBundle.message("scope.selection"));
145                 result.add(local);
146               }
147             }
148           }
149         }
150       }
151     }
152
153     if (usageView) {
154       addHierarchyScope(project, result);
155       UsageView selectedUsageView = UsageViewManager.getInstance(project).getSelectedUsageView();
156       if (selectedUsageView != null && !selectedUsageView.isSearchInProgress()) {
157         final Set<Usage> usages = ContainerUtil.newTroveSet(selectedUsageView.getUsages());
158         usages.removeAll(selectedUsageView.getExcludedUsages());
159         final List<PsiElement> results = new ArrayList<PsiElement>(usages.size());
160
161         if (prevSearchFiles) {
162           final Set<VirtualFile> files = collectFiles(usages, true);
163           if (!files.isEmpty()) {
164             GlobalSearchScope prev = new GlobalSearchScope(project) {
165               private Set<VirtualFile> myFiles = null;
166
167               @NotNull
168               @Override
169               public String getDisplayName() {
170                 return IdeBundle.message("scope.files.in.previous.search.result");
171               }
172
173               @Override
174               public synchronized boolean contains(@NotNull VirtualFile file) {
175                 if (myFiles == null) {
176                   myFiles = collectFiles(usages, false);
177                 }
178                 return myFiles.contains(file);
179               }
180
181               @Override
182               public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
183                 return 0;
184               }
185
186               @Override
187               public boolean isSearchInModuleContent(@NotNull Module aModule) {
188                 return true;
189               }
190
191               @Override
192               public boolean isSearchInLibraries() {
193                 return true;
194               }
195             };
196             result.add(prev);
197           }
198         }
199         else {
200           for (Usage usage : usages) {
201             if (usage instanceof PsiElementUsage) {
202               final PsiElement element = ((PsiElementUsage)usage).getElement();
203               if (element != null && element.isValid() && element.getContainingFile() != null) {
204                 results.add(element);
205               }
206             }
207           }
208
209           if (!results.isEmpty()) {
210             result.add(new LocalSearchScope(PsiUtilCore.toPsiElementArray(results), IdeBundle.message("scope.previous.search.results")));
211           }
212         }
213       }
214     }
215
216     final FavoritesManager favoritesManager = FavoritesManager.getInstance(project);
217     if (favoritesManager != null) {
218       for (final String favorite : favoritesManager.getAvailableFavoritesListNames()) {
219         final Collection<TreeItem<Pair<AbstractUrl, String>>> rootUrls = favoritesManager.getFavoritesListRootUrls(favorite);
220         if (rootUrls.isEmpty()) continue;  // ignore unused root
221         result.add(new GlobalSearchScope(project) {
222           @NotNull
223           @Override
224           public String getDisplayName() {
225             return "Favorite \'" + favorite + "\'";
226           }
227
228           @Override
229           public boolean contains(@NotNull final VirtualFile file) {
230             return ApplicationManager.getApplication().runReadAction((Computable<Boolean>)() -> favoritesManager.contains(favorite, file));
231           }
232
233           @Override
234           public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
235             return 0;
236           }
237
238           @Override
239           public boolean isSearchInModuleContent(@NotNull final Module aModule) {
240             return true;
241           }
242
243           @Override
244           public boolean isSearchInLibraries() {
245             return true;
246           }
247         });
248       }
249     }
250
251     ContainerUtil.addIfNotNull(result, getSelectedFilesScope(project, dataContext));
252
253     return ContainerUtil.newArrayList(result);
254   }
255
256   private static void addHierarchyScope(@NotNull Project project, Collection<SearchScope> result) {
257     final ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.HIERARCHY);
258     if (toolWindow == null) {
259       return;
260     }
261     final ContentManager contentManager = toolWindow.getContentManager();
262     final Content content = contentManager.getSelectedContent();
263     if (content == null) {
264       return;
265     }
266     final String name = content.getDisplayName();
267     final JComponent component = content.getComponent();
268     if (!(component instanceof HierarchyBrowserBase)) {
269       return;
270     }
271     final HierarchyBrowserBase hierarchyBrowserBase = (HierarchyBrowserBase)component;
272     final PsiElement[] elements = hierarchyBrowserBase.getAvailableElements();
273     if (elements.length > 0) {
274       result.add(new LocalSearchScope(elements, "Hierarchy '" + name + "' (visible nodes only)"));
275     }
276   }
277
278   @Nullable
279   private static SearchScope getSelectedFilesScope(final Project project, @Nullable DataContext dataContext) {
280     final VirtualFile[] filesOrDirs = dataContext == null ? null : CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
281     if (filesOrDirs != null) {
282       final List<VirtualFile> selectedFiles = ContainerUtil.filter(filesOrDirs, new Condition<VirtualFile>() {
283         @Override
284         public boolean value(VirtualFile file) {
285           return !file.isDirectory();
286         }
287       });
288       if (!selectedFiles.isEmpty()) {
289         return GlobalSearchScope.filesScope(project, selectedFiles, "Selected Files");
290       }
291     }
292     return null;
293   }
294
295   protected static Set<VirtualFile> collectFiles(Set<Usage> usages, boolean findFirst) {
296     final Set<VirtualFile> files = new HashSet<VirtualFile>();
297     for (Usage usage : usages) {
298       if (usage instanceof PsiElementUsage) {
299         PsiElement psiElement = ((PsiElementUsage)usage).getElement();
300         if (psiElement != null && psiElement.isValid()) {
301           PsiFile psiFile = psiElement.getContainingFile();
302           if (psiFile != null) {
303             VirtualFile file = psiFile.getVirtualFile();
304             if (file != null) {
305               files.add(file);
306               if (findFirst) return files;
307             }
308           }
309         }
310       }
311     }
312     return files;
313   }
314 }