Skip to content

Commit

Permalink
Extract IndentGuideRenderer into a separate class
Browse files Browse the repository at this point in the history
GitOrigin-RevId: 63f936407d95ed340b978fb7d4c6de4fbb8d94ab
  • Loading branch information
kirillgla authored and intellij-monorepo-bot committed Nov 30, 2020
1 parent 8b92245 commit efe9537
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 123 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package com.intellij.codeInsight.daemon.impl;

import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.impl.view.EditorPainter;
import com.intellij.openapi.editor.impl.view.VisualLinesIterator;
import com.intellij.openapi.editor.markup.CustomHighlighterRenderer;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.ui.paint.LinePainter2D;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;

import java.awt.*;
import java.util.List;

public class IndentGuideRenderer implements CustomHighlighterRenderer {
@Override
public final void paint(@NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) {
int startOffset = highlighter.getStartOffset();
final Document doc = highlighter.getDocument();
if (startOffset >= doc.getTextLength()) return;

final int endOffset = highlighter.getEndOffset();

int off;
int startLine = doc.getLineNumber(startOffset);

final CharSequence chars = doc.getCharsSequence();
do {
int start = doc.getLineStartOffset(startLine);
int end = doc.getLineEndOffset(startLine);
off = CharArrayUtil.shiftForward(chars, start, end, " \t");
startLine--;
}
while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n');

final VisualPosition startPosition = editor.offsetToVisualPosition(off);
int indentColumn = startPosition.column;
if (indentColumn <= 0) return;

final FoldingModel foldingModel = editor.getFoldingModel();
if (foldingModel.isOffsetCollapsed(off)) return;

final FoldRegion headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off)));
final FoldRegion tailRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineStartOffset(doc.getLineNumber(endOffset)));

if (tailRegion != null && tailRegion == headerRegion) return;

final boolean selected = isSelected(editor, endOffset, off, indentColumn);

int lineHeight = editor.getLineHeight();
Point start = editor.visualPositionToXY(startPosition);
start.y += lineHeight;
final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset);
Point end = editor.visualPositionToXY(endPosition);
int maxY = end.y;
if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) {
maxY += lineHeight;
}

Rectangle clip = g.getClipBounds();
if (clip != null) {
if (clip.y >= maxY || clip.y + clip.height <= start.y) {
return;
}
maxY = Math.min(maxY, clip.y + clip.height);
}

if (start.y >= maxY) return;

int targetX = Math.max(0, start.x + EditorPainter.getIndentGuideShift(editor));
final EditorColorsScheme scheme = editor.getColorsScheme();
g.setColor(scheme.getColor(selected ? EditorColors.SELECTED_INDENT_GUIDE_COLOR : EditorColors.INDENT_GUIDE_COLOR));
// There is a possible case that indent line intersects soft wrap-introduced text. Example:
// this is a long line <soft-wrap>
// that| is soft-wrapped
// |
// | <- vertical indent
//
// Also it's possible that no additional intersections are added because of soft wrap:
// this is a long line <soft-wrap>
// | that is soft-wrapped
// |
// | <- vertical indent
// We want to use the following approach then:
// 1. Show only active indent if it crosses soft wrap-introduced text;
// 2. Show indent as is if it doesn't intersect with soft wrap-introduced text;
List<? extends SoftWrap> softWraps = ((EditorEx)editor).getSoftWrapModel().getRegisteredSoftWraps();
if (selected || softWraps.isEmpty()) {
LinePainter2D.paint((Graphics2D)g, targetX, start.y, targetX, maxY - 1);
}
else {
int startY = start.y;
int startVisualLine = startPosition.line + 1;
if (clip != null && startY < clip.y) {
startY = clip.y;
startVisualLine = editor.yToVisualLine(clip.y);
}
VisualLinesIterator it = new VisualLinesIterator((EditorImpl)editor, startVisualLine);
while (!it.atEnd()) {
int currY = it.getY();
if (currY >= startY) {
if (currY >= maxY) break;
if (it.startsWithSoftWrap()) {
SoftWrap softWrap = softWraps.get(it.getStartOrPrevWrapIndex());
if (softWrap.getIndentInColumns() < indentColumn) {
if (startY < currY) {
LinePainter2D.paint((Graphics2D)g, targetX, startY, targetX, currY - 1);
}
startY = currY + lineHeight;
}
}
}
it.advance();
}
if (startY < maxY) {
LinePainter2D.paint((Graphics2D)g, targetX, startY, targetX, maxY - 1);
}
}

}

protected boolean isSelected(@NotNull Editor editor, int endOffset, int off, int indentColumn) {
final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide();
if (guide == null) return false;
return isCaretOnGuide(editor, endOffset, off, indentColumn);
}

protected final boolean isCaretOnGuide(@NotNull Editor editor, int endOffset, int off, int indentColumn) {
final CaretModel caretModel = editor.getCaretModel();
final int caretOffset = caretModel.getOffset();
return caretOffset >= off && caretOffset < endOffset && caretModel.getLogicalPosition().column == indentColumn;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@
import com.intellij.lang.Language;
import com.intellij.lang.LanguageParserDefinitions;
import com.intellij.lang.ParserDefinition;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.IndentGuideDescriptor;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.impl.view.EditorPainter;
import com.intellij.openapi.editor.impl.view.VisualLinesIterator;
import com.intellij.openapi.editor.markup.CustomHighlighterRenderer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.MarkupModel;
Expand All @@ -35,15 +31,12 @@
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.paint.LinePainter2D;
import com.intellij.util.DocumentUtil;
import com.intellij.util.containers.IntStack;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;

import java.awt.*;
import java.util.List;
import java.util.*;

public class IndentsPass extends TextEditorHighlightingPass implements DumbAware {
Expand All @@ -56,120 +49,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
private volatile List<TextRange> myRanges = Collections.emptyList();
private volatile List<IndentGuideDescriptor> myDescriptors = Collections.emptyList();

public static final CustomHighlighterRenderer RENDERER = (editor, highlighter, g) -> {
int startOffset = highlighter.getStartOffset();
final Document doc = highlighter.getDocument();
if (startOffset >= doc.getTextLength()) return;

final int endOffset = highlighter.getEndOffset();

int off;
int startLine = doc.getLineNumber(startOffset);

final CharSequence chars = doc.getCharsSequence();
do {
int start = doc.getLineStartOffset(startLine);
int end = doc.getLineEndOffset(startLine);
off = CharArrayUtil.shiftForward(chars, start, end, " \t");
startLine--;
}
while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n');

final VisualPosition startPosition = editor.offsetToVisualPosition(off);
int indentColumn = startPosition.column;
if (indentColumn <= 0) return;

final FoldingModel foldingModel = editor.getFoldingModel();
if (foldingModel.isOffsetCollapsed(off)) return;

final FoldRegion headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off)));
final FoldRegion tailRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineStartOffset(doc.getLineNumber(endOffset)));

if (tailRegion != null && tailRegion == headerRegion) return;

final boolean selected;
final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide();
if (guide != null) {
final CaretModel caretModel = editor.getCaretModel();
final int caretOffset = caretModel.getOffset();
selected =
caretOffset >= off && caretOffset < endOffset && caretModel.getLogicalPosition().column == indentColumn;
}
else {
selected = false;
}

int lineHeight = editor.getLineHeight();
Point start = editor.visualPositionToXY(startPosition);
start.y += lineHeight;
final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset);
Point end = editor.visualPositionToXY(endPosition);
int maxY = end.y;
if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) {
maxY += lineHeight;
}

Rectangle clip = g.getClipBounds();
if (clip != null) {
if (clip.y >= maxY || clip.y + clip.height <= start.y) {
return;
}
maxY = Math.min(maxY, clip.y + clip.height);
}

if (start.y >= maxY) return;

int targetX = Math.max(0, start.x + EditorPainter.getIndentGuideShift(editor));
final EditorColorsScheme scheme = editor.getColorsScheme();
g.setColor(scheme.getColor(selected ? EditorColors.SELECTED_INDENT_GUIDE_COLOR : EditorColors.INDENT_GUIDE_COLOR));

// There is a possible case that indent line intersects soft wrap-introduced text. Example:
// this is a long line <soft-wrap>
// that| is soft-wrapped
// |
// | <- vertical indent
//
// Also it's possible that no additional intersections are added because of soft wrap:
// this is a long line <soft-wrap>
// | that is soft-wrapped
// |
// | <- vertical indent
// We want to use the following approach then:
// 1. Show only active indent if it crosses soft wrap-introduced text;
// 2. Show indent as is if it doesn't intersect with soft wrap-introduced text;
List<? extends SoftWrap> softWraps = ((EditorEx)editor).getSoftWrapModel().getRegisteredSoftWraps();
if (selected || softWraps.isEmpty()) {
LinePainter2D.paint((Graphics2D)g, targetX, start.y, targetX, maxY - 1);
}
else {
int startY = start.y;
int startVisualLine = startPosition.line + 1;
if (clip != null && startY < clip.y) {
startY = clip.y;
startVisualLine = editor.yToVisualLine(clip.y);
}
VisualLinesIterator it = new VisualLinesIterator((EditorImpl)editor, startVisualLine);
while (!it.atEnd()) {
int currY = it.getY();
if (currY >= startY) {
if (currY >= maxY) break;
if (it.startsWithSoftWrap()) {
SoftWrap softWrap = softWraps.get(it.getStartOrPrevWrapIndex());
if (softWrap.getIndentInColumns() < indentColumn) {
if (startY < currY) {
LinePainter2D.paint((Graphics2D)g, targetX, startY, targetX, currY - 1);
}
startY = currY + lineHeight;
}
}
}
it.advance();
}
if (startY < maxY) {
LinePainter2D.paint((Graphics2D)g, targetX, startY, targetX, maxY - 1);
}
}
};
private static final CustomHighlighterRenderer RENDERER = new IndentGuideRenderer();

public IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
super(project, editor.getDocument(), false);
Expand Down

0 comments on commit efe9537

Please sign in to comment.