| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.ide.util.scopeChooser; |
| |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.IdeBundle; |
| import com.intellij.ide.favoritesTreeView.FavoritesManager; |
| import com.intellij.ide.projectView.impl.AbstractUrl; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.actionSystem.LangDataKeys; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.SelectionModel; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.module.*; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.packageDependencies.ChangeListsScopesProvider; |
| import com.intellij.packageDependencies.DependencyValidationManager; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiWhiteSpace; |
| import com.intellij.psi.search.*; |
| import com.intellij.psi.search.scope.packageSet.NamedScope; |
| import com.intellij.psi.search.scope.packageSet.NamedScopeManager; |
| import com.intellij.psi.search.scope.packageSet.NamedScopesHolder; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.ui.ComboboxWithBrowseButton; |
| import com.intellij.ui.ListCellRendererWrapper; |
| import com.intellij.usages.Usage; |
| import com.intellij.usages.UsageView; |
| import com.intellij.usages.UsageViewManager; |
| import com.intellij.usages.rules.PsiElementUsage; |
| import com.intellij.util.PlatformUtils; |
| import com.intellij.util.TreeItem; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.jps.model.java.JavaSourceRootType; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.*; |
| import java.util.List; |
| |
| public class ScopeChooserCombo extends ComboboxWithBrowseButton implements Disposable { |
| private Project myProject; |
| private boolean mySuggestSearchInLibs; |
| private boolean myPrevSearchFiles; |
| private NamedScopesHolder.ScopeListener myScopeListener; |
| private NamedScopeManager myNamedScopeManager; |
| private DependencyValidationManager myValidationManager; |
| private boolean myCurrentSelection = true; |
| private boolean myUsageView = true; |
| |
| public ScopeChooserCombo() { |
| super(new IgnoringComboBox(){ |
| @Override |
| protected boolean isIgnored(Object item) { |
| return item instanceof ScopeSeparator; |
| } |
| }); |
| } |
| |
| public ScopeChooserCombo(final Project project, boolean suggestSearchInLibs, boolean prevSearchWholeFiles, String preselect) { |
| this(); |
| init(project, suggestSearchInLibs, prevSearchWholeFiles, preselect); |
| } |
| |
| public void init(final Project project, final String preselect){ |
| init(project, false, true, preselect); |
| } |
| |
| public void init(final Project project, final boolean suggestSearchInLibs, final boolean prevSearchWholeFiles, final String preselect) { |
| mySuggestSearchInLibs = suggestSearchInLibs; |
| myPrevSearchFiles = prevSearchWholeFiles; |
| myProject = project; |
| myScopeListener = new NamedScopesHolder.ScopeListener() { |
| @Override |
| public void scopesChanged() { |
| final SearchScope selectedScope = getSelectedScope(); |
| rebuildModel(); |
| if (selectedScope != null) { |
| selectScope(selectedScope.getDisplayName()); |
| } |
| } |
| }; |
| myNamedScopeManager = NamedScopeManager.getInstance(project); |
| myNamedScopeManager.addScopeListener(myScopeListener); |
| myValidationManager = DependencyValidationManager.getInstance(project); |
| myValidationManager.addScopeListener(myScopeListener); |
| addActionListener(createScopeChooserListener()); |
| |
| final JComboBox combo = getComboBox(); |
| combo.setRenderer(new ScopeDescriptionWithDelimiterRenderer()); |
| |
| rebuildModel(); |
| |
| selectScope(preselect); |
| } |
| |
| public void setCurrentSelection(boolean currentSelection) { |
| myCurrentSelection = currentSelection; |
| } |
| |
| public void setUsageView(boolean usageView) { |
| myUsageView = usageView; |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| if (myValidationManager != null) { |
| myValidationManager.removeScopeListener(myScopeListener); |
| myValidationManager = null; |
| } |
| if (myNamedScopeManager != null) { |
| myNamedScopeManager.removeScopeListener(myScopeListener); |
| myNamedScopeManager = null; |
| } |
| myScopeListener = null; |
| } |
| |
| private void selectScope(String preselect) { |
| if (preselect != null) { |
| final JComboBox combo = getComboBox(); |
| DefaultComboBoxModel model = (DefaultComboBoxModel)combo.getModel(); |
| for (int i = 0; i < model.getSize(); i++) { |
| ScopeDescriptor descriptor = (ScopeDescriptor)model.getElementAt(i); |
| if (preselect.equals(descriptor.getDisplay())) { |
| combo.setSelectedIndex(i); |
| break; |
| } |
| } |
| } |
| } |
| |
| private ActionListener createScopeChooserListener() { |
| return new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| final String selection = getSelectedScopeName(); |
| final EditScopesDialog dlg = EditScopesDialog.showDialog(myProject, selection); |
| if (dlg.isOK()){ |
| rebuildModel(); |
| final NamedScope namedScope = dlg.getSelectedScope(); |
| if (namedScope != null) { |
| selectScope(namedScope.getName()); |
| } |
| } |
| } |
| }; |
| } |
| |
| private void rebuildModel() { |
| getComboBox().setModel(createModel()); |
| } |
| |
| private DefaultComboBoxModel createModel() { |
| final DefaultComboBoxModel model = new DefaultComboBoxModel(); |
| |
| createPredefinedScopeDescriptors(model); |
| |
| final List<NamedScope> changeLists = ChangeListsScopesProvider.getInstance(myProject).getCustomScopes(); |
| if (!changeLists.isEmpty()) { |
| model.addElement(new ScopeSeparator("VCS Scopes")); |
| for (NamedScope changeListScope : changeLists) { |
| final GlobalSearchScope scope = GlobalSearchScopesCore.filterScope(myProject, changeListScope); |
| model.addElement(new ScopeDescriptor(scope)); |
| } |
| } |
| |
| final List<ScopeDescriptor> customScopes = new ArrayList<ScopeDescriptor>(); |
| final NamedScopesHolder[] holders = NamedScopesHolder.getAllNamedScopeHolders(myProject); |
| for (NamedScopesHolder holder : holders) { |
| final NamedScope[] scopes = holder.getEditableScopes(); // predefined scopes already included |
| for (NamedScope scope : scopes) { |
| final GlobalSearchScope searchScope = GlobalSearchScopesCore.filterScope(myProject, scope); |
| customScopes.add(new ScopeDescriptor(searchScope)); |
| } |
| } |
| if (!customScopes.isEmpty()) { |
| model.addElement(new ScopeSeparator("Custom Scopes")); |
| for (ScopeDescriptor scope : customScopes) { |
| model.addElement(scope); |
| } |
| } |
| |
| return model; |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| if (isPreferredSizeSet()) { |
| return super.getPreferredSize(); |
| } |
| Dimension preferredSize = super.getPreferredSize(); |
| return new Dimension(Math.min(400, preferredSize.width), preferredSize.height); |
| } |
| |
| @Override |
| public Dimension getMinimumSize() { |
| if (isMinimumSizeSet()) { |
| return super.getMinimumSize(); |
| } |
| Dimension minimumSize = super.getMinimumSize(); |
| return new Dimension(Math.min(200, minimumSize.width), minimumSize.height); |
| } |
| |
| private void createPredefinedScopeDescriptors(DefaultComboBoxModel model) { |
| @SuppressWarnings("deprecation") final DataContext context = DataManager.getInstance().getDataContext(); |
| for (SearchScope scope : getPredefinedScopes(myProject, context, mySuggestSearchInLibs, myPrevSearchFiles, myCurrentSelection, myUsageView)) { |
| model.addElement(new ScopeDescriptor(scope)); |
| } |
| for (ScopeDescriptorProvider provider : Extensions.getExtensions(ScopeDescriptorProvider.EP_NAME)) { |
| for (ScopeDescriptor scopeDescriptor : provider.getScopeDescriptors(myProject)) { |
| model.addElement(scopeDescriptor); |
| } |
| } |
| } |
| |
| public static List<SearchScope> getPredefinedScopes(@NotNull final Project project, |
| @Nullable final DataContext dataContext, |
| boolean suggestSearchInLibs, |
| boolean prevSearchFiles, |
| boolean currentSelection, |
| boolean usageView) { |
| Collection<SearchScope> result = ContainerUtil.newLinkedHashSet(); |
| result.add(GlobalSearchScope.projectScope(project)); |
| if (suggestSearchInLibs) { |
| result.add(GlobalSearchScope.allScope(project)); |
| } |
| |
| if (ModuleUtil.isSupportedRootType(project, JavaSourceRootType.TEST_SOURCE)) { |
| result.add(GlobalSearchScopesCore.projectProductionScope(project)); |
| result.add(GlobalSearchScopesCore.projectTestScope(project)); |
| } |
| |
| result.add(GlobalSearchScopes.openFilesScope(project)); |
| |
| if (dataContext != null) { |
| PsiElement dataContextElement = CommonDataKeys.PSI_FILE.getData(dataContext); |
| if (dataContextElement == null) { |
| dataContextElement = CommonDataKeys.PSI_ELEMENT.getData(dataContext); |
| } |
| if (dataContextElement != null) { |
| if (!PlatformUtils.isCidr()) { // TODO: have an API to disable module scopes. |
| Module module = ModuleUtilCore.findModuleForPsiElement(dataContextElement); |
| if (module == null) { |
| module = LangDataKeys.MODULE.getData(dataContext); |
| } |
| if (module != null && !(ModuleType.get(module) instanceof InternalModuleType)) { |
| result.add(module.getModuleScope()); |
| } |
| } |
| if (dataContextElement.getContainingFile() != null) { |
| result.add(new LocalSearchScope(dataContextElement, IdeBundle.message("scope.current.file"))); |
| } |
| } |
| } |
| |
| if (currentSelection) { |
| FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); |
| final Editor selectedTextEditor = fileEditorManager.getSelectedTextEditor(); |
| if (selectedTextEditor != null) { |
| final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(selectedTextEditor.getDocument()); |
| if (psiFile != null) { |
| SelectionModel selectionModel = selectedTextEditor.getSelectionModel(); |
| if (selectionModel.hasSelection()) { |
| int start = selectionModel.getSelectionStart(); |
| final PsiElement startElement = psiFile.findElementAt(start); |
| if (startElement != null) { |
| int end = selectionModel.getSelectionEnd(); |
| final PsiElement endElement = psiFile.findElementAt(end); |
| if (endElement != null) { |
| final PsiElement parent = PsiTreeUtil.findCommonParent(startElement, endElement); |
| if (parent != null) { |
| final List<PsiElement> elements = new ArrayList<PsiElement>(); |
| final PsiElement[] children = parent.getChildren(); |
| TextRange selection = new TextRange(start, end); |
| for (PsiElement child : children) { |
| if (!(child instanceof PsiWhiteSpace) && child.getContainingFile() != null && selection.contains(child.getTextOffset())) { |
| elements.add(child); |
| } |
| } |
| if (!elements.isEmpty()) { |
| SearchScope local = new LocalSearchScope(PsiUtilCore.toPsiElementArray(elements), IdeBundle.message("scope.selection")); |
| result.add(local); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (usageView) { |
| UsageView selectedUsageView = UsageViewManager.getInstance(project).getSelectedUsageView(); |
| if (selectedUsageView != null && !selectedUsageView.isSearchInProgress()) { |
| final Set<Usage> usages = selectedUsageView.getUsages(); |
| final List<PsiElement> results = new ArrayList<PsiElement>(usages.size()); |
| |
| if (prevSearchFiles) { |
| final Set<VirtualFile> files = collectFiles(usages, true); |
| if (!files.isEmpty()) { |
| GlobalSearchScope prev = new GlobalSearchScope(project) { |
| private Set<VirtualFile> myFiles = null; |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| return IdeBundle.message("scope.files.in.previous.search.result"); |
| } |
| |
| @Override |
| public synchronized boolean contains(@NotNull VirtualFile file) { |
| if (myFiles == null) { |
| myFiles = collectFiles(usages, false); |
| } |
| return myFiles.contains(file); |
| } |
| |
| @Override |
| public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { |
| return 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return true; |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return true; |
| } |
| }; |
| result.add(prev); |
| } |
| } |
| else { |
| for (Usage usage : usages) { |
| if (usage instanceof PsiElementUsage) { |
| final PsiElement element = ((PsiElementUsage)usage).getElement(); |
| if (element != null && element.isValid() && element.getContainingFile() != null) { |
| results.add(element); |
| } |
| } |
| } |
| |
| if (!results.isEmpty()) { |
| result.add(new LocalSearchScope(PsiUtilCore.toPsiElementArray(results), IdeBundle.message("scope.previous.search.results"))); |
| } |
| } |
| } |
| } |
| |
| final FavoritesManager favoritesManager = FavoritesManager.getInstance(project); |
| if (favoritesManager != null) { |
| for (final String favorite : favoritesManager.getAvailableFavoritesListNames()) { |
| final Collection<TreeItem<Pair<AbstractUrl,String>>> rootUrls = favoritesManager.getFavoritesListRootUrls(favorite); |
| if (rootUrls.isEmpty()) continue; // ignore unused root |
| result.add(new GlobalSearchScope(project) { |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| return "Favorite \'" + favorite + "\'"; |
| } |
| |
| @Override |
| public boolean contains(@NotNull final VirtualFile file) { |
| return favoritesManager.contains(favorite, file); |
| } |
| |
| @Override |
| public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) { |
| return 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull final Module aModule) { |
| return true; |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return true; |
| } |
| }); |
| } |
| } |
| |
| ContainerUtil.addIfNotNull(result, getSelectedFilesScope(project, dataContext)); |
| |
| return ContainerUtil.newArrayList(result); |
| } |
| |
| @Nullable |
| private static SearchScope getSelectedFilesScope(final Project project, @Nullable DataContext dataContext) { |
| final VirtualFile[] filesOrDirs = dataContext == null ? null : CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext); |
| if (filesOrDirs != null) { |
| final List<VirtualFile> selectedFiles = ContainerUtil.filter(filesOrDirs, new Condition<VirtualFile>() { |
| @Override |
| public boolean value(VirtualFile file) { |
| return !file.isDirectory(); |
| } |
| }); |
| if (!selectedFiles.isEmpty()) { |
| return new DelegatingGlobalSearchScope(GlobalSearchScope.filesScope(project, selectedFiles)){ |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| return "Selected Files"; |
| } |
| }; |
| } |
| } |
| return null; |
| } |
| |
| protected static Set<VirtualFile> collectFiles(Set<Usage> usages, boolean findFirst) { |
| final Set<VirtualFile> files = new HashSet<VirtualFile>(); |
| for (Usage usage : usages) { |
| if (usage instanceof PsiElementUsage) { |
| PsiElement psiElement = ((PsiElementUsage)usage).getElement(); |
| if (psiElement != null && psiElement.isValid()) { |
| PsiFile psiFile = psiElement.getContainingFile(); |
| if (psiFile != null) { |
| VirtualFile file = psiFile.getVirtualFile(); |
| if (file != null) { |
| files.add(file); |
| if (findFirst) return files; |
| } |
| } |
| } |
| } |
| } |
| return files; |
| } |
| |
| @Nullable |
| public SearchScope getSelectedScope() { |
| final JComboBox combo = getComboBox(); |
| int idx = combo.getSelectedIndex(); |
| return idx < 0 ? null : ((ScopeDescriptor)combo.getSelectedItem()).getScope(); |
| } |
| |
| @Nullable |
| public String getSelectedScopeName() { |
| final JComboBox combo = getComboBox(); |
| int idx = combo.getSelectedIndex(); |
| return idx < 0 ? null : ((ScopeDescriptor)combo.getSelectedItem()).getDisplay(); |
| } |
| |
| private static class ScopeSeparator extends ScopeDescriptor { |
| private final String myText; |
| |
| public ScopeSeparator(final String text) { |
| super(null); |
| myText = text; |
| } |
| |
| @Override |
| public String getDisplay() { |
| return myText; |
| } |
| } |
| |
| private static class ScopeDescriptionWithDelimiterRenderer extends ListCellRendererWrapper<ScopeDescriptor> { |
| @Override |
| public void customize(JList list, ScopeDescriptor value, int index, boolean selected, boolean hasFocus) { |
| setText(value.getDisplay()); |
| if (value instanceof ScopeSeparator) { |
| setSeparator(); |
| } |
| } |
| } |
| } |