/*
* Copyright 2000-2016 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.LineMarkerInfo;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.ex.MarkupModelEx;
import com.intellij.openapi.editor.ex.RangeHighlighterEx;
import com.intellij.openapi.editor.impl.DocumentMarkupModel;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.MarkupModel;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
class LineMarkersUtil {
private static final Logger LOG = Logger.getInstance(LineMarkersUtil.class);
static boolean processLineMarkers(@NotNull Project project,
@NotNull Document document,
@NotNull Segment bounds,
int group, // -1 for all
@NotNull Processor<LineMarkerInfo> processor) {
MarkupModelEx markupModel = (MarkupModelEx)DocumentMarkupModel.forDocument(document, project, true);
return markupModel.processRangeHighlightersOverlappingWith(bounds.getStartOffset(), bounds.getEndOffset(),
highlighter -> {
LineMarkerInfo info = getLineMarkerInfo(highlighter);
return info == null || group != -1 && info.updatePass != group || processor.process(info);
}
);
}
static void setLineMarkersToEditor(@NotNull Project project,
@NotNull Document document,
@NotNull Segment bounds,
@NotNull Collection<LineMarkerInfo> markers,
int group) {
ApplicationManager.getApplication().assertIsDispatchThread();
MarkupModelEx markupModel = (MarkupModelEx)DocumentMarkupModel.forDocument(document, project, true);
HighlightersRecycler toReuse = new HighlightersRecycler();
processLineMarkers(project, document, bounds, group, info -> {
toReuse.recycleHighlighter(info.highlighter);
return true;
});
if (LOG.isDebugEnabled()) {
List<LineMarkerInfo> oldMarkers = DaemonCodeAnalyzerImpl.getLineMarkers(document, project);
LOG.debug("LineMarkersUtil.setLineMarkersToEditor(markers: "+markers+", group: " + group+
"); oldMarkers: "+oldMarkers+"; reused: "+toReuse.forAllInGarbageBin().size());
}
for (final LineMarkerInfo info : markers) {
PsiElement element = info.getElement();
if (element == null) {
continue;
}
TextRange textRange = element.getTextRange();
if (textRange == null) continue;
TextRange elementRange = InjectedLanguageManager.getInstance(project).injectedToHost(element, textRange);
if (!TextRange.containsRange(bounds, elementRange)) {
continue;
}
createOrReuseLineMarker(info, markupModel, toReuse);
}
for (RangeHighlighter highlighter : toReuse.forAllInGarbageBin()) {
highlighter.dispose();
}
}
private static final Key<LineMarkerInfo> LINE_MARKER_INFO = Key.create("LINE_MARKER_INFO");
@NotNull
private static RangeHighlighter createOrReuseLineMarker(@NotNull LineMarkerInfo info,
@NotNull MarkupModel markupModel,
@Nullable HighlightersRecycler toReuse) {
RangeHighlighter highlighter = toReuse == null ? null : toReuse.pickupHighlighterFromGarbageBin(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX);
if (highlighter == null) {
highlighter = markupModel.addRangeHighlighter(info.startOffset, info.endOffset, HighlighterLayer.ADDITIONAL_SYNTAX, null, HighlighterTargetArea.LINES_IN_RANGE);
}
highlighter.putUserData(LINE_MARKER_INFO, info);
LineMarkerInfo.LineMarkerGutterIconRenderer newRenderer = (LineMarkerInfo.LineMarkerGutterIconRenderer)info.createGutterRenderer();
LineMarkerInfo.LineMarkerGutterIconRenderer oldRenderer = highlighter.getGutterIconRenderer() instanceof LineMarkerInfo.LineMarkerGutterIconRenderer ? (LineMarkerInfo.LineMarkerGutterIconRenderer)highlighter.getGutterIconRenderer() : null;
boolean rendererChanged = oldRenderer == null || newRenderer == null || !newRenderer.equals(oldRenderer);
boolean lineSeparatorColorChanged = !Comparing.equal(highlighter.getLineSeparatorColor(), info.separatorColor);
boolean lineSeparatorPlacementChanged = !Comparing.equal(highlighter.getLineSeparatorPlacement(), info.separatorPlacement);
if (rendererChanged || lineSeparatorColorChanged || lineSeparatorPlacementChanged) {
((MarkupModelEx)markupModel).changeAttributesInBatch((RangeHighlighterEx)highlighter, markerEx -> {
if (rendererChanged) {
markerEx.setGutterIconRenderer(newRenderer);
}
if (lineSeparatorColorChanged) {
markerEx.setLineSeparatorColor(info.separatorColor);
}
if (lineSeparatorPlacementChanged) {
markerEx.setLineSeparatorPlacement(info.separatorPlacement);
}
});
}
info.highlighter = highlighter;
return highlighter;
}
static void addLineMarkerToEditorIncrementally(@NotNull Project project,
@NotNull Document document,
@NotNull LineMarkerInfo marker) {
ApplicationManager.getApplication().assertIsDispatchThread();
MarkupModelEx markupModel = (MarkupModelEx)DocumentMarkupModel.forDocument(document, project, true);
LineMarkerInfo[] markerInTheWay = {null};
boolean allIsClear = markupModel.processRangeHighlightersOverlappingWith(marker.startOffset, marker.endOffset,
highlighter -> (markerInTheWay[0] = getLineMarkerInfo(highlighter)) == null);
if (allIsClear) {
createOrReuseLineMarker(marker, markupModel, null);
}
if (LOG.isDebugEnabled()) {
LOG.debug("LineMarkersUtil.addLineMarkerToEditorIncrementally: "+marker+" "+(allIsClear ? "created" : " (was not added because "+markerInTheWay[0] +" was in the way)"));
}
}
private static LineMarkerInfo getLineMarkerInfo(@NotNull RangeHighlighter highlighter) {
return highlighter.getUserData(LINE_MARKER_INFO);
}
}