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