@Override
  public final void showCoverage(final CoverageSuitesBundle suite) {
    // Store the values of myFile and myEditor in local variables to avoid an NPE after dispose() has been called in the EDT.
    final PsiFile psiFile = myFile;
    final Editor editor = myEditor;
    final Document document = myDocument;
    if (editor == null || psiFile == null || document == null) return;
    final VirtualFile file = getVirtualFile(psiFile);
    if (file == null || !file.isValid()) return;

    if (getOrCreateHighlighters(false) != null) {
      //highlighters already collected - no need to do it twice
      return;
    }

    final CoverageEngine engine = suite.getCoverageEngine();
    final Module module = ReadAction.compute(() -> ModuleUtilCore.findModuleForPsiElement(psiFile));
    if (module != null) {
      if (engine.recompileProjectAndRerunAction(module, suite, () -> CoverageDataManager.getInstance(myProject).chooseSuitesBundle(suite))) {
        return;
      }
    }

    // let's find old content in local history and build mapping from old lines to new one
    final long fileTimeStamp = file.getTimeStamp();
    final long coverageTimeStamp = suite.getLastCoverageTimeStamp();
    synchronized (this) {
      if (myMapper == null || myMapper.getTimeStamp() != coverageTimeStamp) {
        myMapper = new LineHistoryMapper(myProject, file, document, coverageTimeStamp);
      }
    }
    final Int2IntMap oldToNewLineMapping;
    // local history doesn't index libraries, so let's distinguish libraries content with other one
    if (engine.isInLibrarySource(myProject, file)) {
      if (fileTimeStamp > coverageTimeStamp) {
        showEditorWarningMessage(CoverageBundle.message("coverage.data.outdated"));
        return;
      }
      oldToNewLineMapping = null;
    }
    else if (myMapper.canGetFastMapping()) {
      oldToNewLineMapping = myMapper.getOldToNewLineMapping();
      if (oldToNewLineMapping == null) {
        // if history for file isn't available let's check timestamps
        if (fileTimeStamp > coverageTimeStamp && classesArePresentInCoverageData(suite, engine.getQualifiedNames(psiFile))) {
          showEditorWarningMessage(CoverageBundle.message("coverage.data.outdated"));
          return;
        }
      }
    }
    else {
      oldToNewLineMapping = null;
      ApplicationManager.getApplication().executeOnPooledThread(() -> {
        var mapping = myMapper.getOldToNewLineMapping();
        if (mapping == null) return;
        removeHighlighters();
        myUpdateAlarm.cancelAllRequests();
        synchronized (myLock) {
          var listenerDisposable = myListenerDisposable;
          if (listenerDisposable != null) {
            Disposer.dispose(listenerDisposable);
          }
        }
        showCoverage(suite);
      });
    }

    // now if oldToNewLineMapping is null we should use f(x) = x mapping
    final MarkupModel markupModel = DocumentMarkupModel.forDocument(document, myProject, true);
    if (getOrCreateHighlighters(true) == null) return;
    final TreeMap<Integer, LineData> executableLines = new TreeMap<>();
    final TreeMap<Integer, String> classNames = new TreeMap<>();
    collectLinesInFile(suite, psiFile, module, oldToNewLineMapping, markupModel, executableLines, classNames);

    final DocumentListener documentListener = new DocumentListener() {
      @Override
      public void documentChanged(@NotNull final DocumentEvent e) {
        myMapper.clear();
        int offset = e.getOffset();
        final int lineNumber = document.getLineNumber(offset);
        final int lastLineNumber = document.getLineNumber(offset + e.getNewLength());
        if (!removeChangedHighlighters(lineNumber, lastLineNumber, document)) return;
        if (!myUpdateAlarm.isDisposed()) {
          myUpdateAlarm.addRequest(() -> {
            Int2IntMap newToOldLineMapping = myMapper.canGetFastMapping() ? myMapper.getNewToOldLineMapping() : null;
            if (newToOldLineMapping != null) {
              final int lastLine = Math.min(document.getLineCount() - 1, lastLineNumber);
              for (int line = lineNumber; line <= lastLine; line++) {
                if (!newToOldLineMapping.containsKey(line)) continue;
                int oldLineNumber = newToOldLineMapping.get(line);
                var lineData = executableLines.get(oldLineNumber);
                if (lineData == null) continue;
                addHighlighter(markupModel, executableLines, suite, oldLineNumber, line, classNames.get(oldLineNumber));
              }
            }
          }, 100);
        }
      }
    };
    synchronized (myLock) {
      if (!myIsDisposed) {
        myListenerDisposable = Disposer.newDisposable(this);
        document.addDocumentListener(documentListener, myListenerDisposable);
      }
    }
  }
@Override
  public final void showCoverage(final CoverageSuitesBundle suite)