private void collectNonCoveredFileInfo( final File outputFile, final List<RangeHighlighter> highlighters, final MarkupModel markupModel, final TreeMap<Integer, LineData> executableLines, final boolean coverageByTestApplicable) { final CoverageSuitesBundle coverageSuite = CoverageDataManager.getInstance(myProject).getCurrentSuitesBundle(); if (coverageSuite == null) return; final TIntIntHashMap mapping; if (outputFile.lastModified() < getVirtualFile().getTimeStamp()) { mapping = getOldToNewLineMapping(outputFile.lastModified()); if (mapping == null) return; } else { mapping = null; } final List<Integer> uncoveredLines = coverageSuite .getCoverageEngine() .collectSrcLinesForUntouchedFile(outputFile, coverageSuite); final int lineCount = myDocument.getLineCount(); if (uncoveredLines == null) { for (int lineNumber = 0; lineNumber < lineCount; lineNumber++) { addHighlighter( outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite, lineNumber, lineNumber); } } else { for (int lineNumber : uncoveredLines) { if (lineNumber >= lineCount) { continue; } final int updatedLineNumber = mapping != null ? mapping.get(lineNumber) : lineNumber; addHighlighter( outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable, coverageSuite, lineNumber, updatedLineNumber); } } }
private RangeHighlighter createRangeHighlighter( final long date, final MarkupModel markupModel, final boolean coverageByTestApplicable, final TreeMap<Integer, LineData> executableLines, @Nullable final String className, final int line, final int lineNumberInCurrent, @NotNull final CoverageSuitesBundle coverageSuite, Object[] lines) { EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); final TextAttributes attributes = scheme.getAttributes(CoverageLineMarkerRenderer.getAttributesKey(line, executableLines)); TextAttributes textAttributes = null; if (attributes.getBackgroundColor() != null) { textAttributes = attributes; } final int startOffset = myDocument.getLineStartOffset(lineNumberInCurrent); final int endOffset = myDocument.getLineEndOffset(lineNumberInCurrent); final RangeHighlighter highlighter = markupModel.addRangeHighlighter( startOffset, endOffset, HighlighterLayer.SELECTION - 1, textAttributes, HighlighterTargetArea.LINES_IN_RANGE); final Function<Integer, Integer> newToOldConverter = newLine -> { if (myEditor == null) return -1; final TIntIntHashMap oldLineMapping = getNewToOldLineMapping(date); return oldLineMapping != null ? oldLineMapping.get(newLine.intValue()) : newLine.intValue(); }; final Function<Integer, Integer> oldToNewConverter = newLine -> { if (myEditor == null) return -1; final TIntIntHashMap newLineMapping = getOldToNewLineMapping(date); return newLineMapping != null ? newLineMapping.get(newLine.intValue()) : newLine.intValue(); }; final CoverageLineMarkerRenderer markerRenderer = coverageSuite .getCoverageEngine() .getLineMarkerRenderer( line, className, executableLines, coverageByTestApplicable, coverageSuite, newToOldConverter, oldToNewConverter, CoverageDataManager.getInstance(myProject).isSubCoverageActive()); highlighter.setLineMarkerRenderer(markerRenderer); final LineData lineData = className != null ? (LineData) lines[line + 1] : null; if (lineData != null && lineData.getStatus() == LineCoverage.NONE) { highlighter.setErrorStripeMarkColor(markerRenderer.getErrorStripeColor(myEditor)); highlighter.setThinErrorStripeMark(true); highlighter.setGreedyToLeft(true); highlighter.setGreedyToRight(true); } return highlighter; }
public void showCoverageInformation(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 MarkupModel markupModel = DocumentMarkupModel.forDocument(document, myProject, true); final List<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>(); final ProjectData data = suite.getCoverageData(); if (data == null) { coverageDataNotFound(suite); return; } final CoverageEngine engine = suite.getCoverageEngine(); final Set<String> qualifiedNames = engine.getQualifiedNames(psiFile); // let's find old content in local history and build mapping from old lines to new one // local history doesn't index libraries, so let's distinguish libraries content with other one final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); final VirtualFile file = psiFile.getVirtualFile(); LOG.assertTrue(file != null); final long fileTimeStamp = file.getTimeStamp(); final long coverageTimeStamp = suite.getLastCoverageTimeStamp(); final TIntIntHashMap oldToNewLineMapping; // do not show coverage info over cls if (engine.isInLibraryClasses(myProject, file)) { return; } // if in libraries content if (projectFileIndex.isInLibrarySource(file)) { // compare file and coverage timestamps if (fileTimeStamp > coverageTimeStamp) { showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated")); return; } oldToNewLineMapping = null; } else { // check local history oldToNewLineMapping = getOldToNewLineMapping(coverageTimeStamp); if (oldToNewLineMapping == null) { // if history for file isn't available let's check timestamps if (fileTimeStamp > coverageTimeStamp && classesArePresentInCoverageData(data, qualifiedNames)) { showEditorWarningMessage(CodeInsightBundle.message("coverage.data.outdated")); return; } } } if (editor.getUserData(COVERAGE_HIGHLIGHTERS) != null) { // highlighters already collected - no need to do it twice return; } final Module module = ApplicationManager.getApplication() .runReadAction( new Computable<Module>() { @Nullable @Override public Module compute() { return ModuleUtilCore.findModuleForPsiElement(psiFile); } }); if (module != null) { if (engine.recompileProjectAndRerunAction( module, suite, () -> CoverageDataManager.getInstance(myProject).chooseSuitesBundle(suite))) { return; } } // now if oldToNewLineMapping is null we should use f(x)=id(x) mapping // E.g. all *.class files for java source file with several classes final Set<File> outputFiles = engine.getCorrespondingOutputFiles(psiFile, module, suite); final boolean subCoverageActive = CoverageDataManager.getInstance(myProject).isSubCoverageActive(); final boolean coverageByTestApplicable = suite.isCoverageByTestApplicable() && !(subCoverageActive && suite.isCoverageByTestEnabled()); final TreeMap<Integer, LineData> executableLines = new TreeMap<Integer, LineData>(); final TreeMap<Integer, Object[]> classLines = new TreeMap<Integer, Object[]>(); final TreeMap<Integer, String> classNames = new TreeMap<Integer, String>(); class HighlightersCollector { private void collect(File outputFile, final String qualifiedName) { final ClassData fileData = data.getClassData(qualifiedName); if (fileData != null) { final Object[] lines = fileData.getLines(); if (lines != null) { final Object[] postProcessedLines = suite.getCoverageEngine().postProcessExecutableLines(lines, editor); for (Object lineData : postProcessedLines) { if (lineData instanceof LineData) { final int line = ((LineData) lineData).getLineNumber() - 1; final int lineNumberInCurrent; if (oldToNewLineMapping != null) { // use mapping based on local history if (!oldToNewLineMapping.contains(line)) { continue; } lineNumberInCurrent = oldToNewLineMapping.get(line); } else { // use id mapping lineNumberInCurrent = line; } LOG.assertTrue(lineNumberInCurrent < document.getLineCount()); executableLines.put(line, (LineData) lineData); classLines.put(line, postProcessedLines); classNames.put(line, qualifiedName); ApplicationManager.getApplication() .invokeLater( () -> { if (lineNumberInCurrent >= document.getLineCount()) return; final RangeHighlighter highlighter = createRangeHighlighter( suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines, qualifiedName, line, lineNumberInCurrent, suite, postProcessedLines); highlighters.add(highlighter); }); } } } } else if (outputFile != null && !subCoverageActive && engine.includeUntouchedFileInCoverage(qualifiedName, outputFile, psiFile, suite)) { collectNonCoveredFileInfo( outputFile, highlighters, markupModel, executableLines, coverageByTestApplicable); } } } final HighlightersCollector collector = new HighlightersCollector(); if (!outputFiles.isEmpty()) { for (File outputFile : outputFiles) { final String qualifiedName = engine.getQualifiedName(outputFile, psiFile); if (qualifiedName != null) { collector.collect(outputFile, qualifiedName); } } } else { // check non-compilable classes which present in ProjectData for (String qName : qualifiedNames) { collector.collect(null, qName); } } ApplicationManager.getApplication() .invokeLater( () -> { if (myEditor != null && highlighters.size() > 0) { editor.putUserData(COVERAGE_HIGHLIGHTERS, highlighters); } }); final DocumentListener documentListener = new DocumentAdapter() { @Override public void documentChanged(final DocumentEvent e) { myNewToOldLines = null; myOldToNewLines = null; List<RangeHighlighter> rangeHighlighters = editor.getUserData(COVERAGE_HIGHLIGHTERS); if (rangeHighlighters == null) rangeHighlighters = new ArrayList<RangeHighlighter>(); int offset = e.getOffset(); final int lineNumber = document.getLineNumber(offset); final int lastLineNumber = document.getLineNumber(offset + e.getNewLength()); final TextRange changeRange = new TextRange( document.getLineStartOffset(lineNumber), document.getLineEndOffset(lastLineNumber)); for (Iterator<RangeHighlighter> it = rangeHighlighters.iterator(); it.hasNext(); ) { final RangeHighlighter highlighter = it.next(); if (!highlighter.isValid() || TextRange.create(highlighter).intersects(changeRange)) { highlighter.dispose(); it.remove(); } } final List<RangeHighlighter> highlighters = rangeHighlighters; myUpdateAlarm.cancelAllRequests(); if (!myUpdateAlarm.isDisposed()) { myUpdateAlarm.addRequest( () -> { final TIntIntHashMap newToOldLineMapping = getNewToOldLineMapping(suite.getLastCoverageTimeStamp()); if (newToOldLineMapping != null) { ApplicationManager.getApplication() .invokeLater( () -> { if (editor.isDisposed()) return; for (int line = lineNumber; line <= lastLineNumber; line++) { final int oldLineNumber = newToOldLineMapping.get(line); final LineData lineData = executableLines.get(oldLineNumber); if (lineData != null) { RangeHighlighter rangeHighlighter = createRangeHighlighter( suite.getLastCoverageTimeStamp(), markupModel, coverageByTestApplicable, executableLines, classNames.get(oldLineNumber), oldLineNumber, line, suite, classLines.get(oldLineNumber)); highlighters.add(rangeHighlighter); } } editor.putUserData( COVERAGE_HIGHLIGHTERS, highlighters.size() > 0 ? highlighters : null); }); } }, 100); } } }; document.addDocumentListener(documentListener); editor.putUserData(COVERAGE_DOCUMENT_LISTENER, documentListener); }