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