Merge remote-tracking branch 'origin/master' into develar/is
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ui / InspectionResultsView.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInspection.ui;
18
19 import com.intellij.CommonBundle;
20 import com.intellij.analysis.AnalysisScope;
21 import com.intellij.analysis.AnalysisUIOptions;
22 import com.intellij.codeHighlighting.HighlightDisplayLevel;
23 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
24 import com.intellij.codeInspection.*;
25 import com.intellij.codeInspection.ex.*;
26 import com.intellij.codeInspection.offlineViewer.OfflineInspectionRVContentProvider;
27 import com.intellij.codeInspection.reference.RefElement;
28 import com.intellij.codeInspection.reference.RefEntity;
29 import com.intellij.codeInspection.ui.actions.ExportHTMLAction;
30 import com.intellij.codeInspection.ui.actions.InvokeQuickFixAction;
31 import com.intellij.diff.util.DiffUtil;
32 import com.intellij.icons.AllIcons;
33 import com.intellij.ide.*;
34 import com.intellij.ide.actions.ContextHelpAction;
35 import com.intellij.ide.actions.exclusion.ExclusionHandler;
36 import com.intellij.injected.editor.VirtualFileWindow;
37 import com.intellij.openapi.Disposable;
38 import com.intellij.openapi.actionSystem.*;
39 import com.intellij.openapi.application.Application;
40 import com.intellij.openapi.application.ApplicationManager;
41 import com.intellij.openapi.application.ModalityState;
42 import com.intellij.openapi.application.ReadAction;
43 import com.intellij.openapi.diagnostic.Logger;
44 import com.intellij.openapi.editor.*;
45 import com.intellij.openapi.editor.colors.EditorColors;
46 import com.intellij.openapi.editor.ex.EditorEx;
47 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
48 import com.intellij.openapi.project.DumbAware;
49 import com.intellij.openapi.project.Project;
50 import com.intellij.openapi.ui.Splitter;
51 import com.intellij.openapi.ui.popup.JBPopup;
52 import com.intellij.openapi.util.Disposer;
53 import com.intellij.openapi.util.Key;
54 import com.intellij.openapi.util.Pair;
55 import com.intellij.openapi.util.TextRange;
56 import com.intellij.openapi.vfs.VirtualFile;
57 import com.intellij.openapi.wm.ToolWindowId;
58 import com.intellij.openapi.wm.ToolWindowManager;
59 import com.intellij.pom.Navigatable;
60 import com.intellij.profile.Profile;
61 import com.intellij.profile.ProfileChangeAdapter;
62 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
63 import com.intellij.psi.*;
64 import com.intellij.psi.util.PsiUtilCore;
65 import com.intellij.ui.*;
66 import com.intellij.util.ConcurrencyUtil;
67 import com.intellij.util.EditSourceOnDoubleClickHandler;
68 import com.intellij.util.OpenSourceUtil;
69 import com.intellij.util.containers.ContainerUtil;
70 import com.intellij.util.containers.FactoryMap;
71 import com.intellij.util.ui.UIUtil;
72 import com.intellij.util.ui.tree.TreeUtil;
73 import gnu.trove.THashSet;
74 import org.jetbrains.annotations.NonNls;
75 import org.jetbrains.annotations.NotNull;
76 import org.jetbrains.annotations.Nullable;
77
78 import javax.swing.*;
79 import javax.swing.event.TreeSelectionEvent;
80 import javax.swing.event.TreeSelectionListener;
81 import javax.swing.tree.DefaultMutableTreeNode;
82 import javax.swing.tree.TreeNode;
83 import javax.swing.tree.TreePath;
84 import java.awt.*;
85 import java.awt.event.InputEvent;
86 import java.awt.event.KeyAdapter;
87 import java.awt.event.KeyEvent;
88 import java.awt.event.MouseEvent;
89 import java.util.*;
90 import java.util.List;
91 import java.util.concurrent.ConcurrentHashMap;
92 import java.util.concurrent.ConcurrentMap;
93
94 /**
95  * @author max
96  */
97 public class InspectionResultsView extends JPanel implements Disposable, OccurenceNavigator, DataProvider {
98   private static final Logger LOG = Logger.getInstance(InspectionResultsView.class);
99
100   public static final DataKey<InspectionResultsView> DATA_KEY = DataKey.create("inspectionView");
101   private static final Key<Boolean> PREVIEW_EDITOR_IS_REUSED_KEY = Key.create("inspection.tool.window.preview.editor.is.reused");
102
103   private final Project myProject;
104   private final InspectionTree myTree;
105   private final ConcurrentMap<HighlightDisplayLevel, ConcurrentMap<String, InspectionGroupNode>> myGroups =
106     ContainerUtil.newConcurrentMap();
107   private final OccurenceNavigator myOccurenceNavigator;
108   private volatile InspectionProfile myInspectionProfile;
109   @NotNull
110   private final AnalysisScope myScope;
111   @NonNls
112   private static final String HELP_ID = "reference.toolWindows.inspections";
113   private final ConcurrentMap<HighlightDisplayLevel, InspectionSeverityGroupNode> mySeverityGroupNodes = ContainerUtil.newConcurrentMap();
114
115   private final Splitter mySplitter;
116   @NotNull
117   private final GlobalInspectionContextImpl myGlobalInspectionContext;
118   private boolean myRerun;
119   private volatile boolean myDisposed;
120   private int myUpdatingRequestors; //accessed only in edt
121   private boolean myApplyingFix; //accessed only in edt
122
123   @NotNull
124   private final InspectionRVContentProvider myProvider;
125   private final ExclusionHandler<InspectionTreeNode> myExclusionHandler;
126   private EditorEx myPreviewEditor;
127   private InspectionTreeLoadingProgressAware myLoadingProgressPreview;
128   private final ExcludedInspectionTreeNodesManager myExcludedInspectionTreeNodesManager;
129   private final FactoryMap<String, Set<Object>> mySuppressedNodes = new FactoryMap<String, Set<Object>>() {
130     @Nullable
131     @Override
132     protected Set<Object> create(String key) {
133       return new THashSet<>();
134     }
135   };
136   private final ConcurrentMap<String, Set<SuppressIntentionAction>> mySuppressActions = new ConcurrentHashMap<>();
137
138   private final Object myTreeStructureUpdateLock = new Object();
139
140   public InspectionResultsView(@NotNull GlobalInspectionContextImpl globalInspectionContext,
141                                @NotNull InspectionRVContentProvider provider) {
142     setLayout(new BorderLayout());
143     myProject = globalInspectionContext.getProject();
144     myInspectionProfile = globalInspectionContext.getCurrentProfile();
145     myScope = globalInspectionContext.getCurrentScope();
146     myGlobalInspectionContext = globalInspectionContext;
147     myProvider = provider;
148     myExcludedInspectionTreeNodesManager = new ExcludedInspectionTreeNodesManager(provider instanceof OfflineInspectionRVContentProvider,
149                                                                                   isSingleInspectionRun());
150
151     myTree = new InspectionTree(myProject, globalInspectionContext, this);
152     initTreeListeners();
153
154     myOccurenceNavigator = initOccurenceNavigator();
155
156     mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION);
157     mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT));
158     mySplitter.setHonorComponentsMinimumSize(false);
159
160     mySplitter.addPropertyChangeListener(evt -> {
161       if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) {
162         myGlobalInspectionContext.setSplitterProportion(((Float)evt.getNewValue()).floatValue());
163       }
164     });
165     add(mySplitter, BorderLayout.CENTER);
166     myExclusionHandler = new ExclusionHandler<InspectionTreeNode>() {
167       @Override
168       public boolean isNodeExclusionAvailable(@NotNull InspectionTreeNode node) {
169         return true;
170       }
171
172       @Override
173       public boolean isNodeExcluded(@NotNull InspectionTreeNode node) {
174         return node.isExcluded(myExcludedInspectionTreeNodesManager);
175       }
176
177       @Override
178       public void excludeNode(@NotNull InspectionTreeNode node) {
179         node.excludeElement(myExcludedInspectionTreeNodesManager);
180       }
181
182       @Override
183       public void includeNode(@NotNull InspectionTreeNode node) {
184         node.amnestyElement(myExcludedInspectionTreeNodesManager);
185       }
186
187       @Override
188       public boolean isActionEnabled(boolean isExcludeAction) {
189         return isExcludeAction || !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS;
190       }
191
192       @Override
193       public void onDone(boolean isExcludeAction) {
194         if (isExcludeAction) {
195           if (myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS) {
196             synchronized (myTreeStructureUpdateLock) {
197               final TreePath[] selected = myTree.getSelectionPaths();
198               if (selected != null) {
199                 final TreePath commonParent = TreeUtil.findCommonPath(selected);
200                 if (!selectCommonNextSibling(selected, commonParent)) {
201                   TreeUtil.removeSelected(myTree);
202                   TreeUtil.selectPath(myTree, commonParent);
203                 }
204               }
205             }
206           }
207           myTree.revalidate();
208           myTree.repaint();
209         }
210         else {
211           InspectionViewPsiTreeChangeAdapter.resetTree(InspectionResultsView.this);
212         }
213         syncRightPanel();
214       }
215
216       private boolean selectCommonNextSibling(@NotNull TreePath[] selected, @NotNull TreePath commonParent) {
217         final int pathCount = commonParent.getPathCount() + 1;
218         for (TreePath path : selected) {
219           if (path.getPathCount() != pathCount) {
220             return false;
221           }
222         }
223         final InspectionTreeNode parent = (InspectionTreeNode)commonParent.getLastPathComponent();
224         final int[] indices = new int[selected.length];
225         for (int i = 0; i < selected.length; i++) {
226           TreePath path = selected[i];
227           indices[i] = parent.getIndex((TreeNode)path.getLastPathComponent());
228         }
229         Arrays.sort(indices);
230         int prevIdx = -1;
231         for (int idx: indices) {
232           if (prevIdx != -1) {
233             if (idx - prevIdx != 1) {
234               return false;
235             }
236           }
237           prevIdx = idx;
238         }
239         final int toSelect = indices[indices.length - 1] + 1;
240         if (parent.getChildCount() > toSelect) {
241           final TreeNode nodeToSelect = parent.getChildAt(toSelect);
242           TreeUtil.removeSelected(myTree);
243           TreeUtil.selectNode(myTree, nodeToSelect);
244           return true;
245         }
246         return false;
247       }
248     };
249     createActionsToolbar();
250     PsiManager.getInstance(myProject).addPsiTreeChangeListener(new InspectionViewPsiTreeChangeAdapter(this), this);
251
252     final InspectionProjectProfileManager profileManager = InspectionProjectProfileManager.getInstance(myProject);
253     profileManager.addProfileChangeListener(new ProfileChangeAdapter() {
254       @Override
255       public void profileChanged(Profile profile) {
256         if (profile == profileManager.getCurrentProfile()) {
257           myTree.revalidate();
258           myTree.repaint();
259           syncRightPanel();
260         }
261       }
262     }, this);
263   }
264
265   private void initTreeListeners() {
266     myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
267       @Override
268       public void valueChanged(TreeSelectionEvent e) {
269         if (myTree.isUnderQueueUpdate()) return;
270         syncRightPanel();
271         if (isAutoScrollMode()) {
272           OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
273         }
274       }
275     });
276
277     EditSourceOnDoubleClickHandler.install(myTree);
278
279     myTree.addKeyListener(new KeyAdapter() {
280       @Override
281       public void keyPressed(KeyEvent e) {
282         if (e.getKeyCode() == KeyEvent.VK_ENTER) {
283           OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
284         }
285       }
286     });
287
288     PopupHandler.installPopupHandler(myTree, IdeActions.INSPECTION_TOOL_WINDOW_TREE_POPUP, ActionPlaces.CODE_INSPECTION);
289     SmartExpander.installOn(myTree);
290   }
291
292   private OccurenceNavigatorSupport initOccurenceNavigator() {
293     return new OccurenceNavigatorSupport(myTree) {
294       @Override
295       @Nullable
296       protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
297         if (node instanceof InspectionTreeNode && ((InspectionTreeNode)node).isExcluded(myExcludedInspectionTreeNodesManager)) {
298           return null;
299         }
300         if (node instanceof RefElementNode) {
301           final RefElementNode refNode = (RefElementNode)node;
302           if (refNode.hasDescriptorsUnder()) return null;
303           final RefEntity element = refNode.getElement();
304           if (element == null || !element.isValid()) return null;
305           final CommonProblemDescriptor problem = refNode.getDescriptor();
306           if (problem != null) {
307             return navigate(problem);
308           }
309           if (element instanceof RefElement) {
310             return getOpenFileDescriptor((RefElement)element);
311           }
312         }
313         else if (node instanceof ProblemDescriptionNode) {
314           boolean isValid;
315           if (((ProblemDescriptionNode)node).isValid()) {
316             if (((ProblemDescriptionNode)node).isQuickFixAppliedFromView()) {
317               isValid = ((ProblemDescriptionNode)node).calculateIsValid();
318             } else {
319               isValid = true;
320             }
321           } else {
322             isValid = false;
323           }
324           return isValid
325                  ? navigate(((ProblemDescriptionNode)node).getDescriptor())
326                  : InspectionResultsViewUtil.getNavigatableForInvalidNode((ProblemDescriptionNode)node);
327         }
328         return null;
329       }
330
331       @Nullable
332       private Navigatable navigate(final CommonProblemDescriptor descriptor) {
333         return getSelectedNavigatable(descriptor);
334       }
335
336       @Override
337       public String getNextOccurenceActionName() {
338         return InspectionsBundle.message("inspection.action.go.next");
339       }
340
341       @Override
342       public String getPreviousOccurenceActionName() {
343         return InspectionsBundle.message("inspection.actiongo.prev");
344       }
345     };
346   }
347
348   private void createActionsToolbar() {
349     final JComponent leftActionsToolbar = createLeftActionsToolbar();
350     final JComponent rightActionsToolbar = createRightActionsToolbar();
351
352     JPanel westPanel = new JPanel(new BorderLayout());
353     westPanel.add(leftActionsToolbar, BorderLayout.WEST);
354     westPanel.add(rightActionsToolbar, BorderLayout.EAST);
355     add(westPanel, BorderLayout.WEST);
356   }
357
358   @SuppressWarnings({"NonStaticInitializer"})
359   private JComponent createRightActionsToolbar() {
360     DefaultActionGroup specialGroup = new DefaultActionGroup();
361     specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupBySeverityAction(this));
362     specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupByDirectoryAction(this));
363     specialGroup.add(myGlobalInspectionContext.getUIOptions().createFilterResolvedItemsAction(this));
364     specialGroup.add(myGlobalInspectionContext.createToggleAutoscrollAction());
365     specialGroup.add(new ExportHTMLAction(this));
366     specialGroup.add(ActionManager.getInstance().getAction("EditInspectionSettings"));
367     specialGroup.add(new InvokeQuickFixAction(this));
368     return createToolbar(specialGroup);
369   }
370
371   private JComponent createLeftActionsToolbar() {
372     final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
373     DefaultActionGroup group = new DefaultActionGroup();
374     group.add(new RerunAction(this));
375     group.add(new CloseAction());
376     final TreeExpander treeExpander = new TreeExpander() {
377       @Override
378       public void expandAll() {
379         TreeUtil.expandAll(myTree);
380       }
381
382       @Override
383       public boolean canExpand() {
384         return true;
385       }
386
387       @Override
388       public void collapseAll() {
389         TreeUtil.collapseAll(myTree, 0);
390       }
391
392       @Override
393       public boolean canCollapse() {
394         return true;
395       }
396     };
397     group.add(actionsManager.createExpandAllAction(treeExpander, myTree));
398     group.add(actionsManager.createCollapseAllAction(treeExpander, myTree));
399     group.add(actionsManager.createPrevOccurenceAction(getOccurenceNavigator()));
400     group.add(actionsManager.createNextOccurenceAction(getOccurenceNavigator()));
401     group.add(new ContextHelpAction(HELP_ID));
402
403     return createToolbar(group);
404   }
405
406   private JComponent createToolbar(final DefaultActionGroup specialGroup) {
407     final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.CODE_INSPECTION, specialGroup, false);
408     //toolbar.setTargetComponent(this);
409     return toolbar.getComponent();
410   }
411
412   @Override
413   public void dispose() {
414     InspectionResultsViewUtil.releaseEditor(myPreviewEditor);
415     mySplitter.dispose();
416     myInspectionProfile = null;
417     myDisposed = true;
418     if (myLoadingProgressPreview != null) {
419       Disposer.dispose(myLoadingProgressPreview);
420       myLoadingProgressPreview = null;
421     }
422   }
423
424
425   private boolean isAutoScrollMode() {
426     String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId();
427     return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE &&
428            (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION));
429   }
430
431   @Nullable
432   private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) {
433     final VirtualFile[] file = new VirtualFile[1];
434     final int[] offset = new int[1];
435
436     ApplicationManager.getApplication().runReadAction(() -> {
437       PsiElement psiElement = refElement.getElement();
438       if (psiElement != null) {
439         final PsiFile containingFile = psiElement.getContainingFile();
440         if (containingFile != null) {
441           file[0] = containingFile.getVirtualFile();
442           offset[0] = psiElement.getTextOffset();
443         }
444       }
445       else {
446         file[0] = null;
447       }
448     });
449
450     if (file[0] != null && file[0].isValid()) {
451       return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]);
452     }
453     return null;
454   }
455
456   public void setApplyingFix(boolean applyingFix) {
457     myApplyingFix = applyingFix;
458     syncRightPanel();
459   }
460
461   public void openRightPanelIfNeed() {
462     if (mySplitter.getSecondComponent() == null) {
463       syncRightPanel();
464     }
465   }
466
467   public void syncRightPanel() {
468     final Editor oldEditor = myPreviewEditor;
469     if (myLoadingProgressPreview != null) {
470       Disposer.dispose(myLoadingProgressPreview);
471       myLoadingProgressPreview = null;
472     }
473     if (myApplyingFix) {
474       final InspectionToolWrapper wrapper = myTree.getSelectedToolWrapper(true);
475       LOG.assertTrue(wrapper != null);
476       mySplitter.setSecondComponent(InspectionResultsViewUtil.getApplyingFixLabel(wrapper));
477     } else {
478       if (myTree.getSelectionModel().getSelectionCount() != 1) {
479         if (myTree.getSelectedToolWrapper(true) == null) {
480           mySplitter.setSecondComponent(InspectionResultsViewUtil.getNothingToShowTextLabel());
481         }
482         else {
483           showInRightPanel(myTree.getCommonSelectedElement());
484         }
485       }
486       else {
487         TreePath pathSelected = myTree.getSelectionModel().getLeadSelectionPath();
488         if (pathSelected != null) {
489           final InspectionTreeNode node = (InspectionTreeNode)pathSelected.getLastPathComponent();
490           if (node instanceof ProblemDescriptionNode) {
491             final ProblemDescriptionNode problemNode = (ProblemDescriptionNode)node;
492             showInRightPanel(problemNode.getElement());
493           }
494           else if (node instanceof InspectionPackageNode ||
495                    node instanceof InspectionModuleNode ||
496                    node instanceof RefElementNode) {
497             showInRightPanel(node.getContainingFileLocalEntity());
498           }
499           else if (node instanceof InspectionNode) {
500             if (myGlobalInspectionContext.getPresentation(((InspectionNode)node).getToolWrapper()).isDummy()) {
501               mySplitter.setSecondComponent(InspectionResultsViewUtil.getNothingToShowTextLabel());
502             }
503             else {
504               showInRightPanel(null);
505             }
506           }
507           else if (node instanceof InspectionGroupNode || node instanceof InspectionSeverityGroupNode) {
508             final InspectionViewNavigationPanel panel = new InspectionViewNavigationPanel(node, myTree);
509             myLoadingProgressPreview = panel;
510             mySplitter.setSecondComponent(panel);
511           }
512           else {
513             LOG.error("Unexpected node: " + node.getClass());
514           }
515         }
516       }
517     }
518     if (oldEditor != null) {
519       if (Boolean.TRUE.equals(oldEditor.getUserData(PREVIEW_EDITOR_IS_REUSED_KEY))) {
520         oldEditor.putUserData(PREVIEW_EDITOR_IS_REUSED_KEY, null);
521       }
522       else {
523         InspectionResultsViewUtil.releaseEditor(oldEditor);
524         if (oldEditor == myPreviewEditor) {
525           myPreviewEditor = null;
526         }
527       }
528     }
529   }
530
531   private void showInRightPanel(@Nullable final RefEntity refEntity) {
532     Cursor currentCursor = getCursor();
533     try {
534       setCursor(new Cursor(Cursor.WAIT_CURSOR));
535       final JPanel editorPanel = new JPanel();
536       editorPanel.setLayout(new BorderLayout());
537       final int problemCount = myTree.getSelectedProblemCount(true);
538       JComponent previewPanel = null;
539       final InspectionToolWrapper tool = myTree.getSelectedToolWrapper(true);
540       if (tool != null && refEntity != null && refEntity.isValid()) {
541         final InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(tool);
542         previewPanel = presentation.getCustomPreviewPanel(refEntity);
543       }
544       EditorEx previewEditor = null;
545       if (previewPanel == null) {
546         final Pair<JComponent, EditorEx> panelAndEditor = createBaseRightComponentFor(problemCount, refEntity);
547         previewPanel = panelAndEditor.getFirst();
548         previewEditor = panelAndEditor.getSecond();
549       }
550       editorPanel.add(previewPanel, BorderLayout.CENTER);
551       if (problemCount > 0) {
552         final JComponent fixToolbar = QuickFixPreviewPanelFactory.create(this);
553         if (fixToolbar != null) {
554           if (fixToolbar instanceof InspectionTreeLoadingProgressAware) {
555             myLoadingProgressPreview = (InspectionTreeLoadingProgressAware)fixToolbar;
556           }
557           if (previewEditor != null) {
558             previewPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));
559           }
560           editorPanel.add(fixToolbar, BorderLayout.NORTH);
561         }
562       }
563       if (previewEditor != null) {
564         new ProblemPreviewEditorPresentation(previewEditor, this);
565       }
566       mySplitter.setSecondComponent(editorPanel);
567     }
568     finally {
569       setCursor(currentCursor);
570     }
571   }
572
573   private Pair<JComponent, EditorEx> createBaseRightComponentFor(int problemCount, RefEntity selectedEntity) {
574     if (selectedEntity instanceof RefElement &&
575         selectedEntity.isValid() &&
576         !(((RefElement)selectedEntity).getElement() instanceof PsiDirectory)) {
577       PsiElement selectedElement = ((RefElement)selectedEntity).getElement();
578       if (problemCount == 1) {
579         CommonProblemDescriptor[] descriptors = myTree.getSelectedDescriptors();
580         if (descriptors.length != 0) {
581           final CommonProblemDescriptor descriptor = descriptors[0];
582           if (descriptor instanceof ProblemDescriptorBase) {
583             final PsiElement element = ((ProblemDescriptorBase)descriptor).getPsiElement();
584             if (element != null) {
585               selectedElement = element;
586             }
587           }
588         }
589       }
590       final PsiFile file = selectedElement.getContainingFile();
591       final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
592       if (document == null) {
593         return Pair.create(InspectionResultsViewUtil.createLabelForText("Can't open preview for \'" + file.getName() + "\'"), null);
594       }
595
596       if (reuseEditorFor(document)) {
597         myPreviewEditor.putUserData(PREVIEW_EDITOR_IS_REUSED_KEY, true);
598         myPreviewEditor.getFoldingModel().runBatchFoldingOperation(() -> {
599           myPreviewEditor.getFoldingModel().clearFoldRegions();
600         });
601         myPreviewEditor.getMarkupModel().removeAllHighlighters();
602       }
603       else {
604         myPreviewEditor = (EditorEx)EditorFactory.getInstance().createEditor(document, myProject, file.getVirtualFile(), true);
605         DiffUtil.setFoldingModelSupport(myPreviewEditor);
606         final EditorSettings settings = myPreviewEditor.getSettings();
607         settings.setLineNumbersShown(false);
608         settings.setLineMarkerAreaShown(false);
609         settings.setAdditionalColumnsCount(0);
610         settings.setAdditionalLinesCount(0);
611         settings.setLeadingWhitespaceShown(true);
612         myPreviewEditor.getColorsScheme().setColor(EditorColors.GUTTER_BACKGROUND, myPreviewEditor.getColorsScheme().getDefaultBackground());
613         myPreviewEditor.getScrollPane().setBorder(IdeBorderFactory.createEmptyBorder());
614       }
615       if (problemCount == 0) {
616         myPreviewEditor.getScrollingModel().scrollTo(myPreviewEditor.offsetToLogicalPosition(selectedElement.getTextOffset()), ScrollType.CENTER_UP);
617       }
618       myPreviewEditor.getSettings().setFoldingOutlineShown(problemCount > 1);
619       myPreviewEditor.getComponent().setBorder(IdeBorderFactory.createEmptyBorder());
620       return Pair.create(myPreviewEditor.getComponent(), myPreviewEditor);
621     }
622     else if (selectedEntity == null) {
623       return Pair.create(new InspectionNodeInfo(myTree, myProject), null);
624     }
625     else if (selectedEntity.isValid()) {
626       return Pair.create(InspectionResultsViewUtil.getPreviewIsNotAvailable(selectedEntity), null);
627     }
628     return Pair.create(InspectionResultsViewUtil.getInvalidEntityLabel(selectedEntity), null);
629   }
630
631   private boolean reuseEditorFor(Document document) {
632     return myPreviewEditor != null && !myPreviewEditor.isDisposed() && myPreviewEditor.getDocument() == document;
633   }
634
635   @NotNull
636   public InspectionNode addTool(@NotNull final InspectionToolWrapper toolWrapper,
637                                 HighlightDisplayLevel errorLevel,
638                                 boolean groupedBySeverity,
639                                 boolean isSingleInspectionRun) {
640     String groupName =
641       toolWrapper.getGroupDisplayName().isEmpty() ? InspectionProfileEntry.GENERAL_GROUP_NAME : toolWrapper.getGroupDisplayName();
642     InspectionTreeNode parentNode = getToolParentNode(groupName, toolWrapper.getGroupPath(), errorLevel, groupedBySeverity, isSingleInspectionRun);
643     InspectionNode toolNode = new InspectionNode(toolWrapper, myInspectionProfile);
644     boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE;
645     toolNode = myProvider.appendToolNodeContent(myGlobalInspectionContext, toolNode, parentNode, showStructure, groupedBySeverity);
646     InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(toolWrapper);
647     toolNode = presentation.createToolNode(myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure, groupedBySeverity);
648     ((DefaultInspectionToolPresentation)presentation).setToolNode(toolNode);
649     registerActionShortcuts(presentation);
650     return toolNode;
651   }
652
653   private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) {
654     ApplicationManager.getApplication().invokeLater(() -> {
655       final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY, null);
656       if (fixes != null) {
657         for (QuickFixAction fix : fixes) {
658           fix.registerCustomShortcutSet(fix.getShortcutSet(), this);
659         }
660       }
661     });
662   }
663
664   @NotNull
665   public Set<SuppressIntentionAction> getSuppressActions(InspectionToolWrapper wrapper) {
666     return mySuppressActions.computeIfAbsent(wrapper.getShortName(), (w) -> {
667       final SuppressIntentionAction[] actions = InspectionManagerEx.getSuppressActions(wrapper);
668       return actions == null ? Collections.emptySet() : ContainerUtil.newLinkedHashSet(actions);
669     });
670   }
671
672   public Set<Object> getSuppressedNodes(String toolId) {
673     return mySuppressedNodes.get(toolId);
674   }
675
676   @NotNull
677   public ExcludedInspectionTreeNodesManager getExcludedManager() {
678     return myExcludedInspectionTreeNodesManager;
679   }
680
681   @Nullable
682   public String getCurrentProfileName() {
683     return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName();
684   }
685
686   public InspectionProfile getCurrentProfile() {
687     return myInspectionProfile;
688   }
689
690   public void update() {
691     ApplicationManager.getApplication().assertIsDispatchThread();
692     final Application app = ApplicationManager.getApplication();
693     final Runnable buildAction = () -> {
694       try {
695         setUpdating(true);
696         synchronized (getTreeStructureUpdateLock()) {
697           mySeverityGroupNodes.clear();
698           myGroups.clear();
699           myTree.removeAllNodes();
700           addTools(myGlobalInspectionContext.getTools().values());
701         }
702       }
703       finally {
704         setUpdating(false);
705         UIUtil.invokeLaterIfNeeded(() -> myTree.restoreExpansionAndSelection(false));
706       }
707     };
708     if (app.isUnitTestMode()) {
709       buildAction.run();
710     } else {
711       app.executeOnPooledThread(buildAction);
712     }
713   }
714
715   public void setUpdating(boolean isUpdating) {
716     final Runnable update = () -> {
717       if (isUpdating) {
718         myUpdatingRequestors++;
719       } else {
720         myUpdatingRequestors--;
721       }
722       boolean hasUpdatingRequestors = myUpdatingRequestors > 0;
723       myTree.setPaintBusy(hasUpdatingRequestors);
724       if (!hasUpdatingRequestors && myLoadingProgressPreview != null) {
725         myLoadingProgressPreview.treeLoaded();
726       }
727       //TODO Dmitrii Batkovich it's a hack (preview panel should be created during selection update)
728       if (!hasUpdatingRequestors && mySplitter.getSecondComponent() == null) {
729         if (myTree.getSelectionModel().getSelectionPath() == null) {
730           TreeUtil.selectFirstNode(myTree);
731         }
732         syncRightPanel();
733       }
734     };
735     final Application app = ApplicationManager.getApplication();
736     if (app.isDispatchThread()) {
737       update.run();
738     }
739     else {
740       app.invokeLater(update, ModalityState.any());
741     }
742   }
743
744   public Object getTreeStructureUpdateLock() {
745     return myTreeStructureUpdateLock;
746   }
747
748   public void addTools(Collection<Tools> tools) {
749     synchronized (myTreeStructureUpdateLock) {
750       InspectionProfileImpl profile = (InspectionProfileImpl)myInspectionProfile;
751       boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY;
752       boolean singleInspectionRun = isSingleInspectionRun();
753       for (Tools currentTools : tools) {
754         InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool();
755         if (myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS &&
756             myExcludedInspectionTreeNodesManager.containsInspectionNode(defaultToolWrapper)) {
757           continue;
758         }
759         final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName());
760         for (ScopeToolState state : myProvider.getTools(currentTools)) {
761           InspectionToolWrapper toolWrapper = state.getTool();
762           if (ReadAction.compute(() -> myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper))) {
763             //ReadAction.run(
764             //  () ->
765                 addTool(toolWrapper,
766                                          profile.getErrorLevel(key, state.getScope(myProject), myProject),
767                                          isGroupedBySeverity,
768                                          singleInspectionRun)
769             //)
770             ;
771           }
772         }
773       }
774     }
775   }
776
777
778   @NotNull
779   private InspectionTreeNode getToolParentNode(@NotNull String groupName,
780                                                @NotNull String[] groupPath,
781                                                HighlightDisplayLevel errorLevel,
782                                                boolean groupedBySeverity,
783                                                boolean isSingleInspectionRun) {
784     if (!groupedBySeverity && isSingleInspectionRun) {
785       return getTree().getRoot();
786     }
787     if (groupName.isEmpty()) {
788       return getRelativeRootNode(groupedBySeverity, errorLevel);
789     }
790     ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel);
791     if (map == null) {
792       map = ConcurrencyUtil.cacheOrGet(myGroups, errorLevel, ContainerUtil.newConcurrentMap());
793     }
794     InspectionGroupNode group;
795     if (groupedBySeverity) {
796       group = map.get(groupName);
797     }
798     else {
799       group = null;
800       for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) {
801         if ((group = groupMap.get(groupName)) != null) break;
802       }
803     }
804     if (group == null) {
805       if (isSingleInspectionRun) {
806         return getRelativeRootNode(true, errorLevel);
807       }
808       group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName, groupPath));
809       if (!myDisposed) {
810         getRelativeRootNode(groupedBySeverity, errorLevel).insertByOrder(group, false);
811       }
812     }
813     return group;
814   }
815
816   @NotNull
817   private InspectionTreeNode getRelativeRootNode(boolean isGroupedBySeverity, HighlightDisplayLevel level) {
818     if (isGroupedBySeverity) {
819       InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level);
820       if (severityGroupNode == null) {
821         InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level);
822         severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode);
823         if (severityGroupNode == newNode) {
824           InspectionTreeNode root = myTree.getRoot();
825           root.insertByOrder(severityGroupNode, false);
826         }
827       }
828       return severityGroupNode;
829     }
830     return myTree.getRoot();
831   }
832
833   private OccurenceNavigator getOccurenceNavigator() {
834     return myOccurenceNavigator;
835   }
836
837   @Override
838   public boolean hasNextOccurence() {
839     return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence();
840   }
841
842   @Override
843   public boolean hasPreviousOccurence() {
844     return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence();
845   }
846
847   @Override
848   public OccurenceInfo goNextOccurence() {
849     return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null;
850   }
851
852   @Override
853   public OccurenceInfo goPreviousOccurence() {
854     return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null;
855   }
856
857   @Override
858   public String getNextOccurenceActionName() {
859     return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : "";
860   }
861
862   @Override
863   public String getPreviousOccurenceActionName() {
864     return myOccurenceNavigator != null ? myOccurenceNavigator.getPreviousOccurenceActionName() : "";
865   }
866
867   @NotNull
868   public Project getProject() {
869     return myProject;
870   }
871
872   @Override
873   public Object getData(String dataId) {
874     if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID;
875     if (DATA_KEY.is(dataId)) return this;
876     if (ExclusionHandler.EXCLUSION_HANDLER.is(dataId)) return myExclusionHandler;
877     if (myTree == null) return null;
878     TreePath[] paths = myTree.getSelectionPaths();
879
880     if (paths == null || paths.length == 0) return null;
881
882     if (paths.length > 1) {
883       if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
884         return collectPsiElements();
885       }
886       return null;
887     }
888
889     TreePath path = paths[0];
890
891     InspectionTreeNode selectedNode = (InspectionTreeNode)path.getLastPathComponent();
892
893     if (!CommonDataKeys.NAVIGATABLE.is(dataId) && !CommonDataKeys.PSI_ELEMENT.is(dataId)) {
894       return null;
895     }
896
897     if (selectedNode instanceof RefElementNode) {
898       final RefElementNode refElementNode = (RefElementNode)selectedNode;
899       RefEntity refElement = refElementNode.getElement();
900       if (refElement == null) return null;
901       final RefEntity item = refElement.getRefManager().getRefinedElement(refElement);
902
903       if (!item.isValid()) return null;
904
905       PsiElement psiElement = item instanceof RefElement ? ((RefElement)item).getElement() : null;
906       if (psiElement == null) return null;
907
908       final CommonProblemDescriptor problem = refElementNode.getDescriptor();
909       if (problem != null) {
910         if (problem instanceof ProblemDescriptor) {
911           PsiElement elementFromDescriptor = ((ProblemDescriptor)problem).getPsiElement();
912           if (elementFromDescriptor == null) {
913             final InspectionTreeNode node = (InspectionTreeNode)refElementNode.getChildAt(0);
914             if (node.isValid()) {
915               return null;
916             }
917           } else {
918             psiElement = elementFromDescriptor;
919           }
920         }
921         else {
922           return null;
923         }
924       }
925
926       if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
927         return getSelectedNavigatable(problem, psiElement);
928       }
929       else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
930         return psiElement.isValid() ? psiElement : null;
931       }
932     }
933     else if (selectedNode instanceof ProblemDescriptionNode && CommonDataKeys.NAVIGATABLE.is(dataId)) {
934       Navigatable navigatable = getSelectedNavigatable(((ProblemDescriptionNode)selectedNode).getDescriptor());
935       return navigatable == null ? InspectionResultsViewUtil.getNavigatableForInvalidNode((ProblemDescriptionNode)selectedNode) : navigatable;
936     }
937
938     return null;
939   }
940
941   @Nullable
942   private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) {
943     return getSelectedNavigatable(descriptor,
944                                   descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getPsiElement() : null);
945   }
946
947   @Nullable
948   private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor, final PsiElement psiElement) {
949     if (descriptor instanceof ProblemDescriptorBase) {
950       Navigatable navigatable = ((ProblemDescriptorBase)descriptor).getNavigatable();
951       if (navigatable != null) {
952         return navigatable;
953       }
954     }
955     if (psiElement == null || !psiElement.isValid()) return null;
956     PsiFile containingFile = psiElement.getContainingFile();
957     VirtualFile virtualFile = containingFile == null ? null : containingFile.getVirtualFile();
958
959     if (virtualFile != null) {
960       int startOffset = psiElement.getTextOffset();
961       if (descriptor instanceof ProblemDescriptorBase) {
962         final TextRange textRange = ((ProblemDescriptorBase)descriptor).getTextRangeForNavigation();
963         if (textRange != null) {
964           if (virtualFile instanceof VirtualFileWindow) {
965             virtualFile = ((VirtualFileWindow)virtualFile).getDelegate();
966           }
967           startOffset = textRange.getStartOffset();
968         }
969       }
970       return new OpenFileDescriptor(myProject, virtualFile, startOffset);
971     }
972     return null;
973   }
974
975   private PsiElement[] collectPsiElements() {
976     RefEntity[] refElements = myTree.getSelectedElements();
977     List<PsiElement> psiElements = new ArrayList<PsiElement>();
978     for (RefEntity refElement : refElements) {
979       PsiElement psiElement = refElement instanceof RefElement ? ((RefElement)refElement).getElement() : null;
980       if (psiElement != null && psiElement.isValid()) {
981         psiElements.add(psiElement);
982       }
983     }
984
985     return PsiUtilCore.toPsiElementArray(psiElements);
986   }
987
988   @NotNull
989   public InspectionTree getTree() {
990     return myTree;
991   }
992
993   @NotNull
994   public GlobalInspectionContextImpl getGlobalInspectionContext() {
995     return myGlobalInspectionContext;
996   }
997
998   @NotNull
999   public InspectionRVContentProvider getProvider() {
1000     return myProvider;
1001   }
1002
1003   public boolean isSingleToolInSelection() {
1004     return myTree != null && myTree.getSelectedToolWrapper(true) != null;
1005   }
1006
1007   public boolean isRerun() {
1008     boolean rerun = myRerun;
1009     myRerun = false;
1010     return rerun;
1011   }
1012
1013   public boolean isSingleInspectionRun() {
1014     return myInspectionProfile.getSingleTool() != null;
1015   }
1016
1017   public static void showPopup(AnActionEvent e, JBPopup popup) {
1018     final InputEvent event = e.getInputEvent();
1019     if (event instanceof MouseEvent) {
1020       popup.showUnderneathOf(event.getComponent());
1021     }
1022     else {
1023       popup.showInBestPositionFor(e.getDataContext());
1024     }
1025   }
1026
1027   @NotNull
1028   public AnalysisScope getScope() {
1029     return myScope;
1030   }
1031
1032   public boolean isUpdating() {
1033     return myUpdatingRequestors > 0;
1034   }
1035
1036   void updateRightPanelLoading() {
1037     if (!myDisposed && isUpdating() && myLoadingProgressPreview != null) {
1038       myLoadingProgressPreview.updateLoadingProgress();
1039     }
1040   }
1041
1042   public boolean hasProblems() {
1043     return hasProblems(myGlobalInspectionContext.getTools().values(), myGlobalInspectionContext, myProvider);
1044   }
1045
1046   public static boolean hasProblems(@NotNull Collection<Tools> tools,
1047                                     @NotNull GlobalInspectionContextImpl context,
1048                                     @NotNull InspectionRVContentProvider contentProvider) {
1049     for (Tools currentTools : tools) {
1050       for (ScopeToolState state : contentProvider.getTools(currentTools)) {
1051         InspectionToolWrapper toolWrapper = state.getTool();
1052         if (contentProvider.checkReportedProblems(context, toolWrapper)) {
1053           return true;
1054         }
1055       }
1056     }
1057     return false;
1058   }
1059
1060   public boolean isDisposed() {
1061     return myDisposed;
1062   }
1063
1064   private class CloseAction extends AnAction implements DumbAware {
1065     private CloseAction() {
1066       super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel);
1067     }
1068
1069     @Override
1070     public void actionPerformed(AnActionEvent e) {
1071       myGlobalInspectionContext.close(true);
1072     }
1073   }
1074
1075   public void updateCurrentProfile() {
1076     final String name = myInspectionProfile.getName();
1077     myInspectionProfile = (InspectionProfile)myInspectionProfile.getProfileManager().getProfile(name);
1078   }
1079
1080   private class RerunAction extends AnAction {
1081     public RerunAction(JComponent comp) {
1082       super(InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"),
1083             AllIcons.Actions.Rerun);
1084       registerCustomShortcutSet(CommonShortcuts.getRerun(), comp);
1085     }
1086
1087     @Override
1088     public void update(AnActionEvent e) {
1089       e.getPresentation().setEnabled(isRerunAvailable());
1090     }
1091
1092     @Override
1093     public void actionPerformed(AnActionEvent e) {
1094       rerun();
1095     }
1096
1097     private void rerun() {
1098       InspectionResultsView.this.rerun();
1099     }
1100   }
1101
1102   public boolean isRerunAvailable() {
1103     return !(myProvider instanceof OfflineInspectionRVContentProvider) && myScope.isValid();
1104   }
1105
1106   public void rerun() {
1107     myRerun = true;
1108     if (myScope.isValid()) {
1109       AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions());
1110       myGlobalInspectionContext.setTreeState(getTree().getTreeState());
1111       myGlobalInspectionContext.doInspections(myScope);
1112     }
1113   }
1114 }