<commit_before>/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.codeInspection.ui;

import com.intellij.CommonBundle;
import com.intellij.analysis.AnalysisScope;
import com.intellij.analysis.AnalysisUIOptions;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.*;
import com.intellij.codeInspection.offlineViewer.OfflineInspectionRVContentProvider;
import com.intellij.codeInspection.reference.RefElement;
import com.intellij.codeInspection.reference.RefEntity;
import com.intellij.codeInspection.ui.actions.ExportHTMLAction;
import com.intellij.codeInspection.ui.actions.InvokeQuickFixAction;
import com.intellij.diff.util.DiffUtil;
import com.intellij.icons.AllIcons;
import com.intellij.ide.*;
import com.intellij.ide.actions.ContextHelpAction;
import com.intellij.ide.actions.exclusion.ExclusionHandler;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.pom.Navigatable;
import com.intellij.profile.ProfileChangeAdapter;
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.*;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.ObjectUtils;
import com.intellij.util.OpenSourceUtil;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;

/**
 * @author max
 */
public class InspectionResultsView extends JPanel implements Disposable, OccurenceNavigator, DataProvider {
  private static final Logger LOG = Logger.getInstance(InspectionResultsView.class);

  public static final DataKey<InspectionResultsView> DATA_KEY = DataKey.create("inspectionView");
  private static final Key<Boolean> PREVIEW_EDITOR_IS_REUSED_KEY = Key.create("inspection.tool.window.preview.editor.is.reused");

  private final Project myProject;
  private final InspectionTree myTree;
  private final ConcurrentMap<HighlightDisplayLevel, ConcurrentMap<String, InspectionGroupNode>> myGroups =
    ContainerUtil.newConcurrentMap();
  private final OccurenceNavigator myOccurenceNavigator;
  private volatile InspectionProfileImpl myInspectionProfile;
  private final boolean mySettingsEnabled;
  @NotNull
  private final AnalysisScope myScope;
  @NonNls
  private static final String HELP_ID = "reference.toolWindows.inspections";
  private final ConcurrentMap<HighlightDisplayLevel, InspectionSeverityGroupNode> mySeverityGroupNodes = ContainerUtil.newConcurrentMap();

  private final Splitter mySplitter;
  @NotNull
  private final GlobalInspectionContextImpl myGlobalInspectionContext;
  private boolean myRerun;
  private volatile boolean myDisposed;
  private int myUpdatingRequestors; //accessed only in edt
  private boolean myApplyingFix; //accessed only in edt

  @NotNull
  private final InspectionRVContentProvider myProvider;
  private final ExclusionHandler<InspectionTreeNode> myExclusionHandler;
  private EditorEx myPreviewEditor;
  private InspectionTreeLoadingProgressAware myLoadingProgressPreview;
  private final ExcludedInspectionTreeNodesManager myExcludedInspectionTreeNodesManager;
  @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
  private final FactoryMap<String, Set<Object>> mySuppressedNodes = new FactoryMap<String, Set<Object>>() {
    @Override
    protected Set<Object> create(String key) {
      return new THashSet<>();
    }
  };
  private final InspectionViewSuppressActionHolder mySuppressActionHolder = new InspectionViewSuppressActionHolder();

  private final Object myTreeStructureUpdateLock = new Object();
  private final ExecutorService myTreeUpdater = AppExecutorUtil.createBoundedApplicationPoolExecutor("inspection-view-tree-updater", 1);

  public InspectionResultsView(@NotNull GlobalInspectionContextImpl globalInspectionContext,
                               @NotNull InspectionRVContentProvider provider) {
    setLayout(new BorderLayout());
    myProject = globalInspectionContext.getProject();
    myInspectionProfile = globalInspectionContext.getCurrentProfile();
    myScope = globalInspectionContext.getCurrentScope();
    myGlobalInspectionContext = globalInspectionContext;
    myProvider = provider;
    myExcludedInspectionTreeNodesManager = new ExcludedInspectionTreeNodesManager(provider instanceof OfflineInspectionRVContentProvider,
                                                                                  isSingleInspectionRun());

    myTree = new InspectionTree(myProject, globalInspectionContext, this);
    initTreeListeners();

    myOccurenceNavigator = initOccurenceNavigator();

    mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION);
    mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT));
    mySplitter.setHonorComponentsMinimumSize(false);

    mySplitter.addPropertyChangeListener(evt -> {
      if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) {
        myGlobalInspectionContext.setSplitterProportion(((Float)evt.getNewValue()).floatValue());
      }
    });
    add(mySplitter, BorderLayout.CENTER);
    myExclusionHandler = new ExclusionHandler<InspectionTreeNode>() {
      @Override
      public boolean isNodeExclusionAvailable(@NotNull InspectionTreeNode node) {
        return true;
      }

      @Override
      public boolean isNodeExcluded(@NotNull InspectionTreeNode node) {
        return node.isExcluded(myExcludedInspectionTreeNodesManager);
      }

      @Override
      public void excludeNode(@NotNull InspectionTreeNode node) {
        node.excludeElement(myExcludedInspectionTreeNodesManager);
      }

      @Override
      public void includeNode(@NotNull InspectionTreeNode node) {
        node.amnestyElement(myExcludedInspectionTreeNodesManager);
      }

      @Override
      public boolean isActionEnabled(boolean isExcludeAction) {
        return isExcludeAction || !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS;
      }

      @Override
      public void onDone(boolean isExcludeAction) {
        if (isExcludeAction) {
          if (myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS) {
            synchronized (myTreeStructureUpdateLock) {
              final TreePath[] selected = myTree.getSelectionPaths();
              if (selected != null) {
                final TreePath commonParent = TreeUtil.findCommonPath(selected);
                if (!selectCommonNextSibling(selected, commonParent)) {
                  TreeUtil.removeSelected(myTree);
                  TreeUtil.selectPath(myTree, commonParent);
                }
              }
            }
          }
          myTree.revalidate();
          myTree.repaint();
        }
        else {
          resetTree();
        }
        syncRightPanel();
      }

      private boolean selectCommonNextSibling(@NotNull TreePath[] selected, @NotNull TreePath commonParent) {
        final int pathCount = commonParent.getPathCount() + 1;
        for (TreePath path : selected) {
          if (path.getPathCount() != pathCount) {
            return false;
          }
        }
        final InspectionTreeNode parent = (InspectionTreeNode)commonParent.getLastPathComponent();
        final int[] indices = new int[selected.length];
        for (int i = 0; i < selected.length; i++) {
          TreePath path = selected[i];
          indices[i] = parent.getIndex((TreeNode)path.getLastPathComponent());
        }
        Arrays.sort(indices);
        int prevIdx = -1;
        for (int idx: indices) {
          if (prevIdx != -1) {
            if (idx - prevIdx != 1) {
              return false;
            }
          }
          prevIdx = idx;
        }
        final int toSelect = indices[indices.length - 1] + 1;
        if (parent.getChildCount() > toSelect) {
          final TreeNode nodeToSelect = parent.getChildAt(toSelect);
          TreeUtil.removeSelected(myTree);
          TreeUtil.selectNode(myTree, nodeToSelect);
          return true;
        }
        return false;
      }
    };
    createActionsToolbar();
    PsiManager.getInstance(myProject).addPsiTreeChangeListener(new InspectionViewPsiTreeChangeAdapter(this), this);

    ProjectInspectionProfileManager profileManager = ProjectInspectionProfileManager.getInstance(myProject);
    profileManager.addProfileChangeListener(new ProfileChangeAdapter() {
      @Override
      public void profileChanged(InspectionProfile profile) {
        if (profile == profileManager.getCurrentProfile()) {
          InspectionResultsView.this.profileChanged();
        }
      }
    }, this);

    if (!isSingleInspectionRun()) {
      mySettingsEnabled = true;
    } else {
      InspectionProfileImpl profile = getCurrentProfile();
      String toolId = ObjectUtils.notNull(profile.getSingleTool());
      InspectionToolWrapper tool = ObjectUtils.notNull(profile.getInspectionTool(toolId, getProject()));
      JComponent toolPanel = tool.getTool().createOptionsPanel();
      mySettingsEnabled = toolPanel != null;
    }
  }

  void profileChanged() {
    myTree.revalidate();
    myTree.repaint();
    syncRightPanel();
  }

  private void initTreeListeners() {
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      return;
    }
    myTree.getSelectionModel().addTreeSelectionListener(e -> {
      if (myTree.isUnderQueueUpdate()) return;
      syncRightPanel();
      if (isAutoScrollMode()) {
        OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(this), false);
      }
    });

    EditSourceOnDoubleClickHandler.install(myTree);

    myTree.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
          OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
        }
      }
    });

    PopupHandler.installPopupHandler(myTree, IdeActions.INSPECTION_TOOL_WINDOW_TREE_POPUP, ActionPlaces.CODE_INSPECTION);
    SmartExpander.installOn(myTree);
  }

  private OccurenceNavigatorSupport initOccurenceNavigator() {
    return new OccurenceNavigatorSupport(myTree) {
      @Override
      @Nullable
      protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
        if (node instanceof InspectionTreeNode && ((InspectionTreeNode)node).isExcluded(myExcludedInspectionTreeNodesManager)) {
          return null;
        }
        if (node instanceof RefElementNode) {
          final RefElementNode refNode = (RefElementNode)node;
          if (refNode.hasDescriptorsUnder()) return null;
          final RefEntity element = refNode.getElement();
          if (element == null || !element.isValid()) return null;
          final CommonProblemDescriptor problem = refNode.getDescriptor();
          if (problem != null) {
            return navigate(problem);
          }
          if (element instanceof RefElement) {
            return getOpenFileDescriptor((RefElement)element);
          }
        }
        else if (node instanceof ProblemDescriptionNode) {
          boolean isValid;
          if (((ProblemDescriptionNode)node).isValid()) {
            if (((ProblemDescriptionNode)node).isQuickFixAppliedFromView()) {
              isValid = ((ProblemDescriptionNode)node).calculateIsValid();
            } else {
              isValid = true;
            }
          } else {
            isValid = false;
          }
          return isValid
                 ? navigate(((ProblemDescriptionNode)node).getDescriptor())
                 : InspectionResultsViewUtil.getNavigatableForInvalidNode((ProblemDescriptionNode)node);
        }
        return null;
      }

      @Nullable
      private Navigatable navigate(final CommonProblemDescriptor descriptor) {
        return getSelectedNavigatable(descriptor);
      }

      @Override
      public String getNextOccurenceActionName() {
        return InspectionsBundle.message("inspection.action.go.next");
      }

      @Override
      public String getPreviousOccurenceActionName() {
        return InspectionsBundle.message("inspection.actiongo.prev");
      }
    };
  }

  private void createActionsToolbar() {
    final JComponent leftActionsToolbar = createLeftActionsToolbar();
    final JComponent rightActionsToolbar = createRightActionsToolbar();

    JPanel westPanel = new JPanel(new BorderLayout());
    westPanel.add(leftActionsToolbar, BorderLayout.WEST);
    westPanel.add(rightActionsToolbar, BorderLayout.EAST);
    add(westPanel, BorderLayout.WEST);
  }

  @SuppressWarnings("NonStaticInitializer")
  private JComponent createRightActionsToolbar() {
    DefaultActionGroup specialGroup = new DefaultActionGroup();
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupBySeverityAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupByDirectoryAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createFilterResolvedItemsAction(this));
    specialGroup.add(myGlobalInspectionContext.createToggleAutoscrollAction());
    specialGroup.add(new ExportHTMLAction(this));
    specialGroup.add(ActionManager.getInstance().getAction("EditInspectionSettings"));
    specialGroup.add(new InvokeQuickFixAction(this));
    return createToolbar(specialGroup);
  }

  private JComponent createLeftActionsToolbar() {
    final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
    DefaultActionGroup group = new DefaultActionGroup();
    group.add(new RerunAction(this));
    group.add(new CloseAction());
    final TreeExpander treeExpander = new DefaultTreeExpander(myTree);
    group.add(actionsManager.createExpandAllAction(treeExpander, myTree));
    group.add(actionsManager.createCollapseAllAction(treeExpander, myTree));
    group.add(actionsManager.createPrevOccurenceAction(getOccurenceNavigator()));
    group.add(actionsManager.createNextOccurenceAction(getOccurenceNavigator()));
    group.add(new ContextHelpAction(HELP_ID));

    return createToolbar(group);
  }

  private static JComponent createToolbar(final DefaultActionGroup specialGroup) {
    final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.CODE_INSPECTION, specialGroup, false);
    //toolbar.setTargetComponent(this);
    return toolbar.getComponent();
  }

  @Override
  public void dispose() {
    InspectionResultsViewUtil.releaseEditor(myPreviewEditor);
    mySplitter.dispose();
    myInspectionProfile = null;
    myDisposed = true;
    if (myLoadingProgressPreview != null) {
      Disposer.dispose(myLoadingProgressPreview);
      myLoadingProgressPreview = null;
    }
  }


  private boolean isAutoScrollMode() {
    String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId();
    return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE &&
           (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION));
  }

  Object getTreeStructureUpdateLock() {
    return myTreeStructureUpdateLock;
  }

  ExecutorService getTreeUpdater() {
    return myTreeUpdater;
  }

  @Nullable
  private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) {
    final VirtualFile[] file = new VirtualFile[1];
    final int[] offset = new int[1];

    ApplicationManager.getApplication().runReadAction(() -> {
      PsiElement psiElement = refElement.getElement();
      if (psiElement != null) {
        final PsiFile containingFile = psiElement.getContainingFile();
        if (containingFile != null) {
          file[0] = containingFile.getVirtualFile();
          offset[0] = psiElement.getTextOffset();
        }
      }
      else {
        file[0] = null;
      }
    });

    if (file[0] != null && file[0].isValid()) {
      return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]);
    }
    return null;
  }

  public void setApplyingFix(boolean applyingFix) {
    myApplyingFix = applyingFix;
    syncRightPanel();
  }

  void openRightPanelIfNeed() {
    if (mySplitter.getSecondComponent() == null) {
      syncRightPanel();
    }
  }

  public void syncRightPanel() {
    final Editor oldEditor = myPreviewEditor;
    if (myLoadingProgressPreview != null) {
      Disposer.dispose(myLoadingProgressPreview);
      myLoadingProgressPreview = null;
    }
    if (myApplyingFix) {
      final InspectionToolWrapper wrapper = myTree.getSelectedToolWrapper(true);
      LOG.assertTrue(wrapper != null);
      mySplitter.setSecondComponent(InspectionResultsViewUtil.getApplyingFixLabel(wrapper));
    } else {
      if (myTree.getSelectionModel().getSelectionCount() != 1) {
        if (myTree.getSelectedToolWrapper(true) == null) {
          mySplitter.setSecondComponent(InspectionResultsViewUtil.getNothingToShowTextLabel());
        }
        else {
          showInRightPanel(myTree.getCommonSelectedElement());
        }
      }
      else {
        TreePath pathSelected = myTree.getSelectionModel().getLeadSelectionPath();
        if (pathSelected != null) {
          final InspectionTreeNode node = (InspectionTreeNode)pathSelected.getLastPathComponent();
          if (node instanceof ProblemDescriptionNode) {
            final ProblemDescriptionNode problemNode = (ProblemDescriptionNode)node;
            showInRightPanel(problemNode.getElement());
          }
          else if (node instanceof InspectionPackageNode ||
                   node instanceof InspectionModuleNode ||
                   node instanceof RefElementNode) {
            showInRightPanel(node.getContainingFileLocalEntity());
          }
          else if (node instanceof InspectionNode) {
            if (myGlobalInspectionContext.getPresentation(((InspectionNode)node).getToolWrapper()).isDummy()) {
              mySplitter.setSecondComponent(InspectionResultsViewUtil.getNothingToShowTextLabel());
            }
            else {
              showInRightPanel(null);
            }
          }
          else if (node instanceof InspectionGroupNode || node instanceof InspectionSeverityGroupNode) {
            final InspectionViewNavigationPanel panel = new InspectionViewNavigationPanel(node, myTree);
            myLoadingProgressPreview = panel;
            mySplitter.setSecondComponent(panel);
          }
          else {
            LOG.error("Unexpected node: " + node.getClass());
          }
        }
      }
    }
    if (oldEditor != null) {
      if (Boolean.TRUE.equals(oldEditor.getUserData(PREVIEW_EDITOR_IS_REUSED_KEY))) {
        oldEditor.putUserData(PREVIEW_EDITOR_IS_REUSED_KEY, null);
      }
      else {
        InspectionResultsViewUtil.releaseEditor(oldEditor);
        if (oldEditor == myPreviewEditor) {
          myPreviewEditor = null;
        }
      }
    }
  }

  private void showInRightPanel(@Nullable final RefEntity refEntity) {
    final JPanel editorPanel = new JPanel();
    editorPanel.setLayout(new BorderLayout());
    final int problemCount = myTree.getSelectedProblemCount(true);
    JComponent previewPanel = null;
    final InspectionToolWrapper tool = myTree.getSelectedToolWrapper(true);
    if (tool != null && refEntity != null && refEntity.isValid()) {
      final TreePath path = myTree.getSelectionPath();
      if (path == null || !(path.getLastPathComponent() instanceof ProblemDescriptionNode)) {
        final InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(tool);
        previewPanel = presentation.getCustomPreviewPanel(refEntity);
      }
    }
    EditorEx previewEditor = null;
    if (previewPanel == null) {
      final Pair<JComponent, EditorEx> panelAndEditor = createBaseRightComponentFor(problemCount, refEntity);
      previewPanel = panelAndEditor.getFirst();
      previewEditor = panelAndEditor.getSecond();
    }
    editorPanel.add(previewPanel, BorderLayout.CENTER);
    if (problemCount > 0) {
      final JComponent fixToolbar = QuickFixPreviewPanelFactory.create(this);
      if (fixToolbar != null) {
        if (fixToolbar instanceof InspectionTreeLoadingProgressAware) {
          myLoadingProgressPreview = (InspectionTreeLoadingProgressAware)fixToolbar;
        }
        if (previewEditor != null) {
          previewPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));
        }
        editorPanel.add(fixToolbar, BorderLayout.NORTH);
      }
    }
    if (previewEditor != null) {
      new ProblemPreviewEditorPresentation(previewEditor, this);
    }
    mySplitter.setSecondComponent(editorPanel);
  }

  private Pair<JComponent, EditorEx> createBaseRightComponentFor(int problemCount, RefEntity selectedEntity) {
    if (selectedEntity instanceof RefElement &&
        selectedEntity.isValid() &&
        !(((RefElement)selectedEntity).getElement() instanceof PsiDirectory)) {
      PsiElement selectedElement = ((RefElement)selectedEntity).getElement();
      if (problemCount == 1) {
        CommonProblemDescriptor[] descriptors = myTree.getSelectedDescriptors();
        if (descriptors.length != 0) {
          final CommonProblemDescriptor descriptor = descriptors[0];
          if (descriptor instanceof ProblemDescriptorBase) {
            final PsiElement element = ((ProblemDescriptorBase)descriptor).getPsiElement();
            if (element != null) {
              selectedElement = element;
            }
          }
        }
      }
      final PsiFile file = selectedElement.getContainingFile();
      final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
      if (document == null) {
        return Pair.create(InspectionResultsViewUtil.createLabelForText("Can't open preview for \'" + file.getName() + "\'"), null);
      }

      if (reuseEditorFor(document)) {
        myPreviewEditor.putUserData(PREVIEW_EDITOR_IS_REUSED_KEY, true);
        myPreviewEditor.getFoldingModel().runBatchFoldingOperation(() -> myPreviewEditor.getFoldingModel().clearFoldRegions());
        myPreviewEditor.getMarkupModel().removeAllHighlighters();
      }
      else {
        myPreviewEditor = (EditorEx)EditorFactory.getInstance().createEditor(document, myProject, file.getVirtualFile(), true);
        DiffUtil.setFoldingModelSupport(myPreviewEditor);
        final EditorSettings settings = myPreviewEditor.getSettings();
        settings.setLineNumbersShown(false);
        settings.setFoldingOutlineShown(true);
        settings.setLineMarkerAreaShown(true);
        settings.setGutterIconsShown(false);
        settings.setAdditionalColumnsCount(0);
        settings.setAdditionalLinesCount(0);
        settings.setLeadingWhitespaceShown(true);
        myPreviewEditor.getColorsScheme().setColor(EditorColors.GUTTER_BACKGROUND, myPreviewEditor.getColorsScheme().getDefaultBackground());
        myPreviewEditor.getScrollPane().setBorder(IdeBorderFactory.createEmptyBorder());
      }
      if (problemCount == 0) {
        myPreviewEditor.getScrollingModel().scrollTo(myPreviewEditor.offsetToLogicalPosition(selectedElement.getTextOffset()), ScrollType.CENTER_UP);
      }
      myPreviewEditor.getComponent().setBorder(IdeBorderFactory.createEmptyBorder());
      return Pair.create(myPreviewEditor.getComponent(), myPreviewEditor);
    }
    if (selectedEntity == null) {
      return Pair.create(new InspectionNodeInfo(myTree, myProject), null);
    }
    if (selectedEntity.isValid()) {
      return Pair.create(InspectionResultsViewUtil.getPreviewIsNotAvailable(selectedEntity), null);
    }
    return Pair.create(InspectionResultsViewUtil.getInvalidEntityLabel(selectedEntity), null);
  }

  private boolean reuseEditorFor(Document document) {
    return myPreviewEditor != null && !myPreviewEditor.isDisposed() && myPreviewEditor.getDocument() == document;
  }

  private void addTool(@NotNull final InspectionToolWrapper toolWrapper,
                       HighlightDisplayLevel errorLevel,
                       boolean groupedBySeverity,
                       boolean isSingleInspectionRun) {
    String groupName =
      toolWrapper.getGroupDisplayName().isEmpty() ? InspectionProfileEntry.GENERAL_GROUP_NAME : toolWrapper.getGroupDisplayName();
    InspectionTreeNode parentNode = getToolParentNode(groupName, toolWrapper.getGroupPath(), errorLevel, groupedBySeverity, isSingleInspectionRun);
    InspectionNode toolNode = new InspectionNode(toolWrapper, myInspectionProfile);
    boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE;
    toolNode = myProvider.appendToolNodeContent(myGlobalInspectionContext, toolNode, parentNode, showStructure, groupedBySeverity);
    InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(toolWrapper);
    presentation.createToolNode(myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure, groupedBySeverity);
    registerActionShortcuts(presentation);
  }

  private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) {
    ApplicationManager.getApplication().invokeLater(() -> {
      final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY, null);
      if (fixes != null) {
        for (QuickFixAction fix : fixes) {
          fix.registerCustomShortcutSet(fix.getShortcutSet(), this);
        }
      }
    });
  }

  public InspectionViewSuppressActionHolder getSuppressActionHolder() {
    return mySuppressActionHolder;
  }

  public Set<Object> getSuppressedNodes(String toolId) {
    return mySuppressedNodes.get(toolId);
  }

  @NotNull
  public ExcludedInspectionTreeNodesManager getExcludedManager() {
    return myExcludedInspectionTreeNodesManager;
  }

  @Nullable
  public String getCurrentProfileName() {
    return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName();
  }

  public InspectionProfileImpl getCurrentProfile() {
    return myInspectionProfile;
  }

  void addProblemDescriptors(InspectionToolWrapper wrapper, RefEntity refElement, CommonProblemDescriptor[] descriptors) {
    myTreeUpdater.submit(() -> ReadAction.run(() -> {
      if (!isDisposed()) {
        ApplicationManager.getApplication().assertReadAccessAllowed();
        synchronized (myTreeStructureUpdateLock) {
          final AnalysisUIOptions uiOptions = myGlobalInspectionContext.getUIOptions();
          final InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(wrapper);
          if (presentation.getToolNode() == null) {
            addTool(wrapper, HighlightDisplayLevel.find(presentation.getSeverity((RefElement)refElement)),
                    uiOptions.GROUP_BY_SEVERITY, isSingleInspectionRun());
          }
          final InspectionNode toolNode = presentation.getToolNode();
          LOG.assertTrue(toolNode != null);
          final Map<RefEntity, CommonProblemDescriptor[]> problems = new HashMap<>();
          problems.put(refElement, descriptors);
          final Map<String, Set<RefEntity>> contents = new HashMap<>();
          final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement);
          Set<RefEntity> content = contents.computeIfAbsent(groupName, __ -> new HashSet<>());
          content.add(refElement);

          getProvider().appendToolNodeContent(myGlobalInspectionContext,
                                              toolNode,
                                              (InspectionTreeNode)toolNode.getParent(),
                                              uiOptions.SHOW_STRUCTURE,<commit_msg>fixed compilation under jdk9<commit_after>/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.codeInspection.ui;

import com.intellij.CommonBundle;
import com.intellij.ReviseWhenPortedToJDK;
import com.intellij.analysis.AnalysisScope;
import com.intellij.analysis.AnalysisUIOptions;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.*;
import com.intellij.codeInspection.offlineViewer.OfflineInspectionRVContentProvider;
import com.intellij.codeInspection.reference.RefElement;
import com.intellij.codeInspection.reference.RefEntity;
import com.intellij.codeInspection.ui.actions.ExportHTMLAction;
import com.intellij.codeInspection.ui.actions.InvokeQuickFixAction;
import com.intellij.diff.util.DiffUtil;
import com.intellij.icons.AllIcons;
import com.intellij.ide.*;
import com.intellij.ide.actions.ContextHelpAction;
import com.intellij.ide.actions.exclusion.ExclusionHandler;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.pom.Navigatable;
import com.intellij.profile.ProfileChangeAdapter;
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.*;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.ObjectUtils;
import com.intellij.util.OpenSourceUtil;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;

/**
 * @author max
 */
public class InspectionResultsView extends JPanel implements Disposable, OccurenceNavigator, DataProvider {
  private static final Logger LOG = Logger.getInstance(InspectionResultsView.class);

  public static final DataKey<InspectionResultsView> DATA_KEY = DataKey.create("inspectionView");
  private static final Key<Boolean> PREVIEW_EDITOR_IS_REUSED_KEY = Key.create("inspection.tool.window.preview.editor.is.reused");

  private final Project myProject;
  private final InspectionTree myTree;
  private final ConcurrentMap<HighlightDisplayLevel, ConcurrentMap<String, InspectionGroupNode>> myGroups =
    ContainerUtil.newConcurrentMap();
  private final OccurenceNavigator myOccurenceNavigator;
  private volatile InspectionProfileImpl myInspectionProfile;
  private final boolean mySettingsEnabled;
  @NotNull
  private final AnalysisScope myScope;
  @NonNls
  private static final String HELP_ID = "reference.toolWindows.inspections";
  private final ConcurrentMap<HighlightDisplayLevel, InspectionSeverityGroupNode> mySeverityGroupNodes = ContainerUtil.newConcurrentMap();

  private final Splitter mySplitter;
  @NotNull
  private final GlobalInspectionContextImpl myGlobalInspectionContext;
  private boolean myRerun;
  private volatile boolean myDisposed;
  private int myUpdatingRequestors; //accessed only in edt
  private boolean myApplyingFix; //accessed only in edt

  @NotNull
  private final InspectionRVContentProvider myProvider;
  private final ExclusionHandler<InspectionTreeNode> myExclusionHandler;
  private EditorEx myPreviewEditor;
  private InspectionTreeLoadingProgressAware myLoadingProgressPreview;
  private final ExcludedInspectionTreeNodesManager myExcludedInspectionTreeNodesManager;
  @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
  private final FactoryMap<String, Set<Object>> mySuppressedNodes = new FactoryMap<String, Set<Object>>() {
    @Override
    protected Set<Object> create(String key) {
      return new THashSet<>();
    }
  };
  private final InspectionViewSuppressActionHolder mySuppressActionHolder = new InspectionViewSuppressActionHolder();

  private final Object myTreeStructureUpdateLock = new Object();
  private final ExecutorService myTreeUpdater = AppExecutorUtil.createBoundedApplicationPoolExecutor("inspection-view-tree-updater", 1);

  public InspectionResultsView(@NotNull GlobalInspectionContextImpl globalInspectionContext,
                               @NotNull InspectionRVContentProvider provider) {
    setLayout(new BorderLayout());
    myProject = globalInspectionContext.getProject();
    myInspectionProfile = globalInspectionContext.getCurrentProfile();
    myScope = globalInspectionContext.getCurrentScope();
    myGlobalInspectionContext = globalInspectionContext;
    myProvider = provider;
    myExcludedInspectionTreeNodesManager = new ExcludedInspectionTreeNodesManager(provider instanceof OfflineInspectionRVContentProvider,
                                                                                  isSingleInspectionRun());

    myTree = new InspectionTree(myProject, globalInspectionContext, this);
    initTreeListeners();

    myOccurenceNavigator = initOccurenceNavigator();

    mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(myProject).SPLITTER_PROPORTION);
    mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT));
    mySplitter.setHonorComponentsMinimumSize(false);

    mySplitter.addPropertyChangeListener(evt -> {
      if (Splitter.PROP_PROPORTION.equals(evt.getPropertyName())) {
        myGlobalInspectionContext.setSplitterProportion(((Float)evt.getNewValue()).floatValue());
      }
    });
    add(mySplitter, BorderLayout.CENTER);
    myExclusionHandler = new ExclusionHandler<InspectionTreeNode>() {
      @Override
      public boolean isNodeExclusionAvailable(@NotNull InspectionTreeNode node) {
        return true;
      }

      @Override
      public boolean isNodeExcluded(@NotNull InspectionTreeNode node) {
        return node.isExcluded(myExcludedInspectionTreeNodesManager);
      }

      @Override
      public void excludeNode(@NotNull InspectionTreeNode node) {
        node.excludeElement(myExcludedInspectionTreeNodesManager);
      }

      @Override
      public void includeNode(@NotNull InspectionTreeNode node) {
        node.amnestyElement(myExcludedInspectionTreeNodesManager);
      }

      @Override
      public boolean isActionEnabled(boolean isExcludeAction) {
        return isExcludeAction || !myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS;
      }

      @Override
      public void onDone(boolean isExcludeAction) {
        if (isExcludeAction) {
          if (myGlobalInspectionContext.getUIOptions().FILTER_RESOLVED_ITEMS) {
            synchronized (myTreeStructureUpdateLock) {
              final TreePath[] selected = myTree.getSelectionPaths();
              if (selected != null) {
                final TreePath commonParent = TreeUtil.findCommonPath(selected);
                if (!selectCommonNextSibling(selected, commonParent)) {
                  TreeUtil.removeSelected(myTree);
                  TreeUtil.selectPath(myTree, commonParent);
                }
              }
            }
          }
          myTree.revalidate();
          myTree.repaint();
        }
        else {
          resetTree();
        }
        syncRightPanel();
      }

      private boolean selectCommonNextSibling(@NotNull TreePath[] selected, @NotNull TreePath commonParent) {
        final int pathCount = commonParent.getPathCount() + 1;
        for (TreePath path : selected) {
          if (path.getPathCount() != pathCount) {
            return false;
          }
        }
        final InspectionTreeNode parent = (InspectionTreeNode)commonParent.getLastPathComponent();
        final int[] indices = new int[selected.length];
        for (int i = 0; i < selected.length; i++) {
          TreePath path = selected[i];
          indices[i] = parent.getIndex((TreeNode)path.getLastPathComponent());
        }
        Arrays.sort(indices);
        int prevIdx = -1;
        for (int idx: indices) {
          if (prevIdx != -1) {
            if (idx - prevIdx != 1) {
              return false;
            }
          }
          prevIdx = idx;
        }
        final int toSelect = indices[indices.length - 1] + 1;
        if (parent.getChildCount() > toSelect) {
          final TreeNode nodeToSelect = parent.getChildAt(toSelect);
          TreeUtil.removeSelected(myTree);
          TreeUtil.selectNode(myTree, nodeToSelect);
          return true;
        }
        return false;
      }
    };
    createActionsToolbar();
    PsiManager.getInstance(myProject).addPsiTreeChangeListener(new InspectionViewPsiTreeChangeAdapter(this), this);

    ProjectInspectionProfileManager profileManager = ProjectInspectionProfileManager.getInstance(myProject);
    profileManager.addProfileChangeListener(new ProfileChangeAdapter() {
      @Override
      public void profileChanged(InspectionProfile profile) {
        if (profile == profileManager.getCurrentProfile()) {
          InspectionResultsView.this.profileChanged();
        }
      }
    }, this);

    if (!isSingleInspectionRun()) {
      mySettingsEnabled = true;
    } else {
      InspectionProfileImpl profile = getCurrentProfile();
      String toolId = ObjectUtils.notNull(profile.getSingleTool());
      InspectionToolWrapper tool = ObjectUtils.notNull(profile.getInspectionTool(toolId, getProject()));
      JComponent toolPanel = tool.getTool().createOptionsPanel();
      mySettingsEnabled = toolPanel != null;
    }
  }

  void profileChanged() {
    myTree.revalidate();
    myTree.repaint();
    syncRightPanel();
  }

  private void initTreeListeners() {
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      return;
    }
    myTree.getSelectionModel().addTreeSelectionListener(e -> {
      if (myTree.isUnderQueueUpdate()) return;
      syncRightPanel();
      if (isAutoScrollMode()) {
        OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(this), false);
      }
    });

    EditSourceOnDoubleClickHandler.install(myTree);

    myTree.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
          OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(InspectionResultsView.this), false);
        }
      }
    });

    PopupHandler.installPopupHandler(myTree, IdeActions.INSPECTION_TOOL_WINDOW_TREE_POPUP, ActionPlaces.CODE_INSPECTION);
    SmartExpander.installOn(myTree);
  }

  private OccurenceNavigatorSupport initOccurenceNavigator() {
    return new OccurenceNavigatorSupport(myTree) {
      @Override
      @Nullable
      protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
        if (node instanceof InspectionTreeNode && ((InspectionTreeNode)node).isExcluded(myExcludedInspectionTreeNodesManager)) {
          return null;
        }
        if (node instanceof RefElementNode) {
          final RefElementNode refNode = (RefElementNode)node;
          if (refNode.hasDescriptorsUnder()) return null;
          final RefEntity element = refNode.getElement();
          if (element == null || !element.isValid()) return null;
          final CommonProblemDescriptor problem = refNode.getDescriptor();
          if (problem != null) {
            return navigate(problem);
          }
          if (element instanceof RefElement) {
            return getOpenFileDescriptor((RefElement)element);
          }
        }
        else if (node instanceof ProblemDescriptionNode) {
          boolean isValid;
          if (((ProblemDescriptionNode)node).isValid()) {
            if (((ProblemDescriptionNode)node).isQuickFixAppliedFromView()) {
              isValid = ((ProblemDescriptionNode)node).calculateIsValid();
            } else {
              isValid = true;
            }
          } else {
            isValid = false;
          }
          return isValid
                 ? navigate(((ProblemDescriptionNode)node).getDescriptor())
                 : InspectionResultsViewUtil.getNavigatableForInvalidNode((ProblemDescriptionNode)node);
        }
        return null;
      }

      @Nullable
      private Navigatable navigate(final CommonProblemDescriptor descriptor) {
        return getSelectedNavigatable(descriptor);
      }

      @Override
      public String getNextOccurenceActionName() {
        return InspectionsBundle.message("inspection.action.go.next");
      }

      @Override
      public String getPreviousOccurenceActionName() {
        return InspectionsBundle.message("inspection.actiongo.prev");
      }
    };
  }

  private void createActionsToolbar() {
    final JComponent leftActionsToolbar = createLeftActionsToolbar();
    final JComponent rightActionsToolbar = createRightActionsToolbar();

    JPanel westPanel = new JPanel(new BorderLayout());
    westPanel.add(leftActionsToolbar, BorderLayout.WEST);
    westPanel.add(rightActionsToolbar, BorderLayout.EAST);
    add(westPanel, BorderLayout.WEST);
  }

  @SuppressWarnings("NonStaticInitializer")
  private JComponent createRightActionsToolbar() {
    DefaultActionGroup specialGroup = new DefaultActionGroup();
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupBySeverityAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createGroupByDirectoryAction(this));
    specialGroup.add(myGlobalInspectionContext.getUIOptions().createFilterResolvedItemsAction(this));
    specialGroup.add(myGlobalInspectionContext.createToggleAutoscrollAction());
    specialGroup.add(new ExportHTMLAction(this));
    specialGroup.add(ActionManager.getInstance().getAction("EditInspectionSettings"));
    specialGroup.add(new InvokeQuickFixAction(this));
    return createToolbar(specialGroup);
  }

  private JComponent createLeftActionsToolbar() {
    final CommonActionsManager actionsManager = CommonActionsManager.getInstance();
    DefaultActionGroup group = new DefaultActionGroup();
    group.add(new RerunAction(this));
    group.add(new CloseAction());
    final TreeExpander treeExpander = new DefaultTreeExpander(myTree);
    group.add(actionsManager.createExpandAllAction(treeExpander, myTree));
    group.add(actionsManager.createCollapseAllAction(treeExpander, myTree));
    group.add(actionsManager.createPrevOccurenceAction(getOccurenceNavigator()));
    group.add(actionsManager.createNextOccurenceAction(getOccurenceNavigator()));
    group.add(new ContextHelpAction(HELP_ID));

    return createToolbar(group);
  }

  private static JComponent createToolbar(final DefaultActionGroup specialGroup) {
    final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.CODE_INSPECTION, specialGroup, false);
    //toolbar.setTargetComponent(this);
    return toolbar.getComponent();
  }

  @Override
  public void dispose() {
    InspectionResultsViewUtil.releaseEditor(myPreviewEditor);
    mySplitter.dispose();
    myInspectionProfile = null;
    myDisposed = true;
    if (myLoadingProgressPreview != null) {
      Disposer.dispose(myLoadingProgressPreview);
      myLoadingProgressPreview = null;
    }
  }


  private boolean isAutoScrollMode() {
    String activeToolWindowId = ToolWindowManager.getInstance(myProject).getActiveToolWindowId();
    return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE &&
           (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION));
  }

  Object getTreeStructureUpdateLock() {
    return myTreeStructureUpdateLock;
  }

  ExecutorService getTreeUpdater() {
    return myTreeUpdater;
  }

  @Nullable
  private static OpenFileDescriptor getOpenFileDescriptor(final RefElement refElement) {
    final VirtualFile[] file = new VirtualFile[1];
    final int[] offset = new int[1];

    ApplicationManager.getApplication().runReadAction(() -> {
      PsiElement psiElement = refElement.getElement();
      if (psiElement != null) {
        final PsiFile containingFile = psiElement.getContainingFile();
        if (containingFile != null) {
          file[0] = containingFile.getVirtualFile();
          offset[0] = psiElement.getTextOffset();
        }
      }
      else {
        file[0] = null;
      }
    });

    if (file[0] != null && file[0].isValid()) {
      return new OpenFileDescriptor(refElement.getRefManager().getProject(), file[0], offset[0]);
    }
    return null;
  }

  public void setApplyingFix(boolean applyingFix) {
    myApplyingFix = applyingFix;
    syncRightPanel();
  }

  void openRightPanelIfNeed() {
    if (mySplitter.getSecondComponent() == null) {
      syncRightPanel();
    }
  }

  public void syncRightPanel() {
    final Editor oldEditor = myPreviewEditor;
    if (myLoadingProgressPreview != null) {
      Disposer.dispose(myLoadingProgressPreview);
      myLoadingProgressPreview = null;
    }
    if (myApplyingFix) {
      final InspectionToolWrapper wrapper = myTree.getSelectedToolWrapper(true);
      LOG.assertTrue(wrapper != null);
      mySplitter.setSecondComponent(InspectionResultsViewUtil.getApplyingFixLabel(wrapper));
    } else {
      if (myTree.getSelectionModel().getSelectionCount() != 1) {
        if (myTree.getSelectedToolWrapper(true) == null) {
          mySplitter.setSecondComponent(InspectionResultsViewUtil.getNothingToShowTextLabel());
        }
        else {
          showInRightPanel(myTree.getCommonSelectedElement());
        }
      }
      else {
        TreePath pathSelected = myTree.getSelectionModel().getLeadSelectionPath();
        if (pathSelected != null) {
          final InspectionTreeNode node = (InspectionTreeNode)pathSelected.getLastPathComponent();
          if (node instanceof ProblemDescriptionNode) {
            final ProblemDescriptionNode problemNode = (ProblemDescriptionNode)node;
            showInRightPanel(problemNode.getElement());
          }
          else if (node instanceof InspectionPackageNode ||
                   node instanceof InspectionModuleNode ||
                   node instanceof RefElementNode) {
            showInRightPanel(node.getContainingFileLocalEntity());
          }
          else if (node instanceof InspectionNode) {
            if (myGlobalInspectionContext.getPresentation(((InspectionNode)node).getToolWrapper()).isDummy()) {
              mySplitter.setSecondComponent(InspectionResultsViewUtil.getNothingToShowTextLabel());
            }
            else {
              showInRightPanel(null);
            }
          }
          else if (node instanceof InspectionGroupNode || node instanceof InspectionSeverityGroupNode) {
            final InspectionViewNavigationPanel panel = new InspectionViewNavigationPanel(node, myTree);
            myLoadingProgressPreview = panel;
            mySplitter.setSecondComponent(panel);
          }
          else {
            LOG.error("Unexpected node: " + node.getClass());
          }
        }
      }
    }
    if (oldEditor != null) {
      if (Boolean.TRUE.equals(oldEditor.getUserData(PREVIEW_EDITOR_IS_REUSED_KEY))) {
        oldEditor.putUserData(PREVIEW_EDITOR_IS_REUSED_KEY, null);
      }
      else {
        InspectionResultsViewUtil.releaseEditor(oldEditor);
        if (oldEditor == myPreviewEditor) {
          myPreviewEditor = null;
        }
      }
    }
  }

  private void showInRightPanel(@Nullable final RefEntity refEntity) {
    final JPanel editorPanel = new JPanel();
    editorPanel.setLayout(new BorderLayout());
    final int problemCount = myTree.getSelectedProblemCount(true);
    JComponent previewPanel = null;
    final InspectionToolWrapper tool = myTree.getSelectedToolWrapper(true);
    if (tool != null && refEntity != null && refEntity.isValid()) {
      final TreePath path = myTree.getSelectionPath();
      if (path == null || !(path.getLastPathComponent() instanceof ProblemDescriptionNode)) {
        final InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(tool);
        previewPanel = presentation.getCustomPreviewPanel(refEntity);
      }
    }
    EditorEx previewEditor = null;
    if (previewPanel == null) {
      final Pair<JComponent, EditorEx> panelAndEditor = createBaseRightComponentFor(problemCount, refEntity);
      previewPanel = panelAndEditor.getFirst();
      previewEditor = panelAndEditor.getSecond();
    }
    editorPanel.add(previewPanel, BorderLayout.CENTER);
    if (problemCount > 0) {
      final JComponent fixToolbar = QuickFixPreviewPanelFactory.create(this);
      if (fixToolbar != null) {
        if (fixToolbar instanceof InspectionTreeLoadingProgressAware) {
          myLoadingProgressPreview = (InspectionTreeLoadingProgressAware)fixToolbar;
        }
        if (previewEditor != null) {
          previewPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP));
        }
        editorPanel.add(fixToolbar, BorderLayout.NORTH);
      }
    }
    if (previewEditor != null) {
      new ProblemPreviewEditorPresentation(previewEditor, this);
    }
    mySplitter.setSecondComponent(editorPanel);
  }

  private Pair<JComponent, EditorEx> createBaseRightComponentFor(int problemCount, RefEntity selectedEntity) {
    if (selectedEntity instanceof RefElement &&
        selectedEntity.isValid() &&
        !(((RefElement)selectedEntity).getElement() instanceof PsiDirectory)) {
      PsiElement selectedElement = ((RefElement)selectedEntity).getElement();
      if (problemCount == 1) {
        CommonProblemDescriptor[] descriptors = myTree.getSelectedDescriptors();
        if (descriptors.length != 0) {
          final CommonProblemDescriptor descriptor = descriptors[0];
          if (descriptor instanceof ProblemDescriptorBase) {
            final PsiElement element = ((ProblemDescriptorBase)descriptor).getPsiElement();
            if (element != null) {
              selectedElement = element;
            }
          }
        }
      }
      final PsiFile file = selectedElement.getContainingFile();
      final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
      if (document == null) {
        return Pair.create(InspectionResultsViewUtil.createLabelForText("Can't open preview for \'" + file.getName() + "\'"), null);
      }

      if (reuseEditorFor(document)) {
        myPreviewEditor.putUserData(PREVIEW_EDITOR_IS_REUSED_KEY, true);
        myPreviewEditor.getFoldingModel().runBatchFoldingOperation(() -> myPreviewEditor.getFoldingModel().clearFoldRegions());
        myPreviewEditor.getMarkupModel().removeAllHighlighters();
      }
      else {
        myPreviewEditor = (EditorEx)EditorFactory.getInstance().createEditor(document, myProject, file.getVirtualFile(), true);
        DiffUtil.setFoldingModelSupport(myPreviewEditor);
        final EditorSettings settings = myPreviewEditor.getSettings();
        settings.setLineNumbersShown(false);
        settings.setFoldingOutlineShown(true);
        settings.setLineMarkerAreaShown(true);
        settings.setGutterIconsShown(false);
        settings.setAdditionalColumnsCount(0);
        settings.setAdditionalLinesCount(0);
        settings.setLeadingWhitespaceShown(true);
        myPreviewEditor.getColorsScheme().setColor(EditorColors.GUTTER_BACKGROUND, myPreviewEditor.getColorsScheme().getDefaultBackground());
        myPreviewEditor.getScrollPane().setBorder(IdeBorderFactory.createEmptyBorder());
      }
      if (problemCount == 0) {
        myPreviewEditor.getScrollingModel().scrollTo(myPreviewEditor.offsetToLogicalPosition(selectedElement.getTextOffset()), ScrollType.CENTER_UP);
      }
      myPreviewEditor.getComponent().setBorder(IdeBorderFactory.createEmptyBorder());
      return Pair.create(myPreviewEditor.getComponent(), myPreviewEditor);
    }
    if (selectedEntity == null) {
      return Pair.create(new InspectionNodeInfo(myTree, myProject), null);
    }
    if (selectedEntity.isValid()) {
      return Pair.create(InspectionResultsViewUtil.getPreviewIsNotAvailable(selectedEntity), null);
    }
    return Pair.create(InspectionResultsViewUtil.getInvalidEntityLabel(selectedEntity), null);
  }

  private boolean reuseEditorFor(Document document) {
    return myPreviewEditor != null && !myPreviewEditor.isDisposed() && myPreviewEditor.getDocument() == document;
  }

  private void addTool(@NotNull final InspectionToolWrapper toolWrapper,
                       HighlightDisplayLevel errorLevel,
                       boolean groupedBySeverity,
                       boolean isSingleInspectionRun) {
    String groupName =
      toolWrapper.getGroupDisplayName().isEmpty() ? InspectionProfileEntry.GENERAL_GROUP_NAME : toolWrapper.getGroupDisplayName();
    InspectionTreeNode parentNode = getToolParentNode(groupName, toolWrapper.getGroupPath(), errorLevel, groupedBySeverity, isSingleInspectionRun);
    InspectionNode toolNode = new InspectionNode(toolWrapper, myInspectionProfile);
    boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE;
    toolNode = myProvider.appendToolNodeContent(myGlobalInspectionContext, toolNode, parentNode, showStructure, groupedBySeverity);
    InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(toolWrapper);
    presentation.createToolNode(myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure, groupedBySeverity);
    registerActionShortcuts(presentation);
  }

  private void registerActionShortcuts(@NotNull InspectionToolPresentation presentation) {
    ApplicationManager.getApplication().invokeLater(() -> {
      final QuickFixAction[] fixes = presentation.getQuickFixes(RefEntity.EMPTY_ELEMENTS_ARRAY, null);
      if (fixes != null) {
        for (QuickFixAction fix : fixes) {
          fix.registerCustomShortcutSet(fix.getShortcutSet(), this);
        }
      }
    });
  }

  public InspectionViewSuppressActionHolder getSuppressActionHolder() {
    return mySuppressActionHolder;
  }

  public Set<Object> getSuppressedNodes(String toolId) {
    return mySuppressedNodes.get(toolId);
  }

  @NotNull
  public ExcludedInspectionTreeNodesManager getExcludedManager() {
    return myExcludedInspectionTreeNodesManager;
  }

  @Nullable
  public String getCurrentProfileName() {
    return myInspectionProfile == null ? null : myInspectionProfile.getDisplayName();
  }

  public InspectionProfileImpl getCurrentProfile() {
    return myInspectionProfile;
  }

  @ReviseWhenPortedToJDK("9")
  void addProblemDescriptors(InspectionToolWrapper wrapper, RefEntity refElement, CommonProblemDescriptor[] descriptors) {
    // redundant cast to fix compilation under jdk9
    myTreeUpdater.submit((Runnable)() -> ReadAction.run(() -> {
      if (!isDisposed()) {
        ApplicationManager.getApplication().assertReadAccessAllowed();
        synchronized (myTreeStructureUpdateLock) {
          final AnalysisUIOptions uiOptions = myGlobalInspectionContext.getUIOptions();
          final InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(wrapper);
          if (presentation.getToolNode() == null) {
            addTool(wrapper, HighlightDisplayLevel.find(presentation.getSeverity((RefElement)refElement)),
                    uiOptions.GROUP_BY_SEVERITY, isSingleInspectionRun());
          }
          final InspectionNode toolNode = presentation.getToolNode();
          LOG.assertTrue(toolNode != null);
          final Map<RefEntity, CommonProblemDescriptor[]> problems = new HashMap<>();
          problems.put(refElement, descriptors);
          final Map<String, Set<RefEntity>> contents = new HashMap<>();
          final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement);
          Set<RefEntity> content = contents.computeIfAbsent(groupName, __ -> new HashSet<>());
          content.add(refElement);

          getProvider().appendToolNodeContent(myGlobalInspectionContext,
                                              toolNode,
                                              (InspectionTreeNode)toolNode.getParent(),
                                              uiOptions.SHOW_STRUCTURE,
{'fraction_non_alphanumeric': 0.09106870734777711, 'fraction_numerical': 0.0012754256940303453, 'mean_word_length': 3.7541538703834947, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4377823', 'n_tokens_mistral': 16266, 'n_tokens_neox': 16080, 'n_words': 2898}