blob: f5d074e59401c8908c964426d89a74f2a7f1c3a8 [file]
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.coverage;
import com.intellij.coverage.view.CoverageViewManager;
import com.intellij.coverage.view.CoverageViewSuiteListener;
import com.intellij.execution.RunManager;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.execution.configurations.RunnerSettings;
import com.intellij.execution.configurations.coverage.CoverageEnabledConfiguration;
import com.intellij.execution.impl.RunManagerImpl;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.colors.EditorColorsListener;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.event.EditorFactoryEvent;
import com.intellij.openapi.editor.event.EditorFactoryListener;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerListener;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.rt.coverage.data.ClassData;
import com.intellij.rt.coverage.data.LineCoverage;
import com.intellij.rt.coverage.data.LineData;
import com.intellij.rt.coverage.data.ProjectData;
import com.intellij.ui.UIBundle;
import com.intellij.util.Alarm;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.UIUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
/**
* @author ven
*/
public class CoverageDataManagerImpl extends CoverageDataManager {
private final List<CoverageSuiteListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private static final Logger LOG = Logger.getInstance(CoverageDataManagerImpl.class);
@NonNls
private static final String SUITE = "SUITE";
private final Project myProject;
private final Set<CoverageSuite> myCoverageSuites = new HashSet<>();
private boolean myIsProjectClosing = false;
private final Object myLock = new Object();
private boolean mySubCoverageIsActive;
private final VirtualFileContentsChangedAdapter myContentListener = new VirtualFileContentsChangedAdapter() {
@Override
protected void onFileChange(@NotNull VirtualFile fileOrDirectory) {
if (myCurrentSuiteRoots != null && VfsUtilCore.isUnder(fileOrDirectory.getPath(), myCurrentSuiteRoots)) {
myCurrentSuitesBundle.restoreCoverageData();
updateCoverageData(myCurrentSuitesBundle);
}
}
@Override
protected void onBeforeFileChange(@NotNull VirtualFile fileOrDirectory) { }
};
private Set<LocalFileSystem.WatchRequest> myWatchRequests;
private List<String> myCurrentSuiteRoots;
@Override
public CoverageSuitesBundle getCurrentSuitesBundle() {
return myCurrentSuitesBundle;
}
private CoverageSuitesBundle myCurrentSuitesBundle;
private final Object ANNOTATORS_LOCK = new Object();
private final Map<Editor, SrcFileAnnotator> myAnnotators = new HashMap<>();
public CoverageDataManagerImpl(@NotNull Project project) {
myProject = project;
MessageBusConnection connection = project.getMessageBus().connect();
connection.subscribe(EditorColorsManager.TOPIC, new EditorColorsListener() {
@Override
public void globalSchemeChange(EditorColorsScheme scheme) {
chooseSuitesBundle(myCurrentSuitesBundle);
}
});
connection.subscribe(ProjectManager.TOPIC, new ProjectManagerListener() {
@Override
public void projectOpened(@NotNull Project project) {
if (project == myProject) {
EditorFactory.getInstance().addEditorFactoryListener(new CoverageEditorFactoryListener(), myProject);
}
}
@Override
public void projectClosing(@NotNull Project project) {
if (project != myProject) {
return;
}
synchronized (myLock) {
myIsProjectClosing = true;
}
}
});
final CoverageViewSuiteListener coverageViewListener = createCoverageViewListener();
if (coverageViewListener != null) {
addSuiteListener(coverageViewListener, myProject);
}
CoverageRunner.EP_NAME.addExtensionPointListener(new ExtensionPointListener<CoverageRunner>() {
@Override
public void extensionRemoved(@NotNull CoverageRunner coverageRunner, @NotNull PluginDescriptor pluginDescriptor) {
CoverageSuitesBundle suitesBundle = getCurrentSuitesBundle();
if (suitesBundle != null &&
Arrays.stream(suitesBundle.getSuites()).anyMatch(suite -> coverageRunner == suite.getRunner())) {
chooseSuitesBundle(null);
}
RunManager runManager = RunManager.getInstance(project);
List<RunConfiguration> configurations = runManager.getAllConfigurationsList();
for (RunConfiguration configuration : configurations) {
if (configuration instanceof RunConfigurationBase) {
CoverageEnabledConfiguration coverageEnabledConfiguration =
((RunConfigurationBase)configuration).getCopyableUserData(CoverageEnabledConfiguration.COVERAGE_KEY);
if (coverageEnabledConfiguration != null) {
coverageEnabledConfiguration.coverageRunnerExtensionRemoved(coverageRunner);
}
}
}
//cleanup created templates
((RunManagerImpl)runManager).reloadSchemes();
for (CoverageSuite suite : getSuites()) {
if (suite instanceof BaseCoverageSuite) {
CoverageRunner runner = suite.getRunner();
if (runner == coverageRunner) {
((BaseCoverageSuite)suite).setRunner(null);
}
}
}
}
}, myProject);
CoverageEngine.EP_NAME.addExtensionPointListener(new ExtensionPointListener<CoverageEngine>() {
@Override
public void extensionRemoved(@NotNull CoverageEngine coverageEngine, @NotNull PluginDescriptor pluginDescriptor) {
CoverageSuitesBundle suitesBundle = getCurrentSuitesBundle();
if (suitesBundle != null && suitesBundle.getCoverageEngine() == coverageEngine) {
chooseSuitesBundle(null);
}
myCoverageSuites.removeIf(suite -> suite.getCoverageEngine() == coverageEngine);
}
}, myProject);
}
@Nullable
protected CoverageViewSuiteListener createCoverageViewListener() {
return new CoverageViewSuiteListener(this, myProject);
}
@Override
public void readExternal(Element element) throws InvalidDataException {
for (Element suiteElement : element.getChildren(SUITE)) {
final CoverageRunner coverageRunner = BaseCoverageSuite.readRunnerAttribute(suiteElement);
// skip unknown runners
if (coverageRunner == null) {
// collect gc
final CoverageFileProvider fileProvider = BaseCoverageSuite.readDataFileProviderAttribute(suiteElement);
if (fileProvider.isValid()) {
//deleteCachedCoverage(fileProvider.getCoverageDataFilePath());
}
continue;
}
CoverageSuite suite = null;
for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) {
if (coverageRunner.acceptsCoverageEngine(engine)) {
suite = engine.createEmptyCoverageSuite(coverageRunner);
if (suite != null) {
if (suite instanceof BaseCoverageSuite) {
((BaseCoverageSuite)suite).setProject(myProject);
}
break;
}
}
}
if (suite != null) {
try {
suite.readExternal(suiteElement);
myCoverageSuites.add(suite);
}
catch (NumberFormatException e) {
//try next suite
}
}
}
}
@Override
public void writeExternal(final Element element) throws WriteExternalException {
for (CoverageSuite coverageSuite : myCoverageSuites) {
final Element suiteElement = new Element(SUITE);
element.addContent(suiteElement);
coverageSuite.writeExternal(suiteElement);
}
}
@Override
public CoverageSuite addCoverageSuite(final String name, final CoverageFileProvider fileProvider, final String[] filters, final long lastCoverageTimeStamp,
@Nullable final String suiteToMergeWith,
final CoverageRunner coverageRunner,
final boolean collectLineInfo,
final boolean tracingEnabled) {
final CoverageSuite suite = createCoverageSuite(coverageRunner, name, fileProvider, filters, lastCoverageTimeStamp, suiteToMergeWith, collectLineInfo, tracingEnabled);
if (suiteToMergeWith == null || !name.equals(suiteToMergeWith)) {
removeCoverageSuite(suite);
}
myCoverageSuites.remove(suite); // remove previous instance
myCoverageSuites.add(suite); // add new instance
return suite;
}
@Override
public CoverageSuite addExternalCoverageSuite(String selectedFileName,
long timeStamp,
CoverageRunner coverageRunner,
CoverageFileProvider fileProvider) {
final CoverageSuite suite = createCoverageSuite(coverageRunner, selectedFileName, fileProvider, ArrayUtilRt.EMPTY_STRING_ARRAY, timeStamp, null, false, false);
myCoverageSuites.add(suite);
return suite;
}
@Override
public CoverageSuite addCoverageSuite(final CoverageEnabledConfiguration config) {
final String name = config.getName() + " Coverage Results";
final String covFilePath = config.getCoverageFilePath();
assert covFilePath != null; // Shouldn't be null here!
final CoverageRunner coverageRunner = config.getCoverageRunner();
LOG.assertTrue(coverageRunner != null, "Coverage runner id = " + config.getRunnerId());
final DefaultCoverageFileProvider fileProvider = new DefaultCoverageFileProvider(new File(covFilePath));
final CoverageSuite suite = createCoverageSuite(config, name, coverageRunner, fileProvider);
// remove previous instance
removeCoverageSuite(suite);
// add new instance
myCoverageSuites.add(suite);
return suite;
}
@Override
public void removeCoverageSuite(final CoverageSuite suite) {
suite.deleteCachedCoverageData();
myCoverageSuites.remove(suite);
if (myCurrentSuitesBundle != null && myCurrentSuitesBundle.contains(suite)) {
CoverageSuite[] suites = myCurrentSuitesBundle.getSuites();
suites = ArrayUtil.remove(suites, suite);
chooseSuitesBundle(suites.length > 0 ? new CoverageSuitesBundle(suites) : null);
}
}
@Override
public CoverageSuite @NotNull [] getSuites() {
return myCoverageSuites.toArray(new CoverageSuite[0]);
}
@Override
public void chooseSuitesBundle(final CoverageSuitesBundle suite) {
if (myCurrentSuitesBundle == suite && suite == null) {
return;
}
if (myWatchRequests != null) {
LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests);
VirtualFileManager.getInstance().removeVirtualFileListener(myContentListener);
myWatchRequests = null;
myCurrentSuiteRoots = null;
}
updateCoverageData(suite);
}
private void updateCoverageData(CoverageSuitesBundle suite) {
LOG.assertTrue(!myProject.isDefault());
fireBeforeSuiteChosen();
mySubCoverageIsActive = false;
if (myCurrentSuitesBundle != null) {
myCurrentSuitesBundle.getCoverageEngine().getCoverageAnnotator(myProject).onSuiteChosen(suite);
}
myCurrentSuitesBundle = suite;
disposeAnnotators();
if (suite == null) {
triggerPresentationUpdate();
return;
}
for (CoverageSuite coverageSuite : myCurrentSuitesBundle.getSuites()) {
final boolean suiteFileExists = coverageSuite.getCoverageDataFileProvider().ensureFileExists();
if (!suiteFileExists) {
chooseSuitesBundle(null);
return;
}
}
renewCoverageData(suite);
fireAfterSuiteChosen();
}
@Override
public void coverageGathered(@NotNull final CoverageSuite suite) {
ApplicationManager.getApplication().invokeLater(() -> {
if (myProject.isDisposed()) return;
if (myCurrentSuitesBundle != null) {
final String message = CoverageBundle.message("display.coverage.prompt", suite.getPresentableName());
final CoverageOptionsProvider coverageOptionsProvider = CoverageOptionsProvider.getInstance(myProject);
final DialogWrapper.DoNotAskOption doNotAskOption = new DialogWrapper.DoNotAskOption() {
@Override
public boolean isToBeShown() {
return coverageOptionsProvider.getOptionToReplace() == 3;
}
@Override
public void setToBeShown(boolean value, int exitCode) {
coverageOptionsProvider.setOptionsToReplace(value ? 3 : exitCode);
}
@Override
public boolean canBeHidden() {
return true;
}
@Override
public boolean shouldSaveOptionsOnCancel() {
return true;
}
@NotNull
@Override
public String getDoNotShowMessage() {
return UIBundle.message("dialog.options.do.not.show");
}
};
final String[] options = myCurrentSuitesBundle.getCoverageEngine() == suite.getCoverageEngine() ?
new String[] {
CoverageBundle.message("coverage.replace.active.suites"),
CoverageBundle.message("coverage.add.to.active.suites"),
CoverageBundle.message("coverage.do.not.apply.collected.coverage")} :
new String[] {
CoverageBundle.message("coverage.replace.active.suites"),
CoverageBundle.message("coverage.do.not.apply.collected.coverage")};
final int answer = doNotAskOption.isToBeShown() ? Messages.showDialog(message, CoverageBundle.message("code.coverage"),
options, 1, Messages.getQuestionIcon(),
doNotAskOption) : coverageOptionsProvider.getOptionToReplace();
if (answer == DialogWrapper.OK_EXIT_CODE) {
chooseSuitesBundle(new CoverageSuitesBundle(suite));
}
else if (answer == 1) {
chooseSuitesBundle(new CoverageSuitesBundle(ArrayUtil.append(myCurrentSuitesBundle.getSuites(), suite)));
}
}
else {
chooseSuitesBundle(new CoverageSuitesBundle(suite));
}
});
}
@Override
public void triggerPresentationUpdate() {
renewInformationInEditors();
UIUtil.invokeLaterIfNeeded(() -> {
if (myProject.isDisposed()) return;
ProjectView.getInstance(myProject).refresh();
CoverageViewManager.getInstance(myProject).setReady(true);
});
}
@Override
public void attachToProcess(@NotNull final ProcessHandler handler,
@NotNull final RunConfigurationBase configuration,
final RunnerSettings runnerSettings) {
handler.addProcessListener(new ProcessAdapter() {
@Override
public void processTerminated(@NotNull final ProcessEvent event) {
processGatheredCoverage(configuration, runnerSettings);
}
});
}
@Override
public void processGatheredCoverage(@NotNull RunConfigurationBase configuration, RunnerSettings runnerSettings) {
if (runnerSettings instanceof CoverageRunnerData) {
processGatheredCoverage(configuration);
}
}
/**
* Called from EDT, on external coverage suite choosing
*/
public void addRootsToWatch(List<? extends CoverageSuite> suites) {
myCurrentSuiteRoots = ContainerUtil.map(suites, suite -> suite.getCoverageDataFileName());
LocalFileSystem fileSystem = LocalFileSystem.getInstance();
myCurrentSuiteRoots.forEach(path -> fileSystem.refreshAndFindFileByPath(path));
myWatchRequests = fileSystem.addRootsToWatch(myCurrentSuiteRoots, true);
VirtualFileManager.getInstance().addVirtualFileListener(myContentListener);
}
public static void processGatheredCoverage(RunConfigurationBase configuration) {
final Project project = configuration.getProject();
if (project.isDisposed()) return;
final CoverageDataManager coverageDataManager = CoverageDataManager.getInstance(project);
final CoverageEnabledConfiguration coverageEnabledConfiguration = CoverageEnabledConfiguration.getOrCreate(configuration);
final CoverageSuite coverageSuite = coverageEnabledConfiguration.getCurrentCoverageSuite();
if (coverageSuite != null) {
((BaseCoverageSuite)coverageSuite).setConfiguration(configuration);
coverageDataManager.coverageGathered(coverageSuite);
}
}
protected void renewCoverageData(@NotNull final CoverageSuitesBundle suite) {
if (myCurrentSuitesBundle != null) {
myCurrentSuitesBundle.getCoverageEngine().getCoverageAnnotator(myProject).renewCoverageData(suite, this);
}
}
private void renewInformationInEditors() {
final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
final VirtualFile[] openFiles = fileEditorManager.getOpenFiles();
for (VirtualFile openFile : openFiles) {
final FileEditor[] allEditors = fileEditorManager.getAllEditors(openFile);
applyInformationToEditor(allEditors, openFile);
}
}
private void applyInformationToEditor(FileEditor[] editors, final VirtualFile file) {
final PsiFile psiFile = doInReadActionIfProjectOpen(() -> PsiManager.getInstance(myProject).findFile(file));
if (psiFile != null && myCurrentSuitesBundle != null && psiFile.isPhysical()) {
final CoverageEngine engine = myCurrentSuitesBundle.getCoverageEngine();
if (!engine.coverageEditorHighlightingApplicableTo(psiFile)) {
return;
}
for (FileEditor editor : editors) {
if (editor instanceof TextEditor) {
final Editor textEditor = ((TextEditor)editor).getEditor();
SrcFileAnnotator annotator;
synchronized (ANNOTATORS_LOCK) {
annotator = myAnnotators.remove(textEditor);
}
if (annotator != null) {
Disposer.dispose(annotator);
}
break;
}
}
for (FileEditor editor : editors) {
if (editor instanceof TextEditor) {
final Editor textEditor = ((TextEditor)editor).getEditor();
SrcFileAnnotator annotator = getAnnotator(textEditor);
if (annotator == null) {
annotator = new SrcFileAnnotator(psiFile, textEditor);
synchronized (ANNOTATORS_LOCK) {
myAnnotators.put(textEditor, annotator);
}
}
if (myCurrentSuitesBundle != null && engine.acceptedByFilters(psiFile, myCurrentSuitesBundle)) {
annotator.showCoverageInformation(myCurrentSuitesBundle);
}
}
}
}
}
@Override
public <T> T doInReadActionIfProjectOpen(Computable<T> computation) {
synchronized(myLock) {
if (myIsProjectClosing) return null;
}
return ApplicationManager.getApplication().runReadAction(computation);
}
@Override
public void selectSubCoverage(@NotNull final CoverageSuitesBundle suite, final List<String> testNames) {
suite.restoreCoverageData();
final ProjectData data = suite.getCoverageData();
if (data == null) return;
mySubCoverageIsActive = true;
final Map<String, Set<Integer>> executionTrace = new HashMap<>();
for (CoverageSuite coverageSuite : suite.getSuites()) {
suite.getCoverageEngine().collectTestLines(testNames, coverageSuite, executionTrace);
}
final ProjectData projectData = new ProjectData();
for (String className : executionTrace.keySet()) {
ClassData loadedClassData = projectData.getClassData(className);
if (loadedClassData == null) {
loadedClassData = projectData.getOrCreateClassData(className);
}
final Set<Integer> lineNumbers = executionTrace.get(className);
final ClassData oldData = data.getClassData(className);
LOG.assertTrue(oldData != null, "missed className: \"" + className + "\"");
final Object[] oldLines = oldData.getLines();
LOG.assertTrue(oldLines != null);
int maxNumber = oldLines.length;
for (Integer lineNumber : lineNumbers) {
if (lineNumber >= maxNumber) {
maxNumber = lineNumber + 1;
}
}
final LineData[] lines = new LineData[maxNumber];
for (Integer line : lineNumbers) {
final int lineIdx = line.intValue() - 1;
String methodSig = null;
if (lineIdx < oldData.getLines().length) {
final LineData oldLineData = oldData.getLineData(lineIdx);
if (oldLineData != null) {
methodSig = oldLineData.getMethodSignature();
}
}
final LineData lineData = new LineData(lineIdx, methodSig);
if (methodSig != null) {
loadedClassData.registerMethodSignature(lineData);
}
lineData.setStatus(LineCoverage.FULL);
lines[lineIdx] = lineData;
}
loadedClassData.setLines(lines);
}
suite.setCoverageData(projectData);
renewCoverageData(suite);
}
@Override
public void restoreMergedCoverage(@NotNull final CoverageSuitesBundle suite) {
mySubCoverageIsActive = false;
suite.restoreCoverageData();
renewCoverageData(suite);
}
@Override
public void addSuiteListener(final CoverageSuiteListener listener, Disposable parentDisposable) {
myListeners.add(listener);
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
myListeners.remove(listener);
}
});
}
public void fireBeforeSuiteChosen() {
for (CoverageSuiteListener listener : myListeners) {
listener.beforeSuiteChosen();
}
}
public void fireAfterSuiteChosen() {
for (CoverageSuiteListener listener : myListeners) {
listener.afterSuiteChosen();
}
}
@Override
public boolean isSubCoverageActive() {
return mySubCoverageIsActive;
}
@Nullable
public SrcFileAnnotator getAnnotator(Editor editor) {
synchronized (ANNOTATORS_LOCK) {
return myAnnotators.get(editor);
}
}
public void disposeAnnotators() {
synchronized (ANNOTATORS_LOCK) {
for (SrcFileAnnotator annotator : myAnnotators.values()) {
if (annotator != null) {
Disposer.dispose(annotator);
}
}
myAnnotators.clear();
}
}
@NotNull
private static CoverageSuite createCoverageSuite(final CoverageEnabledConfiguration config,
final String name,
final CoverageRunner coverageRunner,
final DefaultCoverageFileProvider fileProvider) {
CoverageSuite suite = null;
for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) {
if (coverageRunner.acceptsCoverageEngine(engine) && engine.isApplicableTo(config.getConfiguration())) {
suite = engine.createCoverageSuite(coverageRunner, name, fileProvider, config);
if (suite != null) {
break;
}
}
}
LOG.assertTrue(suite != null, "Cannot create coverage suite for runner: " + coverageRunner.getPresentableName());
return suite;
}
@NotNull
private CoverageSuite createCoverageSuite(final CoverageRunner coverageRunner,
final String name,
final CoverageFileProvider fileProvider,
final String[] filters,
final long lastCoverageTimeStamp,
final String suiteToMergeWith,
final boolean collectLineInfo,
final boolean tracingEnabled) {
CoverageSuite suite = null;
for (CoverageEngine engine : CoverageEngine.EP_NAME.getExtensions()) {
if (coverageRunner.acceptsCoverageEngine(engine)) {
suite = engine.createCoverageSuite(coverageRunner, name, fileProvider, filters, lastCoverageTimeStamp,
suiteToMergeWith, collectLineInfo, tracingEnabled, false, myProject);
if (suite != null) {
break;
}
}
}
LOG.assertTrue(suite != null, "Cannot create coverage suite for runner: " + coverageRunner.getPresentableName());
return suite;
}
private class CoverageEditorFactoryListener implements EditorFactoryListener {
private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myProject);
private final Map<Editor, Runnable> myCurrentEditors = new HashMap<>();
@Override
public void editorCreated(@NotNull EditorFactoryEvent event) {
synchronized (myLock) {
if (myIsProjectClosing) return;
}
final Editor editor = event.getEditor();
if (editor.getProject() != myProject) return;
final PsiFile psiFile = ReadAction.compute(() -> {
if (myProject.isDisposed()) return null;
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
final Document document = editor.getDocument();
return documentManager.getPsiFile(document);
});
if (psiFile != null && myCurrentSuitesBundle != null && psiFile.isPhysical()) {
final CoverageEngine engine = myCurrentSuitesBundle.getCoverageEngine();
if (!engine.coverageEditorHighlightingApplicableTo(psiFile)) {
return;
}
SrcFileAnnotator annotator = getAnnotator(editor);
if (annotator == null) {
annotator = new SrcFileAnnotator(psiFile, editor);
}
final SrcFileAnnotator finalAnnotator = annotator;
synchronized (ANNOTATORS_LOCK) {
myAnnotators.put(editor, finalAnnotator);
}
final Runnable request = () -> {
if (myProject.isDisposed()) return;
if (myCurrentSuitesBundle != null) {
if (engine.acceptedByFilters(psiFile, myCurrentSuitesBundle)) {
finalAnnotator.showCoverageInformation(myCurrentSuitesBundle);
}
}
};
myCurrentEditors.put(editor, request);
myAlarm.addRequest(request, 100);
}
}
@Override
public void editorReleased(@NotNull EditorFactoryEvent event) {
final Editor editor = event.getEditor();
if (editor.getProject() != myProject) return;
try {
final SrcFileAnnotator fileAnnotator;
synchronized (ANNOTATORS_LOCK) {
fileAnnotator = myAnnotators.remove(editor);
}
if (fileAnnotator != null) {
Disposer.dispose(fileAnnotator);
}
}
finally {
final Runnable request = myCurrentEditors.remove(editor);
if (request != null) {
myAlarm.cancelRequest(request);
}
}
}
}
}