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