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