/*
* 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.openapi.editor.impl.view;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.ex.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.util.Comparing;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.CommonProcessors;
import com.intellij.util.DocumentUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Iterator over editor's text contents. Each iteration step corresponds to a text fragment having common graphical attributes
* (font style, foreground and background color, effect type and color).
*/
// This class should replace com.intellij.openapi.editor.impl.IterationState when new editor rendering engine will become default
public class IterationState {
private static final Logger LOG = Logger.getInstance(IterationState.class);
public static final Comparator<RangeHighlighterEx> BY_LAYER_THEN_ATTRIBUTES = (o1, o2) -> {
final int result = LayerComparator.INSTANCE.compare(o1, o2);
if (result != 0) {
return result;
}
// There is a possible case when more than one highlighter target the same region (e.g. 'identifier under caret' and 'identifier').
// We want to prefer the one that defines foreground color to the one that doesn't define (has either fore- or background colors
// while the other one has only foreground color). See IDEA-85697 for concrete example.
final TextAttributes a1 = o1.getTextAttributes();
final TextAttributes a2 = o2.getTextAttributes();
if (a1 == null ^ a2 == null) {
return a1 == null ? 1 : -1;
}
if (a1 == null) {
return result;
}
final Color fore1 = a1.getForegroundColor();
final Color fore2 = a2.getForegroundColor();
if (fore1 == null ^ fore2 == null) {
return fore1 == null ? 1 : -1;
}
final Color back1 = a1.getBackgroundColor();
final Color back2 = a2.getBackgroundColor();
if (back1 == null ^ back2 == null) {
return back1 == null ? 1 : -1;
}
return result;
};
private static final Comparator<RangeHighlighterEx> BY_AFFECTED_END_OFFSET_REVERSED =
(r1, r2) -> r2.getAffectedAreaEndOffset() - r1.getAffectedAreaEndOffset();
private static final CaretData NULL_CARET_DATA = new CaretData(-1, -1, false, false,
ArrayUtilRt.EMPTY_INT_ARRAY, ArrayUtilRt.EMPTY_INT_ARRAY);
private final TextAttributes myMergedAttributes = new TextAttributes();
@Nullable
private final HighlighterIterator myHighlighterIterator;
private final HighlighterSweep myView;
private final HighlighterSweep myDoc;
private int myStartOffset;
private int myEndOffset;
private final int myEnd;
private int myCurrentSelectionIndex = 0;
private Color myCurrentBackgroundColor;
private Color myLastBackgroundColor;
private final List<RangeHighlighterEx> myCurrentHighlighters = new ArrayList<>();
private final FoldingModelEx myFoldingModel;
private final TextAttributes myFoldTextAttributes;
private FoldRegion myCurrentFold;
private final TextAttributes mySelectionAttributes;
private final TextAttributes myCaretRowAttributes;
private final Color myDefaultBackground;
private final Color myDefaultForeground;
private final int myDefaultFontType;
private final List<TextAttributes> myCachedAttributesList = new ArrayList<>(5);
private final DocumentEx myDocument;
private final EditorEx myEditor;
private final CaretData myCaretData;
private final Color myReadOnlyColor;
private final boolean myUseOnlyFullLineHighlighters;
private final boolean myReverseIteration;
private boolean myNextIsFoldRegion;
public IterationState(@NotNull EditorEx editor, int start, int end, @Nullable CaretData caretData, boolean useOnlyFullLineHighlighters,
boolean useOnlyFontOrForegroundAffectingHighlighters, boolean useFoldRegions, boolean iterateBackwards) {
ApplicationManager.getApplication().assertReadAccessAllowed();
myDocument = editor.getDocument();
assert !DocumentUtil.isInsideSurrogatePair(myDocument, start);
assert !DocumentUtil.isInsideSurrogatePair(myDocument, end);
myStartOffset = start;
myEnd = end;
myEditor = editor;
myUseOnlyFullLineHighlighters = useOnlyFullLineHighlighters;
myReverseIteration = iterateBackwards;
LOG.assertTrue(myReverseIteration ? myStartOffset >= myEnd : myStartOffset <= myEnd);
myHighlighterIterator = useOnlyFullLineHighlighters ? null : editor.getHighlighter().createIterator(start);
myCaretData = ObjectUtils.notNull(caretData, NULL_CARET_DATA);
myFoldingModel = useFoldRegions ? editor.getFoldingModel() : null;
myFoldTextAttributes = useFoldRegions ? myFoldingModel.getPlaceholderAttributes() : null;
mySelectionAttributes = editor.getSelectionModel().getTextAttributes();
myReadOnlyColor = myEditor.getColorsScheme().getColor(EditorColors.READONLY_FRAGMENT_BACKGROUND_COLOR);
CaretModel caretModel = editor.getCaretModel();
myCaretRowAttributes = editor.isRendererMode() ? null : caretModel.getTextAttributes();
myDefaultBackground = editor.getColorsScheme().getDefaultBackground();
myDefaultForeground = editor.getColorsScheme().getDefaultForeground();
TextAttributes defaultAttributes = editor.getColorsScheme().getAttributes(HighlighterColors.TEXT);
myDefaultFontType = defaultAttributes == null ? Font.PLAIN : defaultAttributes.getFontType();
MarkupModelEx editorMarkup = editor.getMarkupModel();
myView = new HighlighterSweep(editorMarkup, start, myEnd, useOnlyFullLineHighlighters, useOnlyFontOrForegroundAffectingHighlighters);
MarkupModelEx docMarkup = editor.getFilteredDocumentMarkupModel();
myDoc = new HighlighterSweep(docMarkup, start, myEnd, useOnlyFullLineHighlighters, useOnlyFontOrForegroundAffectingHighlighters);
myEndOffset = myStartOffset;
advance();
}
public static CaretData createCaretData(@NotNull EditorEx editor) {
Document document = editor.getDocument();
CaretModel caretModel = editor.getCaretModel();
SoftWrapModel softWrapModel = editor.getSoftWrapModel();
int caretRowStart = caretModel.getVisualLineStart();
int caretRowEnd = caretModel.getVisualLineEnd();
if (caretRowEnd == document.getTextLength() && document.getLineCount() > 0 &&
caretRowEnd > document.getLineStartOffset(document.getLineCount() - 1)) {
caretRowEnd++;
}
boolean caretRowStartsWithSoftWrap = softWrapModel.getSoftWrap(caretRowStart) != null;
boolean caretRowEndsWithSoftWrap = softWrapModel.getSoftWrap(caretRowEnd) != null;
List<Caret> carets = editor.getCaretModel().getAllCarets();
int caretCount = carets.size();
int[] selectionStarts = new int[caretCount];
int[] selectionEnds = new int[caretCount];
for (int i = 0; i < caretCount; i++) {
Caret caret = carets.get(i);
selectionStarts[i] = caret.getSelectionStart();
selectionEnds[i] = caret.getSelectionEnd();
}
return new CaretData(caretRowStart, caretRowEnd, caretRowStartsWithSoftWrap, caretRowEndsWithSoftWrap, selectionStarts, selectionEnds);
}
private class HighlighterSweep {
private RangeHighlighterEx myNextHighlighter;
int i;
private final RangeHighlighterEx[] highlighters;
private HighlighterSweep(@NotNull MarkupModelEx markupModel, int start, int end,
final boolean onlyFullLine, final boolean onlyFontOrForegroundAffecting) {
// we have to get all highlighters in advance and sort them by affected offsets
// since these can be different from the real offsets the highlighters are sorted by in the tree. (See LINES_IN_RANGE perverts)
final List<RangeHighlighterEx> list = new ArrayList<>();
markupModel.processRangeHighlightersOverlappingWith(myReverseIteration ? end : start, myReverseIteration ? start : end,
new CommonProcessors.CollectProcessor<RangeHighlighterEx>(list) {
@Override
protected boolean accept(RangeHighlighterEx ex) {
return (!onlyFullLine ||
ex.getTargetArea() == HighlighterTargetArea.LINES_IN_RANGE) &&
(!onlyFontOrForegroundAffecting ||
EditorUtil.attributesImpactFontStyleOrColor(ex.getTextAttributes()));
}
});
highlighters = list.isEmpty() ? RangeHighlighterEx.EMPTY_ARRAY : list.toArray(new RangeHighlighterEx[list.size()]);
Arrays.sort(highlighters, myReverseIteration ? BY_AFFECTED_END_OFFSET_REVERSED : RangeHighlighterEx.BY_AFFECTED_START_OFFSET);
while (i < highlighters.length) {
RangeHighlighterEx highlighter = highlighters[i++];
if (!skipHighlighter(highlighter)) {
myNextHighlighter = highlighter;
break;
}
}
}
private void advance() {
if (myNextHighlighter != null) {
if (myReverseIteration ?
getAlignedEndOffset(myNextHighlighter) < myStartOffset :
getAlignedStartOffset(myNextHighlighter) > myStartOffset) {
return;
}
myCurrentHighlighters.add(myNextHighlighter);
myNextHighlighter = null;
}
while (i < highlighters.length) {
RangeHighlighterEx highlighter = highlighters[i++];
if (!skipHighlighter(highlighter)) {
if (myReverseIteration ?
getAlignedEndOffset(highlighter) < myStartOffset :
getAlignedStartOffset(highlighter) > myStartOffset) {
myNextHighlighter = highlighter;
break;
}
else {
myCurrentHighlighters.add(highlighter);
}
}
}
}
private int getMinSegmentHighlighterEnd() {
if (myNextHighlighter != null) {
return myReverseIteration ? getAlignedEndOffset(myNextHighlighter) : getAlignedStartOffset(myNextHighlighter);
}
return myReverseIteration ? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
}
private boolean skipHighlighter(@NotNull RangeHighlighterEx highlighter) {
if (!highlighter.isValid() || highlighter.isAfterEndOfLine() || highlighter.getTextAttributes() == null) return true;
final FoldRegion region = myFoldingModel == null ? null :
myFoldingModel.getCollapsedRegionAtOffset(highlighter.getAffectedAreaStartOffset());
if (region != null && region == myFoldingModel.getCollapsedRegionAtOffset(highlighter.getAffectedAreaEndOffset())) return true;
return false;
}
public void advance() {
myNextIsFoldRegion = false;
myStartOffset = myEndOffset;
advanceSegmentHighlighters();
advanceCurrentSelectionIndex();
if (!myUseOnlyFullLineHighlighters) {
myCurrentFold = myFoldingModel == null ? null :
myFoldingModel.getCollapsedRegionAtOffset(myReverseIteration ? myStartOffset - 1 : myStartOffset);
}
if (myCurrentFold != null) {
myEndOffset = myReverseIteration ? myCurrentFold.getStartOffset() : myCurrentFold.getEndOffset();
}
else {
myEndOffset = getHighlighterEnd(myStartOffset);
setEndOffsetIfCloser(getSelectionEnd());
setEndOffsetIfCloser(getMinSegmentHighlightersEnd());
int foldRangesEnd = getFoldRangesEnd(myStartOffset);
setEndOffsetIfCloser(foldRangesEnd);
setEndOffsetIfCloser(getCaretEnd(myStartOffset));
setEndOffsetIfCloser(getGuardedBlockEnd(myStartOffset));
myNextIsFoldRegion = myEndOffset == foldRangesEnd && myEndOffset < myEnd;
}
assert !DocumentUtil.isInsideSurrogatePair(myDocument, myEndOffset);
reinit();
}
private void setEndOffsetIfCloser(int offset) {
if (myReverseIteration ? offset > myEndOffset : offset < myEndOffset) {
myEndOffset = offset;
}
}
private int getHighlighterEnd(int start) {
if (myHighlighterIterator == null) {
return myEnd;
}
while (!myHighlighterIterator.atEnd()) {
int end = alignOffset(myReverseIteration ? myHighlighterIterator.getStart() : myHighlighterIterator.getEnd());
if (myReverseIteration ? end < start : end > start) {
return end;
}
if (myReverseIteration) {
myHighlighterIterator.retreat();
}
else {
myHighlighterIterator.advance();
}
}
return myEnd;
}
private int getCaretEnd(int start) {
return getNearestValueAhead(start, myCaretData.caretRowStart, myCaretData.caretRowEnd);
}
private int getNearestValueAhead(int offset, int rangeStart, int rangeEnd) {
if (myReverseIteration) {
if (rangeEnd < offset) {
return rangeEnd;
}
if (rangeStart < offset) {
return rangeStart;
}
}
else {
if (rangeStart > offset) {
return rangeStart;
}
if (rangeEnd > offset) {
return rangeEnd;
}
}
return myEnd;
}
private int getGuardedBlockEnd(int start) {
if (myUseOnlyFullLineHighlighters) {
return myEnd;
}
List<RangeMarker> blocks = myDocument.getGuardedBlocks();
int result = myEnd;
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < blocks.size(); i++) {
RangeMarker block = blocks.get(i);
int nearestValue = getNearestValueAhead(start, alignOffset(block.getStartOffset()), alignOffset(block.getEndOffset()));
result = myReverseIteration ? Math.max(result, nearestValue) : Math.min(result, nearestValue);
}
return result;
}
private void advanceCurrentSelectionIndex() {
while (myCurrentSelectionIndex < myCaretData.selectionsSize() && (myReverseIteration ?
myStartOffset <= myCaretData.selectionStart(myCurrentSelectionIndex, true) :
myStartOffset >= myCaretData.selectionEnd(myCurrentSelectionIndex, false))) {
myCurrentSelectionIndex++;
}
}
private int getSelectionEnd() {
if (myCurrentSelectionIndex >= myCaretData.selectionsSize()) {
return myEnd;
}
return getNearestValueAhead(myStartOffset,
myCaretData.selectionStart(myCurrentSelectionIndex, myReverseIteration),
myCaretData.selectionEnd(myCurrentSelectionIndex, myReverseIteration));
}
private boolean isInSelection(boolean atBreak) {
return myCurrentSelectionIndex < myCaretData.selectionsSize() &&
(myReverseIteration ? lessThan(myStartOffset, myCaretData.selectionEnd(myCurrentSelectionIndex, true), !atBreak)
: lessThan(myCaretData.selectionStart(myCurrentSelectionIndex, false), myStartOffset, !atBreak));
}
private static boolean lessThan(int x, int y, boolean orEquals) {
return x < y || orEquals && x == y;
}
private void advanceSegmentHighlighters() {
myDoc.advance();
myView.advance();
boolean fileEnd = myStartOffset == myDocument.getTextLength();
for (int i = myCurrentHighlighters.size() - 1; i >= 0; i--) {
RangeHighlighterEx highlighter = myCurrentHighlighters.get(i);
if (myReverseIteration ?
getAlignedStartOffset(highlighter) >= myStartOffset :
fileEnd && highlighter.getTargetArea() == HighlighterTargetArea.LINES_IN_RANGE ?
getAlignedEndOffset(highlighter) < myStartOffset :
getAlignedEndOffset(highlighter) <= myStartOffset) {
myCurrentHighlighters.remove(i);
}
}
}
private int getFoldRangesEnd(int startOffset) {
if (myUseOnlyFullLineHighlighters || myFoldingModel == null) {
return myEnd;
}
int end = myEnd;
FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
if (topLevelCollapsed != null) {
if (myReverseIteration) {
for (int i = myFoldingModel.getLastCollapsedRegionBefore(startOffset);
i >= 0 && i < topLevelCollapsed.length;
i--) {
FoldRegion range = topLevelCollapsed[i];
if (!range.isValid()) continue;
int rangeEnd = range.getEndOffset();
if (rangeEnd < startOffset) {
if (rangeEnd > end) {
end = rangeEnd;
}
else {
break;
}
}
}
}
else {
for (int i = myFoldingModel.getLastCollapsedRegionBefore(startOffset) + 1;
i >= 0 && i < topLevelCollapsed.length;
i++) {
FoldRegion range = topLevelCollapsed[i];
if (!range.isValid()) continue;
int rangeEnd = range.getStartOffset();
if (rangeEnd > startOffset) {
if (rangeEnd < end) {
end = rangeEnd;
}
else {
break;
}
}
}
}
}
return end;
}
private int getMinSegmentHighlightersEnd() {
int end = myEnd;
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < myCurrentHighlighters.size(); i++) {
RangeHighlighterEx highlighter = myCurrentHighlighters.get(i);
if (myReverseIteration) {
end = Math.max(end, getAlignedStartOffset(highlighter));
}
else {
end = Math.min(end, getAlignedEndOffset(highlighter));
}
}
end = myReverseIteration ? Math.max(end, myDoc.getMinSegmentHighlighterEnd()) : Math.min(end, myDoc.getMinSegmentHighlighterEnd());
end = myReverseIteration ? Math.max(end, myView.getMinSegmentHighlighterEnd()) : Math.min(end, myView.getMinSegmentHighlighterEnd());
return end;
}
private void reinit() {
setAttributes(myMergedAttributes, false);
myLastBackgroundColor = myCurrentBackgroundColor;
myCurrentBackgroundColor = myMergedAttributes.getBackgroundColor();
}
public TextAttributes getBreakAttributes() {
TextAttributes attributes = new TextAttributes();
setAttributes(attributes, true);
return attributes;
}
private void setAttributes(TextAttributes attributes, boolean atBreak) {
boolean isInSelection = isInSelection(atBreak);
boolean isInCaretRow = isInCaretRow(!myReverseIteration, myReverseIteration);
boolean isInGuardedBlock = false;
if (!myUseOnlyFullLineHighlighters) {
RangeMarker guard = myDocument.getOffsetGuard(myReverseIteration ? myStartOffset - 1 : myStartOffset);
isInGuardedBlock = guard != null && (!atBreak || myReverseIteration ? guard.getEndOffset() > myStartOffset
: guard.getStartOffset() < myStartOffset);
}
TextAttributes syntax = myHighlighterIterator == null || myHighlighterIterator.atEnd() ? null
: (atBreak &&
myStartOffset == (myReverseIteration ? myHighlighterIterator.getEnd() : myHighlighterIterator.getStart()))
? null
: myHighlighterIterator.getTextAttributes();
TextAttributes selection = isInSelection ? mySelectionAttributes : null;
TextAttributes caret = isInCaretRow ? myCaretRowAttributes : null;
TextAttributes fold = myCurrentFold != null ? myFoldTextAttributes : null;
TextAttributes guard = isInGuardedBlock
? new TextAttributes(null, myReadOnlyColor, null, EffectType.BOXED, Font.PLAIN)
: null;
final int size = myCurrentHighlighters.size();
if (size > 1) {
ContainerUtil.quickSort(myCurrentHighlighters, BY_LAYER_THEN_ATTRIBUTES);
}
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < size; i++) {
RangeHighlighterEx highlighter = myCurrentHighlighters.get(i);
if (highlighter.getTextAttributes() == TextAttributes.ERASE_MARKER) {
syntax = null;
}
}
List<TextAttributes> cachedAttributes = myCachedAttributesList;
cachedAttributes.clear();
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < size; i++) {
RangeHighlighterEx highlighter = myCurrentHighlighters.get(i);
if (atBreak && highlighter.getTargetArea() == HighlighterTargetArea.EXACT_RANGE &&
myStartOffset == (myReverseIteration ? highlighter.getEndOffset() : highlighter.getStartOffset())) continue;
if (highlighter.getLayer() < HighlighterLayer.SELECTION) {
if (selection != null) {
cachedAttributes.add(selection);
selection = null;
}
}
if (fold != null && highlighter.getLayer() < HighlighterLayer.GUARDED_BLOCKS) {
cachedAttributes.add(fold);
fold = null;
}
if (guard != null && highlighter.getLayer() < HighlighterLayer.GUARDED_BLOCKS) {
cachedAttributes.add(guard);
guard = null;
}
if (caret != null && highlighter.getLayer() < HighlighterLayer.CARET_ROW) {
cachedAttributes.add(caret);
caret = null;
}
if (syntax != null && highlighter.getLayer() < HighlighterLayer.SYNTAX) {
cachedAttributes.add(syntax);
syntax = null;
}
TextAttributes textAttributes = highlighter.getTextAttributes();
if (textAttributes != null && textAttributes != TextAttributes.ERASE_MARKER) {
cachedAttributes.add(textAttributes);
}
}
if (selection != null) cachedAttributes.add(selection);
if (fold != null) cachedAttributes.add(fold);
if (guard != null) cachedAttributes.add(guard);
if (caret != null) cachedAttributes.add(caret);
if (syntax != null) cachedAttributes.add(syntax);
Color fore = null;
Color back = isInGuardedBlock ? myReadOnlyColor : null;
Color effect = null;
EffectType effectType = null;
int fontType = 0;
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < cachedAttributes.size(); i++) {
TextAttributes attrs = cachedAttributes.get(i);
if (fore == null) {
fore = attrs.getForegroundColor();
}
if (back == null) {
back = attrs.getBackgroundColor();
}
if (fontType == Font.PLAIN) {
fontType = attrs.getFontType();
}
if (effect == null) {
effect = attrs.getEffectColor();
effectType = attrs.getEffectType();
}
}
if (fore == null) fore = myDefaultForeground;
if (back == null) back = myDefaultBackground;
if (effectType == null) effectType = EffectType.BOXED;
if (fontType == Font.PLAIN) fontType = myDefaultFontType;
attributes.setAttributes(fore, back, effect, null, effectType, fontType);
}
private boolean isInCaretRow(boolean includeLineStart, boolean includeLineEnd) {
return myStartOffset > myCaretData.caretRowStart && myStartOffset < myCaretData.caretRowEnd ||
includeLineStart && myStartOffset == myCaretData.caretRowStart || includeLineEnd && myStartOffset == myCaretData.caretRowEnd;
}
public boolean atEnd() {
return myReverseIteration ? myStartOffset <= myEnd : myStartOffset >= myEnd;
}
public int getStartOffset() {
return myStartOffset;
}
public int getEndOffset() {
return myEndOffset;
}
@NotNull
public TextAttributes getMergedAttributes() {
return myMergedAttributes;
}
public FoldRegion getCurrentFold() {
return myCurrentFold;
}
public boolean nextIsFoldRegion() {
return myNextIsFoldRegion;
}
@NotNull
public TextAttributes getPastLineEndBackgroundAttributes() {
myMergedAttributes.setBackgroundColor(myEditor.getSoftWrapModel().getSoftWrap(myStartOffset) != null ? getBreakBackgroundColor(true) :
myCurrentBackgroundColor);
return myMergedAttributes;
}
@NotNull
public TextAttributes getBeforeLineStartBackgroundAttributes() {
return new TextAttributes(null, getBreakBackgroundColor(false), null, null, Font.PLAIN);
}
private Color getBreakBackgroundColor(boolean lineEnd) {
return Comparing.equal(myCurrentBackgroundColor, myLastBackgroundColor) ? myCurrentBackgroundColor :
isInCaretRow(!myCaretData.caretRowStartsWithSoftWrap || !lineEnd, myCaretData.caretRowEndsWithSoftWrap && lineEnd) ?
myCaretRowAttributes.getBackgroundColor() : myDefaultBackground;
}
private int alignOffset(int offset) {
return DocumentUtil.alignToCodePointBoundary(myDocument, offset);
}
private int getAlignedStartOffset(RangeHighlighterEx highlighter) {
return alignOffset(highlighter.getAffectedAreaStartOffset());
}
private int getAlignedEndOffset(RangeHighlighterEx highlighter) {
return alignOffset(highlighter.getAffectedAreaEndOffset());
}
private static class LayerComparator implements Comparator<RangeHighlighterEx> {
private static final LayerComparator INSTANCE = new LayerComparator();
@Override
public int compare(RangeHighlighterEx o1, RangeHighlighterEx o2) {
int layerDiff = o2.getLayer() - o1.getLayer();
if (layerDiff != 0) {
return layerDiff;
}
// prefer more specific region
int o1Length = o1.getAffectedAreaEndOffset() - o1.getAffectedAreaStartOffset();
int o2Length = o2.getAffectedAreaEndOffset() - o2.getAffectedAreaStartOffset();
return o1Length - o2Length;
}
}
public static class CaretData {
private final int caretRowStart;
private final int caretRowEnd;
private final boolean caretRowStartsWithSoftWrap;
private final boolean caretRowEndsWithSoftWrap;
private final int[] selectionStarts;
private final int[] selectionEnds;
private CaretData(int caretRowStart,
int caretRowEnd,
boolean caretRowStartsWithSoftWrap,
boolean caretRowEndsWithSoftWrap,
int[] selectionStarts,
int[] selectionEnds) {
this.caretRowStart = caretRowStart;
this.caretRowEnd = caretRowEnd;
this.caretRowStartsWithSoftWrap = caretRowStartsWithSoftWrap;
this.caretRowEndsWithSoftWrap = caretRowEndsWithSoftWrap;
this.selectionStarts = selectionStarts;
this.selectionEnds = selectionEnds;
}
private int selectionsSize() {
return selectionStarts.length;
}
private int selectionStart(int index, boolean reverse) {
return selectionStarts[reverse ? selectionStarts.length - 1 - index : index];
}
private int selectionEnd(int index, boolean reverse) {
return selectionEnds[reverse ? selectionStarts.length - 1 - index : index];
}
}
}