2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.coverage;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.history.FileRevisionTimestampComparator;
21 import com.intellij.history.LocalHistory;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.openapi.Disposable;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.editor.colors.EditorColorsManager;
29 import com.intellij.openapi.editor.colors.EditorColorsScheme;
30 import com.intellij.openapi.editor.event.DocumentAdapter;
31 import com.intellij.openapi.editor.event.DocumentEvent;
32 import com.intellij.openapi.editor.event.DocumentListener;
33 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
34 import com.intellij.openapi.editor.markup.*;
35 import com.intellij.openapi.fileEditor.FileEditor;
36 import com.intellij.openapi.fileEditor.FileEditorManager;
37 import com.intellij.openapi.fileEditor.TextEditor;
38 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
39 import com.intellij.openapi.module.Module;
40 import com.intellij.openapi.module.ModuleUtilCore;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.roots.ProjectFileIndex;
43 import com.intellij.openapi.roots.ProjectRootManager;
44 import com.intellij.openapi.util.Computable;
45 import com.intellij.openapi.util.Key;
46 import com.intellij.openapi.util.Ref;
47 import com.intellij.openapi.util.TextRange;
48 import com.intellij.openapi.util.text.LineTokenizer;
49 import com.intellij.openapi.vcs.AbstractVcs;
50 import com.intellij.openapi.vcs.FilePath;
51 import com.intellij.openapi.vcs.actions.VcsContextFactory;
52 import com.intellij.openapi.vcs.history.VcsFileRevision;
53 import com.intellij.openapi.vcs.history.VcsHistoryProvider;
54 import com.intellij.openapi.vcs.history.VcsHistorySession;
55 import com.intellij.openapi.vfs.VirtualFile;
56 import com.intellij.psi.PsiFile;
57 import com.intellij.reference.SoftReference;
58 import com.intellij.rt.coverage.data.ClassData;
59 import com.intellij.rt.coverage.data.LineCoverage;
60 import com.intellij.rt.coverage.data.LineData;
61 import com.intellij.rt.coverage.data.ProjectData;
62 import com.intellij.ui.EditorNotificationPanel;
63 import com.intellij.util.Alarm;
64 import com.intellij.util.Function;
65 import com.intellij.util.diff.Diff;
66 import com.intellij.util.diff.FilesTooBigForDiffException;
67 import com.intellij.vcsUtil.VcsUtil;
68 import gnu.trove.TIntIntHashMap;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
78 public class SrcFileAnnotator implements Disposable {
79 private static final Logger LOG = Logger.getInstance("#com.intellij.coverage.SrcFileAnnotator");
80 public static final Key<List<RangeHighlighter>> COVERAGE_HIGHLIGHTERS = Key.create("COVERAGE_HIGHLIGHTERS");
81 private static final Key<DocumentListener> COVERAGE_DOCUMENT_LISTENER = Key.create("COVERAGE_DOCUMENT_LISTENER");
82 public static final Key<Map<FileEditor, EditorNotificationPanel>> NOTIFICATION_PANELS = Key.create("NOTIFICATION_PANELS");
84 private PsiFile myFile;
85 private Editor myEditor;
86 private Document myDocument;
87 private final Project myProject;
89 private SoftReference<TIntIntHashMap> myNewToOldLines;
90 private SoftReference<TIntIntHashMap> myOldToNewLines;
91 private SoftReference<byte[]> myOldContent;
92 private final static Object LOCK = new Object();
94 private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this);
96 public SrcFileAnnotator(final PsiFile file, final Editor editor) {
99 myProject = file.getProject();
100 myDocument = myEditor.getDocument();
104 public void hideCoverageData() {
105 Editor editor = myEditor;
106 PsiFile file = myFile;
107 Document document = myDocument;
108 if (editor == null || editor.isDisposed() || file == null || document == null) return;
109 final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
110 final List<RangeHighlighter> highlighters = editor.getUserData(COVERAGE_HIGHLIGHTERS);
111 if (highlighters != null) {
112 for (final RangeHighlighter highlighter : highlighters) {
113 ApplicationManager.getApplication().invokeLater(() -> highlighter.dispose());
115 editor.putUserData(COVERAGE_HIGHLIGHTERS, null);
118 final Map<FileEditor, EditorNotificationPanel> map = file.getCopyableUserData(NOTIFICATION_PANELS);
120 final VirtualFile vFile = getVirtualFile(file);
121 boolean freeAll = !fileEditorManager.isFileOpen(vFile);
122 file.putCopyableUserData(NOTIFICATION_PANELS, null);
123 for (FileEditor fileEditor : map.keySet()) {
124 if (!freeAll && !isCurrentEditor(fileEditor)) {
127 fileEditorManager.removeTopComponent(fileEditor, map.get(fileEditor));
132 final DocumentListener documentListener = editor.getUserData(COVERAGE_DOCUMENT_LISTENER);
133 if (documentListener != null) {
134 document.removeDocumentListener(documentListener);
135 editor.putUserData(COVERAGE_DOCUMENT_LISTENER, null);
140 private static String[] getCoveredLines(@NotNull byte[] oldContent, VirtualFile vFile) {
141 final String text = LoadTextUtil.getTextByBinaryPresentation(oldContent, vFile, false, false).toString();
142 return LineTokenizer.tokenize(text, false);
145 @NotNull private static String[] getUpToDateLines(final Document document) {
146 final Ref<String[]> linesRef = new Ref<>();
147 final Runnable runnable = () -> {
148 final int lineCount = document.getLineCount();
149 final String[] lines = new String[lineCount];
150 final CharSequence chars = document.getCharsSequence();
151 for (int i = 0; i < lineCount; i++) {
152 lines[i] = chars.subSequence(document.getLineStartOffset(i), document.getLineEndOffset(i)).toString();
156 ApplicationManager.getApplication().runReadAction(runnable);
158 return linesRef.get();
161 private static TIntIntHashMap getCoverageVersionToCurrentLineMapping(Diff.Change change, int firstNLines) {
162 TIntIntHashMap result = new TIntIntHashMap();
163 int prevLineInFirst = 0;
164 int prevLineInSecond = 0;
165 while (change != null) {
167 for (int l = 0; l < change.line0 - prevLineInFirst; l++) {
168 result.put(prevLineInFirst + l, prevLineInSecond + l);
171 prevLineInFirst = change.line0 + change.deleted;
172 prevLineInSecond = change.line1 + change.inserted;
174 change = change.link;
177 for (int i = prevLineInFirst; i < firstNLines; i++) {
178 result.put(i, prevLineInSecond + i - prevLineInFirst);
185 private TIntIntHashMap getOldToNewLineMapping(final long date, MyEditorBean editorBean) {
186 if (myOldToNewLines == null) {
187 myOldToNewLines = doGetLineMapping(date, true, editorBean);
188 if (myOldToNewLines == null) return null;
190 return myOldToNewLines.get();
194 private TIntIntHashMap getNewToOldLineMapping(final long date, MyEditorBean editorBean) {
195 if (myNewToOldLines == null) {
196 myNewToOldLines = doGetLineMapping(date, false, editorBean);
197 if (myNewToOldLines == null) return null;
199 return myNewToOldLines.get();
203 private SoftReference<TIntIntHashMap> doGetLineMapping(final long date, boolean oldToNew, MyEditorBean editorBean) {
204 VirtualFile virtualFile = editorBean.getVFile();
205 final byte[] oldContent;
206 synchronized (LOCK) {
207 if (myOldContent == null) {
208 if (ApplicationManager.getApplication().isDispatchThread()) return null;
209 final LocalHistory localHistory = LocalHistory.getInstance();
210 byte[] byteContent = localHistory.getByteContent(virtualFile, new FileRevisionTimestampComparator() {
211 public boolean isSuitable(long revisionTimestamp) {
212 return revisionTimestamp < date;
216 if (byteContent == null && virtualFile.getTimeStamp() > date) {
217 byteContent = loadFromVersionControl(date, virtualFile);
219 myOldContent = new SoftReference<>(byteContent);
221 oldContent = myOldContent.get();
224 if (oldContent == null) return null;
225 String[] coveredLines = getCoveredLines(oldContent, virtualFile);
226 final Document document = editorBean.getDocument();
227 if (document == null) return null;
228 String[] currentLines = getUpToDateLines(document);
230 String[] oldLines = oldToNew ? coveredLines : currentLines;
231 String[] newLines = oldToNew ? currentLines : coveredLines;
235 change = Diff.buildChanges(oldLines, newLines);
237 catch (FilesTooBigForDiffException e) {
241 return new SoftReference<>(getCoverageVersionToCurrentLineMapping(change, oldLines.length));
245 private byte[] loadFromVersionControl(long date, VirtualFile f) {
247 final AbstractVcs vcs = VcsUtil.getVcsFor(myProject, f);
248 if (vcs == null) return null;
250 final VcsHistoryProvider historyProvider = vcs.getVcsHistoryProvider();
251 if (historyProvider == null) return null;
253 final FilePath filePath = VcsContextFactory.SERVICE.getInstance().createFilePathOn(f);
254 final VcsHistorySession session = historyProvider.createSessionFor(filePath);
255 if (session == null) return null;
257 final List<VcsFileRevision> list = session.getRevisionList();
260 for (VcsFileRevision revision : list) {
261 final Date revisionDate = revision.getRevisionDate();
262 if (revisionDate == null) {
266 if (revisionDate.getTime() < date) {
267 return revision.loadContent();
272 catch (Exception e) {
279 public void showCoverageInformation(final CoverageSuitesBundle suite) {
280 // Store the values of myFile and myEditor in local variables to avoid an NPE after dispose() has been called in the EDT.
281 final PsiFile psiFile = myFile;
282 final Editor editor = myEditor;
283 final Document document = myDocument;
284 if (editor == null || psiFile == null || document == null) return;
285 final VirtualFile file = getVirtualFile(psiFile);
286 final MyEditorBean editorBean = new MyEditorBean(editor, file, document);
287 final MarkupModel markupModel = DocumentMarkupModel.forDocument(document, myProject, true);
288 final List<RangeHighlighter> highlighters = new ArrayList<>();
289 final ProjectData data = suite.getCoverageData();
291 coverageDataNotFound(suite);
294 final CoverageEngine engine = suite.getCoverageEngine();
295 final Set<String> qualifiedNames = engine.getQualifiedNames(psiFile);
297 // let's find old content in local history and build mapping from old lines to new one
298 // local history doesn't index libraries, so let's distinguish libraries content with other one
299 final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
300 final long fileTimeStamp = file.getTimeStamp();
301 final long coverageTimeStamp = suite.getLastCoverageTimeStamp();
302 final TIntIntHashMap oldToNewLineMapping;
304 //do not show coverage info over cls
305 if (engine.isInLibraryClasses(myProject, file)) {
308 // if in libraries content
309 if (projectFileIndex.isInLibrarySource(file)) {
310 // compare file and coverage timestamps
311 if (fileTimeStamp > coverageTimeStamp) {
312 showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated"));
315 oldToNewLineMapping = null;
318 // check local history
319 oldToNewLineMapping = getOldToNewLineMapping(coverageTimeStamp, editorBean);
320 if (oldToNewLineMapping == null) {
322 // if history for file isn't available let's check timestamps
323 if (fileTimeStamp > coverageTimeStamp && classesArePresentInCoverageData(data, qualifiedNames)) {
324 showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated"));
330 if (editor.getUserData(COVERAGE_HIGHLIGHTERS) != null) {
331 //highlighters already collected - no need to do it twice
335 final Module module = ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
338 public Module compute() {
339 return ModuleUtilCore.findModuleForPsiElement(psiFile);
342 if (module != null) {
343 if (engine.recompileProjectAndRerunAction(module, suite, () -> CoverageDataManager.getInstance(myProject).chooseSuitesBundle(suite))) {
348 // now if oldToNewLineMapping is null we should use f(x)=id(x) mapping
350 // E.g. all *.class files for java source file with several classes
351 final Set<File> outputFiles = engine.getCorrespondingOutputFiles(psiFile, module, suite);
353 final boolean subCoverageActive = CoverageDataManager.getInstance(myProject).isSubCoverageActive();
354 final boolean coverageByTestApplicable = suite.isCoverageByTestApplicable() && !(subCoverageActive && suite.isCoverageByTestEnabled());
355 final TreeMap<Integer, LineData> executableLines = new TreeMap<>();
356 final TreeMap<Integer, Object[]> classLines = new TreeMap<>();
357 final TreeMap<Integer, String> classNames = new TreeMap<>();
358 class HighlightersCollector {
359 private void collect(File outputFile, final String qualifiedName) {
360 final ClassData fileData = data.getClassData(qualifiedName);
361 if (fileData != null) {
362 final Object[] lines = fileData.getLines();
364 final Object[] postProcessedLines = suite.getCoverageEngine().postProcessExecutableLines(lines, editor);
365 for (Object lineData : postProcessedLines) {
366 if (lineData instanceof LineData) {
367 final int line = ((LineData)lineData).getLineNumber() - 1;
368 final int lineNumberInCurrent;
369 if (oldToNewLineMapping != null) {
370 // use mapping based on local history
371 if (!oldToNewLineMapping.contains(line)) {
374 lineNumberInCurrent = oldToNewLineMapping.get(line);
378 lineNumberInCurrent = line;
380 LOG.assertTrue(lineNumberInCurrent < document.getLineCount());
381 executableLines.put(line, (LineData)lineData);
383 classLines.put(line, postProcessedLines);
384 classNames.put(line, qualifiedName);
386 ApplicationManager.getApplication().invokeLater(() -> {
387 if (lineNumberInCurrent >= document.getLineCount()) return;
388 if (editorBean.isDisposed()) return;
389 final RangeHighlighter highlighter =
390 createRangeHighlighter(suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines,
391 qualifiedName, line, lineNumberInCurrent, suite, postProcessedLines, editorBean);
392 highlighters.add(highlighter);
398 else if (outputFile != null &&
399 !subCoverageActive &&
400 engine.includeUntouchedFileInCoverage(qualifiedName, outputFile, psiFile, suite)) {
401 collectNonCoveredFileInfo(outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, editorBean);
406 final HighlightersCollector collector = new HighlightersCollector();
407 if (!outputFiles.isEmpty()) {
408 for (File outputFile : outputFiles) {
409 final String qualifiedName = engine.getQualifiedName(outputFile, psiFile);
410 if (qualifiedName != null) {
411 collector.collect(outputFile, qualifiedName);
415 else { //check non-compilable classes which present in ProjectData
416 for (String qName : qualifiedNames) {
417 collector.collect(null, qName);
420 ApplicationManager.getApplication().invokeLater(() -> {
421 if (!editorBean.isDisposed() && highlighters.size() > 0) {
422 editor.putUserData(COVERAGE_HIGHLIGHTERS, highlighters);
426 final DocumentListener documentListener = new DocumentAdapter() {
428 public void documentChanged(final DocumentEvent e) {
429 myNewToOldLines = null;
430 myOldToNewLines = null;
431 List<RangeHighlighter> rangeHighlighters = editor.getUserData(COVERAGE_HIGHLIGHTERS);
432 if (rangeHighlighters == null) rangeHighlighters = new ArrayList<>();
433 int offset = e.getOffset();
434 final int lineNumber = document.getLineNumber(offset);
435 final int lastLineNumber = document.getLineNumber(offset + e.getNewLength());
436 final TextRange changeRange =
437 new TextRange(document.getLineStartOffset(lineNumber), document.getLineEndOffset(lastLineNumber));
438 for (Iterator<RangeHighlighter> it = rangeHighlighters.iterator(); it.hasNext(); ) {
439 final RangeHighlighter highlighter = it.next();
440 if (!highlighter.isValid() || TextRange.create(highlighter).intersects(changeRange)) {
441 highlighter.dispose();
445 final List<RangeHighlighter> highlighters = rangeHighlighters;
446 myUpdateAlarm.cancelAllRequests();
447 if (!myUpdateAlarm.isDisposed()) {
448 myUpdateAlarm.addRequest(() -> {
449 final TIntIntHashMap newToOldLineMapping = getNewToOldLineMapping(suite.getLastCoverageTimeStamp(), editorBean);
450 if (newToOldLineMapping != null) {
451 ApplicationManager.getApplication().invokeLater(() -> {
452 if (editorBean.isDisposed()) return;
453 for (int line = lineNumber; line <= lastLineNumber; line++) {
454 final int oldLineNumber = newToOldLineMapping.get(line);
455 final LineData lineData = executableLines.get(oldLineNumber);
456 if (lineData != null) {
457 RangeHighlighter rangeHighlighter =
458 createRangeHighlighter(suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines,
459 classNames.get(oldLineNumber), oldLineNumber, line, suite,
460 classLines.get(oldLineNumber), editorBean);
461 highlighters.add(rangeHighlighter);
464 editor.putUserData(COVERAGE_HIGHLIGHTERS, highlighters.size() > 0 ? highlighters : null);
471 document.addDocumentListener(documentListener);
472 editor.putUserData(COVERAGE_DOCUMENT_LISTENER, documentListener);
475 private static boolean classesArePresentInCoverageData(ProjectData data, Set<String> qualifiedNames) {
476 for (String qualifiedName : qualifiedNames) {
477 if (data.getClassData(qualifiedName) != null) {
484 private RangeHighlighter createRangeHighlighter(final long date, final MarkupModel markupModel,
485 final boolean coverageByTestApplicable,
486 final TreeMap<Integer, LineData> executableLines, @Nullable final String className,
488 final int lineNumberInCurrent,
489 @NotNull final CoverageSuitesBundle coverageSuite, Object[] lines,
490 @NotNull MyEditorBean editorBean) {
491 EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
492 final TextAttributes attributes = scheme.getAttributes(CoverageLineMarkerRenderer.getAttributesKey(line, executableLines));
493 TextAttributes textAttributes = null;
494 if (attributes.getBackgroundColor() != null) {
495 textAttributes = attributes;
497 Document document = editorBean.getDocument();
498 Editor editor = editorBean.getEditor();
499 final int startOffset = document.getLineStartOffset(lineNumberInCurrent);
500 final int endOffset = document.getLineEndOffset(lineNumberInCurrent);
501 final RangeHighlighter highlighter =
502 markupModel.addRangeHighlighter(startOffset, endOffset, HighlighterLayer.SELECTION - 1, textAttributes, HighlighterTargetArea.LINES_IN_RANGE);
503 final Function<Integer, Integer> newToOldConverter = newLine -> {
504 if (editor == null) return -1;
505 final TIntIntHashMap oldLineMapping = getNewToOldLineMapping(date, editorBean);
506 return oldLineMapping != null ? oldLineMapping.get(newLine.intValue()) : newLine.intValue();
508 final Function<Integer, Integer> oldToNewConverter = newLine -> {
509 if (editor == null) return -1;
510 final TIntIntHashMap newLineMapping = getOldToNewLineMapping(date, editorBean);
511 return newLineMapping != null ? newLineMapping.get(newLine.intValue()) : newLine.intValue();
513 final CoverageLineMarkerRenderer markerRenderer = coverageSuite.getCoverageEngine()
514 .getLineMarkerRenderer(line, className, executableLines, coverageByTestApplicable, coverageSuite, newToOldConverter,
515 oldToNewConverter, CoverageDataManager.getInstance(myProject).isSubCoverageActive());
516 highlighter.setLineMarkerRenderer(markerRenderer);
518 final LineData lineData = className != null ? (LineData)lines[line + 1] : null;
519 if (lineData != null && lineData.getStatus() == LineCoverage.NONE) {
520 highlighter.setErrorStripeMarkColor(markerRenderer.getErrorStripeColor(editor));
521 highlighter.setThinErrorStripeMark(true);
522 highlighter.setGreedyToLeft(true);
523 highlighter.setGreedyToRight(true);
528 private void showEditorWarningMessage(final String message) {
529 Editor textEditor = myEditor;
530 PsiFile file = myFile;
531 ApplicationManager.getApplication().invokeLater(() -> {
532 if (textEditor == null || textEditor.isDisposed() || file == null) return;
533 final FileEditorManager fileEditorManager = FileEditorManager.getInstance(myProject);
534 final VirtualFile vFile = file.getVirtualFile();
535 assert vFile != null;
536 Map<FileEditor, EditorNotificationPanel> map = file.getCopyableUserData(NOTIFICATION_PANELS);
538 map = new HashMap<>();
539 file.putCopyableUserData(NOTIFICATION_PANELS, map);
542 final FileEditor[] editors = fileEditorManager.getAllEditors(vFile);
543 for (final FileEditor editor : editors) {
544 if (isCurrentEditor(editor)) {
545 final EditorNotificationPanel panel = new EditorNotificationPanel() {
547 myLabel.setIcon(AllIcons.General.ExclMark);
548 myLabel.setText(message);
551 panel.createActionLabel("Close", () -> fileEditorManager.removeTopComponent(editor, panel));
552 map.put(editor, panel);
553 fileEditorManager.addTopComponent(editor, panel);
560 private boolean isCurrentEditor(FileEditor editor) {
561 return editor instanceof TextEditor && ((TextEditor)editor).getEditor() == myEditor;
564 private void collectNonCoveredFileInfo(final File outputFile,
565 final List<RangeHighlighter> highlighters, final MarkupModel markupModel,
566 final TreeMap<Integer, LineData> executableLines,
567 final boolean coverageByTestApplicable,
568 @NotNull MyEditorBean editorBean) {
569 final CoverageSuitesBundle coverageSuite = CoverageDataManager.getInstance(myProject).getCurrentSuitesBundle();
570 if (coverageSuite == null) return;
571 Document document = editorBean.getDocument();
572 VirtualFile file = editorBean.getVFile();
573 final TIntIntHashMap mapping;
574 if (outputFile.lastModified() < file.getTimeStamp()) {
575 mapping = getOldToNewLineMapping(outputFile.lastModified(), editorBean);
576 if (mapping == null) return;
583 final List<Integer> uncoveredLines = coverageSuite.getCoverageEngine().collectSrcLinesForUntouchedFile(outputFile, coverageSuite);
585 final int lineCount = document.getLineCount();
586 if (uncoveredLines == null) {
587 for (int lineNumber = 0; lineNumber < lineCount; lineNumber++) {
588 addHighlighter(outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite,
589 lineNumber, lineNumber, editorBean);
593 for (int lineNumber : uncoveredLines) {
594 if (lineNumber >= lineCount) {
598 final int updatedLineNumber = mapping != null ? mapping.get(lineNumber) : lineNumber;
600 addHighlighter(outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite,
601 lineNumber, updatedLineNumber, editorBean);
606 private void addHighlighter(final File outputFile,
607 final List<RangeHighlighter> highlighters,
608 final MarkupModel markupModel,
609 final TreeMap<Integer, LineData> executableLines,
610 final boolean coverageByTestApplicable,
611 final CoverageSuitesBundle coverageSuite,
612 final int lineNumber,
613 final int updatedLineNumber,
614 @NotNull MyEditorBean editorBean) {
615 executableLines.put(updatedLineNumber, null);
616 ApplicationManager.getApplication().invokeLater(() -> {
617 if (editorBean.isDisposed()) return;
618 final RangeHighlighter highlighter =
619 createRangeHighlighter(outputFile.lastModified(), markupModel, coverageByTestApplicable, executableLines, null, lineNumber,
620 updatedLineNumber, coverageSuite, null, editorBean);
621 highlighters.add(highlighter);
625 private static VirtualFile getVirtualFile(PsiFile file) {
626 final VirtualFile vFile = file.getVirtualFile();
627 LOG.assertTrue(vFile != null);
632 private void coverageDataNotFound(final CoverageSuitesBundle suite) {
633 showEditorWarningMessage(CodeInsightBundle.message("coverage.data.not.found"));
634 for (CoverageSuite coverageSuite : suite.getSuites()) {
635 CoverageDataManager.getInstance(myProject).removeCoverageSuite(coverageSuite);
639 public void dispose() {
646 static class MyEditorBean {
647 private final Editor myEditor;
648 private final VirtualFile myVFile;
649 private final Document myDocument;
651 public MyEditorBean(Editor editor, VirtualFile VFile, Document document) {
654 myDocument = document;
657 public boolean isDisposed() {
658 return myEditor == null ||
659 myEditor.isDisposed() ||
664 public Document getDocument() {
668 public Editor getEditor() {
672 public VirtualFile getVFile() {