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