Merge remote-tracking branch 'origin/master'
[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(true);
417       LOG.assertTrue(wrapper != null);
418       mySplitter.setSecondComponent(InspectionResultsViewUtil.getApplyingFixLabel(wrapper));
419     } else {
420       if (myTree.getSelectionModel().getSelectionCount() != 1) {
421         if (myTree.getSelectedToolWrapper(true) == 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(true);
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     ((DefaultInspectionToolPresentation)presentation).setToolNode(toolNode);
588     registerActionShortcuts(presentation);
589     return toolNode;
590   }
591
592   private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) {
593     ApplicationManager.getApplication().invokeLater(() -> {
594       final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY, null);
595       if (fixes != null) {
596         for (QuickFixAction fix : fixes) {
597           fix.registerCustomShortcutSet(fix.getShortcutSet(), this);
598         }
599       }
600     });
601   }
602
603   @NotNull
604   public Set<SuppressIntentionAction> getSuppressActions(InspectionToolWrapper wrapper) {
605     return mySuppressActions.computeIfAbsent(wrapper.getShortName(), (w) -> {
606       final SuppressIntentionAction[] actions = InspectionManagerEx.getSuppressActions(wrapper);
607       return actions == null ? Collections.emptySet() : ContainerUtil.newLinkedHashSet(actions);
608     });
609   }
610
611   public Set<Object> getSuppressedNodes() {
612     return mySuppressedNodes;
613   }
614
615   @NotNull
616   public ExcludedInspectionTreeNodesManager getExcludedManager() {
617     return myExcludedInspectionTreeNodesManager;
618   }
619
620   @Nullable
621   public String getCurrentProfileName() {
622     return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName();
623   }
624
625   public InspectionProfile getCurrentProfile() {
626     return myInspectionProfile;
627   }
628
629   public void update() {
630     ApplicationManager.getApplication().assertIsDispatchThread();
631     final Application app = ApplicationManager.getApplication();
632     final Runnable buildAction = () -> {
633       try {
634         setUpdating(true);
635         synchronized (getTreeStructureUpdateLock()) {
636           mySeverityGroupNodes.clear();
637           myGroups.clear();
638           myTree.removeAllNodes();
639           addTools(myGlobalInspectionContext.getTools().values());
640         }
641       }
642       finally {
643         setUpdating(false);
644         UIUtil.invokeLaterIfNeeded(() -> myTree.restoreExpansionAndSelection(false));
645       }
646     };
647     if (app.isUnitTestMode()) {
648       buildAction.run();
649     } else {
650       app.executeOnPooledThread(() -> app.runReadAction(buildAction));
651     }
652   }
653
654   public void setUpdating(boolean isUpdating) {
655     final Runnable update = () -> {
656       if (isUpdating) {
657         myUpdatingRequestors++;
658       } else {
659         myUpdatingRequestors--;
660       }
661       boolean hasUpdatingRequestors = myUpdatingRequestors > 0;
662       myTree.setPaintBusy(hasUpdatingRequestors);
663       if (!hasUpdatingRequestors && myLoadingProgressPreview != null) {
664         myLoadingProgressPreview.treeLoaded();
665       }
666       //TODO Dmitrii Batkovich it's a hack (preview panel should be created during selection update)
667       if (!hasUpdatingRequestors && mySplitter.getSecondComponent() == null) {
668         if (myTree.getSelectionModel().getSelectionPath() == null) {
669           TreeUtil.selectFirstNode(myTree);
670         }
671         syncRightPanel();
672       }
673     };
674     final Application app = ApplicationManager.getApplication();
675     if (app.isDispatchThread()) {
676       update.run();
677     }
678     else {
679       app.invokeLater(update, ModalityState.any());
680     }
681   }
682
683   public Object getTreeStructureUpdateLock() {
684     return myTreeStructureUpdateLock;
685   }
686
687   public void addTools(Collection<Tools> tools) {
688     synchronized (myTreeStructureUpdateLock) {
689       InspectionProfileImpl profile = (InspectionProfileImpl)myInspectionProfile;
690       boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY;
691       boolean singleInspectionRun = myGlobalInspectionContext.isSingleInspectionRun();
692       for (Tools currentTools : tools) {
693         InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool();
694         if (myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS &&
695             myExcludedInspectionTreeNodesManager.containsInspectionNode(defaultToolWrapper)) {
696           continue;
697         }
698         final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName());
699         for (ScopeToolState state : myProvider.getTools(currentTools)) {
700           InspectionToolWrapper toolWrapper = state.getTool();
701           if (myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper)) {
702             addTool(toolWrapper,
703                     profile.getErrorLevel(key, state.getScope(myProject), myProject),
704                     isGroupedBySeverity,
705                     singleInspectionRun);
706           }
707         }
708       }
709     }
710   }
711
712
713   @NotNull
714   private InspectionTreeNode getToolParentNode(@NotNull String groupName,
715                                                @NotNull String[] groupPath,
716                                                HighlightDisplayLevel errorLevel,
717                                                boolean groupedBySeverity,
718                                                boolean isSingleInspectionRun) {
719     if (!groupedBySeverity && isSingleInspectionRun) {
720       return getTree().getRoot();
721     }
722     if (groupName.isEmpty()) {
723       return getRelativeRootNode(groupedBySeverity, errorLevel);
724     }
725     ConcurrentMap<String, InspectionGroupNode> map = myGroups.get(errorLevel);
726     if (map == null) {
727       map = ConcurrencyUtil.cacheOrGet(myGroups, errorLevel, ContainerUtil.newConcurrentMap());
728     }
729     InspectionGroupNode group;
730     if (groupedBySeverity) {
731       group = map.get(groupName);
732     }
733     else {
734       group = null;
735       for (Map<String, InspectionGroupNode> groupMap : myGroups.values()) {
736         if ((group = groupMap.get(groupName)) != null) break;
737       }
738     }
739     if (group == null) {
740       if (isSingleInspectionRun) {
741         return getRelativeRootNode(true, errorLevel);
742       }
743       group = ConcurrencyUtil.cacheOrGet(map, groupName, new InspectionGroupNode(groupName, groupPath));
744       if (!myDisposed) {
745         getRelativeRootNode(groupedBySeverity, errorLevel).insertByOrder(group, false);
746       }
747     }
748     return group;
749   }
750
751   @NotNull
752   private InspectionTreeNode getRelativeRootNode(boolean isGroupedBySeverity, HighlightDisplayLevel level) {
753     if (isGroupedBySeverity) {
754       InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level);
755       if (severityGroupNode == null) {
756         InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myProject, level);
757         severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode);
758         if (severityGroupNode == newNode) {
759           InspectionTreeNode root = myTree.getRoot();
760           root.insertByOrder(severityGroupNode, false);
761         }
762       }
763       return severityGroupNode;
764     }
765     return myTree.getRoot();
766   }
767
768   private OccurenceNavigator getOccurenceNavigator() {
769     return myOccurenceNavigator;
770   }
771
772   @Override
773   public boolean hasNextOccurence() {
774     return myOccurenceNavigator != null && myOccurenceNavigator.hasNextOccurence();
775   }
776
777   @Override
778   public boolean hasPreviousOccurence() {
779     return myOccurenceNavigator != null && myOccurenceNavigator.hasPreviousOccurence();
780   }
781
782   @Override
783   public OccurenceInfo goNextOccurence() {
784     return myOccurenceNavigator != null ? myOccurenceNavigator.goNextOccurence() : null;
785   }
786
787   @Override
788   public OccurenceInfo goPreviousOccurence() {
789     return myOccurenceNavigator != null ? myOccurenceNavigator.goPreviousOccurence() : null;
790   }
791
792   @Override
793   public String getNextOccurenceActionName() {
794     return myOccurenceNavigator != null ? myOccurenceNavigator.getNextOccurenceActionName() : "";
795   }
796
797   @Override
798   public String getPreviousOccurenceActionName() {
799     return myOccurenceNavigator != null ? myOccurenceNavigator.getPreviousOccurenceActionName() : "";
800   }
801
802   @NotNull
803   public Project getProject() {
804     return myProject;
805   }
806
807   @Override
808   public Object getData(String dataId) {
809     if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID;
810     if (DATA_KEY.is(dataId)) return this;
811     if (ExclusionHandler.EXCLUSION_HANDLER.is(dataId)) return myExclusionHandler;
812     if (myTree == null) return null;
813     TreePath[] paths = myTree.getSelectionPaths();
814
815     if (paths == null || paths.length == 0) return null;
816
817     if (paths.length > 1) {
818       if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
819         return collectPsiElements();
820       }
821       return null;
822     }
823
824     TreePath path = paths[0];
825
826     InspectionTreeNode selectedNode = (InspectionTreeNode)path.getLastPathComponent();
827
828     if (!CommonDataKeys.NAVIGATABLE.is(dataId) && !CommonDataKeys.PSI_ELEMENT.is(dataId)) {
829       return null;
830     }
831
832     if (selectedNode instanceof RefElementNode) {
833       final RefElementNode refElementNode = (RefElementNode)selectedNode;
834       RefEntity refElement = refElementNode.getElement();
835       if (refElement == null) return null;
836       final RefEntity item = refElement.getRefManager().getRefinedElement(refElement);
837
838       if (!item.isValid()) return null;
839
840       PsiElement psiElement = item instanceof RefElement ? ((RefElement)item).getElement() : null;
841       if (psiElement == null) return null;
842
843       final CommonProblemDescriptor problem = refElementNode.getDescriptor();
844       if (problem != null) {
845         if (problem instanceof ProblemDescriptor) {
846           PsiElement elementFromDescriptor = ((ProblemDescriptor)problem).getPsiElement();
847           if (elementFromDescriptor == null) {
848             final InspectionTreeNode node = (InspectionTreeNode)refElementNode.getChildAt(0);
849             if (node.isValid()) {
850               return null;
851             }
852           } else {
853             psiElement = elementFromDescriptor;
854           }
855         }
856         else {
857           return null;
858         }
859       }
860
861       if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
862         return getSelectedNavigatable(problem, psiElement);
863       }
864       else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
865         return psiElement.isValid() ? psiElement : null;
866       }
867     }
868     else if (selectedNode instanceof ProblemDescriptionNode && CommonDataKeys.NAVIGATABLE.is(dataId)) {
869       Navigatable navigatable = getSelectedNavigatable(((ProblemDescriptionNode)selectedNode).getDescriptor());
870       return navigatable == null ? InspectionResultsViewUtil.getNavigatableForInvalidNode((ProblemDescriptionNode)selectedNode) : navigatable;
871     }
872
873     return null;
874   }
875
876   @Nullable
877   private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) {
878     return getSelectedNavigatable(descriptor,
879                                   descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getPsiElement() : null);
880   }
881
882   @Nullable
883   private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor, final PsiElement psiElement) {
884     if (descriptor instanceof ProblemDescriptorBase) {
885       Navigatable navigatable = ((ProblemDescriptorBase)descriptor).getNavigatable();
886       if (navigatable != null) {
887         return navigatable;
888       }
889     }
890     if (psiElement == null || !psiElement.isValid()) return null;
891     PsiFile containingFile = psiElement.getContainingFile();
892     VirtualFile virtualFile = containingFile == null ? null : containingFile.getVirtualFile();
893
894     if (virtualFile != null) {
895       int startOffset = psiElement.getTextOffset();
896       if (descriptor instanceof ProblemDescriptorBase) {
897         final TextRange textRange = ((ProblemDescriptorBase)descriptor).getTextRangeForNavigation();
898         if (textRange != null) {
899           if (virtualFile instanceof VirtualFileWindow) {
900             virtualFile = ((VirtualFileWindow)virtualFile).getDelegate();
901           }
902           startOffset = textRange.getStartOffset();
903         }
904       }
905       return new OpenFileDescriptor(myProject, virtualFile, startOffset);
906     }
907     return null;
908   }
909
910   private PsiElement[] collectPsiElements() {
911     RefEntity[] refElements = myTree.getSelectedElements();
912     List<PsiElement> psiElements = new ArrayList<PsiElement>();
913     for (RefEntity refElement : refElements) {
914       PsiElement psiElement = refElement instanceof RefElement ? ((RefElement)refElement).getElement() : null;
915       if (psiElement != null && psiElement.isValid()) {
916         psiElements.add(psiElement);
917       }
918     }
919
920     return PsiUtilCore.toPsiElementArray(psiElements);
921   }
922
923   @NotNull
924   public InspectionTree getTree() {
925     return myTree;
926   }
927
928   @NotNull
929   public GlobalInspectionContextImpl getGlobalInspectionContext() {
930     return myGlobalInspectionContext;
931   }
932
933   @NotNull
934   public InspectionRVContentProvider getProvider() {
935     return myProvider;
936   }
937
938   public boolean isSingleToolInSelection() {
939     return myTree != null && myTree.getSelectedToolWrapper(true) != null;
940   }
941
942   public boolean isRerun() {
943     boolean rerun = myRerun;
944     myRerun = false;
945     return rerun;
946   }
947
948   public boolean isProfileDefined() {
949     return myInspectionProfile != null && myInspectionProfile.isEditable();
950   }
951
952   public static void showPopup(AnActionEvent e, JBPopup popup) {
953     final InputEvent event = e.getInputEvent();
954     if (event instanceof MouseEvent) {
955       popup.showUnderneathOf(event.getComponent());
956     }
957     else {
958       popup.showInBestPositionFor(e.getDataContext());
959     }
960   }
961
962   @NotNull
963   public AnalysisScope getScope() {
964     return myScope;
965   }
966
967   public boolean isUpdating() {
968     return myUpdatingRequestors > 0;
969   }
970
971   void updateRightPanelLoading() {
972     if (!myDisposed && isUpdating() && myLoadingProgressPreview != null) {
973       myLoadingProgressPreview.updateLoadingProgress();
974     }
975   }
976
977   public boolean hasProblems() {
978     return hasProblems(myGlobalInspectionContext.getTools().values(), myGlobalInspectionContext, myProvider);
979   }
980
981   public static boolean hasProblems(@NotNull Collection<Tools> tools,
982                                     @NotNull GlobalInspectionContextImpl context,
983                                     @NotNull InspectionRVContentProvider contentProvider) {
984     for (Tools currentTools : tools) {
985       for (ScopeToolState state : contentProvider.getTools(currentTools)) {
986         InspectionToolWrapper toolWrapper = state.getTool();
987         if (contentProvider.checkReportedProblems(context, toolWrapper)) {
988           return true;
989         }
990       }
991     }
992     return false;
993   }
994
995   public boolean isDisposed() {
996     return myDisposed;
997   }
998
999   private class CloseAction extends AnAction implements DumbAware {
1000     private CloseAction() {
1001       super(CommonBundle.message("action.close"), null, AllIcons.Actions.Cancel);
1002     }
1003
1004     @Override
1005     public void actionPerformed(AnActionEvent e) {
1006       myGlobalInspectionContext.close(true);
1007     }
1008   }
1009
1010   public void updateCurrentProfile() {
1011     final String name = myInspectionProfile.getName();
1012     myInspectionProfile = (InspectionProfile)myInspectionProfile.getProfileManager().getProfile(name);
1013   }
1014
1015   private class RerunAction extends AnAction {
1016     public RerunAction(JComponent comp) {
1017       super(InspectionsBundle.message("inspection.action.rerun"), InspectionsBundle.message("inspection.action.rerun"),
1018             AllIcons.Actions.Rerun);
1019       registerCustomShortcutSet(CommonShortcuts.getRerun(), comp);
1020     }
1021
1022     @Override
1023     public void update(AnActionEvent e) {
1024       e.getPresentation().setEnabled(myScope.isValid());
1025     }
1026
1027     @Override
1028     public void actionPerformed(AnActionEvent e) {
1029       rerun();
1030     }
1031
1032     private void rerun() {
1033       myRerun = true;
1034       if (myScope.isValid()) {
1035         AnalysisUIOptions.getInstance(myProject).save(myGlobalInspectionContext.getUIOptions());
1036         myGlobalInspectionContext.setTreeState(getTree().getTreeState());
1037         myGlobalInspectionContext.doInspections(myScope);
1038       }
1039     }
1040   }
1041 }