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