ignore empty favorites in scope choosers
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / util / scopeChooser / ScopeChooserCombo.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 package com.intellij.ide.util.scopeChooser;
17
18 import com.intellij.ide.DataManager;
19 import com.intellij.ide.IdeBundle;
20 import com.intellij.ide.favoritesTreeView.FavoritesManager;
21 import com.intellij.ide.projectView.impl.AbstractUrl;
22 import com.intellij.ide.ui.ListCellRendererWrapper;
23 import com.intellij.openapi.Disposable;
24 import com.intellij.openapi.actionSystem.DataContext;
25 import com.intellij.openapi.actionSystem.LangDataKeys;
26 import com.intellij.openapi.actionSystem.PlatformDataKeys;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.extensions.Extensions;
29 import com.intellij.openapi.fileEditor.FileEditorManager;
30 import com.intellij.openapi.module.Module;
31 import com.intellij.openapi.module.ModuleUtil;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.util.Pair;
34 import com.intellij.openapi.vfs.VirtualFile;
35 import com.intellij.packageDependencies.ChangeListsScopesProvider;
36 import com.intellij.packageDependencies.DependencyValidationManager;
37 import com.intellij.psi.PsiDocumentManager;
38 import com.intellij.psi.PsiElement;
39 import com.intellij.psi.PsiFile;
40 import com.intellij.psi.PsiWhiteSpace;
41 import com.intellij.psi.search.*;
42 import com.intellij.psi.search.scope.packageSet.NamedScope;
43 import com.intellij.psi.search.scope.packageSet.NamedScopeManager;
44 import com.intellij.psi.search.scope.packageSet.NamedScopesHolder;
45 import com.intellij.psi.util.PsiTreeUtil;
46 import com.intellij.psi.util.PsiUtilBase;
47 import com.intellij.psi.util.PsiUtilCore;
48 import com.intellij.ui.ComboboxWithBrowseButton;
49 import com.intellij.usages.Usage;
50 import com.intellij.usages.UsageView;
51 import com.intellij.usages.UsageViewManager;
52 import com.intellij.usages.rules.PsiElementUsage;
53 import com.intellij.util.ArrayUtil;
54 import com.intellij.util.PlatformUtils;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import java.awt.event.ActionEvent;
60 import java.awt.event.ActionListener;
61 import java.util.*;
62
63 public class ScopeChooserCombo extends ComboboxWithBrowseButton implements Disposable {
64   private Project myProject;
65   private boolean mySuggestSearchInLibs;
66   private boolean myPrevSearchFiles;
67
68   private NamedScopesHolder.ScopeListener myScopeListener;
69   private NamedScopeManager myNamedScopeManager;
70   private DependencyValidationManager myValidationManager;
71
72   public ScopeChooserCombo() {
73   }
74
75   public ScopeChooserCombo(final Project project, boolean suggestSearchInLibs, boolean prevSearchWholeFiles, String preselect) {
76     init(project, suggestSearchInLibs, prevSearchWholeFiles,  preselect);
77   }
78
79   public void init(final Project project, final String preselect){
80     init(project, false, true, preselect);    
81   }
82
83   public void init(final Project project, final boolean suggestSearchInLibs, final boolean prevSearchWholeFiles,  final String preselect) {
84     mySuggestSearchInLibs = suggestSearchInLibs;
85     myPrevSearchFiles = prevSearchWholeFiles;
86     final JComboBox combo = getComboBox();
87     myProject = project;
88     myScopeListener = new NamedScopesHolder.ScopeListener() {
89       public void scopesChanged() {
90         final SearchScope selectedScope = getSelectedScope();
91         rebuildModel();
92         if (selectedScope != null) {
93           selectScope(selectedScope.getDisplayName());
94         }
95       }
96     };
97     myNamedScopeManager = NamedScopeManager.getInstance(project);
98     myNamedScopeManager.addScopeListener(myScopeListener);
99     myValidationManager = DependencyValidationManager.getInstance(project);
100     myValidationManager.addScopeListener(myScopeListener);
101     addActionListener(createScopeChooserListener());
102
103     combo.setRenderer(new ListCellRendererWrapper<ScopeDescriptor>(combo.getRenderer()) {
104       @Override
105       public void customize(final JList list, final ScopeDescriptor value, final int index, final boolean selected, final boolean hasFocus) {
106         if (value != null) setText(value.getDisplay());
107       }
108     });
109
110     rebuildModel();
111
112     selectScope(preselect);
113   }
114
115   @Override
116   public void dispose() {
117     super.dispose();
118     if (myValidationManager != null) {
119       myValidationManager.removeScopeListener(myScopeListener);
120       myValidationManager = null;
121     }
122     if (myNamedScopeManager != null) {
123       myNamedScopeManager.removeScopeListener(myScopeListener);
124       myNamedScopeManager = null;
125     }
126     myScopeListener = null;
127   }
128
129   private void selectScope(String preselect) {
130     if (preselect != null) {
131       final JComboBox combo = getComboBox();
132       DefaultComboBoxModel model = (DefaultComboBoxModel)combo.getModel();
133       for (int i = 0; i < model.getSize(); i++) {
134         ScopeDescriptor descriptor = (ScopeDescriptor)model.getElementAt(i);
135         if (preselect.equals(descriptor.getDisplay())) {
136           combo.setSelectedIndex(i);
137           break;
138         }
139       }
140     }
141   }
142
143   private ActionListener createScopeChooserListener() {
144     return new ActionListener() {
145       public void actionPerformed(ActionEvent e) {
146         final String selection = getSelectedScopeName();
147         final EditScopesDialog dlg = EditScopesDialog.showDialog(myProject, selection);
148         if (dlg.isOK()){
149           rebuildModel();
150           final NamedScope namedScope = dlg.getSelectedScope();
151           if (namedScope != null) {
152             selectScope(namedScope.getName());
153           }
154         }
155       }
156     };
157   }
158
159   private void rebuildModel() {
160     getComboBox().setModel(createModel());
161   }
162
163   private DefaultComboBoxModel createModel() {
164     DefaultComboBoxModel model = new DefaultComboBoxModel();
165     createPredefinedScopeDescriptors(model);
166
167     List<NamedScope> changeLists = ChangeListsScopesProvider.getInstance(myProject).getCustomScopes();
168     for (NamedScope changeListScope : changeLists) {
169       model.addElement(new ScopeDescriptor(GlobalSearchScopes.filterScope(myProject, changeListScope)));
170     }
171     final NamedScopesHolder[] holders = NamedScopesHolder.getAllNamedScopeHolders(myProject);
172     for (NamedScopesHolder holder : holders) {
173       NamedScope[] scopes = holder.getEditableScopes(); //predefined scopes already included
174       for (NamedScope scope : scopes) {
175         model.addElement(new ScopeDescriptor(GlobalSearchScopes.filterScope(myProject, scope)));
176       }
177     }
178
179     return model;
180   }
181
182
183   private void createPredefinedScopeDescriptors(DefaultComboBoxModel model) {
184     for (SearchScope scope : getPredefinedScopes(myProject, DataManager.getInstance().getDataContext(), mySuggestSearchInLibs, myPrevSearchFiles, true, true)) {
185       model.addElement(new ScopeDescriptor(scope));
186     }
187     for (ScopeDescriptorProvider provider : Extensions.getExtensions(ScopeDescriptorProvider.EP_NAME)) {
188       for (ScopeDescriptor scopeDescriptor : provider.getScopeDescriptors(myProject)) {
189         model.addElement(scopeDescriptor);
190       }
191     }
192   }
193
194   public static List<SearchScope> getPredefinedScopes(@NotNull final Project project,
195                                                       @Nullable final DataContext dataContext,
196                                                       boolean suggestSearchInLibs,
197                                                       boolean prevSearchFiles,
198                                                       boolean currentSelection,
199                                                       boolean usageView) {
200     ArrayList<SearchScope> result = new ArrayList<SearchScope>();
201     result.add(GlobalSearchScope.projectScope(project));
202     if (suggestSearchInLibs) {
203       result.add(GlobalSearchScope.allScope(project));
204     }
205
206     if (!PlatformUtils.isCidr()) { // TODO: fix these scopes in AppCode
207       result.add(GlobalSearchScopes.projectProductionScope(project));
208       result.add(GlobalSearchScopes.projectTestScope(project));
209     }
210
211     if (dataContext != null) {
212       PsiElement dataContextElement = getDataContextElement(dataContext);
213
214       if (dataContextElement != null) {
215         if (!PlatformUtils.isCidr()) { // TODO: have an API to disable module scopes.
216           Module module = ModuleUtil.findModuleForPsiElement(dataContextElement);
217           if (module == null) {
218             module = LangDataKeys.MODULE.getData(dataContext);
219           }
220           if (module != null) {
221             result.add(module.getModuleScope());
222           }
223         }
224         if (dataContextElement.getContainingFile() != null) {
225           result.add(new LocalSearchScope(dataContextElement, IdeBundle.message("scope.current.file")));
226         }
227       }
228     }
229
230     if (currentSelection) {
231       FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
232       final Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor();
233       if (selectedTextEditor != null) {
234         final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(selectedTextEditor.getDocument());
235         if (psiFile != null) {
236           if (selectedTextEditor.getSelectionModel().hasSelection()) {
237             final PsiElement startElement = psiFile.findElementAt(selectedTextEditor.getSelectionModel().getSelectionStart());
238             if (startElement != null) {
239               final PsiElement endElement = psiFile.findElementAt(selectedTextEditor.getSelectionModel().getSelectionEnd());
240               if (endElement != null) {
241                 final PsiElement parent = PsiTreeUtil.findCommonParent(startElement, endElement);
242                 if (parent != null) {
243                   final List<PsiElement> elements = new ArrayList<PsiElement>();
244                   final PsiElement[] children = parent.getChildren();
245                   for (PsiElement child : children) {
246                     if (!(child instanceof PsiWhiteSpace) && child.getContainingFile() != null) {
247                       elements.add(child);
248                     }
249                   }
250                   if (!elements.isEmpty()) {
251                     SearchScope local = new LocalSearchScope(PsiUtilCore.toPsiElementArray(elements), IdeBundle.message("scope.selection"));
252                     result.add(local);
253                   }
254                 }
255               }
256             }
257           }
258         }
259       }
260     }
261
262     if (usageView) {
263       UsageView selectedUsageView = UsageViewManager.getInstance(project).getSelectedUsageView();
264       if (selectedUsageView != null && !selectedUsageView.isSearchInProgress()) {
265         final Set<Usage> usages = selectedUsageView.getUsages();
266         final List<PsiElement> results = new ArrayList<PsiElement>(usages.size());
267
268         if (prevSearchFiles) {
269           final Set<VirtualFile> files = new HashSet<VirtualFile>();
270           for (Usage usage : usages) {
271             if (usage instanceof PsiElementUsage) {
272               PsiElement psiElement = ((PsiElementUsage)usage).getElement();
273               if (psiElement != null && psiElement.isValid()) {
274                 PsiFile psiFile = psiElement.getContainingFile();
275                 if (psiFile != null) {
276                   VirtualFile file = psiFile.getVirtualFile();
277                   if (file != null) files.add(file);
278                 }
279               }
280             }
281           }
282           if (!files.isEmpty()) {
283             GlobalSearchScope prev = new GlobalSearchScope(project) {
284               public String getDisplayName() {
285                 return IdeBundle.message("scope.files.in.previous.search.result");
286               }
287
288               public boolean contains(VirtualFile file) {
289                 return files.contains(file);
290               }
291
292               public int compare(VirtualFile file1, VirtualFile file2) {
293                 return 0;
294               }
295
296               public boolean isSearchInModuleContent(@NotNull Module aModule) {
297                 return true;
298               }
299
300               public boolean isSearchInLibraries() {
301                 return true;
302               }
303             };
304             result.add(prev);
305           }
306         }
307         else {
308           for (Usage usage : usages) {
309             if (usage instanceof PsiElementUsage) {
310               final PsiElement element = ((PsiElementUsage)usage).getElement();
311               if (element != null && element.isValid() && element.getContainingFile() != null) {
312                 results.add(element);
313               }
314             }
315           }
316
317           if (!results.isEmpty()) {
318             result.add(new LocalSearchScope(PsiUtilBase.toPsiElementArray(results), IdeBundle.message("scope.previous.search.results")));
319           }
320         }
321       }
322     }
323
324     final FavoritesManager favoritesManager = FavoritesManager.getInstance(project);
325     String[] favoritesLists = favoritesManager == null ? ArrayUtil.EMPTY_STRING_ARRAY : favoritesManager.getAvailableFavoritesLists();
326     for (final String favorite : favoritesLists) {
327       final Collection<Pair<AbstractUrl,String>> rootUrls = favoritesManager.getFavoritesListRootUrls(favorite);
328       if (rootUrls.isEmpty()) continue; //ignore unused root
329       result.add(new GlobalSearchScope(project) {
330         @Override
331         public String getDisplayName() {
332           return "Favorite \'" + favorite + "\'";
333         }
334
335         @Override
336         public boolean contains(final VirtualFile file) {
337           return favoritesManager.contains(favorite, file);
338         }
339
340         @Override
341         public int compare(final VirtualFile file1, final VirtualFile file2) {
342           return 0;
343         }
344
345         @Override
346         public boolean isSearchInModuleContent(@NotNull final Module aModule) {
347           return true;
348         }
349
350         @Override
351         public boolean isSearchInLibraries() {
352           return true;
353         }
354       });
355     }
356
357     if (dataContext != null) {
358       final VirtualFile[] files = PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
359       if (files != null) {
360         final List<VirtualFile> openFiles = Arrays.asList(files);
361         result.add(new DelegatingGlobalSearchScope(GlobalSearchScope.filesScope(project, openFiles)){
362           @Override
363           public String getDisplayName() {
364             return "Selected files";
365           }
366         });
367       }
368     }
369
370     return result;
371   }
372
373   public static PsiElement getDataContextElement(DataContext dataContext) {
374     PsiElement dataContextElement = LangDataKeys.PSI_FILE.getData(dataContext);
375
376     if (dataContextElement == null) {
377       dataContextElement = LangDataKeys.PSI_ELEMENT.getData(dataContext);
378     }
379     return dataContextElement;
380   }
381
382
383   @Nullable
384   public SearchScope getSelectedScope() {
385     JComboBox combo = getComboBox();
386     int idx = combo.getSelectedIndex();
387     if (idx < 0) return null;
388     return ((ScopeDescriptor)combo.getSelectedItem()).getScope();
389   }
390
391   @Nullable
392   public String getSelectedScopeName() {
393     JComboBox combo = getComboBox();
394     int idx = combo.getSelectedIndex();
395     if (idx < 0) return null;
396     return ((ScopeDescriptor)combo.getSelectedItem()).getDisplay();
397   }
398 }