IJ-CR-2678: Allow to change active filter rules in custom usage view
[idea/community.git] / platform / usageView / src / com / intellij / usages / impl / UsageViewImpl.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.usages.impl;
3
4 import com.intellij.concurrency.JobSchedulerImpl;
5 import com.intellij.find.FindManager;
6 import com.intellij.icons.AllIcons;
7 import com.intellij.ide.*;
8 import com.intellij.ide.actions.exclusion.ExclusionHandler;
9 import com.intellij.ide.plugins.DynamicPluginListener;
10 import com.intellij.ide.plugins.IdeaPluginDescriptor;
11 import com.intellij.lang.Language;
12 import com.intellij.navigation.NavigationItem;
13 import com.intellij.openapi.Disposable;
14 import com.intellij.openapi.actionSystem.*;
15 import com.intellij.openapi.actionSystem.ex.ActionUtil;
16 import com.intellij.openapi.application.ApplicationManager;
17 import com.intellij.openapi.application.ModalityState;
18 import com.intellij.openapi.application.ReadAction;
19 import com.intellij.openapi.command.CommandProcessor;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.progress.ProgressIndicator;
22 import com.intellij.openapi.progress.util.ProgressWrapper;
23 import com.intellij.openapi.project.DumbService;
24 import com.intellij.openapi.project.IndexNotReadyException;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.ui.Messages;
27 import com.intellij.openapi.ui.SimpleToolWindowPanel;
28 import com.intellij.openapi.ui.Splitter;
29 import com.intellij.openapi.util.*;
30 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.pom.Navigatable;
33 import com.intellij.psi.*;
34 import com.intellij.psi.impl.PsiDocumentManagerBase;
35 import com.intellij.ui.*;
36 import com.intellij.ui.components.JBLabel;
37 import com.intellij.ui.components.JBPanelWithEmptyText;
38 import com.intellij.ui.components.JBTabbedPane;
39 import com.intellij.ui.content.Content;
40 import com.intellij.ui.treeStructure.Tree;
41 import com.intellij.usageView.UsageInfo;
42 import com.intellij.usageView.UsageViewBundle;
43 import com.intellij.usageView.UsageViewContentManager;
44 import com.intellij.usages.*;
45 import com.intellij.usages.impl.actions.RuleAction;
46 import com.intellij.usages.rules.*;
47 import com.intellij.util.Consumer;
48 import com.intellij.util.EditSourceOnDoubleClickHandler;
49 import com.intellij.util.EditSourceOnEnterKeyHandler;
50 import com.intellij.util.ReflectionUtil;
51 import com.intellij.util.SingleAlarm;
52 import com.intellij.util.concurrency.AppExecutorUtil;
53 import com.intellij.util.concurrency.BoundedTaskExecutor;
54 import com.intellij.util.concurrency.EdtExecutorService;
55 import com.intellij.util.containers.ContainerUtil;
56 import com.intellij.util.containers.MultiMap;
57 import com.intellij.util.messages.MessageBusConnection;
58 import com.intellij.util.ui.DialogUtil;
59 import com.intellij.util.ui.EDT;
60 import com.intellij.util.ui.JBUI;
61 import com.intellij.util.ui.UIUtil;
62 import com.intellij.util.ui.tree.TreeModelAdapter;
63 import com.intellij.util.ui.tree.TreeUtil;
64 import gnu.trove.THashSet;
65 import gnu.trove.TIntArrayList;
66 import org.jetbrains.annotations.NonNls;
67 import org.jetbrains.annotations.NotNull;
68 import org.jetbrains.annotations.Nullable;
69 import org.jetbrains.annotations.TestOnly;
70
71 import javax.swing.*;
72 import javax.swing.event.*;
73 import javax.swing.plaf.TreeUI;
74 import javax.swing.plaf.basic.BasicTreeUI;
75 import javax.swing.tree.*;
76 import java.awt.*;
77 import java.awt.event.*;
78 import java.util.List;
79 import java.util.*;
80 import java.util.concurrent.*;
81 import java.util.stream.Collectors;
82 import java.util.stream.Stream;
83
84 import static java.util.stream.Collectors.groupingBy;
85
86 public class UsageViewImpl implements UsageViewEx {
87   private static final GroupNode.NodeComparator COMPARATOR = new GroupNode.NodeComparator();
88   private static final Logger LOG = Logger.getInstance(UsageViewImpl.class);
89   @NonNls public static final String SHOW_RECENT_FIND_USAGES_ACTION_ID = "UsageView.ShowRecentFindUsages";
90
91   private final UsageNodeTreeBuilder myBuilder;
92   private MyPanel myRootPanel; // accessed in EDT only
93   private JTree myTree; // accessed in EDT only
94   private final ScheduledFuture<?> myFireEventsFuture;
95   private Content myContent;
96
97   private final UsageViewPresentation myPresentation;
98   private final UsageTarget[] myTargets;
99   protected UsageGroupingRule[] myGroupingRules;
100   protected UsageFilteringRule[] myFilteringRules;
101   private final Factory<? extends UsageSearcher> myUsageSearcherFactory;
102   private final Project myProject;
103
104   private volatile boolean mySearchInProgress = true;
105   private final ExporterToTextFile myTextFileExporter = new ExporterToTextFile(this, getUsageViewSettings());
106   private final SingleAlarm myUpdateAlarm = new SingleAlarm(() -> {
107     if (isDisposed()) {
108       return;
109     }
110     PsiDocumentManagerBase documentManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(getProject());
111     documentManager.cancelAndRunWhenAllCommitted("UpdateUsageView", this::updateImmediately);
112   }, 300);
113
114   private final ExclusionHandlerEx<DefaultMutableTreeNode> myExclusionHandler;
115   private final Map<Usage, UsageNode> myUsageNodes = new ConcurrentHashMap<>();
116   public static final UsageNode NULL_NODE = new UsageNode(null, NullUsage.INSTANCE);
117   private final ButtonPanel myButtonPanel;
118   private boolean myNeedUpdateButtons;
119   private final JComponent myAdditionalComponent = new JPanel(new BorderLayout());
120   private volatile boolean isDisposed;
121   private volatile boolean myChangesDetected;
122
123   public static final Comparator<Usage> USAGE_COMPARATOR = (o1, o2) -> {
124     if (o1 == o2) return 0;
125     if (o1 == NullUsage.INSTANCE) return -1;
126     if (o2 == NullUsage.INSTANCE) return 1;
127     if (o1 instanceof Comparable && o2 instanceof Comparable && o1.getClass() == o2.getClass()) {
128       //noinspection unchecked
129       int selfcompared = ((Comparable<Usage>)o1).compareTo(o2);
130       if (selfcompared != 0) return selfcompared;
131
132       if (o1 instanceof UsageInFile && o2 instanceof UsageInFile) {
133         UsageInFile u1 = (UsageInFile)o1;
134         UsageInFile u2 = (UsageInFile)o2;
135
136         VirtualFile f1 = u1.getFile();
137         VirtualFile f2 = u2.getFile();
138
139         if (f1 != null && f1.isValid() && f2 != null && f2.isValid()) {
140           return f1.getPresentableUrl().compareTo(f2.getPresentableUrl());
141         }
142       }
143
144       return 0;
145     }
146     return o1.toString().compareTo(o2.toString());
147   };
148   @NonNls public static final String HELP_ID = "ideaInterface.find";
149   private UsageContextPanel myCurrentUsageContextPanel;
150   private List<UsageContextPanel.Provider> myUsageContextPanelProviders;
151   private UsageContextPanel.Provider myCurrentUsageContextProvider;
152
153   private JPanel myCentralPanel; // accessed in EDT only
154
155   @NotNull
156   private final GroupNode myRoot;
157   private final UsageViewTreeModelBuilder myModel;
158   private final Object lock = new Object();
159   private Splitter myPreviewSplitter; // accessed in EDT only
160   private volatile ProgressIndicator associatedProgress; // the progress that current find usages is running under
161
162   // true if usages tree is currently expanding or collapsing
163   // (either at the end of find usages thanks to the 'expand usages after find' setting or
164   // because the user pressed 'expand all' or 'collapse all' button. During this, some ugly hacks applied
165   // to speed up the expanding (see getExpandedDescendants() here and UsageViewTreeCellRenderer.customizeCellRenderer())
166   private boolean myExpandingCollapsing;
167   private final UsageViewTreeCellRenderer myUsageViewTreeCellRenderer;
168   private Usage myOriginUsage;
169   @Nullable private Action myRerunAction;
170   private boolean myDisposeSmartPointersOnClose = true;
171   private final ExecutorService updateRequests = AppExecutorUtil
172     .createBoundedApplicationPoolExecutor("Usage View Update Requests", AppExecutorUtil.getAppExecutorService(),
173                                           JobSchedulerImpl.getJobPoolParallelism(), this);
174   private final List<ExcludeListener> myExcludeListeners = ContainerUtil.createConcurrentList();
175   private final Set<Pair<Class<? extends PsiReference>, Language>> myReportedReferenceClasses = ContainerUtil.newConcurrentSet();
176
177   public UsageViewImpl(@NotNull Project project,
178                        @NotNull UsageViewPresentation presentation,
179                        UsageTarget @NotNull [] targets,
180                        @Nullable Factory<? extends UsageSearcher> usageSearcherFactory) {
181     // fire events every 50 ms, not more often to batch requests
182     myFireEventsFuture =
183       EdtExecutorService.getScheduledExecutorInstance().scheduleWithFixedDelay(this::fireEvents, 50, 50, TimeUnit.MILLISECONDS);
184     Disposer.register(this, () -> myFireEventsFuture.cancel(false));
185
186     myPresentation = presentation;
187     myTargets = targets;
188     myUsageSearcherFactory = usageSearcherFactory;
189     myProject = project;
190
191     myButtonPanel = new ButtonPanel();
192
193     myModel = new UsageViewTreeModelBuilder(myPresentation, targets);
194     myRoot = (GroupNode)myModel.getRoot();
195
196     UsageModelTracker myModelTracker = new UsageModelTracker(project);
197     Disposer.register(this, myModelTracker);
198
199     myGroupingRules = getActiveGroupingRules(project, getUsageViewSettings());
200     myFilteringRules = getActiveFilteringRules(project);
201
202     myBuilder = new UsageNodeTreeBuilder(myTargets, myGroupingRules, myFilteringRules, myRoot, myProject);
203
204     MessageBusConnection messageBusConnection = myProject.getMessageBus().connect(this);
205     messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, this::rulesChanged);
206     messageBusConnection.subscribe(DynamicPluginListener.TOPIC, new DynamicPluginListener() {
207       @Override
208       public void pluginLoaded(@NotNull IdeaPluginDescriptor pluginDescriptor) {
209         rulesChanged();
210       }
211
212       @Override
213       public void pluginUnloaded(@NotNull IdeaPluginDescriptor pluginDescriptor, boolean isUpdate) {
214         rulesChanged();
215       }
216     });
217
218     myUsageViewTreeCellRenderer = new UsageViewTreeCellRenderer(this);
219     if (!myPresentation.isDetachedMode()) {
220       UIUtil.invokeLaterIfNeeded(() -> {
221         // lock here to avoid concurrent execution of this init and dispose in other thread
222         synchronized (lock) {
223           if (isDisposed()) return;
224           myTree = new Tree(myModel) {
225             {
226               ToolTipManager.sharedInstance().registerComponent(this);
227             }
228
229             @Override
230             public boolean isRootVisible() {
231               return false;  // to avoid re-building model when it calls setRootVisible(true)
232             }
233
234             @Override
235             public String getToolTipText(MouseEvent e) {
236               TreePath path = getPathForLocation(e.getX(), e.getY());
237               if (path != null) {
238                 if (getCellRenderer() instanceof UsageViewTreeCellRenderer) {
239                   return UsageViewTreeCellRenderer.getTooltipFromPresentation(path.getLastPathComponent());
240                 }
241               }
242               return null;
243             }
244
245             @Override
246             public boolean isPathEditable(@NotNull TreePath path) {
247               return path.getLastPathComponent() instanceof UsageViewTreeModelBuilder.TargetsRootNode;
248             }
249
250             // hack to avoid quadratic expandAll()
251             @Override
252             public Enumeration<TreePath> getExpandedDescendants(TreePath parent) {
253               return myExpandingCollapsing ? Collections.emptyEnumeration() : super.getExpandedDescendants(parent);
254             }
255           };
256           myTree.setName("UsageViewTree");
257
258           myRootPanel = new MyPanel(myTree);
259           Disposer.register(this, myRootPanel);
260           myTree.setModel(myModel);
261
262           myRootPanel.setLayout(new BorderLayout());
263
264           SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(false, true);
265           myRootPanel.add(toolWindowPanel, BorderLayout.CENTER);
266
267           toolWindowPanel.setToolbar(createActionsToolbar());
268
269           myCentralPanel = new JPanel(new BorderLayout());
270           setupCentralPanel();
271
272           initTree();
273           toolWindowPanel.setContent(myCentralPanel);
274
275           myTree.setCellRenderer(myUsageViewTreeCellRenderer);
276           //noinspection SSBasedInspection
277           SwingUtilities.invokeLater(() -> {
278             if (!isDisposed()) {
279               collapseAll();
280             }
281           });
282
283           myModelTracker.addListener(isPropertyChange -> {
284             if (!isPropertyChange) {
285               myChangesDetected = true;
286             }
287             updateLater();
288           }, this);
289
290           if (myPresentation.isShowCancelButton()) {
291             addButtonToLowerPane(this::close, UsageViewBundle.message("usage.view.cancel.button"));
292           }
293
294           myTree.getSelectionModel().addTreeSelectionListener(__ -> {
295             //noinspection SSBasedInspection
296             SwingUtilities.invokeLater(() -> {
297               if (!isDisposed()) {
298                 updateOnSelectionChanged();
299                 myNeedUpdateButtons = true;
300               }
301             });
302           });
303           myModel.addTreeModelListener(new TreeModelAdapter() {
304             @Override
305             protected void process(@NotNull TreeModelEvent event, @NotNull EventType type) {
306               myNeedUpdateButtons = true;
307             }
308           });
309
310           myTree.addFocusListener(new FocusAdapter() {
311             @Override
312             public void focusGained(FocusEvent e) {
313               if (rulesChanged) {
314                 rulesChanged = false;
315                 rulesChanged();
316               }
317             }
318           });
319         }
320       });
321     }
322     myExclusionHandler = new ExclusionHandlerEx<DefaultMutableTreeNode>() {
323       @Override
324       public boolean isNodeExclusionAvailable(@NotNull DefaultMutableTreeNode node) {
325         return node instanceof UsageNode;
326       }
327
328       @Override
329       public boolean isNodeExcluded(@NotNull DefaultMutableTreeNode node) {
330         return ((UsageNode)node).isDataExcluded();
331       }
332
333       @Override
334       public void excludeNode(@NotNull DefaultMutableTreeNode node) {
335         Set<Node> nodes = new HashSet<>();
336         collectAllChildNodes(node, nodes);
337         collectParentNodes(node, true, nodes);
338         setExcludeNodes(nodes, true, true);
339       }
340
341       @Override
342       public void excludeNodeSilently(@NotNull DefaultMutableTreeNode node) {
343         Set<Node> nodes = new HashSet<>();
344         collectAllChildNodes(node, nodes);
345         collectParentNodes(node, true, nodes);
346         setExcludeNodes(nodes, true, false);
347       }
348
349       // include the parent if its all children (except the "node" itself) excluded flags are "almostAllChildrenExcluded"
350       private void collectParentNodes(@NotNull DefaultMutableTreeNode node,
351                                       boolean almostAllChildrenExcluded,
352                                       @NotNull Set<? super Node> nodes) {
353         TreeNode parent = node.getParent();
354         if (parent == myRoot || !(parent instanceof GroupNode)) return;
355         GroupNode parentNode = (GroupNode)parent;
356         List<Node> otherNodes;
357         synchronized (parentNode) {
358           otherNodes = ContainerUtil.filter(parentNode.getChildren(), n -> n.isExcluded() != almostAllChildrenExcluded);
359         }
360         if (otherNodes.size() == 1 && otherNodes.get(0) == node) {
361           nodes.add(parentNode);
362           collectParentNodes(parentNode, almostAllChildrenExcluded, nodes);
363         }
364       }
365
366       private void setExcludeNodes(@NotNull Set<? extends Node> nodes, boolean excluded, boolean updateImmediately) {
367         Set<Usage> affectedUsages = new LinkedHashSet<>();
368         for (Node node : nodes) {
369           Object userObject = node.getUserObject();
370           if (userObject instanceof Usage) {
371             affectedUsages.add((Usage)userObject);
372           }
373           node.setExcluded(excluded, edtFireTreeNodesChangedQueue);
374         }
375
376         if (updateImmediately) {
377           updateImmediatelyNodesUpToRoot(nodes);
378
379           for (ExcludeListener listener : myExcludeListeners) {
380             listener.fireExcluded(affectedUsages, excluded);
381           }
382         }
383       }
384
385       @Override
386       public void includeNode(@NotNull DefaultMutableTreeNode node) {
387         Set<Node> nodes = new HashSet<>();
388         collectAllChildNodes(node, nodes);
389         collectParentNodes(node, false, nodes);
390         setExcludeNodes(nodes, false, true);
391       }
392
393       @Override
394       public boolean isActionEnabled(boolean isExcludeAction) {
395         return getPresentation().isExcludeAvailable();
396       }
397
398       @Override
399       public void onDone(boolean isExcludeAction) {
400         EDT.assertIsEdt();
401         if (myRootPanel.hasNextOccurence()) {
402           myRootPanel.goNextOccurence();
403         }
404       }
405     };
406   }
407
408   @NotNull
409   public UsageViewSettings getUsageViewSettings() {
410     return UsageViewSettings.getInstance();
411   }
412
413   // nodes just changed: parent node -> changed child
414   // this collection is needed for firing javax.swing.tree.DefaultTreeModel.nodesChanged() events in batch
415   // has to be linked because events for child nodes should be fired after events for parent nodes
416   private final MultiMap<Node, Node> fireTreeNodesChangedMap = MultiMap.createLinked(); // guarded by fireTreeNodesChangedMap
417
418   private final Consumer<Node> edtFireTreeNodesChangedQueue = node -> {
419     if (!getPresentation().isDetachedMode()) {
420       synchronized (fireTreeNodesChangedMap) {
421         Node parent = (Node)node.getParent();
422         if (parent != null) {
423           fireTreeNodesChangedMap.putValue(parent, node);
424         }
425       }
426     }
427   };
428
429   /**
430    * Type of a change that occurs in the GroupNode.myChildren
431    * and has to be applied to the swing children list
432    */
433   enum NodeChangeType {
434     ADDED, REMOVED, REPLACED
435   }
436
437   /**
438    * Collection of info about a change that occurs in the GroupNode.myChildren
439    * and has to be applied to the swing children list including affected nodes and the type of the change
440    */
441   static class NodeChange {
442     @NotNull
443     private final NodeChangeType nodeChangeType;
444     /**
445      * The one that was replaced or removed, or a parent for the added node
446      */
447     @NotNull
448     private final Node parentNode;
449
450     /**
451      * The one that was added or the one that replaced the first
452      */
453     @Nullable
454     private final Node childNode;
455
456     NodeChange(@NotNull NodeChangeType nodeChangeType, @NotNull Node parentNode, @Nullable Node childNode) {
457       this.nodeChangeType = nodeChangeType;
458       this.parentNode = parentNode;
459       this.childNode = childNode;
460     }
461
462     @NotNull Node getParentNode() {
463       return parentNode;
464     }
465
466     @Override
467     public boolean equals(Object o) {
468       if (this == o) return true;
469       if (o == null || getClass() != o.getClass()) return false;
470       NodeChange that = (NodeChange)o;
471       return nodeChangeType == that.nodeChangeType &&
472              Objects.equals(parentNode, that.parentNode) &&
473              Objects.equals(childNode, that.childNode);
474     }
475
476     @Override
477     public int hashCode() {
478       return Objects.hash(nodeChangeType, parentNode, childNode);
479     }
480
481     private boolean isValid() {
482       boolean parentValid = !parentNode.isStructuralChangeDetected();
483
484       boolean childValid = true;
485       if (childNode != null) {
486         childValid = !childNode.isStructuralChangeDetected();
487       }
488
489       return parentValid && childValid;
490     }
491   }
492
493   /**
494    * Executes a new appendUsage request with the updateRequests executor
495    */
496   private final Consumer<Usage> invalidatedUsagesConsumer = (@NotNull Usage usage) -> {
497     if (!getPresentation().isDetachedMode() && !isDisposed) {
498       myUsageNodes.remove(usage);
499       addUpdateRequest(() -> ReadAction.run(() -> doAppendUsage(usage)));
500     }
501   };
502
503
504   /**
505    * Set of node changes coming from the model to be applied to the Swing elements
506    */
507   private final Set<NodeChange> modelToSwingNodeChanges = new LinkedHashSet<>(); //guarded by modelToSwingNodeChanges
508
509   private final Consumer<NodeChange> edtModelToSwingNodeChangesQueue = (@NotNull NodeChange parent) -> {
510     if (!getPresentation().isDetachedMode()) {
511       synchronized (modelToSwingNodeChanges) {
512         modelToSwingNodeChanges.add(parent);
513       }
514     }
515   };
516
517
518   /**
519    * for each node synchronize its model children (com.intellij.usages.impl.GroupNode.getChildren())
520    * and its Swing children (javax.swing.tree.DefaultMutableTreeNode.children)
521    * by applying correspondent changes from one to another and by issuing corresponding DefaultMutableTreeNode event notifications
522    * (fireTreeNodesInserted/nodesWereRemoved/fireTreeStructureChanged)
523    * this method is called regularly every 50ms to fire events in batch
524    */
525   private void fireEvents() {
526     EDT.assertIsEdt();
527
528     syncModelWithSwingNodes();
529     fireEventsForChangedNodes();
530   }
531
532   /**
533    * group nodes from changedNodesToFire by their parents
534    * and issue corresponding javax.swing.tree.DefaultTreeModel.fireTreeNodesChanged()
535    */
536   private void fireEventsForChangedNodes() {
537     TIntArrayList indicesToFire = new TIntArrayList();
538     List<Node> nodesToFire = new ArrayList<>();
539
540     List<Map.Entry<Node, Collection<Node>>> changed;
541     synchronized (fireTreeNodesChangedMap) {
542       changed = new ArrayList<>(fireTreeNodesChangedMap.entrySet());
543       fireTreeNodesChangedMap.clear();
544     }
545     for (Map.Entry<Node, Collection<Node>> entry : changed) {
546       Node parentNode = entry.getKey();
547       Collection<Node> childrenToUpdate = entry.getValue();
548       for (int i = 0; i < parentNode.getChildCount(); i++) {
549         Node childNode = (Node)parentNode.getChildAt(i);
550         if (childrenToUpdate.contains(childNode)) {
551           nodesToFire.add(childNode);
552           indicesToFire.add(i);
553         }
554       }
555
556       myModel.fireTreeNodesChanged(parentNode, myModel.getPathToRoot(parentNode), indicesToFire.toNativeArray(),
557                                    nodesToFire.toArray(new Node[0]));
558       indicesToFire.clear();
559       nodesToFire.clear();
560     }
561   }
562
563   /**
564    * Iterating over all changes that come from the model children list in a GroupNode
565    * and applying all those changes to the swing list of children to synchronize those
566    */
567   private void syncModelWithSwingNodes() {
568     List<NodeChange> nodeChanges;
569     synchronized (modelToSwingNodeChanges) {
570       nodeChanges = new ArrayList<>(modelToSwingNodeChanges);
571       modelToSwingNodeChanges.clear();
572     }
573
574     TIntArrayList indicesToFire = new TIntArrayList();
575     List<Node> nodesToFire = new ArrayList<>();
576
577     //first grouping changes by parent node
578     Map<Node, List<NodeChange>> groupByParent = nodeChanges.stream().collect(groupingBy(NodeChange::getParentNode));
579     for (Node parentNode : groupByParent.keySet()) {
580       synchronized (parentNode) {
581         List<NodeChange> changes = groupByParent.get(parentNode);
582         List<NodeChange> addedToThisNode = new ArrayList<>();
583         //removing node
584         for (NodeChange change : changes) {
585           if (change.nodeChangeType.equals(NodeChangeType.REMOVED) || change.nodeChangeType.equals(NodeChangeType.REPLACED)) {
586             GroupNode grandParent = (GroupNode)parentNode.getParent();
587             int index = grandParent.getSwingChildren().indexOf(parentNode);
588             if (index >= 0) {
589               grandParent.getSwingChildren().remove(parentNode);
590               myModel.nodesWereRemoved(grandParent, new int[]{index}, new Object[]{parentNode});
591               myModel.fireTreeStructureChanged(grandParent, myModel.getPathToRoot(parentNode), new int[]{index}, new Object[]{parentNode});
592               if (parentNode instanceof UsageNode) {
593                 grandParent.incrementUsageCount(-1);
594               }
595               //if this node was removed than we can skip all the other changes related to it
596               break;
597             }
598           }
599           else {
600             addedToThisNode.add(change);
601           }
602         }
603
604         //adding children nodes in batch
605
606         if (!addedToThisNode.isEmpty()) {
607           for (NodeChange change : addedToThisNode) {
608             if (!change.isValid()) {
609               continue;
610             }
611             Node childNode = change.childNode;
612             if (childNode == null) {
613               continue;
614             }
615             synchronized (childNode) {
616               List<Node> swingChildren = ((GroupNode)parentNode).getSwingChildren();
617               boolean contains = swingChildren.contains(childNode);
618               if (!contains) {
619                 nodesToFire.add(childNode);
620
621                 parentNode.insertNewNode(childNode, 0);
622                 swingChildren.sort(COMPARATOR);
623                 indicesToFire.add(swingChildren.indexOf(change.childNode));
624
625                 if (childNode instanceof UsageNode) {
626                   ((GroupNode)parentNode).incrementUsageCount(1);
627                 }
628               }
629             }
630             if (!indicesToFire.isEmpty()) {
631               myModel.fireTreeNodesInserted(parentNode, myModel.getPathToRoot(parentNode), indicesToFire.toNativeArray(),
632                                             nodesToFire.toArray(new Node[0]));
633               indicesToFire.clear();
634               nodesToFire.clear();
635             }
636           }
637         }
638       }
639     }
640   }
641
642   @Override
643   public void searchFinished() {
644     drainQueuedUsageNodes();
645     setSearchInProgress(false);
646   }
647
648   @Override
649   public boolean searchHasBeenCancelled() {
650     ProgressIndicator progress = associatedProgress;
651     return progress != null && progress.isCanceled();
652   }
653
654   @Override
655   public void cancelCurrentSearch() {
656     ProgressIndicator progress = associatedProgress;
657     if (progress != null) {
658       ProgressWrapper.unwrapAll(progress).cancel();
659     }
660   }
661
662   private void clearRendererCache() {
663     EDT.assertIsEdt();
664     if (myExpandingCollapsing) return; // to avoid quadratic row enumeration
665     // clear renderer cache of node preferred size
666     TreeUI ui = myTree.getUI();
667     if (ui instanceof BasicTreeUI) {
668       AbstractLayoutCache treeState = ReflectionUtil.getField(BasicTreeUI.class, ui, AbstractLayoutCache.class, "treeState");
669       Rectangle visibleRect = myTree.getVisibleRect();
670       int rowForLocation = myTree.getClosestRowForLocation(0, visibleRect.y);
671       int visibleRowCount = getVisibleRowCount();
672       List<Node> toUpdate = new ArrayList<>();
673       for (int i = rowForLocation + visibleRowCount + 1; i >= rowForLocation; i--) {
674         TreePath eachPath = myTree.getPathForRow(i);
675         if (eachPath == null) continue;
676
677         treeState.invalidatePathBounds(eachPath);
678         Object node = eachPath.getLastPathComponent();
679         if (node instanceof UsageNode) {
680           toUpdate.add((Node)node);
681         }
682       }
683       queueUpdateBulk(toUpdate, () -> {
684         if (!isDisposed()) {
685           myTree.repaint(visibleRect);
686         }
687       });
688     }
689     else {
690       myTree.setCellRenderer(myUsageViewTreeCellRenderer);
691     }
692   }
693
694   private int getVisibleRowCount() {
695     EDT.assertIsEdt();
696     return TreeUtil.getVisibleRowCount(myTree);
697   }
698
699   private void setupCentralPanel() {
700     EDT.assertIsEdt();
701
702     JScrollPane treePane = ScrollPaneFactory.createScrollPane(myTree);
703     // add reaction to scrolling:
704     // since the UsageViewTreeCellRenderer ignores invisible nodes (outside the viewport), their preferred size is incorrect
705     // and we need to recalculate them when the node scrolled into the visible rectangle
706     treePane.getViewport().addChangeListener(__ -> clearRendererCache());
707     myPreviewSplitter = new OnePixelSplitter(false, 0.5f, 0.1f, 0.9f);
708     myPreviewSplitter.setFirstComponent(treePane);
709
710     myCentralPanel.add(myPreviewSplitter, BorderLayout.CENTER);
711
712     updateUsagesContextPanels();
713
714     myCentralPanel.add(myAdditionalComponent, BorderLayout.SOUTH);
715     myAdditionalComponent.add(myButtonPanel, BorderLayout.SOUTH);
716   }
717
718   private void updateUsagesContextPanels() {
719     EDT.assertIsEdt();
720     disposeUsageContextPanels();
721     if (isPreviewUsages()) {
722       myPreviewSplitter.setProportion(getUsageViewSettings().getPreviewUsagesSplitterProportion());
723       JBTabbedPane tabbedPane = new JBTabbedPane(SwingConstants.BOTTOM);
724       tabbedPane.setTabComponentInsets(null);
725
726       UsageContextPanel.Provider[] extensions = UsageContextPanel.Provider.EP_NAME.getExtensions(myProject);
727       myUsageContextPanelProviders = ContainerUtil.filter(extensions, provider -> provider.isAvailableFor(this));
728       Map<String, JComponent> components = new LinkedHashMap<>();
729       for (UsageContextPanel.Provider provider : myUsageContextPanelProviders) {
730         JComponent component;
731         if (myCurrentUsageContextProvider == null || myCurrentUsageContextProvider == provider) {
732           myCurrentUsageContextProvider = provider;
733           myCurrentUsageContextPanel = provider.create(this);
734           component = myCurrentUsageContextPanel.createComponent();
735         }
736         else {
737           component = new JLabel();
738         }
739         components.put(provider.getTabTitle(), component);
740       }
741       JBPanelWithEmptyText panel = new JBPanelWithEmptyText(new BorderLayout());
742       if (components.size() == 1) {
743         panel.add(components.values().iterator().next(), BorderLayout.CENTER);
744       }
745       else {
746         for (Map.Entry<String, JComponent> entry : components.entrySet()) {
747           tabbedPane.addTab(entry.getKey(), entry.getValue());
748         }
749         int index = myUsageContextPanelProviders.indexOf(myCurrentUsageContextProvider);
750         tabbedPane.setSelectedIndex(index);
751         tabbedPane.addChangeListener(e -> {
752           int currentIndex = tabbedPane.getSelectedIndex();
753           UsageContextPanel.Provider selectedProvider = myUsageContextPanelProviders.get(currentIndex);
754           if (selectedProvider != myCurrentUsageContextProvider) {
755             tabSelected(selectedProvider);
756           }
757         });
758         panel.add(tabbedPane, BorderLayout.CENTER);
759       }
760       myPreviewSplitter.setSecondComponent(panel);
761     }
762     else {
763       myPreviewSplitter.setSecondComponent(null);
764       myPreviewSplitter.setProportion(1);
765     }
766
767     myRootPanel.revalidate();
768     myRootPanel.repaint();
769   }
770
771   private void tabSelected(@NotNull UsageContextPanel.Provider provider) {
772     EDT.assertIsEdt();
773     myCurrentUsageContextProvider = provider;
774     updateUsagesContextPanels();
775     updateOnSelectionChanged();
776   }
777
778   private void disposeUsageContextPanels() {
779     EDT.assertIsEdt();
780     if (myCurrentUsageContextPanel != null) {
781       saveSplitterProportions();
782       Disposer.dispose(myCurrentUsageContextPanel);
783       myCurrentUsageContextPanel = null;
784     }
785   }
786
787   boolean isPreviewUsages() {
788     return myPresentation.isReplaceMode() ? getUsageViewSettings().isReplacePreviewUsages() : getUsageViewSettings().isPreviewUsages();
789   }
790
791   void setPreviewUsages(boolean state) {
792     if (myPresentation.isReplaceMode()) {
793       getUsageViewSettings().setReplacePreviewUsages(state);
794     }
795     else {
796       getUsageViewSettings().setPreviewUsages(state);
797     }
798   }
799
800   protected UsageFilteringRule @NotNull [] getActiveFilteringRules(Project project) {
801     List<UsageFilteringRuleProvider> providers = UsageFilteringRuleProvider.EP_NAME.getExtensionList();
802     List<UsageFilteringRule> list = new ArrayList<>(providers.size());
803     for (UsageFilteringRuleProvider provider : providers) {
804       ContainerUtil.addAll(list, provider.getActiveRules(project));
805     }
806     return list.toArray(UsageFilteringRule.EMPTY_ARRAY);
807   }
808
809
810   protected static UsageGroupingRule @NotNull [] getActiveGroupingRules(@NotNull Project project,
811                                                                         @NotNull UsageViewSettings usageViewSettings) {
812     List<UsageGroupingRuleProvider> providers = UsageGroupingRuleProvider.EP_NAME.getExtensionList();
813     List<UsageGroupingRule> list = new ArrayList<>(providers.size());
814     for (UsageGroupingRuleProvider provider : providers) {
815       ContainerUtil.addAll(list, provider.getActiveRules(project, usageViewSettings));
816     }
817
818     list.sort(Comparator.comparingInt(UsageGroupingRule::getRank));
819     return list.toArray(UsageGroupingRule.EMPTY_ARRAY);
820   }
821
822   private void initTree() {
823     EDT.assertIsEdt();
824     myTree.setShowsRootHandles(true);
825     SmartExpander.installOn(myTree);
826     TreeUtil.installActions(myTree);
827     EditSourceOnDoubleClickHandler.install(myTree);
828     EditSourceOnEnterKeyHandler.install(myTree);
829
830     TreeUtil.promiseSelectFirst(myTree);
831     PopupHandler.installPopupHandler(myTree, IdeActions.GROUP_USAGE_VIEW_POPUP, ActionPlaces.USAGE_VIEW_POPUP);
832
833     myTree.addTreeExpansionListener(new TreeExpansionListener() {
834       @Override
835       public void treeExpanded(TreeExpansionEvent event) {
836         clearRendererCache();
837
838         TreePath path = event.getPath();
839         Object component = path.getLastPathComponent();
840         if (component instanceof Node) {
841           Node node = (Node)component;
842           if (!myExpandingCollapsing && node.needsUpdate()) {
843             List<Node> toUpdate = new ArrayList<>();
844             checkNodeValidity(node, path, toUpdate);
845             queueUpdateBulk(toUpdate, EmptyRunnable.getInstance());
846           }
847         }
848       }
849
850       @Override
851       public void treeCollapsed(TreeExpansionEvent event) {
852         clearRendererCache();
853       }
854     });
855
856     TreeUIHelper.getInstance().installTreeSpeedSearch(myTree, o -> {
857       Object value = o.getLastPathComponent();
858       TreeCellRenderer renderer = myTree.getCellRenderer();
859       if (renderer instanceof UsageViewTreeCellRenderer) {
860         UsageViewTreeCellRenderer coloredRenderer = (UsageViewTreeCellRenderer)renderer;
861         return coloredRenderer.getPlainTextForNode(value);
862       }
863       return value == null ? null : value.toString();
864     }, true);
865   }
866
867   @NotNull
868   private JComponent createActionsToolbar() {
869     EDT.assertIsEdt();
870
871     DefaultActionGroup group = new DefaultActionGroup() {
872       @Override
873       public void update(@NotNull AnActionEvent e) {
874         super.update(e);
875         myButtonPanel.update();
876       }
877
878       @Override
879       public boolean isDumbAware() {
880         return true;
881       }
882     };
883
884     AnAction[] actions = createActions();
885     for (AnAction action : actions) {
886       if (action != null) {
887         group.add(action);
888       }
889     }
890     return toUsageViewToolbar(group);
891   }
892
893   @NotNull
894   private JComponent toUsageViewToolbar(@NotNull DefaultActionGroup group) {
895     EDT.assertIsEdt();
896     ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, group, false);
897     actionToolbar.setTargetComponent(myRootPanel);
898     return actionToolbar.getComponent();
899   }
900
901   @SuppressWarnings("WeakerAccess") // used in rider
902   protected boolean isPreviewUsageActionEnabled() {
903     return true;
904   }
905
906   public void addFilteringActions(@NotNull DefaultActionGroup group) {
907     EDT.assertIsEdt();
908     addFilteringActions(group, true);
909   }
910
911   protected void addFilteringActions(@NotNull DefaultActionGroup group, boolean includeExtensionPoints) {
912     if (getPresentation().isMergeDupLinesAvailable()) {
913       MergeDupLines mergeDupLines = new MergeDupLines();
914       JComponent component = myRootPanel;
915       if (component != null) {
916         mergeDupLines.registerCustomShortcutSet(mergeDupLines.getShortcutSet(), component, this);
917       }
918       group.add(mergeDupLines);
919     }
920     if (includeExtensionPoints) {
921       addFilteringFromExtensionPoints(group);
922     }
923   }
924
925   /**
926    * Creates filtering actions for the toolbar
927    */
928   protected void addFilteringFromExtensionPoints(@NotNull DefaultActionGroup group) {
929     for (UsageFilteringRuleProvider provider : UsageFilteringRuleProvider.EP_NAME.getExtensionList()) {
930       AnAction[] actions = provider.createFilteringActions(this);
931       for (AnAction action : actions) {
932         group.add(action);
933       }
934     }
935   }
936
937   protected final TreeExpander treeExpander = new TreeExpander() {
938     @Override
939     public void expandAll() {
940       UsageViewImpl.this.expandAll();
941       getUsageViewSettings().setExpanded(true);
942     }
943
944     @Override
945     public boolean canExpand() {
946       return true;
947     }
948
949     @Override
950     public void collapseAll() {
951       UsageViewImpl.this.collapseAll();
952       getUsageViewSettings().setExpanded(false);
953     }
954
955     @Override
956     public boolean canCollapse() {
957       return true;
958     }
959   };
960
961   protected AnAction @NotNull [] createActions() {
962     EDT.assertIsEdt();
963
964     CommonActionsManager actionsManager = CommonActionsManager.getInstance();
965
966     JComponent component = getComponent();
967
968     AnAction expandAllAction = actionsManager.createExpandAllAction(treeExpander, component);
969     AnAction collapseAllAction = actionsManager.createCollapseAllAction(treeExpander, component);
970
971     Disposer.register(this, () -> {
972       expandAllAction.unregisterCustomShortcutSet(component);
973       collapseAllAction.unregisterCustomShortcutSet(component);
974     });
975
976     DefaultActionGroup group = new DefaultActionGroup();
977     group.setPopup(true);
978     group.getTemplatePresentation().setIcon(AllIcons.Actions.GroupBy);
979     group.getTemplatePresentation().setText(UsageViewBundle.messagePointer("action.group.by.title"));
980     group.getTemplatePresentation().setDescription(UsageViewBundle.messagePointer("action.group.by.title"));
981     group.getTemplatePresentation().setMultipleChoice(true);
982     AnAction[] groupingActions = createGroupingActions();
983     for (AnAction a : groupingActions) {
984       a.getTemplatePresentation().setMultipleChoice(true);
985     }
986     if (groupingActions.length > 0) {
987       group.add(new Separator(UsageViewBundle.message("action.group.by.title")));
988       group.addAll(groupingActions);
989       group.add(new Separator());
990     }
991
992     addFilteringActions(group, false);
993     DefaultActionGroup filteringSubgroup = new DefaultActionGroup();
994     addFilteringFromExtensionPoints(filteringSubgroup);
995
996     return new AnAction[]{
997       ActionManager.getInstance().getAction("UsageView.Rerun"),
998       actionsManager.createPrevOccurenceAction(myRootPanel),
999       actionsManager.createNextOccurenceAction(myRootPanel),
1000       new Separator(),
1001       group,
1002       filteringSubgroup,
1003       expandAllAction,
1004       collapseAllAction,
1005       new Separator(),
1006       isPreviewUsageActionEnabled() ? new PreviewUsageAction(this) : null,
1007       new Separator(),
1008       canShowSettings() ? new ShowSettings() : null,
1009     };
1010   }
1011
1012   protected boolean canShowSettings() {
1013     if (myTargets.length == 0) return false;
1014     NavigationItem target = myTargets[0];
1015     return target instanceof ConfigurableUsageTarget;
1016   }
1017
1018   private static ConfigurableUsageTarget getConfigurableTarget(UsageTarget @NotNull [] targets) {
1019     ConfigurableUsageTarget configurableUsageTarget = null;
1020     if (targets.length != 0) {
1021       NavigationItem target = targets[0];
1022       if (target instanceof ConfigurableUsageTarget) {
1023         configurableUsageTarget = (ConfigurableUsageTarget)target;
1024       }
1025     }
1026     return configurableUsageTarget;
1027   }
1028
1029   /**
1030    * Creates grouping actions for the toolbar
1031    */
1032   protected AnAction @NotNull [] createGroupingActions() {
1033     List<UsageGroupingRuleProvider> providers = UsageGroupingRuleProvider.EP_NAME.getExtensionList();
1034     List<AnAction> list = new ArrayList<>(providers.size());
1035     for (UsageGroupingRuleProvider provider : providers) {
1036       ContainerUtil.addAll(list, provider.createGroupingActions(this));
1037     }
1038     sortGroupingActions(list);
1039     ActionUtil.moveActionTo(list, "Module", "Flatten Modules", true);
1040     return list.toArray(AnAction.EMPTY_ARRAY);
1041   }
1042
1043
1044   /**
1045    * create*Action() methods can be used in createActions() method in subclasses to create a toolbar
1046    */
1047   protected @NotNull AnAction createPreviewAction() {
1048     return new PreviewUsageAction(this);
1049   }
1050
1051   protected @NotNull AnAction createSettingsAction() {
1052     return new ShowSettings();
1053   }
1054
1055   protected @NotNull AnAction createPreviousOccurrenceAction() {
1056     return CommonActionsManager.getInstance().createPrevOccurenceAction(myRootPanel);
1057   }
1058
1059   protected @NotNull AnAction createNextOccurrenceAction() {
1060     return CommonActionsManager.getInstance().createNextOccurenceAction(myRootPanel);
1061   }
1062
1063   /**
1064    * Sorting AnActions in the Grouping Actions view, default implementation is alphabetical sort
1065    *
1066    * @param actions to sort
1067    */
1068   protected void sortGroupingActions(@NotNull List<? extends AnAction> actions) {
1069     ActionUtil.sortAlphabetically(actions);
1070   }
1071
1072   private boolean shouldTreeReactNowToRuleChanges() {
1073     EDT.assertIsEdt();
1074     return myPresentation.isDetachedMode() || myTree.isShowing();
1075   }
1076
1077   private boolean rulesChanged; // accessed in EDT only
1078
1079   private void rulesChanged() {
1080     EDT.assertIsEdt();
1081     if (!shouldTreeReactNowToRuleChanges()) {
1082       rulesChanged = true;
1083       return;
1084     }
1085
1086     List<UsageState> states = new ArrayList<>();
1087     if (myTree != null) {
1088       captureUsagesExpandState(new TreePath(myTree.getModel().getRoot()), states);
1089     }
1090     List<Usage> allUsages = new ArrayList<>(myUsageNodes.keySet());
1091     allUsages.sort(USAGE_COMPARATOR);
1092     Set<Usage> excludedUsages = getExcludedUsages();
1093     reset();
1094     myGroupingRules = getActiveGroupingRules(myProject, getUsageViewSettings());
1095     myFilteringRules = getActiveFilteringRules(myProject);
1096
1097     myBuilder.setGroupingRules(myGroupingRules);
1098     myBuilder.setFilteringRules(myFilteringRules);
1099
1100     for (int i = allUsages.size() - 1; i >= 0; i--) {
1101       Usage usage = allUsages.get(i);
1102       if (!usage.isValid()) {
1103         allUsages.remove(i);
1104         continue;
1105       }
1106       if (usage instanceof MergeableUsage) {
1107         ((MergeableUsage)usage).reset();
1108       }
1109     }
1110     //noinspection SSBasedInspection
1111     appendUsagesInBulk(allUsages).thenRun(() -> SwingUtilities.invokeLater(() -> {
1112       if (isDisposed()) return;
1113       if (myTree != null) {
1114         excludeUsages(excludedUsages.toArray(Usage.EMPTY_ARRAY));
1115         restoreUsageExpandState(states);
1116         updateImmediately();
1117         if (myCentralPanel != null) {
1118           updateUsagesContextPanels();
1119         }
1120       }
1121     }));
1122   }
1123
1124   private void captureUsagesExpandState(@NotNull TreePath pathFrom, @NotNull Collection<? super UsageState> states) {
1125     EDT.assertIsEdt();
1126     if (!myTree.isExpanded(pathFrom)) {
1127       return;
1128     }
1129     DefaultMutableTreeNode node = (DefaultMutableTreeNode)pathFrom.getLastPathComponent();
1130     int childCount = node.getChildCount();
1131     for (int idx = 0; idx < childCount; idx++) {
1132       TreeNode child = node.getChildAt(idx);
1133       if (child instanceof UsageNode) {
1134         Usage usage = ((UsageNode)child).getUsage();
1135         states.add(new UsageState(usage, myTree.getSelectionModel().isPathSelected(pathFrom.pathByAddingChild(child))));
1136       }
1137       else {
1138         captureUsagesExpandState(pathFrom.pathByAddingChild(child), states);
1139       }
1140     }
1141   }
1142
1143   private void restoreUsageExpandState(@NotNull Collection<? extends UsageState> states) {
1144     EDT.assertIsEdt();
1145     //always expand the last level group
1146     DefaultMutableTreeNode root = (DefaultMutableTreeNode)myTree.getModel().getRoot();
1147     for (int i = root.getChildCount() - 1; i >= 0; i--) {
1148       DefaultMutableTreeNode child = (DefaultMutableTreeNode)root.getChildAt(i);
1149       if (child instanceof GroupNode) {
1150         TreePath treePath = new TreePath(child.getPath());
1151         myTree.expandPath(treePath);
1152       }
1153     }
1154     myTree.getSelectionModel().clearSelection();
1155     for (UsageState usageState : states) {
1156       usageState.restore();
1157     }
1158   }
1159
1160   public void expandAll() {
1161     doExpandingCollapsing(() -> TreeUtil.expandAll(myTree));
1162   }
1163
1164   private void expandTree(int levels) {
1165     doExpandingCollapsing(() -> TreeUtil.expand(myTree, levels));
1166   }
1167
1168   /**
1169    * Allows to skip a lot of {@link #clearRendererCache}, received via {@link TreeExpansionListener}.
1170    *
1171    * @param task that expands or collapses a tree
1172    */
1173   private void doExpandingCollapsing(@NotNull Runnable task) {
1174     if (isDisposed()) return;
1175     EDT.assertIsEdt();
1176     fireEvents();  // drain all remaining insertion events in the queue
1177
1178     myExpandingCollapsing = true;
1179     try {
1180       task.run();
1181     }
1182     finally {
1183       myExpandingCollapsing = false;
1184     }
1185     clearRendererCache();
1186   }
1187
1188   private void collapseAll() {
1189     doExpandingCollapsing(() -> {
1190       TreeUtil.collapseAll(myTree, 3);
1191       myTree.expandRow(0);
1192     });
1193   }
1194
1195   public void expandRoot() {
1196     expandTree(1);
1197   }
1198
1199   @NotNull
1200   DefaultMutableTreeNode getModelRoot() {
1201     EDT.assertIsEdt();
1202     return (DefaultMutableTreeNode)myTree.getModel().getRoot();
1203   }
1204
1205   public void select() {
1206     EDT.assertIsEdt();
1207     // can be null during ctr execution
1208     if (myTree != null) {
1209       myTree.requestFocusInWindow();
1210     }
1211   }
1212
1213   @NotNull
1214   public Project getProject() {
1215     return myProject;
1216   }
1217
1218   @Nullable
1219   public static KeyboardShortcut getShowUsagesWithSettingsShortcut() {
1220     return ActionManager.getInstance().getKeyboardShortcut("ShowSettingsAndFindUsages");
1221   }
1222
1223   static KeyboardShortcut getShowUsagesWithSettingsShortcut(UsageTarget @NotNull [] targets) {
1224     ConfigurableUsageTarget configurableTarget = getConfigurableTarget(targets);
1225     return configurableTarget == null ? getShowUsagesWithSettingsShortcut() : configurableTarget.getShortcut();
1226   }
1227
1228   @Override
1229   public void associateProgress(@NotNull ProgressIndicator indicator) {
1230     associatedProgress = indicator;
1231   }
1232
1233   private static final class MergeDupLines extends RuleAction {
1234     private MergeDupLines() {
1235       super(UsageViewBundle.message("action.merge.same.line"), AllIcons.Toolbar.Filterdups);
1236       setShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK)));
1237     }
1238
1239     @Override
1240     protected boolean getOptionValue(@NotNull AnActionEvent e) {
1241       return getUsageViewSettings(e).isFilterDuplicatedLine();
1242     }
1243
1244     @Override
1245     protected void setOptionValue(@NotNull AnActionEvent e, boolean value) {
1246       getUsageViewSettings(e).setFilterDuplicatedLine(value);
1247     }
1248   }
1249
1250   private final class ShowSettings extends AnAction {
1251     private ShowSettings() {
1252       super(UsageViewBundle.message("action.text.usage.view.settings"), null, AllIcons.General.GearPlain);
1253       ConfigurableUsageTarget configurableUsageTarget = getConfigurableTarget(myTargets);
1254       String description = null;
1255       try {
1256         description = configurableUsageTarget == null
1257                       ? null
1258                       : UsageViewBundle
1259                         .message("action.ShowSettings.show.settings.for.description", configurableUsageTarget.getLongDescriptiveName());
1260       }
1261       catch (IndexNotReadyException ignored) {
1262       }
1263       if (description == null) {
1264         description = UsageViewBundle.message("action.ShowSettings.show.find.usages.settings.dialog.description");
1265       }
1266       getTemplatePresentation().setDescription(description);
1267       KeyboardShortcut shortcut =
1268         configurableUsageTarget == null ? getShowUsagesWithSettingsShortcut() : configurableUsageTarget.getShortcut();
1269       if (shortcut != null) {
1270         registerCustomShortcutSet(new CustomShortcutSet(shortcut), getComponent());
1271       }
1272     }
1273
1274     @Override
1275     public void update(@NotNull AnActionEvent e) {
1276       e.getPresentation().setEnabled(e.getData(CommonDataKeys.EDITOR) == null);
1277     }
1278
1279     @Override
1280     public void actionPerformed(@NotNull AnActionEvent e) {
1281       FindManager.getInstance(getProject()).showSettingsAndFindUsages(myTargets);
1282     }
1283   }
1284
1285   public void refreshUsages() {
1286     reset();
1287     doReRun();
1288   }
1289
1290   /**
1291    * @return usage view which will be shown after re-run (either {@code this} if it knows how to re-run itself, or the new created one otherwise)
1292    */
1293   protected UsageView doReRun() {
1294     myChangesDetected = false;
1295     if (myRerunAction == null) {
1296       UsageViewPresentation rerunPresentation = myPresentation.copy();
1297       rerunPresentation.setRerunHash(System.identityHashCode(myContent));
1298       return UsageViewManager.getInstance(getProject()).
1299         searchAndShowUsages(myTargets, myUsageSearcherFactory, true, false, rerunPresentation, null);
1300     }
1301     myRerunAction.actionPerformed(null);
1302     return this;
1303   }
1304
1305   private void reset() {
1306     EDT.assertIsEdt();
1307     myUsageNodes.clear();
1308     myModel.reset();
1309     synchronized (modelToSwingNodeChanges) {
1310       modelToSwingNodeChanges.clear();
1311     }
1312
1313     if (!myPresentation.isDetachedMode()) {
1314       //noinspection SSBasedInspection
1315       SwingUtilities.invokeLater(() -> expandTree(2));
1316     }
1317   }
1318
1319   void drainQueuedUsageNodes() {
1320     assert !ApplicationManager.getApplication().isDispatchThread() : Thread.currentThread();
1321     UIUtil.invokeAndWaitIfNeeded((Runnable)this::fireEvents);
1322   }
1323
1324   @Override
1325   public void appendUsage(@NotNull Usage usage) {
1326     if (ApplicationManager.getApplication().isDispatchThread()) {
1327       addUpdateRequest(() -> ReadAction.run(() -> doAppendUsage(usage)));
1328     }
1329     else {
1330       doAppendUsage(usage);
1331     }
1332   }
1333
1334   protected void addUpdateRequest(@NotNull Runnable request) {
1335     updateRequests.execute(request);
1336   }
1337
1338   @Override
1339   public void waitForUpdateRequestsCompletion() {
1340     assert !ApplicationManager.getApplication().isDispatchThread();
1341     try {
1342       ((BoundedTaskExecutor)updateRequests).waitAllTasksExecuted(10, TimeUnit.MINUTES);
1343     }
1344     catch (Exception e) {
1345       LOG.error(e);
1346     }
1347   }
1348
1349   @NotNull
1350   @Override
1351   public CompletableFuture<?> appendUsagesInBulk(@NotNull Collection<? extends Usage> usages) {
1352     CompletableFuture<Object> result = new CompletableFuture<>();
1353     addUpdateRequest(() -> ReadAction.run(() -> {
1354       try {
1355         for (Usage usage : usages) {
1356           doAppendUsage(usage);
1357         }
1358         result.complete(null);
1359       }
1360       catch (Exception e) {
1361         result.completeExceptionally(e);
1362         throw e;
1363       }
1364     }));
1365     return result;
1366   }
1367
1368   public UsageNode doAppendUsage(@NotNull Usage usage) {
1369     assert !ApplicationManager.getApplication().isDispatchThread();
1370     // invoke in ReadAction to be be sure that usages are not invalidated while the tree is being built
1371     ApplicationManager.getApplication().assertReadAccessAllowed();
1372     if (!usage.isValid()) {
1373       // because the view is built incrementally, the usage may be already invalid, so need to filter such cases
1374       return null;
1375     }
1376
1377     for (UsageViewElementsListener listener : UsageViewElementsListener.EP_NAME.getExtensionList()) {
1378       listener.beforeUsageAdded(this, usage);
1379     }
1380     reportToFUS(usage);
1381
1382     UsageNode child = myBuilder.appendOrGet(usage, isFilterDuplicateLines(), edtModelToSwingNodeChangesQueue, invalidatedUsagesConsumer);
1383     myUsageNodes.put(usage, child == null ? NULL_NODE : child);
1384
1385     if (child != null && getPresentation().isExcludeAvailable()) {
1386       for (UsageViewElementsListener listener : UsageViewElementsListener.EP_NAME.getExtensionList()) {
1387         if (listener.isExcludedByDefault(this, usage)) {
1388           myExclusionHandler.excludeNodeSilently(child);
1389         }
1390       }
1391     }
1392
1393     for (Node node = child; node != myRoot && node != null; node = (Node)node.getParent()) {
1394       node.update(this, edtFireTreeNodesChangedQueue);
1395     }
1396
1397     return child;
1398   }
1399
1400   private void reportToFUS(@NotNull Usage usage) {
1401     if (usage instanceof PsiElementUsage) {
1402       PsiElementUsage elementUsage = (PsiElementUsage)usage;
1403       Class<? extends PsiReference> referenceClass = elementUsage.getReferenceClass();
1404       PsiElement element = elementUsage.getElement();
1405       if (referenceClass != null || element != null) {
1406         Pair<Class<? extends PsiReference>, Language> pair = Pair.create(referenceClass, element != null ? element.getLanguage() : null);
1407         if (myReportedReferenceClasses.add(pair)) {
1408           UsageViewStatisticsCollector.logUsageShown(myProject, pair.first, pair.second);
1409         }
1410       }
1411     }
1412   }
1413
1414   @Override
1415   public void removeUsage(@NotNull Usage usage) {
1416     removeUsagesBulk(Collections.singleton(usage));
1417   }
1418
1419   @Override
1420   public void removeUsagesBulk(@NotNull Collection<? extends Usage> usages) {
1421     Usage toSelect = getNextToSelect(usages);
1422     UsageNode nodeToSelect = toSelect != null ? myUsageNodes.get(toSelect) : null;
1423
1424     Set<UsageNode> nodes = usagesToNodes(usages.stream()).collect(Collectors.toSet());
1425     usages.forEach(myUsageNodes::remove);
1426     if (!myUsageNodes.isEmpty()) {
1427       Set<UsageInfo> mergedInfos = usages.stream()
1428         .filter(usage -> usage instanceof UsageInfo2UsageAdapter && ((UsageInfo2UsageAdapter)usage).getMergedInfos().length > 1)
1429         .flatMap(usage -> Arrays.stream(((UsageInfo2UsageAdapter)usage).getMergedInfos()))
1430         .collect(Collectors.toSet());
1431       if (!mergedInfos.isEmpty()) {
1432         myUsageNodes.keySet().removeIf(
1433           usage -> usage instanceof UsageInfo2UsageAdapter && mergedInfos.contains(((UsageInfo2UsageAdapter)usage).getUsageInfo()));
1434       }
1435     }
1436
1437     if (!nodes.isEmpty() && !myPresentation.isDetachedMode()) {
1438       UIUtil.invokeLaterIfNeeded(() -> {
1439         if (isDisposed()) return;
1440         DefaultTreeModel treeModel = (DefaultTreeModel)myTree.getModel();
1441         ((GroupNode)treeModel.getRoot()).removeUsagesBulk(nodes, treeModel);
1442         if (nodeToSelect != null) {
1443           TreePath path = new TreePath(nodeToSelect.getPath());
1444           myTree.addSelectionPath(path);
1445         }
1446       });
1447     }
1448   }
1449
1450   @Override
1451   public void includeUsages(Usage @NotNull [] usages) {
1452     usagesToNodes(Arrays.stream(usages))
1453       .forEach(myExclusionHandler::includeNode);
1454   }
1455
1456   @Override
1457   public void excludeUsages(Usage @NotNull [] usages) {
1458     usagesToNodes(Arrays.stream(usages))
1459       .forEach(myExclusionHandler::excludeNode);
1460   }
1461
1462   @NotNull
1463   private Stream<UsageNode> usagesToNodes(@NotNull Stream<? extends Usage> usages) {
1464     return usages
1465       .map(myUsageNodes::get)
1466       .filter(node -> node != NULL_NODE && node != null);
1467   }
1468
1469   @Override
1470   public void selectUsages(Usage @NotNull [] usages) {
1471     EDT.assertIsEdt();
1472     TreePath[] paths = usagesToNodes(Arrays.stream(usages))
1473       .map(node -> new TreePath(node.getPath()))
1474       .toArray(TreePath[]::new);
1475
1476     myTree.setSelectionPaths(paths);
1477     if (paths.length != 0) myTree.scrollPathToVisible(paths[0]);
1478   }
1479
1480   @NotNull
1481   @Override
1482   public JComponent getPreferredFocusableComponent() {
1483     EDT.assertIsEdt();
1484     return myTree != null ? myTree : getComponent();
1485   }
1486
1487   @Override
1488   @NotNull
1489   public JComponent getComponent() {
1490     EDT.assertIsEdt();
1491     return myRootPanel == null ? new JLabel() : myRootPanel;
1492   }
1493
1494   @Override
1495   public int getUsagesCount() {
1496     return myUsageNodes.size();
1497   }
1498
1499   @Override
1500   public void addExcludeListener(@NotNull Disposable disposable, @NotNull ExcludeListener listener) {
1501     myExcludeListeners.add(listener);
1502     Disposer.register(disposable, () -> myExcludeListeners.remove(listener));
1503   }
1504
1505   void setContent(@NotNull Content content) {
1506     myContent = content;
1507     content.setDisposer(this);
1508   }
1509
1510   private void updateImmediately() {
1511     EDT.assertIsEdt();
1512     if (isDisposed()) return;
1513     TreeNode root = (TreeNode)myTree.getModel().getRoot();
1514     List<Node> toUpdate = new ArrayList<>();
1515     checkNodeValidity(root, new TreePath(root), toUpdate);
1516     queueUpdateBulk(toUpdate, EmptyRunnable.getInstance());
1517     updateOnSelectionChanged();
1518   }
1519
1520   private void queueUpdateBulk(@NotNull List<? extends Node> toUpdate, @NotNull Runnable onCompletedInEdt) {
1521     if (toUpdate.isEmpty() || isDisposed()) return;
1522     ReadAction
1523       .nonBlocking(() -> {
1524         for (Node node : toUpdate) {
1525           try {
1526             node.update(this, edtFireTreeNodesChangedQueue);
1527           }
1528           catch (IndexNotReadyException ignore) {
1529           }
1530         }
1531       })
1532       .expireWith(this)
1533       .finishOnUiThread(ModalityState.defaultModalityState(), __ -> onCompletedInEdt.run())
1534       .submit(updateRequests);
1535   }
1536
1537   private void updateImmediatelyNodesUpToRoot(@NotNull Collection<? extends Node> nodes) {
1538     EDT.assertIsEdt();
1539     if (isDisposed()) return;
1540     TreeNode root = (TreeNode)myTree.getModel().getRoot();
1541     Set<Node> queued = new HashSet<>();
1542     List<Node> toUpdate = new ArrayList<>();
1543     while (true) {
1544       Set<Node> parents = new HashSet<>();
1545       for (Node node : nodes) {
1546         toUpdate.add(node);
1547         TreeNode parent = node.getParent();
1548         if (parent != root && parent instanceof Node && queued.add((Node)parent)) {
1549           parents.add((Node)parent);
1550         }
1551       }
1552       if (parents.isEmpty()) break;
1553       nodes = parents;
1554     }
1555     queueUpdateBulk(toUpdate, EmptyRunnable.getInstance());
1556     updateImmediately();
1557   }
1558
1559
1560   private void updateOnSelectionChanged() {
1561     EDT.assertIsEdt();
1562     if (myCurrentUsageContextPanel != null) {
1563       try {
1564         myCurrentUsageContextPanel.updateLayout(getSelectedUsageInfos());
1565       }
1566       catch (IndexNotReadyException ignore) {
1567       }
1568     }
1569   }
1570
1571   private void checkNodeValidity(@NotNull TreeNode node, @NotNull TreePath path, @NotNull List<? super Node> result) {
1572     EDT.assertIsEdt();
1573     boolean shouldCheckChildren = true;
1574     if (myTree.isCollapsed(path)) {
1575       if (node instanceof Node) {
1576         ((Node)node).markNeedUpdate();
1577       }
1578       shouldCheckChildren = false;
1579       // optimization: do not call expensive update() on invisible node
1580     }
1581     UsageViewTreeCellRenderer.RowLocation isVisible =
1582       myUsageViewTreeCellRenderer.isRowVisible(myTree.getRowForPath(new TreePath(((DefaultMutableTreeNode)node).getPath())),
1583                                                myTree.getVisibleRect());
1584
1585     // if row is below visible rectangle, no sense to update it or any children
1586     if (shouldCheckChildren && isVisible != UsageViewTreeCellRenderer.RowLocation.AFTER_VISIBLE_RECT) {
1587       for (int i = 0; i < node.getChildCount(); i++) {
1588         TreeNode child = node.getChildAt(i);
1589         checkNodeValidity(child, path.pathByAddingChild(child), result);
1590       }
1591     }
1592
1593     // call update last, to let children a chance to update their cache first
1594     if (node instanceof Node && node != getModelRoot() && isVisible == UsageViewTreeCellRenderer.RowLocation.INSIDE_VISIBLE_RECT) {
1595       result.add((Node)node);
1596     }
1597   }
1598
1599   void updateLater() {
1600     myUpdateAlarm.cancelAndRequest();
1601   }
1602
1603   @Override
1604   public void close() {
1605     cancelCurrentSearch();
1606     if (myContent != null) {
1607       UsageViewContentManager.getInstance(myProject).closeContent(myContent);
1608     }
1609   }
1610
1611   private void saveSplitterProportions() {
1612     EDT.assertIsEdt();
1613     getUsageViewSettings().setPreviewUsagesSplitterProportion(myPreviewSplitter.getProportion());
1614   }
1615
1616   @Override
1617   public void dispose() {
1618     EDT.assertIsEdt();
1619     disposeUsageContextPanels();
1620     synchronized (lock) {
1621       isDisposed = true;
1622       myUpdateAlarm.cancelAllRequests();
1623
1624       cancelCurrentSearch();
1625       myRerunAction = null;
1626       if (myTree != null) {
1627         ToolTipManager.sharedInstance().unregisterComponent(myTree);
1628       }
1629     }
1630     if (myDisposeSmartPointersOnClose) {
1631       disposeSmartPointers();
1632     }
1633   }
1634
1635   private void disposeSmartPointers() {
1636     List<SmartPsiElementPointer<?>> smartPointers = new ArrayList<>();
1637     for (Usage usage : myUsageNodes.keySet()) {
1638       if (usage instanceof UsageInfo2UsageAdapter) {
1639         SmartPsiElementPointer<?> pointer = ((UsageInfo2UsageAdapter)usage).getUsageInfo().getSmartPointer();
1640         smartPointers.add(pointer);
1641       }
1642     }
1643
1644     if (!smartPointers.isEmpty()) {
1645       for (SmartPsiElementPointer<?> pointer : smartPointers) {
1646         Project project = pointer.getProject();
1647         if (!project.isDisposed()) {
1648           SmartPointerManager.getInstance(project).removePointer(pointer);
1649         }
1650       }
1651     }
1652     myUsageNodes.clear();
1653   }
1654
1655   @Override
1656   public boolean isSearchInProgress() {
1657     return mySearchInProgress;
1658   }
1659
1660   @Override
1661   public void setSearchInProgress(boolean searchInProgress) {
1662     mySearchInProgress = searchInProgress;
1663     if (!myPresentation.isDetachedMode()) {
1664       UIUtil.invokeLaterIfNeeded(() -> {
1665         if (isDisposed()) return;
1666         UsageNode firstUsageNode = myModel.getFirstUsageNode();
1667         if (firstUsageNode == null) return;
1668
1669         Node node = getSelectedNode();
1670         if (node != null && !Comparing.equal(new TreePath(node.getPath()), TreeUtil.getFirstNodePath(myTree))) {
1671           // user has selected node already
1672           return;
1673         }
1674         showNode(firstUsageNode);
1675         if (getUsageViewSettings().isExpanded() && myUsageNodes.size() < 10000) {
1676           expandAll();
1677         }
1678       });
1679     }
1680   }
1681
1682   public boolean isDisposed() {
1683     return isDisposed || myProject.isDisposed();
1684   }
1685
1686   private void showNode(@NotNull UsageNode node) {
1687     EDT.assertIsEdt();
1688     if (!isDisposed() && !myPresentation.isDetachedMode()) {
1689       fireEvents();
1690       TreePath usagePath = new TreePath(node.getPath());
1691       myTree.expandPath(usagePath.getParentPath());
1692       TreeUtil.selectPath(myTree, usagePath);
1693     }
1694   }
1695
1696   @Override
1697   public void setRerunAction(@NotNull Action rerunAction) {
1698     myRerunAction = rerunAction;
1699   }
1700
1701   @Override
1702   public void addButtonToLowerPane(@NotNull Action action) {
1703     EDT.assertIsEdt();
1704     int index = myButtonPanel.getComponentCount();
1705     if (!SystemInfo.isMac && index > 0 && myPresentation.isShowCancelButton()) index--;
1706     myButtonPanel.addButtonAction(index, action);
1707     Object o = action.getValue(Action.ACCELERATOR_KEY);
1708     if (o instanceof KeyStroke) {
1709       myTree.registerKeyboardAction(action, (KeyStroke)o, JComponent.WHEN_FOCUSED);
1710     }
1711   }
1712
1713   @Override
1714   public void addButtonToLowerPane(@NotNull Runnable runnable, @NotNull String text) {
1715     addButtonToLowerPane(new AbstractAction(UIUtil.replaceMnemonicAmpersand(text)) {
1716       @Override
1717       public void actionPerformed(ActionEvent e) {
1718         runnable.run();
1719       }
1720     });
1721   }
1722
1723   @Override
1724   public void setAdditionalComponent(@Nullable JComponent comp) {
1725     BorderLayout layout = (BorderLayout)myAdditionalComponent.getLayout();
1726     Component prev = layout.getLayoutComponent(myAdditionalComponent, BorderLayout.CENTER);
1727     if (prev == comp) return;
1728     if (prev != null) myAdditionalComponent.remove(prev);
1729     if (comp != null) myAdditionalComponent.add(comp, BorderLayout.CENTER);
1730     myAdditionalComponent.revalidate();
1731   }
1732
1733   @Override
1734   public void addButtonToLowerPane(@NotNull Runnable runnable, @NotNull String text, char mnemonic) {
1735     // implemented method is deprecated, so, it just calls non-deprecated overloading one
1736     addButtonToLowerPane(runnable, text);
1737   }
1738
1739   @Override
1740   public void addPerformOperationAction(@NotNull Runnable processRunnable,
1741                                         @NotNull String commandName,
1742                                         @NotNull String cannotMakeString,
1743                                         @NotNull String shortDescription) {
1744     addPerformOperationAction(processRunnable, commandName, cannotMakeString, shortDescription, true);
1745   }
1746
1747   @Override
1748   public void addPerformOperationAction(@NotNull Runnable processRunnable,
1749                                         @NotNull String commandName,
1750                                         @NotNull String cannotMakeString,
1751                                         @NotNull String shortDescription,
1752                                         boolean checkReadOnlyStatus) {
1753     Runnable runnable = new MyPerformOperationRunnable(processRunnable, commandName, cannotMakeString, checkReadOnlyStatus);
1754     addButtonToLowerPane(runnable, shortDescription);
1755   }
1756
1757   private boolean allTargetsAreValid() {
1758     for (UsageTarget target : myTargets) {
1759       if (!target.isValid()) {
1760         return false;
1761       }
1762     }
1763
1764     return true;
1765   }
1766
1767   @NotNull
1768   @Override
1769   public UsageViewPresentation getPresentation() {
1770     return myPresentation;
1771   }
1772
1773   public boolean canPerformReRun() {
1774     if (myRerunAction != null && myRerunAction.isEnabled()) return allTargetsAreValid();
1775     try {
1776       return myUsageSearcherFactory != null && allTargetsAreValid() && myUsageSearcherFactory.create() != null;
1777     }
1778     catch (PsiInvalidElementAccessException e) {
1779       return false;
1780     }
1781   }
1782
1783   private boolean checkReadonlyUsages() {
1784     Set<VirtualFile> readOnlyUsages = getReadOnlyUsagesFiles();
1785
1786     return readOnlyUsages.isEmpty() ||
1787            !ReadonlyStatusHandler.getInstance(myProject).ensureFilesWritable(readOnlyUsages).hasReadonlyFiles();
1788   }
1789
1790   @NotNull
1791   private Set<Usage> getReadOnlyUsages() {
1792     Set<Usage> result = new THashSet<>();
1793     Set<Map.Entry<Usage, UsageNode>> usages = myUsageNodes.entrySet();
1794     for (Map.Entry<Usage, UsageNode> entry : usages) {
1795       Usage usage = entry.getKey();
1796       UsageNode node = entry.getValue();
1797       if (node != null && node != NULL_NODE && !node.isExcluded() && usage.isReadOnly()) {
1798         result.add(usage);
1799       }
1800     }
1801     return result;
1802   }
1803
1804   @NotNull
1805   private Set<VirtualFile> getReadOnlyUsagesFiles() {
1806     Set<Usage> usages = getReadOnlyUsages();
1807     Set<VirtualFile> result = new THashSet<>();
1808     for (Usage usage : usages) {
1809       if (usage instanceof UsageInFile) {
1810         UsageInFile usageInFile = (UsageInFile)usage;
1811         VirtualFile file = usageInFile.getFile();
1812         if (file != null && file.isValid()) result.add(file);
1813       }
1814
1815       if (usage instanceof UsageInFiles) {
1816         UsageInFiles usageInFiles = (UsageInFiles)usage;
1817         ContainerUtil.addAll(result, usageInFiles.getFiles());
1818       }
1819     }
1820     for (UsageTarget target : myTargets) {
1821       VirtualFile[] files = target.getFiles();
1822       if (files == null) continue;
1823       ContainerUtil.addAll(result, files);
1824     }
1825     return result;
1826   }
1827
1828   @Override
1829   @NotNull
1830   public Set<Usage> getExcludedUsages() {
1831     Set<Usage> result = new THashSet<>();
1832     for (Map.Entry<Usage, UsageNode> entry : myUsageNodes.entrySet()) {
1833       UsageNode node = entry.getValue();
1834       Usage usage = entry.getKey();
1835       if (node == NULL_NODE || node == null) {
1836         continue;
1837       }
1838       if (node.isExcluded()) {
1839         result.add(usage);
1840       }
1841     }
1842
1843     return result;
1844   }
1845
1846
1847   @Nullable
1848   private Node getSelectedNode() {
1849     EDT.assertIsEdt();
1850     TreePath leadSelectionPath = myTree.getLeadSelectionPath();
1851     if (leadSelectionPath == null) return null;
1852
1853     DefaultMutableTreeNode node = (DefaultMutableTreeNode)leadSelectionPath.getLastPathComponent();
1854     return node instanceof Node ? (Node)node : null;
1855   }
1856
1857   private Node @Nullable [] getSelectedNodes() {
1858     EDT.assertIsEdt();
1859     TreePath[] leadSelectionPath = myTree.getSelectionPaths();
1860     if (leadSelectionPath == null || leadSelectionPath.length == 0) return null;
1861
1862     List<Node> result = new ArrayList<>();
1863     for (TreePath comp : leadSelectionPath) {
1864       Object lastPathComponent = comp.getLastPathComponent();
1865       if (lastPathComponent instanceof Node) {
1866         Node node = (Node)lastPathComponent;
1867         result.add(node);
1868       }
1869     }
1870     return result.isEmpty() ? null : result.toArray(new Node[0]);
1871   }
1872
1873   @Override
1874   @NotNull
1875   public Set<Usage> getSelectedUsages() {
1876     EDT.assertIsEdt();
1877     TreePath[] selectionPaths = myTree.getSelectionPaths();
1878     if (selectionPaths == null) {
1879       return Collections.emptySet();
1880     }
1881
1882     Set<Usage> usages = new THashSet<>();
1883     for (TreePath selectionPath : selectionPaths) {
1884       DefaultMutableTreeNode node = (DefaultMutableTreeNode)selectionPath.getLastPathComponent();
1885       collectUsages(node, usages);
1886     }
1887
1888     return usages;
1889   }
1890
1891   @Override
1892   @NotNull
1893   public Set<Usage> getUsages() {
1894     return myUsageNodes.keySet();
1895   }
1896
1897   @Override
1898   @NotNull
1899   public List<Usage> getSortedUsages() {
1900     List<Usage> usages = new ArrayList<>(getUsages());
1901     usages.sort(USAGE_COMPARATOR);
1902     return usages;
1903   }
1904
1905   private static void collectUsages(@NotNull DefaultMutableTreeNode node, @NotNull Set<? super Usage> usages) {
1906     if (node instanceof UsageNode) {
1907       UsageNode usageNode = (UsageNode)node;
1908       Usage usage = usageNode.getUsage();
1909       usages.add(usage);
1910     }
1911
1912     Enumeration<?> enumeration = node.children();
1913     while (enumeration.hasMoreElements()) {
1914       DefaultMutableTreeNode child = (DefaultMutableTreeNode)enumeration.nextElement();
1915       collectUsages(child, usages);
1916     }
1917   }
1918
1919   private static void collectAllChildNodes(@NotNull DefaultMutableTreeNode node, @NotNull Set<? super Node> nodes) {
1920     if (node instanceof Node) {
1921       nodes.add((Node)node);
1922     }
1923
1924     Enumeration<?> enumeration = node.children();
1925     while (enumeration.hasMoreElements()) {
1926       DefaultMutableTreeNode child = (DefaultMutableTreeNode)enumeration.nextElement();
1927       collectAllChildNodes(child, nodes);
1928     }
1929   }
1930
1931   private UsageTarget @Nullable [] getSelectedUsageTargets() {
1932     EDT.assertIsEdt();
1933     TreePath[] selectionPaths = myTree.getSelectionPaths();
1934     if (selectionPaths == null) return null;
1935
1936     Set<UsageTarget> targets = new THashSet<>();
1937     for (TreePath selectionPath : selectionPaths) {
1938       Object lastPathComponent = selectionPath.getLastPathComponent();
1939       if (lastPathComponent instanceof UsageTargetNode) {
1940         UsageTargetNode usageTargetNode = (UsageTargetNode)lastPathComponent;
1941         UsageTarget target = usageTargetNode.getTarget();
1942         if (target.isValid()) {
1943           targets.add(target);
1944         }
1945       }
1946     }
1947
1948     return targets.isEmpty() ? null : targets.toArray(UsageTarget.EMPTY_ARRAY);
1949   }
1950
1951   @Nullable
1952   private static Navigatable getNavigatableForNode(@NotNull DefaultMutableTreeNode node, boolean allowRequestFocus) {
1953     Object userObject = node.getUserObject();
1954     if (userObject instanceof Navigatable) {
1955       Navigatable navigatable = (Navigatable)userObject;
1956       return navigatable.canNavigate() ? new Navigatable() {
1957         @Override
1958         public void navigate(boolean requestFocus) {
1959           navigatable.navigate(allowRequestFocus && requestFocus);
1960         }
1961
1962         @Override
1963         public boolean canNavigate() {
1964           return navigatable.canNavigate();
1965         }
1966
1967         @Override
1968         public boolean canNavigateToSource() {
1969           return navigatable.canNavigateToSource();
1970         }
1971       } : null;
1972     }
1973     return null;
1974   }
1975
1976   /* nodes with non-valid data are not included */
1977   private static Navigatable[] getNavigatablesForNodes(Node[] nodes) {
1978     if (nodes == null) {
1979       return null;
1980     }
1981     List<Navigatable> result = new ArrayList<>();
1982     for (Node node : nodes) {
1983       Object userObject = node.getUserObject();
1984       if (userObject instanceof Navigatable) {
1985         result.add((Navigatable)userObject);
1986       }
1987     }
1988     return result.toArray(new Navigatable[0]);
1989   }
1990
1991   boolean areTargetsValid() {
1992     return myModel.areTargetsValid();
1993   }
1994
1995   private final class MyPanel extends JPanel implements TypeSafeDataProvider, OccurenceNavigator, Disposable {
1996     @Nullable private OccurenceNavigatorSupport mySupport;
1997     private final CopyProvider myCopyProvider;
1998
1999     private MyPanel(@NotNull JTree tree) {
2000       mySupport = new OccurenceNavigatorSupport(tree) {
2001         @Override
2002         protected Navigatable createDescriptorForNode(@NotNull DefaultMutableTreeNode node) {
2003           if (node.getChildCount() > 0) return null;
2004           if (node instanceof Node && ((Node)node).isExcluded()) return null;
2005           return getNavigatableForNode(node, !myPresentation.isReplaceMode());
2006         }
2007
2008         @NotNull
2009         @Override
2010         public String getNextOccurenceActionName() {
2011           return UsageViewBundle.message("action.next.occurrence");
2012         }
2013
2014         @NotNull
2015         @Override
2016         public String getPreviousOccurenceActionName() {
2017           return UsageViewBundle.message("action.previous.occurrence");
2018         }
2019       };
2020       myCopyProvider = new TextCopyProvider() {
2021         @Nullable
2022         @Override
2023         public Collection<String> getTextLinesToCopy() {
2024           Node[] selectedNodes = getSelectedNodes();
2025           if (selectedNodes != null && selectedNodes.length > 0) {
2026             List<String> lines = new ArrayList<>();
2027             for (Node node : selectedNodes) {
2028               lines.add(node.getText(UsageViewImpl.this));
2029             }
2030             return lines;
2031           }
2032           return null;
2033         }
2034       };
2035     }
2036
2037     // this is a temp workaround to fix IDEA-192713. [tav] todo: invent something
2038     @Override
2039     protected void processFocusEvent(FocusEvent e) {
2040       super.processFocusEvent(e);
2041       if (e.getID() == FocusEvent.FOCUS_GAINED) {
2042         transferFocus();
2043       }
2044     }
2045
2046     @Override
2047     public void dispose() {
2048       mySupport = null;
2049     }
2050
2051     @Override
2052     public boolean hasNextOccurence() {
2053       return mySupport != null && mySupport.hasNextOccurence();
2054     }
2055
2056     @Override
2057     public boolean hasPreviousOccurence() {
2058       return mySupport != null && mySupport.hasPreviousOccurence();
2059     }
2060
2061     @Override
2062     public OccurenceInfo goNextOccurence() {
2063       return mySupport != null ? mySupport.goNextOccurence() : null;
2064     }
2065
2066     @Override
2067     public OccurenceInfo goPreviousOccurence() {
2068       return mySupport != null ? mySupport.goPreviousOccurence() : null;
2069     }
2070
2071     @NotNull
2072     @Override
2073     public String getNextOccurenceActionName() {
2074       return mySupport != null ? mySupport.getNextOccurenceActionName() : "";
2075     }
2076
2077     @NotNull
2078     @Override
2079     public String getPreviousOccurenceActionName() {
2080       return mySupport != null ? mySupport.getPreviousOccurenceActionName() : "";
2081     }
2082
2083     @Override
2084     public void calcData(@NotNull DataKey key, @NotNull DataSink sink) {
2085       if (key == CommonDataKeys.PROJECT) {
2086         sink.put(CommonDataKeys.PROJECT, myProject);
2087       }
2088       else if (key == USAGE_VIEW_KEY) {
2089         sink.put(USAGE_VIEW_KEY, UsageViewImpl.this);
2090       }
2091       else if (key == ExclusionHandler.EXCLUSION_HANDLER) {
2092         sink.put(ExclusionHandler.EXCLUSION_HANDLER, myExclusionHandler);
2093       }
2094
2095       else if (key == CommonDataKeys.NAVIGATABLE_ARRAY) {
2096         Node[] nodes = ApplicationManager.getApplication().isDispatchThread() ? getSelectedNodes() : null;
2097         sink.put(CommonDataKeys.NAVIGATABLE_ARRAY, getNavigatablesForNodes(nodes));
2098       }
2099
2100       else if (key == PlatformDataKeys.EXPORTER_TO_TEXT_FILE) {
2101         sink.put(PlatformDataKeys.EXPORTER_TO_TEXT_FILE, myTextFileExporter);
2102       }
2103
2104       else if (key == USAGES_KEY) {
2105         Set<Usage> selectedUsages = ApplicationManager.getApplication().isDispatchThread() ? getSelectedUsages() : null;
2106         sink.put(USAGES_KEY, selectedUsages == null ? null : selectedUsages.toArray(Usage.EMPTY_ARRAY));
2107       }
2108
2109       else if (key == USAGE_TARGETS_KEY) {
2110         UsageTarget[] targets = ApplicationManager.getApplication().isDispatchThread() ? getSelectedUsageTargets() : null;
2111         sink.put(USAGE_TARGETS_KEY, targets);
2112       }
2113
2114       else if (key == CommonDataKeys.VIRTUAL_FILE_ARRAY) {
2115         Set<Usage> usages = ApplicationManager.getApplication().isDispatchThread() ? getSelectedUsages() : null;
2116         Usage[] ua = usages == null ? null : usages.toArray(Usage.EMPTY_ARRAY);
2117         UsageTarget[] usageTargets = ApplicationManager.getApplication().isDispatchThread() ? getSelectedUsageTargets() : null;
2118         VirtualFile[] data = UsageDataUtil.provideVirtualFileArray(ua, usageTargets);
2119         sink.put(CommonDataKeys.VIRTUAL_FILE_ARRAY, data);
2120       }
2121       else if (key == PlatformDataKeys.HELP_ID) {
2122         sink.put(PlatformDataKeys.HELP_ID, HELP_ID);
2123       }
2124       else if (key == PlatformDataKeys.COPY_PROVIDER) {
2125         sink.put(PlatformDataKeys.COPY_PROVIDER, myCopyProvider);
2126       }
2127       else if (key == LangDataKeys.PSI_ELEMENT_ARRAY) {
2128         if (ApplicationManager.getApplication().isDispatchThread()) {
2129           sink.put(LangDataKeys.PSI_ELEMENT_ARRAY, getSelectedUsages()
2130             .stream()
2131             .filter(u -> u instanceof PsiElementUsage)
2132             .map(u -> ((PsiElementUsage)u).getElement())
2133             .filter(Objects::nonNull)
2134             .toArray(PsiElement.ARRAY_FACTORY::create));
2135         }
2136       }
2137       else {
2138         // can arrive here outside EDT from usage view preview.
2139         // ignore all these fancy actions in this case.
2140         Node node = ApplicationManager.getApplication().isDispatchThread() ? getSelectedNode() : null;
2141         if (node != null) {
2142           Object userObject = node.getUserObject();
2143           if (userObject instanceof TypeSafeDataProvider) {
2144             ((TypeSafeDataProvider)userObject).calcData(key, sink);
2145           }
2146           else if (userObject instanceof DataProvider) {
2147             DataProvider dataProvider = (DataProvider)userObject;
2148             Object data = dataProvider.getData(key.getName());
2149             if (data != null) {
2150               sink.put(key, data);
2151             }
2152           }
2153         }
2154       }
2155     }
2156   }
2157
2158   private final class ButtonPanel extends JPanel {
2159     private ButtonPanel() {
2160       setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
2161       getProject().getMessageBus().connect(UsageViewImpl.this).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
2162         @Override
2163         public void enteredDumbMode() {
2164           update();
2165         }
2166
2167         @Override
2168         public void exitDumbMode() {
2169           update();
2170         }
2171       });
2172     }
2173
2174     //Here we use
2175     // Action.LONG_DESCRIPTION as hint label for button
2176     // Action.SHORT_DESCRIPTION as a tooltip for button
2177     private void addButtonAction(int index, @NotNull Action action) {
2178       JButton button = new JButton(action);
2179       add(button, index);
2180       DialogUtil.registerMnemonic(button);
2181
2182       if (getBorder() == null) setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));
2183       update();
2184       Object s = action.getValue(Action.LONG_DESCRIPTION);
2185       if (s instanceof String) {
2186         JBLabel label = new JBLabel((String)s);
2187         label.setEnabled(false);
2188         label.setFont(JBUI.Fonts.smallFont());
2189         add(JBUI.Borders.emptyLeft(-1).wrap(label));
2190       }
2191       s = action.getValue(Action.SHORT_DESCRIPTION);
2192       if (s instanceof String) {
2193         button.setToolTipText((String)s);
2194       }
2195       invalidate();
2196       if (getParent() != null) {
2197         getParent().validate();
2198       }
2199     }
2200
2201     void update() {
2202       boolean globallyEnabled = !isSearchInProgress() && !DumbService.isDumb(myProject);
2203       for (int i = 0; i < getComponentCount(); ++i) {
2204         Component component = getComponent(i);
2205         if (component instanceof JButton) {
2206           JButton button = (JButton)component;
2207           Action action = button.getAction();
2208           if (action != null) {
2209             if (myNeedUpdateButtons) {
2210               button.setEnabled(globallyEnabled && action.isEnabled());
2211             }
2212             Object name = action.getValue(Action.NAME);
2213             if (name instanceof String) {
2214               DialogUtil.setTextWithMnemonic(button, (String)name);
2215             }
2216           }
2217           else {
2218             button.setEnabled(globallyEnabled);
2219           }
2220         }
2221       }
2222       myNeedUpdateButtons = false;
2223     }
2224   }
2225
2226   private final class UsageState {
2227     private final Usage myUsage;
2228     private final boolean mySelected;
2229
2230     private UsageState(@NotNull Usage usage, boolean isSelected) {
2231       myUsage = usage;
2232       mySelected = isSelected;
2233     }
2234
2235     private void restore() {
2236       EDT.assertIsEdt();
2237       UsageNode node = myUsageNodes.get(myUsage);
2238       if (node == NULL_NODE || node == null) {
2239         return;
2240       }
2241       DefaultMutableTreeNode parentGroupingNode = (DefaultMutableTreeNode)node.getParent();
2242       if (parentGroupingNode != null) {
2243         TreePath treePath = new TreePath(parentGroupingNode.getPath());
2244         myTree.expandPath(treePath);
2245         if (mySelected) {
2246           myTree.addSelectionPath(treePath.pathByAddingChild(node));
2247         }
2248       }
2249     }
2250   }
2251
2252   private final class MyPerformOperationRunnable implements Runnable {
2253     private final String myCannotMakeString;
2254     private final Runnable myProcessRunnable;
2255     private final String myCommandName;
2256     private final boolean myCheckReadOnlyStatus;
2257
2258     private MyPerformOperationRunnable(@NotNull Runnable processRunnable, @NotNull String commandName, String cannotMakeString,
2259                                        boolean checkReadOnlyStatus) {
2260       myCannotMakeString = cannotMakeString;
2261       myProcessRunnable = processRunnable;
2262       myCommandName = commandName;
2263       myCheckReadOnlyStatus = checkReadOnlyStatus;
2264     }
2265
2266     @Override
2267     public void run() {
2268       if (myCheckReadOnlyStatus && !checkReadonlyUsages()) return;
2269       PsiDocumentManager.getInstance(myProject).commitAllDocuments();
2270       if (myCannotMakeString != null && myChangesDetected) {
2271         String title = UsageViewBundle.message("changes.detected.error.title");
2272         if (canPerformReRun()) {
2273           String[] options = {UsageViewBundle.message("action.description.rerun"), UsageViewBundle.message("button.text.continue"),
2274             UsageViewBundle.message("usage.view.cancel.button")};
2275           String message = myCannotMakeString + "\n\n" + UsageViewBundle.message("dialog.rerun.search");
2276           int answer =
2277             Messages.showYesNoCancelDialog(myProject, message, title, options[0], options[1], options[2], Messages.getErrorIcon());
2278           if (answer == Messages.YES) {
2279             refreshUsages();
2280           }
2281           else if (answer == Messages.CANCEL) {
2282             return;
2283           }
2284           //continue as is
2285         }
2286         else {
2287           Messages.showMessageDialog(myProject, myCannotMakeString, title, Messages.getErrorIcon());
2288           return;
2289         }
2290       }
2291
2292       // can't dispose pointers because refactoring might want to re-use the usage infos from the preview
2293       myDisposeSmartPointersOnClose = false;
2294       close();
2295
2296       try {
2297         CommandProcessor.getInstance().executeCommand(
2298           myProject, myProcessRunnable,
2299           myCommandName,
2300           null
2301         );
2302       }
2303       finally {
2304         disposeSmartPointers();
2305       }
2306     }
2307   }
2308
2309   private List<UsageInfo> getSelectedUsageInfos() {
2310     EDT.assertIsEdt();
2311     return USAGE_INFO_LIST_KEY.getData(DataManager.getInstance().getDataContext(myRootPanel));
2312   }
2313
2314   @NotNull
2315   public GroupNode getRoot() {
2316     return myRoot;
2317   }
2318
2319   @TestOnly
2320   @NotNull
2321   public String getNodeText(@NotNull TreeNode node) {
2322     return myUsageViewTreeCellRenderer.getPlainTextForNode(node);
2323   }
2324
2325   public boolean isVisible(@NotNull Usage usage) {
2326     return myBuilder != null && myBuilder.isVisible(usage);
2327   }
2328
2329   public UsageTarget @NotNull [] getTargets() {
2330     return myTargets;
2331   }
2332
2333   /**
2334    * The element the "find usages" action was invoked on.
2335    * E.g. if the "find usages" was invoked on the reference "getName(2)" pointing to the method "getName()" then the origin usage is this reference.
2336    */
2337   public void setOriginUsage(@NotNull Usage usage) {
2338     myOriginUsage = usage;
2339   }
2340
2341   /**
2342    * true if the {@param usage} points to the element the "find usages" action was invoked on
2343    */
2344   public boolean isOriginUsage(@NotNull Usage usage) {
2345     return
2346       myOriginUsage instanceof UsageInfo2UsageAdapter &&
2347       usage instanceof UsageInfo2UsageAdapter &&
2348       ((UsageInfo2UsageAdapter)usage).getUsageInfo().equals(((UsageInfo2UsageAdapter)myOriginUsage).getUsageInfo());
2349   }
2350
2351   private boolean isFilterDuplicateLines() {
2352     return myPresentation.isMergeDupLinesAvailable() && getUsageViewSettings().isFilterDuplicatedLine();
2353   }
2354
2355   public Usage getNextToSelect(@NotNull Usage toDelete) {
2356     EDT.assertIsEdt();
2357     UsageNode usageNode = myUsageNodes.get(toDelete);
2358     if (usageNode == null || usageNode.getParent().getChildCount() == 0) return null;
2359
2360     DefaultMutableTreeNode node = myRootPanel.mySupport.findNextNodeAfter(myTree, usageNode, true);
2361     if (node == null) node = myRootPanel.mySupport.findNextNodeAfter(myTree, usageNode, false); // last node
2362
2363     return node == null ? null : node.getUserObject() instanceof Usage ? (Usage)node.getUserObject() : null;
2364   }
2365
2366   public Usage getNextToSelect(@NotNull Collection<? extends Usage> toDelete) {
2367     EDT.assertIsEdt();
2368     Usage toSelect = null;
2369     for (Usage usage : toDelete) {
2370       Usage next = getNextToSelect(usage);
2371       if (!toDelete.contains(next)) {
2372         toSelect = next;
2373         break;
2374       }
2375     }
2376     return toSelect;
2377   }
2378
2379   private interface ExclusionHandlerEx<Node> extends ExclusionHandler<Node> {
2380     void excludeNodeSilently(@NotNull Node node);
2381   }
2382 }