private void paintBackground(
      Graphics2D g, Rectangle clip, int startVisualLine, int endVisualLine) {
    int lineCount = myEditor.getVisibleLineCount();
    final Map<Integer, Couple<Integer>> virtualSelectionMap =
        createVirtualSelectionMap(startVisualLine, endVisualLine);
    for (int visualLine = startVisualLine; visualLine <= endVisualLine; visualLine++) {
      int y = myView.visualLineToY(visualLine);
      LineLayout prefixLayout = myView.getPrefixLayout();
      if (visualLine == 0 && prefixLayout != null) {
        paintBackground(g, myView.getPrefixAttributes(), 0, y, prefixLayout.getWidth());
      }
      if (visualLine >= lineCount) break;
      paintLineFragments(
          g,
          clip,
          visualLine,
          y,
          new LineFragmentPainter() {
            @Override
            public void paintBeforeLineStart(
                Graphics2D g, TextAttributes attributes, int columnEnd, float xEnd, int y) {
              paintBackground(g, attributes, 0, y, xEnd);
              paintSelectionOnSecondSoftWrapLineIfNecessary(g, columnEnd, xEnd, y);
            }

            @Override
            public void paint(
                Graphics2D g,
                VisualLineFragmentsIterator.Fragment fragment,
                int start,
                int end,
                TextAttributes attributes,
                float xStart,
                float xEnd,
                int y) {
              paintBackground(g, attributes, xStart, y, xEnd - xStart);
            }

            @Override
            public void paintAfterLineEnd(
                Graphics2D g, Rectangle clip, IterationState it, int columnStart, float x, int y) {
              paintBackground(
                  g, it.getPastLineEndBackgroundAttributes(), x, y, clip.x + clip.width - x);
              int offset = it.getEndOffset();
              SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset);
              if (softWrap == null) {
                paintVirtualSelectionIfNecessary(
                    g, virtualSelectionMap, columnStart, x, clip.x + clip.width, y);
              } else {
                paintSelectionOnFirstSoftWrapLineIfNecessary(
                    g, columnStart, x, clip.x + clip.width, y);
              }
            }
          });
    }
  }
  private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
    Color separatorColor = marker.getLineSeparatorColor();
    LineSeparatorRenderer lineSeparatorRenderer = marker.getLineSeparatorRenderer();
    if (separatorColor == null && lineSeparatorRenderer == null) {
      return;
    }
    int line =
        myDocument.getLineNumber(
            marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP
                ? marker.getStartOffset()
                : marker.getEndOffset());
    int visualLine =
        myView.logicalToVisualPosition(
                new LogicalPosition(
                    line + (marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? 0 : 1),
                    0),
                false)
            .line;
    int y = myView.visualLineToY(visualLine) - 1;
    int endShift = clip.x + clip.width;
    EditorSettings settings = myEditor.getSettings();
    if (settings.isRightMarginShown()
        && myEditor.getColorsScheme().getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
      endShift =
          Math.min(
              endShift,
              settings.getRightMargin(myEditor.getProject()) * myView.getPlainSpaceWidth());
    }

    g.setColor(separatorColor);
    if (lineSeparatorRenderer != null) {
      lineSeparatorRenderer.drawLine(g, 0, endShift, y);
    } else {
      UIUtil.drawLine(g, 0, y, endShift, y);
    }
  }
  private void paintBorderEffect(
      Graphics2D g,
      ClipDetector clipDetector,
      int startOffset,
      int endOffset,
      TextAttributes attributes) {
    if (!clipDetector.rangeCanBeVisible(startOffset, endOffset)) return;
    int startLine = myDocument.getLineNumber(startOffset);
    int endLine = myDocument.getLineNumber(endOffset);
    if (startLine + 1 == endLine
        && startOffset == myDocument.getLineStartOffset(startLine)
        && endOffset == myDocument.getLineStartOffset(endLine)) {
      // special case of line highlighters
      endLine--;
      endOffset = myDocument.getLineEndOffset(endLine);
    }

    boolean rounded = attributes.getEffectType() == EffectType.ROUNDED_BOX;
    int lineHeight = myView.getLineHeight() - 1;
    g.setColor(attributes.getEffectColor());
    VisualPosition startPosition = myView.offsetToVisualPosition(startOffset, true, false);
    VisualPosition endPosition = myView.offsetToVisualPosition(endOffset, false, true);
    if (startPosition.line == endPosition.line) {
      int y = myView.visualLineToY(startPosition.line);
      TFloatArrayList ranges = adjustedLogicalRangeToVisualRanges(startOffset, endOffset);
      for (int i = 0; i < ranges.size() - 1; i += 2) {
        int startX = (int) ranges.get(i);
        int endX = (int) ranges.get(i + 1);
        if (rounded) {
          UIUtil.drawRectPickedOut(g, startX, y, endX - startX, lineHeight);
        } else {
          g.drawRect(startX, y, endX - startX, lineHeight);
        }
      }
    } else {
      int maxWidth = myView.getMaxWidthInLineRange(startPosition.line, endPosition.line) - 1;
      TFloatArrayList leadingRanges =
          adjustedLogicalRangeToVisualRanges(
              startOffset,
              myView.visualPositionToOffset(
                  new VisualPosition(startPosition.line, Integer.MAX_VALUE, true)));
      TFloatArrayList trailingRanges =
          adjustedLogicalRangeToVisualRanges(
              myView.visualPositionToOffset(new VisualPosition(endPosition.line, 0)), endOffset);
      if (!leadingRanges.isEmpty() && !trailingRanges.isEmpty()) {
        boolean containsInnerLines = endPosition.line > startPosition.line + 1;
        int leadingTopY = myView.visualLineToY(startPosition.line);
        int leadingBottomY = leadingTopY + lineHeight;
        int trailingTopY = myView.visualLineToY(endPosition.line);
        int trailingBottomY = trailingTopY + lineHeight;
        float start = 0;
        float end = 0;
        float leftGap = leadingRanges.get(0) - (containsInnerLines ? 0 : trailingRanges.get(0));
        int adjustY =
            leftGap == 0 ? 2 : leftGap > 0 ? 1 : 0; // avoiding 1-pixel gap between aligned lines
        for (int i = 0; i < leadingRanges.size() - 1; i += 2) {
          start = leadingRanges.get(i);
          end = leadingRanges.get(i + 1);
          if (i > 0) {
            drawLine(g, leadingRanges.get(i - 1), leadingBottomY, start, leadingBottomY, rounded);
          }
          drawLine(g, start, leadingBottomY + (i == 0 ? adjustY : 0), start, leadingTopY, rounded);
          if ((i + 2) < leadingRanges.size()) {
            drawLine(g, start, leadingTopY, end, leadingTopY, rounded);
            drawLine(g, end, leadingTopY, end, leadingBottomY, rounded);
          }
        }
        end = Math.max(end, maxWidth);
        drawLine(g, start, leadingTopY, end, leadingTopY, rounded);
        drawLine(g, end, leadingTopY, end, trailingTopY - 1, rounded);
        float targetX = trailingRanges.get(trailingRanges.size() - 1);
        drawLine(g, end, trailingTopY - 1, targetX, trailingTopY - 1, rounded);
        adjustY =
            end == targetX
                ? -2
                : -1; // for lastX == targetX we need to avoid a gap when rounding is used
        for (int i = trailingRanges.size() - 2; i >= 0; i -= 2) {
          start = trailingRanges.get(i);
          end = trailingRanges.get(i + 1);

          drawLine(g, end, trailingTopY + (i == 0 ? adjustY : 0), end, trailingBottomY, rounded);
          drawLine(g, end, trailingBottomY, start, trailingBottomY, rounded);
          drawLine(g, start, trailingBottomY, start, trailingTopY, rounded);
          if (i > 0) {
            drawLine(g, start, trailingTopY, trailingRanges.get(i - 1), trailingTopY, rounded);
          }
        }
        float lastX = start;
        if (containsInnerLines) {
          if (start > 0) {
            drawLine(g, start, trailingTopY, start, trailingTopY - 1, rounded);
            drawLine(g, start, trailingTopY - 1, 0, trailingTopY - 1, rounded);
            drawLine(g, 0, trailingTopY - 1, 0, leadingBottomY + 1, rounded);
          } else {
            drawLine(g, start, trailingTopY, 0, leadingBottomY + 1, rounded);
          }
          lastX = 0;
        }
        targetX = leadingRanges.get(0);
        if (lastX < targetX) {
          drawLine(g, lastX, leadingBottomY + 1, targetX, leadingBottomY + 1, rounded);
        } else {
          drawLine(g, lastX, leadingBottomY + 1, lastX, leadingBottomY, rounded);
          drawLine(g, lastX, leadingBottomY, targetX, leadingBottomY, rounded);
        }
      }
    }
  }
  private void paintTextWithEffects(
      Graphics2D g, Rectangle clip, int startVisualLine, int endVisualLine) {
    final CharSequence text = myDocument.getImmutableCharSequence();
    final EditorImpl.LineWhitespacePaintingStrategy whitespacePaintingStrategy =
        myEditor.new LineWhitespacePaintingStrategy();
    int lineCount = myEditor.getVisibleLineCount();
    for (int visualLine = startVisualLine; visualLine <= endVisualLine; visualLine++) {
      int y = myView.visualLineToY(visualLine) + myView.getAscent();
      LineLayout prefixLayout = myView.getPrefixLayout();
      if (visualLine == 0 && prefixLayout != null) {
        g.setColor(myView.getPrefixAttributes().getForegroundColor());
        paintLineLayoutWithEffect(
            g,
            prefixLayout,
            0,
            y,
            myView.getPrefixAttributes().getEffectColor(),
            myView.getPrefixAttributes().getEffectType());
      }
      if (visualLine >= lineCount) break;

      final int[] currentLogicalLine = new int[] {-1};

      paintLineFragments(
          g,
          clip,
          visualLine,
          y,
          new LineFragmentPainter() {
            @Override
            public void paintBeforeLineStart(
                Graphics2D g, TextAttributes attributes, int columnEnd, float xEnd, int y) {
              SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel();
              int symbolWidth =
                  softWrapModel.getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
              softWrapModel.paint(
                  g,
                  SoftWrapDrawingType.AFTER_SOFT_WRAP,
                  (int) xEnd - symbolWidth,
                  y - myView.getAscent(),
                  myView.getLineHeight());
            }

            @Override
            public void paint(
                Graphics2D g,
                VisualLineFragmentsIterator.Fragment fragment,
                int start,
                int end,
                TextAttributes attributes,
                float xStart,
                float xEnd,
                int y) {
              if (attributes != null && attributes.getForegroundColor() != null) {
                g.setColor(attributes.getForegroundColor());
                fragment.draw(g, xStart, y, start, end);
              }
              if (fragment.getCurrentFoldRegion() == null) {
                int logicalLine = fragment.getStartLogicalLine();
                if (logicalLine != currentLogicalLine[0]) {
                  whitespacePaintingStrategy.update(
                      text,
                      myDocument.getLineStartOffset(logicalLine),
                      myDocument.getLineEndOffset(logicalLine));
                  currentLogicalLine[0] = logicalLine;
                }
                paintWhitespace(
                    g, text, xStart, y, start, end, whitespacePaintingStrategy, fragment);
              }
              if (attributes != null
                  && hasTextEffect(attributes.getEffectColor(), attributes.getEffectType())) {
                paintTextEffect(
                    g, xStart, xEnd, y, attributes.getEffectColor(), attributes.getEffectType());
              }
            }

            @Override
            public void paintAfterLineEnd(
                Graphics2D g,
                Rectangle clip,
                IterationState iterationState,
                int columnStart,
                float x,
                int y) {
              int offset = iterationState.getEndOffset();
              SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel();
              if (softWrapModel.getSoftWrap(offset) == null) {
                int logicalLine = myDocument.getLineNumber(offset);
                paintLineExtensions(g, logicalLine, x, y);
              } else {
                softWrapModel.paint(
                    g,
                    SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED,
                    (int) x,
                    y - myView.getAscent(),
                    myView.getLineHeight());
              }
            }
          });
    }
  }