| /* |
| * 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.usages.impl; |
| |
| import com.intellij.find.FindManager; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.*; |
| import com.intellij.ide.actions.CloseTabToolbarAction; |
| import com.intellij.navigation.NavigationItem; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.ide.CopyPasteManager; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.progress.Task; |
| import com.intellij.openapi.progress.util.ProgressWrapper; |
| import com.intellij.openapi.progress.util.TooManyUsagesStatus; |
| import com.intellij.openapi.project.DumbAware; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.ui.SimpleToolWindowPanel; |
| import com.intellij.openapi.ui.Splitter; |
| import com.intellij.openapi.util.*; |
| import com.intellij.openapi.vfs.ReadonlyStatusHandler; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.SmartPointerManager; |
| import com.intellij.psi.SmartPsiElementPointer; |
| import com.intellij.psi.impl.PsiDocumentManagerBase; |
| import com.intellij.ui.*; |
| import com.intellij.ui.components.JBTabbedPane; |
| import com.intellij.ui.content.Content; |
| import com.intellij.ui.treeStructure.Tree; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.usageView.UsageViewBundle; |
| import com.intellij.usageView.UsageViewManager; |
| import com.intellij.usages.*; |
| import com.intellij.usages.rules.*; |
| import com.intellij.util.*; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.Convertor; |
| import com.intellij.util.containers.TransferToEDTQueue; |
| import com.intellij.util.enumeration.EmptyEnumeration; |
| import com.intellij.util.messages.MessageBusConnection; |
| import com.intellij.util.ui.DialogUtil; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.*; |
| import javax.swing.tree.*; |
| import java.awt.*; |
| import java.awt.datatransfer.StringSelection; |
| import java.awt.event.*; |
| import java.util.*; |
| import java.util.List; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * @author max |
| */ |
| public class UsageViewImpl implements UsageView, UsageModelTracker.UsageModelTrackerListener { |
| @NonNls public static final String SHOW_RECENT_FIND_USAGES_ACTION_ID = "UsageView.ShowRecentFindUsages"; |
| |
| private final UsageNodeTreeBuilder myBuilder; |
| private final MyPanel myRootPanel; |
| private final JTree myTree; |
| private Content myContent; |
| |
| private final UsageViewPresentation myPresentation; |
| private final UsageTarget[] myTargets; |
| private final Factory<UsageSearcher> myUsageSearcherFactory; |
| private final Project myProject; |
| |
| private volatile boolean mySearchInProgress = true; |
| private final ExporterToTextFile myTextFileExporter = new ExporterToTextFile(this); |
| private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); |
| |
| private final UsageModelTracker myModelTracker; |
| private final Map<Usage, UsageNode> myUsageNodes = new ConcurrentHashMap<Usage, UsageNode>(); |
| public static final UsageNode NULL_NODE = new UsageNode(NullUsage.INSTANCE, new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY)); |
| private final ButtonPanel myButtonPanel = new ButtonPanel(); |
| private volatile boolean isDisposed; |
| private volatile boolean myChangesDetected = false; |
| public static final Comparator<Usage> USAGE_COMPARATOR = new Comparator<Usage>() { |
| @Override |
| public int compare(final Usage o1, final Usage o2) { |
| if (o1 == o2) return 0; |
| if (o1 == NULL_NODE) return -1; |
| if (o2 == NULL_NODE) return 1; |
| if (o1 instanceof Comparable && o2 instanceof Comparable) { |
| final int selfcompared = ((Comparable<Usage>)o1).compareTo(o2); |
| if (selfcompared != 0) return selfcompared; |
| |
| if (o1 instanceof UsageInFile && o2 instanceof UsageInFile) { |
| UsageInFile u1 = (UsageInFile)o1; |
| UsageInFile u2 = (UsageInFile)o2; |
| |
| VirtualFile f1 = u1.getFile(); |
| VirtualFile f2 = u2.getFile(); |
| |
| if (f1 != null && f1.isValid() && f2 != null && f2.isValid()) { |
| return f1.getPresentableUrl().compareTo(f2.getPresentableUrl()); |
| } |
| } |
| |
| return 0; |
| } |
| return o1.toString().compareTo(o2.toString()); |
| } |
| }; |
| @NonNls private static final String HELP_ID = "ideaInterface.find"; |
| private UsageContextPanel myCurrentUsageContextPanel; |
| private List<UsageContextPanel.Provider> myUsageContextPanelProviders; |
| private UsageContextPanel.Provider myCurrentUsageContextProvider; |
| |
| private JPanel myCentralPanel; |
| private final GroupNode myRoot; |
| private final UsageViewTreeModelBuilder myModel; |
| private final Object lock = new Object(); |
| private Splitter myPreviewSplitter; |
| private volatile ProgressIndicator associatedProgress; // the progress that current find usages is running under |
| |
| // true if usages tree is currently expanding |
| // (either at the end of find usages thanks to the 'expand usages after find' setting or |
| // because the user pressed 'expand all' button. During this, some ugly hacks applied |
| // to speed up the expanding (see getExpandedDescendants() here and UsageViewTreeCellRenderer.customizeCellRenderer()) |
| private boolean expandingAll; |
| private final UsageViewTreeCellRenderer myUsageViewTreeCellRenderer; |
| |
| UsageViewImpl(@NotNull final Project project, |
| @NotNull UsageViewPresentation presentation, |
| @NotNull UsageTarget[] targets, |
| Factory<UsageSearcher> usageSearcherFactory) { |
| myPresentation = presentation; |
| myTargets = targets; |
| myUsageSearcherFactory = usageSearcherFactory; |
| myProject = project; |
| myTree = new Tree() { |
| { |
| ToolTipManager.sharedInstance().registerComponent(this); |
| } |
| @Override |
| public String getToolTipText(MouseEvent e) { |
| TreePath path = getPathForLocation(e.getX(), e.getY()); |
| if (path != null) { |
| if (getCellRenderer() instanceof UsageViewTreeCellRenderer) { |
| return UsageViewTreeCellRenderer.getTooltipFromPresentation(path.getLastPathComponent()); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean isPathEditable(final TreePath path) { |
| return path.getLastPathComponent() instanceof UsageViewTreeModelBuilder.TargetsRootNode; |
| } |
| |
| // hack to avoid quadratic expandAll() |
| @Override |
| public Enumeration<TreePath> getExpandedDescendants(TreePath parent) { |
| return expandingAll ? EmptyEnumeration.<TreePath>getInstance() : super.getExpandedDescendants(parent); |
| } |
| }; |
| myRootPanel = new MyPanel(myTree); |
| Disposer.register(this, myRootPanel); |
| myModelTracker = new UsageModelTracker(project); |
| Disposer.register(this, myModelTracker); |
| |
| myModel = new UsageViewTreeModelBuilder(myPresentation, targets); |
| myRoot = (GroupNode)myModel.getRoot(); |
| myBuilder = new UsageNodeTreeBuilder(myTargets, getActiveGroupingRules(project), getActiveFilteringRules(project), myRoot); |
| |
| final MessageBusConnection messageBusConnection = myProject.getMessageBus().connect(this); |
| messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, new Runnable() { |
| @Override |
| public void run() { |
| rulesChanged(); |
| } |
| }); |
| |
| myUsageViewTreeCellRenderer = new UsageViewTreeCellRenderer(this); |
| if (!myPresentation.isDetachedMode()) { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| // lock here to avoid concurrent execution of this init and dispose in other thread |
| synchronized (lock) { |
| if (isDisposed) return; |
| myTree.setModel(myModel); |
| |
| myRootPanel.setLayout(new BorderLayout()); |
| |
| SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(false, true); |
| myRootPanel.add(toolWindowPanel, BorderLayout.CENTER); |
| |
| JPanel toolbarPanel = new JPanel(new BorderLayout()); |
| toolbarPanel.add(createActionsToolbar(), BorderLayout.WEST); |
| toolbarPanel.add(createFiltersToolbar(), BorderLayout.CENTER); |
| toolWindowPanel.setToolbar(toolbarPanel); |
| |
| myCentralPanel = new JPanel(new BorderLayout()); |
| setupCentralPanel(); |
| |
| initTree(); |
| toolWindowPanel.setContent(myCentralPanel); |
| |
| myTree.setCellRenderer(myUsageViewTreeCellRenderer); |
| collapseAll(); |
| |
| myModelTracker.addListener(UsageViewImpl.this); |
| |
| if (myPresentation.isShowCancelButton()) { |
| addButtonToLowerPane(new Runnable() { |
| @Override |
| public void run() { |
| close(); |
| } |
| }, UsageViewBundle.message("usage.view.cancel.button")); |
| } |
| |
| myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { |
| @Override |
| public void valueChanged(final TreeSelectionEvent e) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed || myProject.isDisposed()) return; |
| updateOnSelectionChanged(); |
| } |
| }); |
| } |
| }); |
| } |
| } |
| }); |
| } |
| myTransferToEDTQueue = new TransferToEDTQueue<Runnable>("Insert usages", new Processor<Runnable>() { |
| @Override |
| public boolean process(Runnable runnable) { |
| runnable.run(); |
| return true; |
| } |
| }, new Condition<Object>() { |
| @Override |
| public boolean value(Object o) { |
| return isDisposed || project.isDisposed(); |
| } |
| },200); |
| } |
| |
| protected boolean searchHasBeenCancelled() { |
| ProgressIndicator progress = associatedProgress; |
| return progress != null && progress.isCanceled(); |
| } |
| |
| protected void cancelCurrentSearch() { |
| ProgressIndicator progress = associatedProgress; |
| if (progress != null) { |
| ProgressWrapper.unwrap(progress).cancel(); |
| } |
| } |
| |
| private void setupCentralPanel() { |
| myCentralPanel.removeAll(); |
| disposeUsageContextPanels(); |
| |
| JScrollPane treePane = ScrollPaneFactory.createScrollPane(myTree); |
| // add reaction to scrolling: |
| // since the UsageViewTreeCellRenderer ignores invisible nodes (outside the viewport), their preferred size is incorrect |
| // and we need to recalculate it when the node scrolled into visible rectangle |
| treePane.getViewport().addChangeListener(new ChangeListener() { |
| @Override |
| public void stateChanged(ChangeEvent e) { |
| // clear renderer cache of node preferred size |
| myTree.setCellRenderer(myUsageViewTreeCellRenderer); |
| } |
| }); |
| myPreviewSplitter = new Splitter(false, 0.5f, 0.1f, 0.9f); |
| myPreviewSplitter.setFirstComponent(treePane); |
| |
| myCentralPanel.add(myPreviewSplitter, BorderLayout.CENTER); |
| |
| if (UsageViewSettings.getInstance().IS_PREVIEW_USAGES) { |
| myPreviewSplitter.setProportion(UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS); |
| treePane.putClientProperty(UIUtil.KEEP_BORDER_SIDES, SideBorder.RIGHT); |
| final JBTabbedPane tabbedPane = new JBTabbedPane(SwingConstants.BOTTOM){ |
| @NotNull |
| @Override |
| protected Insets getInsetsForTabComponent() { |
| return new Insets(0,0,0,0); |
| } |
| }; |
| |
| UsageContextPanel.Provider[] extensions = Extensions.getExtensions(UsageContextPanel.Provider.EP_NAME, myProject); |
| myUsageContextPanelProviders = ContainerUtil.filter(extensions, new Condition<UsageContextPanel.Provider>() { |
| @Override |
| public boolean value(UsageContextPanel.Provider provider) { |
| return provider.isAvailableFor(UsageViewImpl.this); |
| } |
| }); |
| for (UsageContextPanel.Provider provider : myUsageContextPanelProviders) { |
| JComponent component; |
| if (myCurrentUsageContextProvider == null || myCurrentUsageContextProvider == provider) { |
| myCurrentUsageContextProvider = provider; |
| myCurrentUsageContextPanel = provider.create(this); |
| component = myCurrentUsageContextPanel.createComponent(); |
| } |
| else { |
| component = new JLabel(); |
| } |
| |
| tabbedPane.addTab(provider.getTabTitle(), component); |
| } |
| int index = myUsageContextPanelProviders.indexOf(myCurrentUsageContextProvider); |
| tabbedPane.setSelectedIndex(index); |
| tabbedPane.addChangeListener(new ChangeListener() { |
| @Override |
| public void stateChanged(ChangeEvent e) { |
| int currentIndex = tabbedPane.getSelectedIndex(); |
| UsageContextPanel.Provider selectedProvider = myUsageContextPanelProviders.get(currentIndex); |
| if (selectedProvider != myCurrentUsageContextProvider) { |
| tabSelected(selectedProvider); |
| } |
| } |
| }); |
| tabbedPane.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT)); |
| myPreviewSplitter.setSecondComponent(tabbedPane); |
| } |
| else { |
| myPreviewSplitter.setProportion(1); |
| } |
| |
| myCentralPanel.add(myButtonPanel, BorderLayout.SOUTH); |
| |
| myRootPanel.revalidate(); |
| myRootPanel.repaint(); |
| } |
| |
| private void tabSelected(@NotNull final UsageContextPanel.Provider provider) { |
| myCurrentUsageContextProvider = provider; |
| setupCentralPanel(); |
| updateOnSelectionChanged(); |
| } |
| |
| private void disposeUsageContextPanels() { |
| if (myCurrentUsageContextPanel != null) { |
| saveSplitterProportions(); |
| Disposer.dispose(myCurrentUsageContextPanel); |
| myCurrentUsageContextPanel = null; |
| } |
| } |
| |
| private static UsageFilteringRule[] getActiveFilteringRules(final Project project) { |
| final UsageFilteringRuleProvider[] providers = Extensions.getExtensions(UsageFilteringRuleProvider.EP_NAME); |
| List<UsageFilteringRule> list = new ArrayList<UsageFilteringRule>(providers.length); |
| for (UsageFilteringRuleProvider provider : providers) { |
| ContainerUtil.addAll(list, provider.getActiveRules(project)); |
| } |
| return list.toArray(new UsageFilteringRule[list.size()]); |
| } |
| |
| private static UsageGroupingRule[] getActiveGroupingRules(@NotNull final Project project) { |
| final UsageGroupingRuleProvider[] providers = Extensions.getExtensions(UsageGroupingRuleProvider.EP_NAME); |
| List<UsageGroupingRule> list = new ArrayList<UsageGroupingRule>(providers.length); |
| for (UsageGroupingRuleProvider provider : providers) { |
| ContainerUtil.addAll(list, provider.getActiveRules(project)); |
| } |
| |
| Collections.sort(list, new Comparator<UsageGroupingRule>() { |
| @Override |
| public int compare(final UsageGroupingRule o1, final UsageGroupingRule o2) { |
| return getRank(o1) - getRank(o2); |
| } |
| |
| private int getRank(final UsageGroupingRule rule) { |
| if (rule instanceof OrderableUsageGroupingRule) { |
| return ((OrderableUsageGroupingRule)rule).getRank(); |
| } |
| |
| return Integer.MAX_VALUE; |
| } |
| }); |
| |
| return list.toArray(new UsageGroupingRule[list.size()]); |
| } |
| |
| @Override |
| public void modelChanged(boolean isPropertyChange) { |
| if (!isPropertyChange) { |
| myChangesDetected = true; |
| } |
| updateLater(); |
| } |
| |
| private void initTree() { |
| myTree.setRootVisible(false); |
| myTree.setShowsRootHandles(true); |
| SmartExpander.installOn(myTree); |
| TreeUtil.installActions(myTree); |
| EditSourceOnDoubleClickHandler.install(myTree); |
| myTree.addKeyListener(new KeyAdapter() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (KeyEvent.VK_ENTER == e.getKeyCode()) { |
| TreePath leadSelectionPath = myTree.getLeadSelectionPath(); |
| if (leadSelectionPath == null) return; |
| |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)leadSelectionPath.getLastPathComponent(); |
| if (node instanceof UsageNode) { |
| final Usage usage = ((UsageNode)node).getUsage(); |
| usage.navigate(false); |
| usage.highlightInEditor(); |
| } |
| else if (node.isLeaf()) { |
| Navigatable navigatable = getNavigatableForNode(node); |
| if (navigatable != null && navigatable.canNavigate()) { |
| navigatable.navigate(false); |
| } |
| } |
| } |
| } |
| }); |
| |
| TreeUtil.selectFirstNode(myTree); |
| PopupHandler.installPopupHandler(myTree, IdeActions.GROUP_USAGE_VIEW_POPUP, ActionPlaces.USAGE_VIEW_POPUP); |
| |
| myTree.addTreeExpansionListener(new TreeExpansionListener() { |
| @Override |
| public void treeExpanded(TreeExpansionEvent event) { |
| TreePath path = event.getPath(); |
| Object component = path.getLastPathComponent(); |
| if (!(component instanceof Node)) return; |
| Node node = (Node)component; |
| if (!expandingAll && node.needsUpdate()) { |
| checkNodeValidity(node, path); |
| } |
| } |
| |
| @Override |
| public void treeCollapsed(TreeExpansionEvent event) { |
| } |
| }); |
| |
| TreeUIHelper.getInstance().installTreeSpeedSearch(myTree, new Convertor<TreePath, String>() { |
| @Override |
| public String convert(TreePath o) { |
| Object value = o.getLastPathComponent(); |
| TreeCellRenderer renderer = myTree.getCellRenderer(); |
| if (renderer instanceof UsageViewTreeCellRenderer) { |
| UsageViewTreeCellRenderer coloredRenderer = (UsageViewTreeCellRenderer)renderer; |
| return coloredRenderer.getPlainTextForNode(value); |
| } |
| return value == null ? null : value.toString(); |
| } |
| }, true); |
| } |
| |
| @NotNull |
| private JComponent createActionsToolbar() { |
| DefaultActionGroup group = new DefaultActionGroup() { |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| myButtonPanel.update(); |
| } |
| |
| @Override |
| public boolean isDumbAware() { |
| return true; |
| } |
| }; |
| |
| AnAction[] actions = createActions(); |
| for (final AnAction action : actions) { |
| if (action != null) { |
| group.add(action); |
| } |
| } |
| return toUsageViewToolbar(group); |
| } |
| |
| @NotNull |
| private JComponent toUsageViewToolbar(@NotNull DefaultActionGroup group) { |
| ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, group, false); |
| actionToolbar.setTargetComponent(myRootPanel); |
| return actionToolbar.getComponent(); |
| } |
| |
| @NotNull |
| private JComponent createFiltersToolbar() { |
| final DefaultActionGroup group = new DefaultActionGroup(); |
| |
| final AnAction[] groupingActions = createGroupingActions(); |
| for (AnAction groupingAction : groupingActions) { |
| group.add(groupingAction); |
| } |
| |
| addFilteringActions(group); |
| group.add(new PreviewUsageAction(this)); |
| |
| group.add(new SortMembersAlphabeticallyAction(this)); |
| return toUsageViewToolbar(group); |
| } |
| |
| public void addFilteringActions(@NotNull DefaultActionGroup group) { |
| final JComponent component = getComponent(); |
| |
| if (getPresentation().isMergeDupLinesAvailable()) { |
| final MergeDupLines mergeDupLines = new MergeDupLines(); |
| mergeDupLines.registerCustomShortcutSet(mergeDupLines.getShortcutSet(), component, this); |
| group.add(mergeDupLines); |
| } |
| |
| final UsageFilteringRuleProvider[] providers = Extensions.getExtensions(UsageFilteringRuleProvider.EP_NAME); |
| for (UsageFilteringRuleProvider provider : providers) { |
| AnAction[] actions = provider.createFilteringActions(this); |
| for (AnAction action : actions) { |
| group.add(action); |
| } |
| } |
| } |
| |
| public void scheduleDisposeOnClose(@NotNull Disposable disposable) { |
| Disposer.register(this, disposable); |
| } |
| |
| @NotNull |
| private AnAction[] createActions() { |
| final TreeExpander treeExpander = new TreeExpander() { |
| @Override |
| public void expandAll() { |
| UsageViewImpl.this.expandAll(); |
| UsageViewSettings.getInstance().setExpanded(true); |
| } |
| |
| @Override |
| public boolean canExpand() { |
| return true; |
| } |
| |
| @Override |
| public void collapseAll() { |
| UsageViewImpl.this.collapseAll(); |
| UsageViewSettings.getInstance().setExpanded(false); |
| } |
| |
| @Override |
| public boolean canCollapse() { |
| return true; |
| } |
| }; |
| |
| CommonActionsManager actionsManager = CommonActionsManager.getInstance(); |
| |
| final JComponent component = getComponent(); |
| |
| final AnAction expandAllAction = actionsManager.createExpandAllAction(treeExpander, component); |
| final AnAction collapseAllAction = actionsManager.createCollapseAllAction(treeExpander, component); |
| |
| scheduleDisposeOnClose(new Disposable() { |
| @Override |
| public void dispose() { |
| expandAllAction.unregisterCustomShortcutSet(component); |
| collapseAllAction.unregisterCustomShortcutSet(component); |
| } |
| }); |
| |
| |
| return new AnAction[] { |
| canShowSettings() ? showSettings() : null, |
| canPerformReRun() ? new ReRunAction() : null, |
| new CloseAction(), |
| ActionManager.getInstance().getAction("PinToolwindowTab"), |
| createRecentFindUsagesAction(), |
| expandAllAction, |
| collapseAllAction, |
| actionsManager.createPrevOccurenceAction(myRootPanel), |
| actionsManager.createNextOccurenceAction(myRootPanel), |
| actionsManager.installAutoscrollToSourceHandler(myProject, myTree, new MyAutoScrollToSourceOptionProvider()), |
| actionsManager.createExportToTextFileAction(myTextFileExporter), |
| actionsManager.createHelpAction(HELP_ID) |
| }; |
| } |
| |
| private boolean canShowSettings() { |
| if (myTargets.length == 0) return false; |
| NavigationItem target = myTargets[0]; |
| return target instanceof ConfigurableUsageTarget; |
| } |
| |
| @NotNull |
| private AnAction showSettings() { |
| final ConfigurableUsageTarget configurableUsageTarget = getConfigurableTarget(myTargets); |
| String description = configurableUsageTarget == null ? "Show find usages settings dialog" : "Show settings for "+configurableUsageTarget.getLongDescriptiveName(); |
| return new AnAction("Settings...", description, AllIcons.General.ProjectSettings) { |
| { |
| KeyboardShortcut shortcut = configurableUsageTarget == null ? getShowUsagesWithSettingsShortcut() : configurableUsageTarget.getShortcut(); |
| if (shortcut != null) { |
| registerCustomShortcutSet(new CustomShortcutSet(shortcut), getComponent()); |
| } |
| } |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| FindManager.getInstance(getProject()).showSettingsAndFindUsages(myTargets); |
| } |
| }; |
| } |
| |
| private static ConfigurableUsageTarget getConfigurableTarget(@NotNull UsageTarget[] targets) { |
| ConfigurableUsageTarget configurableUsageTarget = null; |
| if (targets.length != 0) { |
| NavigationItem target = targets[0]; |
| if (target instanceof ConfigurableUsageTarget) { |
| configurableUsageTarget = (ConfigurableUsageTarget)target; |
| } |
| } |
| return configurableUsageTarget; |
| } |
| |
| @NotNull |
| private AnAction createRecentFindUsagesAction() { |
| AnAction action = ActionManager.getInstance().getAction(SHOW_RECENT_FIND_USAGES_ACTION_ID); |
| action.registerCustomShortcutSet(action.getShortcutSet(), getComponent()); |
| return action; |
| } |
| |
| @NotNull |
| private AnAction[] createGroupingActions() { |
| final UsageGroupingRuleProvider[] providers = Extensions.getExtensions(UsageGroupingRuleProvider.EP_NAME); |
| List<AnAction> list = new ArrayList<AnAction>(providers.length); |
| for (UsageGroupingRuleProvider provider : providers) { |
| ContainerUtil.addAll(list, provider.createGroupingActions(this)); |
| } |
| return list.toArray(new AnAction[list.size()]); |
| } |
| |
| private void rulesChanged() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| final List<UsageState> states = new ArrayList<UsageState>(); |
| captureUsagesExpandState(new TreePath(myTree.getModel().getRoot()), states); |
| final List<Usage> allUsages = new ArrayList<Usage>(myUsageNodes.keySet()); |
| Collections.sort(allUsages, USAGE_COMPARATOR); |
| final Set<Usage> excludedUsages = getExcludedUsages(); |
| reset(); |
| myBuilder.setGroupingRules(getActiveGroupingRules(myProject)); |
| myBuilder.setFilteringRules(getActiveFilteringRules(myProject)); |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| for (Usage usage : allUsages) { |
| if (!usage.isValid()) { |
| continue; |
| } |
| if (usage instanceof MergeableUsage) { |
| ((MergeableUsage)usage).reset(); |
| } |
| appendUsage(usage); |
| } |
| } |
| }); |
| excludeUsages(excludedUsages.toArray(new Usage[excludedUsages.size()])); |
| if (myCentralPanel != null) { |
| setupCentralPanel(); |
| } |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed) return; |
| restoreUsageExpandState(states); |
| updateImmediately(); |
| } |
| }); |
| } |
| |
| private void captureUsagesExpandState(TreePath pathFrom, final Collection<UsageState> states) { |
| if (!myTree.isExpanded(pathFrom)) { |
| return; |
| } |
| final DefaultMutableTreeNode node = (DefaultMutableTreeNode)pathFrom.getLastPathComponent(); |
| final int childCount = node.getChildCount(); |
| for (int idx = 0; idx < childCount; idx++) { |
| final TreeNode child = node.getChildAt(idx); |
| if (child instanceof UsageNode) { |
| final Usage usage = ((UsageNode)child).getUsage(); |
| states.add(new UsageState(usage, myTree.getSelectionModel().isPathSelected(pathFrom.pathByAddingChild(child)))); |
| } |
| else { |
| captureUsagesExpandState(pathFrom.pathByAddingChild(child), states); |
| } |
| } |
| } |
| |
| private void restoreUsageExpandState(@NotNull Collection<UsageState> states) { |
| //always expand the last level group |
| final DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTree.getModel().getRoot(); |
| for (int i = root.getChildCount() - 1; i >= 0; i--) { |
| final DefaultMutableTreeNode child = (DefaultMutableTreeNode)root.getChildAt(i); |
| if (child instanceof GroupNode){ |
| final TreePath treePath = new TreePath(child.getPath()); |
| myTree.expandPath(treePath); |
| } |
| } |
| myTree.getSelectionModel().clearSelection(); |
| for (final UsageState usageState : states) { |
| usageState.restore(); |
| } |
| } |
| |
| private void expandAll() { |
| expandingAll = true; |
| try { |
| TreeUtil.expandAll(myTree); |
| } |
| finally { |
| expandingAll = false; |
| } |
| } |
| |
| private void collapseAll() { |
| TreeUtil.collapseAll(myTree, 3); |
| TreeUtil.expand(myTree, 2); |
| } |
| |
| public DefaultMutableTreeNode getModelRoot() { |
| return (DefaultMutableTreeNode)myTree.getModel().getRoot(); |
| } |
| |
| public void select() { |
| if (myTree != null) { |
| myTree.requestFocusInWindow(); |
| } |
| } |
| |
| @NotNull |
| public Project getProject() { |
| return myProject; |
| } |
| |
| @Nullable |
| public static KeyboardShortcut getShowUsagesWithSettingsShortcut() { |
| return ActionManager.getInstance().getKeyboardShortcut("ShowSettingsAndFindUsages"); |
| } |
| |
| static KeyboardShortcut getShowUsagesWithSettingsShortcut(@NotNull UsageTarget[] targets) { |
| ConfigurableUsageTarget configurableTarget = getConfigurableTarget(targets); |
| return configurableTarget == null ? getShowUsagesWithSettingsShortcut() : configurableTarget.getShortcut(); |
| } |
| |
| void associateProgress(ProgressIndicator indicator) { |
| associatedProgress = indicator; |
| } |
| |
| private class CloseAction extends CloseTabToolbarAction { |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| e.getPresentation().setVisible(myContent != null); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| close(); |
| } |
| } |
| |
| private class MergeDupLines extends RuleAction { |
| private MergeDupLines() { |
| super(UsageViewImpl.this, UsageViewBundle.message("action.merge.same.line"), AllIcons.Toolbar.Filterdups); |
| setShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK))); |
| } |
| |
| @Override |
| protected boolean getOptionValue() { |
| return UsageViewSettings.getInstance().isFilterDuplicatedLine(); |
| } |
| |
| @Override |
| protected void setOptionValue(boolean value) { |
| UsageViewSettings.getInstance().setFilterDuplicatedLine(value); |
| } |
| } |
| |
| private class ReRunAction extends AnAction implements DumbAware { |
| private ReRunAction() { |
| super(UsageViewBundle.message("action.rerun"), UsageViewBundle.message("action.description.rerun"), AllIcons.Actions.Rerun); |
| registerCustomShortcutSet(CommonShortcuts.getRerun(), myRootPanel); |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| refreshUsages(); |
| } |
| |
| @Override |
| public void update(AnActionEvent e) { |
| super.update(e); |
| final Presentation presentation = e.getPresentation(); |
| presentation.setEnabled(allTargetsAreValid()); |
| } |
| } |
| |
| private void refreshUsages() { |
| reset(); |
| doReRun(); |
| } |
| |
| private void doReRun() { |
| final AtomicInteger usageCountWithoutDefinition = new AtomicInteger(0); |
| final Project project = myProject; |
| Task.Backgroundable task = new Task.Backgroundable(project, UsageViewManagerImpl.getProgressTitle(myPresentation)) { |
| @Override |
| public void run(@NotNull final ProgressIndicator indicator) { |
| final TooManyUsagesStatus tooManyUsagesStatus = TooManyUsagesStatus.createFor(indicator); |
| setSearchInProgress(true); |
| associateProgress(indicator); |
| |
| myChangesDetected = false; |
| UsageSearcher usageSearcher = myUsageSearcherFactory.create(); |
| usageSearcher.generate(new Processor<Usage>() { |
| @Override |
| public boolean process(final Usage usage) { |
| if (searchHasBeenCancelled()) return false; |
| TooManyUsagesStatus.getFrom(indicator).pauseProcessingIfTooManyUsages(); |
| |
| boolean incrementCounter = !com.intellij.usages.UsageViewManager.isSelfUsage(usage, myTargets); |
| |
| if (incrementCounter) { |
| final int usageCount = usageCountWithoutDefinition.incrementAndGet(); |
| if (usageCount > UsageLimitUtil.USAGES_LIMIT) { |
| if (tooManyUsagesStatus.switchTooManyUsagesStatus()) { |
| UsageViewManagerImpl |
| .showTooManyUsagesWarning(project, tooManyUsagesStatus, indicator, getPresentation(), usageCountWithoutDefinition.get(), UsageViewImpl.this); |
| } |
| } |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| appendUsage(usage); |
| } |
| }); |
| } |
| return !indicator.isCanceled(); |
| } |
| }); |
| drainQueuedUsageNodes(); |
| setSearchInProgress(false); |
| } |
| }; |
| ProgressManager.getInstance().run(task); |
| } |
| |
| private void reset() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| myUsageNodes.clear(); |
| myModel.reset(); |
| if (!myPresentation.isDetachedMode()) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed) return; |
| TreeUtil.expand(myTree, 2); |
| } |
| }); |
| } |
| } |
| |
| private final TransferToEDTQueue<Runnable> myTransferToEDTQueue; |
| public void drainQueuedUsageNodes() { |
| assert !ApplicationManager.getApplication().isDispatchThread() : Thread.currentThread(); |
| UIUtil.invokeAndWaitIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| myTransferToEDTQueue.drain(); |
| } |
| }); |
| } |
| |
| @Override |
| public void appendUsage(@NotNull Usage usage) { |
| doAppendUsage(usage); |
| } |
| |
| @Nullable |
| public UsageNode doAppendUsage(@NotNull Usage usage) { |
| // invoke in ReadAction to be be sure that usages are not invalidated while the tree is being built |
| ApplicationManager.getApplication().assertReadAccessAllowed(); |
| if (!usage.isValid()) { |
| // because the view is built incrementally, the usage may be already invalid, so need to filter such cases |
| return null; |
| } |
| UsageNode node = myBuilder.appendUsage(usage, new Consumer<Runnable>() { |
| @Override |
| public void consume(Runnable runnable) { |
| myTransferToEDTQueue.offer(runnable); |
| } |
| }); |
| if (node != null) { |
| // update and cache flags while the node is still hot |
| node.update(this); |
| } |
| myUsageNodes.put(usage, node == null ? NULL_NODE : node); |
| return node; |
| } |
| |
| @Override |
| public void removeUsage(@NotNull Usage usage) { |
| final UsageNode node = myUsageNodes.remove(usage); |
| if (node != NULL_NODE && node != null && !myPresentation.isDetachedMode()) { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed) return; |
| TreeModel treeModel = myTree.getModel(); |
| ((DefaultTreeModel)treeModel).removeNodeFromParent(node); |
| ((GroupNode)myTree.getModel().getRoot()).removeUsage(node); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void removeUsagesBulk(@NotNull Collection<Usage> usages) { |
| final Set<UsageNode> nodes = new THashSet<UsageNode>(usages.size()); |
| for (Usage usage : usages) { |
| UsageNode node = myUsageNodes.remove(usage); |
| if (node != null && node != NULL_NODE) { |
| nodes.add(node); |
| } |
| } |
| if (!nodes.isEmpty() && !myPresentation.isDetachedMode()) { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed) return; |
| DefaultTreeModel treeModel = (DefaultTreeModel)myTree.getModel(); |
| for (UsageNode node : nodes) { |
| MutableTreeNode parent = (MutableTreeNode)node.getParent(); |
| int childIndex = parent.getIndex(node); |
| if (childIndex != -1) { |
| parent.remove(childIndex); |
| } |
| } |
| ((GroupNode)myTree.getModel().getRoot()).removeUsagesBulk(nodes); |
| |
| treeModel.reload(); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void includeUsages(@NotNull Usage[] usages) { |
| List<TreeNode> nodes = new ArrayList<TreeNode>(usages.length); |
| for (Usage usage : usages) { |
| final UsageNode node = myUsageNodes.get(usage); |
| if (node != NULL_NODE && node != null) { |
| node.setUsageExcluded(false); |
| nodes.add(node); |
| } |
| } |
| updateImmediatelyNodesUpToRoot(nodes); |
| } |
| |
| @Override |
| public void excludeUsages(@NotNull Usage[] usages) { |
| List<TreeNode> nodes = new ArrayList<TreeNode>(usages.length); |
| for (Usage usage : usages) { |
| final UsageNode node = myUsageNodes.get(usage); |
| if (node != NULL_NODE && node != null) { |
| node.setUsageExcluded(true); |
| nodes.add(node); |
| } |
| } |
| updateImmediatelyNodesUpToRoot(nodes); |
| } |
| |
| @Override |
| public void selectUsages(@NotNull Usage[] usages) { |
| List<TreePath> paths = new LinkedList<TreePath>(); |
| |
| for (Usage usage : usages) { |
| final UsageNode node = myUsageNodes.get(usage); |
| |
| if (node != NULL_NODE && node != null) { |
| paths.add(new TreePath(node.getPath())); |
| } |
| } |
| |
| myTree.setSelectionPaths(paths.toArray(new TreePath[paths.size()])); |
| if (!paths.isEmpty()) myTree.scrollPathToVisible(paths.get(0)); |
| } |
| |
| @Override |
| @NotNull |
| public JComponent getComponent() { |
| return myRootPanel; |
| } |
| |
| @Override |
| public int getUsagesCount() { |
| return myUsageNodes.size(); |
| } |
| |
| void setContent(@NotNull Content content) { |
| myContent = content; |
| content.setDisposer(this); |
| } |
| |
| private void updateImmediately() { |
| if (myProject.isDisposed()) return; |
| TreeNode root = (TreeNode)myTree.getModel().getRoot(); |
| checkNodeValidity(root, new TreePath(root)); |
| updateOnSelectionChanged(); |
| } |
| |
| private void updateImmediatelyNodesUpToRoot(@NotNull List<TreeNode> nodes) { |
| if (myProject.isDisposed()) return; |
| TreeNode root = (TreeNode)myTree.getModel().getRoot(); |
| |
| for (int i=0; i<nodes.size(); i++) { |
| TreeNode node = nodes.get(i); |
| if (node instanceof Node) { |
| ((Node)node).update(this); |
| TreeNode parent = node.getParent(); |
| if (parent != root && parent != null) { |
| nodes.add(parent); |
| } |
| } |
| } |
| updateImmediately(); |
| } |
| |
| |
| private void updateOnSelectionChanged() { |
| List<UsageInfo> infos = getSelectedUsageInfos(); |
| if (myCurrentUsageContextPanel != null) { |
| myCurrentUsageContextPanel.updateLayout(infos); |
| } |
| } |
| |
| private void checkNodeValidity(@NotNull TreeNode node, @NotNull TreePath path) { |
| boolean shouldCheckChildren = true; |
| if (myTree.isCollapsed(path)) { |
| if (node instanceof Node) { |
| ((Node)node).markNeedUpdate(); |
| } |
| shouldCheckChildren = false; |
| // optimization: do not call expensive update() on invisible node |
| } |
| UsageViewTreeCellRenderer.RowLocation isVisible = |
| myUsageViewTreeCellRenderer.isRowVisible(myTree.getRowForPath(new TreePath(((DefaultMutableTreeNode)node).getPath())), |
| myTree.getVisibleRect()); |
| |
| // if row is below visible rectangle, no sense to update it or any children |
| if (shouldCheckChildren && isVisible != UsageViewTreeCellRenderer.RowLocation.AFTER_VISIBLE_RECT) { |
| for (int i=0; i < node.getChildCount(); i++) { |
| TreeNode child = node.getChildAt(i); |
| checkNodeValidity(child, path.pathByAddingChild(child)); |
| } |
| } |
| |
| // call update last, to let children a chance to update their cache first |
| if (node instanceof Node && node != getModelRoot() && isVisible == UsageViewTreeCellRenderer.RowLocation.INSIDE_VISIBLE_RECT) { |
| ((Node)node).update(this); |
| } |
| } |
| |
| private void updateLater() { |
| myUpdateAlarm.cancelAllRequests(); |
| myUpdateAlarm.addRequest(new Runnable() { |
| @Override |
| public void run() { |
| if (myProject.isDisposed()) return; |
| PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(myProject); |
| documentManager.cancelAndRunWhenAllCommitted("UpdateUsageView", new Runnable() { |
| @Override |
| public void run() { |
| updateImmediately(); |
| } |
| }); |
| } |
| }, 300); |
| } |
| |
| @Override |
| public void close() { |
| cancelCurrentSearch(); |
| UsageViewManager.getInstance(myProject).closeContent(myContent); |
| } |
| |
| private void saveSplitterProportions() { |
| UsageViewSettings.getInstance().PREVIEW_USAGES_SPLITTER_PROPORTIONS = myPreviewSplitter.getProportion(); |
| } |
| |
| @Override |
| public void dispose() { |
| disposeUsageContextPanels(); |
| synchronized (lock) { |
| isDisposed = true; |
| ToolTipManager.sharedInstance().unregisterComponent(myTree); |
| myModelTracker.removeListener(this); |
| myUpdateAlarm.cancelAllRequests(); |
| } |
| disposeSmartPointers(); |
| } |
| |
| private void disposeSmartPointers() { |
| SmartPointerManager pointerManager = SmartPointerManager.getInstance(getProject()); |
| for (Usage usage : myUsageNodes.keySet()) { |
| if (usage instanceof UsageInfo2UsageAdapter) { |
| SmartPsiElementPointer<?> pointer = ((UsageInfo2UsageAdapter)usage).getUsageInfo().getSmartPointer(); |
| pointerManager.removePointer(pointer); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isSearchInProgress() { |
| return mySearchInProgress; |
| } |
| |
| public void setSearchInProgress(boolean searchInProgress) { |
| mySearchInProgress = searchInProgress; |
| if (!myPresentation.isDetachedMode()) { |
| myTransferToEDTQueue.offer(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed) return; |
| final UsageNode firstUsageNode = myModel.getFirstUsageNode(); |
| if (firstUsageNode == null) return; |
| |
| Node node = getSelectedNode(); |
| if (node != null && !Comparing.equal(new TreePath(node.getPath()), TreeUtil.getFirstNodePath(myTree))) { |
| // user has selected node already |
| return; |
| } |
| showNode(firstUsageNode); |
| if (UsageViewSettings.getInstance().isExpanded() && myUsageNodes.size() < 10000) { |
| expandAll(); |
| } |
| } |
| }); |
| } |
| } |
| |
| public boolean isDisposed() { |
| return isDisposed; |
| } |
| |
| private void showNode(@NotNull final UsageNode node) { |
| if (!myPresentation.isDetachedMode()) { |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| if (isDisposed) return; |
| TreePath usagePath = new TreePath(node.getPath()); |
| myTree.expandPath(usagePath.getParentPath()); |
| TreeUtil.selectPath(myTree, usagePath); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void addButtonToLowerPane(@NotNull Runnable runnable, @NotNull String text) { |
| int index = myButtonPanel.getComponentCount(); |
| if (!SystemInfo.isMac && index > 0 && myPresentation.isShowCancelButton()) index--; |
| myButtonPanel.addButtonRunnable(index, runnable, text); |
| } |
| |
| @Override |
| public void addButtonToLowerPane(@NotNull final Runnable runnable, @NotNull String text, char mnemonic) { |
| // implemented method is deprecated, so, it just calls non-deprecated overloading one |
| addButtonToLowerPane(runnable, text); |
| } |
| |
| @Override |
| public void addPerformOperationAction(@NotNull final Runnable processRunnable, |
| final String commandName, |
| final String cannotMakeString, |
| @NotNull String shortDescription) { |
| addPerformOperationAction(processRunnable, commandName, cannotMakeString, shortDescription, true); |
| } |
| |
| @Override |
| public void addPerformOperationAction(@NotNull Runnable processRunnable, |
| String commandName, |
| String cannotMakeString, |
| @NotNull String shortDescription, |
| boolean checkReadOnlyStatus) { |
| addButtonToLowerPane(newPerformOperationRunnable(processRunnable, commandName, cannotMakeString, checkReadOnlyStatus), shortDescription); |
| } |
| |
| public MyPerformOperationRunnable newPerformOperationRunnable(Runnable processRunnable, |
| String commandName, |
| String cannotMakeString, boolean checkReadOnlyStatus) { |
| return new MyPerformOperationRunnable(cannotMakeString, processRunnable, commandName, checkReadOnlyStatus); |
| } |
| |
| private boolean allTargetsAreValid() { |
| for (UsageTarget target : myTargets) { |
| if (!target.isValid()) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @NotNull |
| @Override |
| public UsageViewPresentation getPresentation() { |
| return myPresentation; |
| } |
| |
| private boolean canPerformReRun() { |
| return myUsageSearcherFactory != null; |
| } |
| |
| private boolean checkReadonlyUsages() { |
| final Set<VirtualFile> readOnlyUsages = getReadOnlyUsagesFiles(); |
| |
| return readOnlyUsages.isEmpty() || |
| !ReadonlyStatusHandler.getInstance(myProject).ensureFilesWritable(VfsUtilCore.toVirtualFileArray(readOnlyUsages)).hasReadonlyFiles(); |
| } |
| |
| @NotNull |
| private Set<Usage> getReadOnlyUsages() { |
| final Set<Usage> result = new THashSet<Usage>(); |
| final Set<Map.Entry<Usage,UsageNode>> usages = myUsageNodes.entrySet(); |
| for (Map.Entry<Usage, UsageNode> entry : usages) { |
| Usage usage = entry.getKey(); |
| UsageNode node = entry.getValue(); |
| if (node != null && node != NULL_NODE && !node.isExcluded() && usage.isReadOnly()) { |
| result.add(usage); |
| } |
| } |
| return result; |
| } |
| |
| @NotNull |
| private Set<VirtualFile> getReadOnlyUsagesFiles() { |
| Set<Usage> usages = getReadOnlyUsages(); |
| Set<VirtualFile> result = new THashSet<VirtualFile>(); |
| for (Usage usage : usages) { |
| if (usage instanceof UsageInFile) { |
| UsageInFile usageInFile = (UsageInFile)usage; |
| VirtualFile file = usageInFile.getFile(); |
| if (file != null) result.add(file); |
| } |
| |
| if (usage instanceof UsageInFiles) { |
| UsageInFiles usageInFiles = (UsageInFiles)usage; |
| ContainerUtil.addAll(result, usageInFiles.getFiles()); |
| } |
| } |
| for (UsageTarget target : myTargets) { |
| VirtualFile[] files = target.getFiles(); |
| if (files == null) continue; |
| ContainerUtil.addAll(result, files); |
| } |
| return result; |
| } |
| |
| @Override |
| @NotNull |
| public Set<Usage> getExcludedUsages() { |
| Set<Usage> result = new THashSet<Usage>(); |
| for (Map.Entry<Usage, UsageNode> entry : myUsageNodes.entrySet()) { |
| UsageNode node = entry.getValue(); |
| Usage usage = entry.getKey(); |
| if (node == NULL_NODE || node == null) { |
| continue; |
| } |
| if (node.isExcluded()) { |
| result.add(usage); |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| @Nullable |
| private Node getSelectedNode() { |
| TreePath leadSelectionPath = myTree.getLeadSelectionPath(); |
| if (leadSelectionPath == null) return null; |
| |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)leadSelectionPath.getLastPathComponent(); |
| return node instanceof Node ? (Node)node : null; |
| } |
| |
| @Nullable |
| private Node[] getSelectedNodes() { |
| TreePath[] leadSelectionPath = myTree.getSelectionPaths(); |
| if (leadSelectionPath == null || leadSelectionPath.length == 0) return null; |
| |
| final List<Node> result = new ArrayList<Node>(); |
| for (TreePath comp : leadSelectionPath) { |
| final Object lastPathComponent = comp.getLastPathComponent(); |
| if (lastPathComponent instanceof Node) { |
| final Node node = (Node)lastPathComponent; |
| result.add(node); |
| } |
| } |
| return result.isEmpty() ? null : result.toArray(new Node[result.size()]); |
| } |
| |
| @Override |
| @Nullable |
| public Set<Usage> getSelectedUsages() { |
| TreePath[] selectionPaths = myTree.getSelectionPaths(); |
| if (selectionPaths == null) { |
| return null; |
| } |
| |
| Set<Usage> usages = new THashSet<Usage>(); |
| for (TreePath selectionPath : selectionPaths) { |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)selectionPath.getLastPathComponent(); |
| collectUsages(node, usages); |
| } |
| |
| return usages; |
| } |
| |
| @Override |
| @NotNull |
| public Set<Usage> getUsages() { |
| return myUsageNodes.keySet(); |
| } |
| |
| @Override |
| @NotNull |
| public List<Usage> getSortedUsages() { |
| List<Usage> usages = new ArrayList<Usage>(getUsages()); |
| Collections.sort(usages, USAGE_COMPARATOR); |
| return usages; |
| } |
| |
| private static void collectUsages(@NotNull DefaultMutableTreeNode node, @NotNull Set<Usage> usages) { |
| if (node instanceof UsageNode) { |
| UsageNode usageNode = (UsageNode)node; |
| final Usage usage = usageNode.getUsage(); |
| usages.add(usage); |
| } |
| |
| Enumeration enumeration = node.children(); |
| while (enumeration.hasMoreElements()) { |
| DefaultMutableTreeNode child = (DefaultMutableTreeNode)enumeration.nextElement(); |
| collectUsages(child, usages); |
| } |
| } |
| |
| @Nullable |
| private UsageTarget[] getSelectedUsageTargets() { |
| TreePath[] selectionPaths = myTree.getSelectionPaths(); |
| if (selectionPaths == null) return null; |
| |
| Set<UsageTarget> targets = new THashSet<UsageTarget>(); |
| for (TreePath selectionPath : selectionPaths) { |
| Object lastPathComponent = selectionPath.getLastPathComponent(); |
| if (lastPathComponent instanceof UsageTargetNode) { |
| UsageTargetNode usageTargetNode = (UsageTargetNode)lastPathComponent; |
| UsageTarget target = usageTargetNode.getTarget(); |
| if (target.isValid()) { |
| targets.add(target); |
| } |
| } |
| } |
| |
| return targets.isEmpty() ? null : targets.toArray(new UsageTarget[targets.size()]); |
| } |
| |
| @Nullable |
| private static Navigatable getNavigatableForNode(@NotNull DefaultMutableTreeNode node) { |
| Object userObject = node.getUserObject(); |
| if (userObject instanceof Navigatable) { |
| final Navigatable navigatable = (Navigatable)userObject; |
| return navigatable.canNavigate() ? navigatable : null; |
| } |
| return null; |
| } |
| |
| /* nodes with non-valid data are not included */ |
| private static Navigatable[] getNavigatablesForNodes(Node[] nodes) { |
| if (nodes == null) { |
| return null; |
| } |
| final List<Navigatable> result = new ArrayList<Navigatable>(); |
| for (final Node node : nodes) { |
| /* |
| if (!node.isDataValid()) { |
| continue; |
| } |
| */ |
| Object userObject = node.getUserObject(); |
| if (userObject instanceof Navigatable) { |
| result.add((Navigatable)userObject); |
| } |
| } |
| return result.toArray(new Navigatable[result.size()]); |
| } |
| |
| public boolean areTargetsValid() { |
| return myModel.areTargetsValid(); |
| } |
| |
| private class MyPanel extends JPanel implements TypeSafeDataProvider, OccurenceNavigator,Disposable, CopyProvider { |
| @Nullable private OccurenceNavigatorSupport mySupport; |
| |
| private MyPanel(@NotNull JTree tree) { |
| mySupport = new OccurenceNavigatorSupport(tree) { |
| @Override |
| protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) { |
| if (node.getChildCount() > 0) return null; |
| if (node instanceof Node && ((Node)node).isExcluded()) return null; |
| return getNavigatableForNode(node); |
| } |
| |
| @Override |
| public String getNextOccurenceActionName() { |
| return UsageViewBundle.message("action.next.occurrence"); |
| } |
| |
| @Override |
| public String getPreviousOccurenceActionName() { |
| return UsageViewBundle.message("action.previous.occurrence"); |
| } |
| }; |
| } |
| |
| @Override |
| public void dispose() { |
| mySupport = null; |
| } |
| |
| @Override |
| public boolean hasNextOccurence() { |
| return mySupport != null && mySupport.hasNextOccurence(); |
| } |
| |
| @Override |
| public boolean hasPreviousOccurence() { |
| return mySupport != null && mySupport.hasPreviousOccurence(); |
| } |
| |
| @Override |
| public OccurenceInfo goNextOccurence() { |
| return mySupport != null ? mySupport.goNextOccurence() : null; |
| } |
| |
| @Override |
| public OccurenceInfo goPreviousOccurence() { |
| return mySupport != null ? mySupport.goPreviousOccurence() : null; |
| } |
| |
| @Override |
| public String getNextOccurenceActionName() { |
| return mySupport != null ? mySupport.getNextOccurenceActionName() : ""; |
| } |
| |
| @Override |
| public String getPreviousOccurenceActionName() { |
| return mySupport != null ? mySupport.getPreviousOccurenceActionName() : ""; |
| } |
| |
| @Override |
| public void performCopy(@NotNull DataContext dataContext) { |
| final Node selectedNode = getSelectedNode(); |
| assert selectedNode != null; |
| final String plainText = selectedNode.getText(UsageViewImpl.this); |
| CopyPasteManager.getInstance().setContents(new StringSelection(plainText.trim())); |
| } |
| |
| @Override |
| public boolean isCopyEnabled(@NotNull DataContext dataContext) { |
| return getSelectedNode() != null; |
| } |
| |
| @Override |
| public boolean isCopyVisible(@NotNull DataContext dataContext) { |
| return true; |
| } |
| |
| @Override |
| public void calcData(final DataKey key, final DataSink sink) { |
| Node node = getSelectedNode(); |
| |
| if (key == CommonDataKeys.PROJECT) { |
| sink.put(CommonDataKeys.PROJECT, myProject); |
| } |
| else if (key == USAGE_VIEW_KEY) { |
| sink.put(USAGE_VIEW_KEY, UsageViewImpl.this); |
| } |
| |
| else if (key == CommonDataKeys.NAVIGATABLE_ARRAY) { |
| sink.put(CommonDataKeys.NAVIGATABLE_ARRAY, getNavigatablesForNodes(getSelectedNodes())); |
| } |
| |
| else if (key == PlatformDataKeys.EXPORTER_TO_TEXT_FILE) { |
| sink.put(PlatformDataKeys.EXPORTER_TO_TEXT_FILE, myTextFileExporter); |
| } |
| |
| else if (key == USAGES_KEY) { |
| final Set<Usage> selectedUsages = getSelectedUsages(); |
| sink.put(USAGES_KEY, selectedUsages != null ? selectedUsages.toArray(new Usage[selectedUsages.size()]) : null); |
| } |
| |
| else if (key == USAGE_TARGETS_KEY) { |
| sink.put(USAGE_TARGETS_KEY, getSelectedUsageTargets()); |
| } |
| |
| else if (key == CommonDataKeys.VIRTUAL_FILE_ARRAY) { |
| final Set<Usage> usages = getSelectedUsages(); |
| Usage[] ua = usages != null ? usages.toArray(new Usage[usages.size()]) : null; |
| VirtualFile[] data = UsageDataUtil.provideVirtualFileArray(ua, getSelectedUsageTargets()); |
| sink.put(CommonDataKeys.VIRTUAL_FILE_ARRAY, data); |
| } |
| |
| else if (key == PlatformDataKeys.HELP_ID) { |
| sink.put(PlatformDataKeys.HELP_ID, HELP_ID); |
| } |
| else if (key == PlatformDataKeys.COPY_PROVIDER) { |
| sink.put(PlatformDataKeys.COPY_PROVIDER, this); |
| } |
| else if (node != null) { |
| Object userObject = node.getUserObject(); |
| if (userObject instanceof TypeSafeDataProvider) { |
| ((TypeSafeDataProvider)userObject).calcData(key, sink); |
| } |
| else if (userObject instanceof DataProvider) { |
| DataProvider dataProvider = (DataProvider)userObject; |
| Object data = dataProvider.getData(key.getName()); |
| if (data != null) { |
| sink.put(key, data); |
| } |
| } |
| } |
| } |
| } |
| |
| private static class MyAutoScrollToSourceOptionProvider implements AutoScrollToSourceOptionProvider { |
| @Override |
| public boolean isAutoScrollMode() { |
| return UsageViewSettings.getInstance().IS_AUTOSCROLL_TO_SOURCE; |
| } |
| |
| @Override |
| public void setAutoScrollMode(boolean state) { |
| UsageViewSettings.getInstance().IS_AUTOSCROLL_TO_SOURCE = state; |
| } |
| } |
| |
| private final class ButtonPanel extends JPanel { |
| private ButtonPanel() { |
| setLayout(new FlowLayout(FlowLayout.LEFT, 8, 0)); |
| } |
| |
| public void addButtonRunnable(int index, final Runnable runnable, String text) { |
| if (getBorder() == null) setBorder(IdeBorderFactory.createBorder(SideBorder.TOP)); |
| |
| final JButton button = new JButton(UIUtil.replaceMnemonicAmpersand(text)); |
| DialogUtil.registerMnemonic(button); |
| |
| button.setFocusable(false); |
| button.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| runnable.run(); |
| } |
| }); |
| |
| add(button, index); |
| |
| invalidate(); |
| if (getParent() != null) { |
| getParent().validate(); |
| } |
| } |
| |
| void update() { |
| for (int i = 0; i < getComponentCount(); ++i) { |
| Component component = getComponent(i); |
| if (component instanceof JButton) { |
| final JButton button = (JButton)component; |
| button.setEnabled(!isSearchInProgress()); |
| } |
| } |
| } |
| } |
| |
| private class UsageState { |
| private final Usage myUsage; |
| private final boolean mySelected; |
| |
| private UsageState(@NotNull Usage usage, boolean isSelected) { |
| myUsage = usage; |
| mySelected = isSelected; |
| } |
| |
| public void restore() { |
| final UsageNode node = myUsageNodes.get(myUsage); |
| if (node == NULL_NODE || node == null) { |
| return; |
| } |
| final DefaultMutableTreeNode parentGroupingNode = (DefaultMutableTreeNode)node.getParent(); |
| if (parentGroupingNode != null) { |
| final TreePath treePath = new TreePath(parentGroupingNode.getPath()); |
| myTree.expandPath(treePath); |
| if (mySelected) { |
| myTree.addSelectionPath(treePath.pathByAddingChild(node)); |
| } |
| } |
| } |
| } |
| |
| private class MyPerformOperationRunnable implements Runnable { |
| private final String myCannotMakeString; |
| private final Runnable myProcessRunnable; |
| private final String myCommandName; |
| private final boolean myCheckReadOnlyStatus; |
| |
| private MyPerformOperationRunnable(final String cannotMakeString, |
| final Runnable processRunnable, |
| final String commandName, |
| boolean checkReadOnlyStatus) { |
| myCannotMakeString = cannotMakeString; |
| myProcessRunnable = processRunnable; |
| myCommandName = commandName; |
| myCheckReadOnlyStatus = checkReadOnlyStatus; |
| } |
| |
| @Override |
| public void run() { |
| if (myCheckReadOnlyStatus && !checkReadonlyUsages()) return; |
| PsiDocumentManager.getInstance(myProject).commitAllDocuments(); |
| if (myCannotMakeString != null && myChangesDetected) { |
| String title = UsageViewBundle.message("changes.detected.error.title"); |
| if (canPerformReRun() && allTargetsAreValid()) { |
| String[] options = {UsageViewBundle.message("action.description.rerun"), UsageViewBundle.message("usage.view.cancel.button")}; |
| String message = myCannotMakeString + "\n\n" + UsageViewBundle.message("dialog.rerun.search"); |
| int answer = Messages.showOkCancelDialog(myProject, message, title, options[0], options[1], Messages.getErrorIcon()); |
| if (answer == Messages.OK) { |
| refreshUsages(); |
| } |
| } |
| else { |
| Messages.showMessageDialog(myProject, myCannotMakeString, title, Messages.getErrorIcon()); |
| //todo[myakovlev] request focus to tree |
| //myUsageView.getTree().requestFocus(); |
| } |
| return; |
| } |
| |
| close(); |
| |
| CommandProcessor.getInstance().executeCommand( |
| myProject, new Runnable() { |
| @Override |
| public void run() { |
| myProcessRunnable.run(); |
| } |
| }, |
| myCommandName, |
| null |
| ); |
| } |
| } |
| |
| private List<UsageInfo> getSelectedUsageInfos() { |
| return USAGE_INFO_LIST_KEY.getData(DataManager.getInstance().getDataContext(myRootPanel)); |
| } |
| |
| public GroupNode getRoot() { |
| return myRoot; |
| } |
| |
| public boolean isVisible(@NotNull Usage usage) { |
| return myBuilder != null && myBuilder.isVisible(usage); |
| } |
| |
| @NotNull |
| public UsageTarget[] getTargets() { |
| return myTargets; |
| } |
| } |