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