| /* |
| * Copyright 2000-2014 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.codeInsight.daemon.impl; |
| |
| import com.intellij.codeInsight.daemon.GutterMark; |
| import com.intellij.lang.annotation.HighlightSeverity; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.RangeMarker; |
| import com.intellij.openapi.editor.colors.EditorColorsScheme; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.editor.ex.DocumentEx; |
| import com.intellij.openapi.editor.ex.MarkupModelEx; |
| import com.intellij.openapi.editor.ex.RangeHighlighterEx; |
| import com.intellij.openapi.editor.ex.SweepProcessor; |
| import com.intellij.openapi.editor.impl.DocumentMarkupModel; |
| import com.intellij.openapi.editor.impl.RangeMarkerTree; |
| import com.intellij.openapi.editor.impl.RedBlackTree; |
| import com.intellij.openapi.editor.markup.*; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.*; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.ContainerUtil; |
| import gnu.trove.THashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.awt.*; |
| import java.util.*; |
| import java.util.List; |
| |
| public class UpdateHighlightersUtil { |
| private static final Comparator<HighlightInfo> BY_START_OFFSET_NODUPS = new Comparator<HighlightInfo>() { |
| @Override |
| public int compare(@NotNull HighlightInfo o1, @NotNull HighlightInfo o2) { |
| int d = o1.getActualStartOffset() - o2.getActualStartOffset(); |
| if (d != 0) return d; |
| d = o1.getActualEndOffset() - o2.getActualEndOffset(); |
| if (d != 0) return d; |
| |
| d = Comparing.compare(o1.getSeverity(), o2.getSeverity()); |
| if (d != 0) return -d; // higher severity first, to prevent warnings overlap errors |
| |
| if (!Comparing.equal(o1.type, o2.type)) { |
| return String.valueOf(o1.type).compareTo(String.valueOf(o2.type)); |
| } |
| |
| if (!Comparing.equal(o1.getGutterIconRenderer(), o2.getGutterIconRenderer())) { |
| return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); |
| } |
| |
| if (!Comparing.equal(o1.forcedTextAttributes, o2.forcedTextAttributes)) { |
| return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); |
| } |
| |
| if (!Comparing.equal(o1.forcedTextAttributesKey, o2.forcedTextAttributesKey)) { |
| return String.valueOf(o1.getGutterIconRenderer()).compareTo(String.valueOf(o2.getGutterIconRenderer())); |
| } |
| |
| return Comparing.compare(o1.getDescription(), o2.getDescription()); |
| } |
| }; |
| |
| private static boolean isCoveredByOffsets(HighlightInfo info, HighlightInfo coveredBy) { |
| return coveredBy.startOffset <= info.startOffset && info.endOffset <= coveredBy.endOffset && info.getGutterIconRenderer() == null; |
| } |
| |
| static void addHighlighterToEditorIncrementally(@NotNull Project project, |
| @NotNull Document document, |
| @NotNull PsiFile file, |
| int startOffset, |
| int endOffset, |
| @NotNull final HighlightInfo info, |
| @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used |
| final int group, |
| @NotNull Map<TextRange, RangeMarker> ranges2markersCache) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| if (isFileLevelOrGutterAnnotation(info)) return; |
| if (info.getStartOffset() < startOffset || info.getEndOffset() > endOffset) return; |
| |
| MarkupModel markup = DocumentMarkupModel.forDocument(document, project, true); |
| final SeverityRegistrar severityRegistrar = SeverityRegistrar.getSeverityRegistrar(project); |
| final boolean myInfoIsError = isSevere(info, severityRegistrar); |
| Processor<HighlightInfo> otherHighlightInTheWayProcessor = new Processor<HighlightInfo>() { |
| @Override |
| public boolean process(HighlightInfo oldInfo) { |
| if (!myInfoIsError && isCovered(info, severityRegistrar, oldInfo)) { |
| return false; |
| } |
| |
| return oldInfo.getGroup() != group || !oldInfo.equalsByActualOffset(info); |
| } |
| }; |
| boolean allIsClear = DaemonCodeAnalyzerEx.processHighlights(document, project, |
| null, info.getActualStartOffset(), info.getActualEndOffset(), |
| otherHighlightInTheWayProcessor); |
| if (allIsClear) { |
| createOrReuseHighlighterFor(info, colorsScheme, document, group, file, (MarkupModelEx)markup, null, ranges2markersCache, severityRegistrar); |
| |
| clearWhiteSpaceOptimizationFlag(document); |
| assertMarkupConsistent(markup, project); |
| } |
| } |
| |
| public static boolean isFileLevelOrGutterAnnotation(HighlightInfo info) { |
| return info.isFileLevelAnnotation() || info.getGutterIconRenderer() != null; |
| } |
| |
| public static void setHighlightersToEditor(@NotNull Project project, |
| @NotNull Document document, |
| int startOffset, |
| int endOffset, |
| @NotNull Collection<HighlightInfo> highlights, |
| @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used |
| int group) { |
| TextRange range = new TextRange(startOffset, endOffset); |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); |
| final DaemonCodeAnalyzerEx codeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(project); |
| codeAnalyzer.cleanFileLevelHighlights(project, group, psiFile); |
| |
| MarkupModel markup = DocumentMarkupModel.forDocument(document, project, true); |
| assertMarkupConsistent(markup, project); |
| |
| setHighlightersInRange(project, document, range, colorsScheme, new ArrayList<HighlightInfo>(highlights), (MarkupModelEx)markup, group); |
| } |
| |
| @Deprecated //for teamcity |
| public static void setHighlightersToEditor(@NotNull Project project, |
| @NotNull Document document, |
| int startOffset, |
| int endOffset, |
| @NotNull Collection<HighlightInfo> highlights, |
| int group) { |
| setHighlightersToEditor(project, document, startOffset, endOffset, highlights, null, group); |
| } |
| |
| // set highlights inside startOffset,endOffset but outside priorityRange |
| static void setHighlightersOutsideRange(@NotNull final Project project, |
| @NotNull final Document document, |
| @NotNull final PsiFile psiFile, |
| @NotNull final List<HighlightInfo> infos, |
| @Nullable final EditorColorsScheme colorsScheme, |
| // if null global scheme will be used |
| final int startOffset, |
| final int endOffset, |
| @NotNull final ProperTextRange priorityRange, |
| final int group) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| final DaemonCodeAnalyzerEx codeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(project); |
| codeAnalyzer.cleanFileLevelHighlights(project, group, psiFile); |
| |
| final MarkupModel markup = DocumentMarkupModel.forDocument(document, project, true); |
| assertMarkupConsistent(markup, project); |
| |
| final SeverityRegistrar severityRegistrar = SeverityRegistrar.getSeverityRegistrar(project); |
| final HighlightersRecycler infosToRemove = new HighlightersRecycler(); |
| ContainerUtil.quickSort(infos, BY_START_OFFSET_NODUPS); |
| |
| Processor<HighlightInfo> processor = new Processor<HighlightInfo>() { |
| @Override |
| public boolean process(HighlightInfo info) { |
| if (info.getGroup() == group) { |
| RangeHighlighter highlighter = info.highlighter; |
| int hiStart = highlighter.getStartOffset(); |
| int hiEnd = highlighter.getEndOffset(); |
| if (!info.isFromInjection() && hiEnd < document.getTextLength() && (hiEnd <= startOffset || hiStart >= endOffset)) { |
| return true; // injections are oblivious to restricting range |
| } |
| boolean toRemove = !(hiEnd == document.getTextLength() && |
| priorityRange.getEndOffset() == document.getTextLength()) && |
| !priorityRange.containsRange(hiStart, hiEnd); |
| if (toRemove) { |
| infosToRemove.recycleHighlighter(highlighter); |
| info.highlighter = null; |
| } |
| } |
| return true; |
| } |
| }; |
| DaemonCodeAnalyzerEx.processHighlightsOverlappingOutside(document, project, null, priorityRange.getStartOffset(), priorityRange.getEndOffset(), processor); |
| |
| final Map<TextRange, RangeMarker> ranges2markersCache = new THashMap<TextRange, RangeMarker>(10); |
| final boolean[] changed = {false}; |
| RangeMarkerTree.sweep(new RangeMarkerTree.Generator<HighlightInfo>(){ |
| @Override |
| public boolean generateInStartOffsetOrder(@NotNull Processor<HighlightInfo> processor) { |
| return ContainerUtil.process(infos, processor); |
| } |
| }, new SweepProcessor<HighlightInfo>() { |
| @Override |
| public boolean process(int offset, @NotNull HighlightInfo info, boolean atStart, @NotNull Collection<HighlightInfo> overlappingIntervals) { |
| if (!atStart) return true; |
| if (!info.isFromInjection() && info.getEndOffset() < document.getTextLength() && (info.getEndOffset() <= startOffset || info.getStartOffset()>=endOffset)) return true; // injections are oblivious to restricting range |
| |
| if (info.isFileLevelAnnotation()) { |
| codeAnalyzer.addFileLevelHighlight(project, group, info, psiFile); |
| changed[0] = true; |
| return true; |
| } |
| if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { |
| return true; |
| } |
| if (info.getStartOffset() < priorityRange.getStartOffset() || info.getEndOffset() > priorityRange.getEndOffset()) { |
| createOrReuseHighlighterFor(info, colorsScheme, document, group, psiFile, (MarkupModelEx)markup, infosToRemove, |
| ranges2markersCache, severityRegistrar); |
| changed[0] = true; |
| } |
| return true; |
| } |
| }); |
| for (RangeHighlighter highlighter : infosToRemove.forAllInGarbageBin()) { |
| highlighter.dispose(); |
| changed[0] = true; |
| } |
| |
| if (changed[0]) { |
| clearWhiteSpaceOptimizationFlag(document); |
| } |
| assertMarkupConsistent(markup, project); |
| } |
| |
| static void setHighlightersInRange(@NotNull final Project project, |
| @NotNull final Document document, |
| @NotNull final TextRange range, |
| @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used |
| @NotNull final List<HighlightInfo> infos, |
| @NotNull final MarkupModelEx markup, |
| final int group) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| final SeverityRegistrar severityRegistrar = SeverityRegistrar.getSeverityRegistrar(project); |
| final HighlightersRecycler infosToRemove = new HighlightersRecycler(); |
| DaemonCodeAnalyzerEx |
| .processHighlights(document, project, null, range.getStartOffset(), range.getEndOffset(), new Processor<HighlightInfo>() { |
| @Override |
| public boolean process(HighlightInfo info) { |
| if (info.getGroup() == group) { |
| RangeHighlighter highlighter = info.highlighter; |
| int hiStart = highlighter.getStartOffset(); |
| int hiEnd = highlighter.getEndOffset(); |
| boolean willBeRemoved = hiEnd == document.getTextLength() && range.getEndOffset() == document.getTextLength() |
| /*|| range.intersectsStrict(hiStart, hiEnd)*/ || range.containsRange(hiStart, hiEnd) /*|| hiStart <= range.getStartOffset() && hiEnd >= range.getEndOffset()*/; |
| if (willBeRemoved) { |
| infosToRemove.recycleHighlighter(highlighter); |
| info.highlighter = null; |
| } |
| } |
| return true; |
| } |
| }); |
| |
| ContainerUtil.quickSort(infos, BY_START_OFFSET_NODUPS); |
| final Map<TextRange, RangeMarker> ranges2markersCache = new THashMap<TextRange, RangeMarker>(10); |
| final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); |
| final DaemonCodeAnalyzerEx codeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(project); |
| final boolean[] changed = {false}; |
| RangeMarkerTree.sweep(new RangeMarkerTree.Generator<HighlightInfo>(){ |
| @Override |
| public boolean generateInStartOffsetOrder(@NotNull final Processor<HighlightInfo> processor) { |
| return ContainerUtil.process(infos, processor); |
| } |
| }, new SweepProcessor<HighlightInfo>() { |
| @Override |
| public boolean process(int offset, @NotNull HighlightInfo info, boolean atStart, @NotNull Collection<HighlightInfo> overlappingIntervals) { |
| if (!atStart) { |
| return true; |
| } |
| if (info.isFileLevelAnnotation() && psiFile != null && psiFile.getViewProvider().isPhysical()) { |
| codeAnalyzer.addFileLevelHighlight(project, group, info, psiFile); |
| changed[0] = true; |
| return true; |
| } |
| if (isWarningCoveredByError(info, overlappingIntervals, severityRegistrar)) { |
| return true; |
| } |
| if (info.getStartOffset() >= range.getStartOffset() && info.getEndOffset() <= range.getEndOffset() && psiFile != null) { |
| createOrReuseHighlighterFor(info, colorsScheme, document, group, psiFile, markup, infosToRemove, ranges2markersCache, severityRegistrar); |
| changed[0] = true; |
| } |
| return true; |
| } |
| }); |
| for (RangeHighlighter highlighter : infosToRemove.forAllInGarbageBin()) { |
| highlighter.dispose(); |
| changed[0] = true; |
| } |
| |
| if (changed[0]) { |
| clearWhiteSpaceOptimizationFlag(document); |
| } |
| assertMarkupConsistent(markup, project); |
| } |
| |
| private static boolean isWarningCoveredByError(@NotNull HighlightInfo info, |
| @NotNull Collection<HighlightInfo> overlappingIntervals, |
| @NotNull SeverityRegistrar severityRegistrar) { |
| if (!isSevere(info, severityRegistrar)) { |
| for (HighlightInfo overlapping : overlappingIntervals) { |
| if (isCovered(info, severityRegistrar, overlapping)) return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isCovered(@NotNull HighlightInfo warning, @NotNull SeverityRegistrar severityRegistrar, @NotNull HighlightInfo candidate) { |
| if (!isCoveredByOffsets(warning, candidate)) return false; |
| HighlightSeverity severity = candidate.getSeverity(); |
| if (severity == HighlightInfoType.SYMBOL_TYPE_SEVERITY) return false; // syntax should not interfere with warnings |
| return isSevere(candidate, severityRegistrar); |
| } |
| |
| private static boolean isSevere(@NotNull HighlightInfo info, @NotNull SeverityRegistrar severityRegistrar) { |
| HighlightSeverity severity = info.getSeverity(); |
| return severityRegistrar.compare(HighlightSeverity.ERROR, severity) <= 0 || severity == HighlightInfoType.SYMBOL_TYPE_SEVERITY; |
| } |
| |
| private static void createOrReuseHighlighterFor(@NotNull final HighlightInfo info, |
| @Nullable final EditorColorsScheme colorsScheme, // if null global scheme will be used |
| @NotNull final Document document, |
| final int group, |
| @NotNull final PsiFile psiFile, |
| @NotNull MarkupModelEx markup, |
| @Nullable HighlightersRecycler infosToRemove, |
| @NotNull final Map<TextRange, RangeMarker> ranges2markersCache, |
| @NotNull SeverityRegistrar severityRegistrar) { |
| int infoStartOffset = info.startOffset; |
| int infoEndOffset = info.endOffset; |
| |
| final int docLength = document.getTextLength(); |
| if (infoEndOffset > docLength) { |
| infoEndOffset = docLength; |
| infoStartOffset = Math.min(infoStartOffset, infoEndOffset); |
| } |
| if (infoEndOffset == infoStartOffset && !info.isAfterEndOfLine()) { |
| if (infoEndOffset == docLength) return; // empty highlighter beyond file boundaries |
| infoEndOffset++; //show something in case of empty highlightinfo |
| } |
| |
| info.setGroup(group); |
| |
| int layer = getLayer(info, severityRegistrar); |
| RangeHighlighterEx highlighter = infosToRemove == null ? null : (RangeHighlighterEx)infosToRemove.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, layer); |
| |
| final TextRange finalInfoRange = new TextRange(infoStartOffset, infoEndOffset); |
| final TextAttributes infoAttributes = info.getTextAttributes(psiFile, colorsScheme); |
| Consumer<RangeHighlighterEx> changeAttributes = new Consumer<RangeHighlighterEx>() { |
| @Override |
| public void consume(RangeHighlighterEx finalHighlighter) { |
| finalHighlighter.setTextAttributes(infoAttributes); |
| |
| info.highlighter = finalHighlighter; |
| finalHighlighter.setAfterEndOfLine(info.isAfterEndOfLine()); |
| |
| Color color = info.getErrorStripeMarkColor(psiFile, colorsScheme); |
| finalHighlighter.setErrorStripeMarkColor(color); |
| if (info != finalHighlighter.getErrorStripeTooltip()) { |
| finalHighlighter.setErrorStripeTooltip(info); |
| } |
| GutterMark renderer = info.getGutterIconRenderer(); |
| finalHighlighter.setGutterIconRenderer((GutterIconRenderer)renderer); |
| |
| ranges2markersCache.put(finalInfoRange, info.highlighter); |
| if (info.quickFixActionRanges != null) { |
| List<Pair<HighlightInfo.IntentionActionDescriptor, RangeMarker>> list = |
| new ArrayList<Pair<HighlightInfo.IntentionActionDescriptor, RangeMarker>>(info.quickFixActionRanges.size()); |
| for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> pair : info.quickFixActionRanges) { |
| TextRange textRange = pair.second; |
| RangeMarker marker = getOrCreate(document, ranges2markersCache, textRange); |
| list.add(Pair.create(pair.first, marker)); |
| } |
| info.quickFixActionMarkers = ContainerUtil.createLockFreeCopyOnWriteList(list); |
| } |
| ProperTextRange fixRange = info.getFixTextRange(); |
| if (finalInfoRange.equals(fixRange)) { |
| info.fixMarker = null; // null means it the same as highlighter' |
| } |
| else { |
| info.fixMarker = getOrCreate(document, ranges2markersCache, fixRange); |
| } |
| } |
| }; |
| |
| if (highlighter == null) { |
| highlighter = markup.addRangeHighlighterAndChangeAttributes(infoStartOffset, infoEndOffset, layer, null, |
| HighlighterTargetArea.EXACT_RANGE, false, changeAttributes); |
| } |
| else { |
| markup.changeAttributesInBatch(highlighter, changeAttributes); |
| } |
| |
| boolean attributesSet = Comparing.equal(infoAttributes, highlighter.getTextAttributes()); |
| assert attributesSet : "Info: " + infoAttributes + |
| "; colorsScheme: " + (colorsScheme == null ? "[global]" : colorsScheme.getName()) + |
| "; highlighter:" + highlighter.getTextAttributes(); |
| } |
| |
| private static int getLayer(@NotNull HighlightInfo info, @NotNull SeverityRegistrar severityRegistrar) { |
| final HighlightSeverity severity = info.getSeverity(); |
| int layer; |
| if (severity == HighlightSeverity.WARNING) { |
| layer = HighlighterLayer.WARNING; |
| } |
| else if (severityRegistrar.compare(severity, HighlightSeverity.ERROR) >= 0) { |
| layer = HighlighterLayer.ERROR; |
| } |
| else if (severity == HighlightInfoType.INJECTED_FRAGMENT_SEVERITY) { |
| layer = HighlighterLayer.CARET_ROW-1; |
| } |
| else if (severity == HighlightInfoType.ELEMENT_UNDER_CARET_SEVERITY) { |
| layer = HighlighterLayer.ELEMENT_UNDER_CARET; |
| } |
| else { |
| layer = HighlighterLayer.ADDITIONAL_SYNTAX; |
| } |
| return layer; |
| } |
| |
| private static RangeMarker getOrCreate(@NotNull Document document, @NotNull Map<TextRange, RangeMarker> ranges2markersCache, @NotNull TextRange textRange) { |
| RangeMarker marker = ranges2markersCache.get(textRange); |
| if (marker == null) { |
| marker = document.createRangeMarker(textRange); |
| ranges2markersCache.put(textRange, marker); |
| } |
| return marker; |
| } |
| |
| private static final Key<Boolean> TYPING_INSIDE_HIGHLIGHTER_OCCURRED = Key.create("TYPING_INSIDE_HIGHLIGHTER_OCCURRED"); |
| static boolean isWhitespaceOptimizationAllowed(@NotNull Document document) { |
| return document.getUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED) == null; |
| } |
| private static void disableWhiteSpaceOptimization(@NotNull Document document) { |
| document.putUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED, Boolean.TRUE); |
| } |
| private static void clearWhiteSpaceOptimizationFlag(@NotNull Document document) { |
| document.putUserData(TYPING_INSIDE_HIGHLIGHTER_OCCURRED, null); |
| } |
| |
| static void updateHighlightersByTyping(@NotNull Project project, @NotNull DocumentEvent e) { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| final Document document = e.getDocument(); |
| if (document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate()) return; |
| |
| final MarkupModel markup = DocumentMarkupModel.forDocument(document, project, true); |
| assertMarkupConsistent(markup, project); |
| |
| final int start = e.getOffset() - 1; |
| final int end = start + Math.max(e.getOldLength(), e.getNewLength()); |
| |
| final List<HighlightInfo> toRemove = new ArrayList<HighlightInfo>(); |
| DaemonCodeAnalyzerEx.processHighlights(document, project, null, start, end, new Processor<HighlightInfo>() { |
| @Override |
| public boolean process(HighlightInfo info) { |
| RangeHighlighter highlighter = info.highlighter; |
| boolean remove = false; |
| if (info.needUpdateOnTyping()) { |
| int highlighterStart = highlighter.getStartOffset(); |
| int highlighterEnd = highlighter.getEndOffset(); |
| if (info.isAfterEndOfLine()) { |
| if (highlighterStart < document.getTextLength()) { |
| highlighterStart += 1; |
| } |
| if (highlighterEnd < document.getTextLength()) { |
| highlighterEnd += 1; |
| } |
| } |
| if (!highlighter.isValid() || start < highlighterEnd && highlighterStart <= end) { |
| remove = true; |
| } |
| } |
| if (remove) { |
| toRemove.add(info); |
| } |
| return true; |
| } |
| }); |
| |
| for (HighlightInfo info : toRemove) { |
| if (!info.highlighter.isValid() || info.type.equals(HighlightInfoType.WRONG_REF)) { |
| info.highlighter.dispose(); |
| } |
| } |
| |
| assertMarkupConsistent(markup, project); |
| |
| if (!toRemove.isEmpty()) { |
| disableWhiteSpaceOptimization(document); |
| } |
| } |
| |
| private static void assertMarkupConsistent(@NotNull final MarkupModel markup, @NotNull Project project) { |
| if (!RedBlackTree.VERIFY) { |
| return; |
| } |
| Document document = markup.getDocument(); |
| DaemonCodeAnalyzerEx.processHighlights(document, project, null, 0, document.getTextLength(), new Processor<HighlightInfo>() { |
| @Override |
| public boolean process(HighlightInfo info) { |
| assert ((MarkupModelEx)markup).containsHighlighter(info.highlighter); |
| return true; |
| } |
| }); |
| RangeHighlighter[] allHighlighters = markup.getAllHighlighters(); |
| for (RangeHighlighter highlighter : allHighlighters) { |
| if (!highlighter.isValid()) continue; |
| Object tooltip = highlighter.getErrorStripeTooltip(); |
| if (!(tooltip instanceof HighlightInfo)) { |
| continue; |
| } |
| final HighlightInfo info = (HighlightInfo)tooltip; |
| boolean contains = !DaemonCodeAnalyzerEx |
| .processHighlights(document, project, null, info.getActualStartOffset(), info.getActualEndOffset(), new Processor<HighlightInfo>() { |
| @Override |
| public boolean process(HighlightInfo highlightInfo) { |
| return BY_START_OFFSET_NODUPS.compare(highlightInfo, info) != 0; |
| } |
| }); |
| assert contains: info; |
| } |
| } |
| } |