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