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