public void paint()

in flutter-idea/src/io/flutter/editor/WidgetIndentsHighlightingPass.java [182:437]


    public void paint(@NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) {
      if (!highlighter.isValid()) {
        return;
      }
      if (!descriptor.widget.isValid()) {
        return;
      }
      final FlutterSettings settings = FlutterSettings.getInstance();

      final Graphics2D g2d = (Graphics2D)g.create();
      // Required to render colors with an alpha channel. Rendering with an
      // alpha chanel makes it easier to keep relationships between shadows
      // and lines looking consistent when the background color changes such
      // as in the case of selection or a different highlighter turning the
      // background yellow.
      g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1));

      final int startOffset = highlighter.getStartOffset();
      final Document doc = highlighter.getDocument();
      final int textLength = doc.getTextLength();
      if (startOffset >= textLength) return;

      final int endOffset = min(highlighter.getEndOffset(), textLength);

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

      final CharSequence chars = doc.getCharsSequence();
      do {
        final int start = doc.getLineStartOffset(startLine);
        final 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;

      // It's considered that indent guide can cross not only white space but comments, javadoc etc. Hence, there is a possible
      // case that the first indent guide line is, say, single-line comment where comment symbols ('//') are located at the first
      // visual column. We need to calculate correct indent guide column then.
      int lineShift = 1;
      if (indentColumn <= 0) {
        indentColumn = descriptor.indentLevel;
        lineShift = 0;
      }
      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 CaretModel caretModel = editor.getCaretModel();
      final int caretOffset = caretModel.getOffset();
      //      updateSelected(editor, highlighter, caretOffset);
      final boolean selected = isSelected;

      final Point start = editor.visualPositionToXY(new VisualPosition(startPosition.line + lineShift, indentColumn));

      final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset);
      final ArrayList<OutlineLocation> childLines = descriptor.childLines;
      final Point end = editor.visualPositionToXY(endPosition);
      double splitY = -1;
      int maxY = end.y;
      boolean includeLastLine = false;
      if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) {
        includeLastLine = true;
      }

      int endLine = doc.getLineNumber(endOffset);
      if (childLines != null && childLines.size() > 0) {
        final VisualPosition endPositionLastChild = editor.offsetToVisualPosition(childLines.get(childLines.size() - 1).getGuideOffset());
        if (endPositionLastChild.line == endPosition.line) {
          // The last child is on the same line as the end of the block.
          // This happens if code wasn't formatted with flutter style, for example:
          //  Center(
          //    child: child);

          includeLastLine = true;
          // TODO(jacobr): make sure we don't run off the edge of the document.
          if ((endLine + 1) < document.getLineCount()) {
            endLine++;
          }
        }
      }
      // By default we stop at the start of the last line instead of the end of the last line in the range.
      if (includeLastLine) {
        maxY += editor.getLineHeight();
      }

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

      final EditorColorsScheme scheme = editor.getColorsScheme();
      final JBColor lineColor = selected ? JBColor.BLUE : OUTLINE_LINE_COLOR;
      g2d.setColor(lineColor);
      final Color pastBlockColor = selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : OUTLINE_LINE_COLOR_PAST_BLOCK;

      // TODO(jacobr): this logic for softwraps is duplicated for the FilteredIndentsHighlightingPass
      // and may be more conservative than sensible for WidgetIndents.

      // 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;

      int y = start.y;
      int newY = start.y;
      final int maxYWithChildren = y;
      final SoftWrapModel softWrapModel = editor.getSoftWrapModel();
      final int lineHeight = editor.getLineHeight();
      int iChildLine = 0;
      for (int i = max(0, startLine + lineShift); i < endLine && newY < maxY; i++) {
        OutlineLocation childLine = null;
        if (childLines != null) {
          while (iChildLine < childLines.size()) {
            final OutlineLocation currentChildLine = childLines.get(iChildLine);
            if (currentChildLine.isValid()) {
              if (currentChildLine.getLine() > i) {
                // We haven't reached child line yet.
                break;
              }
              if (currentChildLine.getLine() == i) {
                childLine = currentChildLine;
                iChildLine++;
                if (iChildLine >= childLines.size()) {
                  splitY = newY + (lineHeight * 0.5);
                }
                break;
              }
            }
            iChildLine++;
          }

          if (childLine != null) {
            final int childIndent = childLine.getIndent();
            // Draw horizontal line to the child.
            final GuidelineOffsetDetail guidelineOffsetDetail = getGuidelineOffsetDetail(childLine, editor, doc, chars);
            final VisualPosition widgetVisualPosition = editor.offsetToVisualPosition(guidelineOffsetDetail.textStartOffset);
            final Point widgetPoint = editor.visualPositionToXY(widgetVisualPosition);

            final int deltaX = widgetPoint.x - start.x;
            // We add a larger amount of panding at the end of the line if the indent is larger up until a max of 6 pixels which is the max
            // amount that looks reasonable. We could remove this and always used a fixed padding.
            final int padding = max(min(abs(deltaX) / 3, 6), 2);
            if (deltaX > 0) {
              // This is the normal case where we draw a foward line to the connected child.
              LinePainter2D.paint(
                g2d,
                start.x + INDENT_GUIDE_DELTA,
                newY + lineHeight * 0.5,
                //start.x + charWidth  * childIndent - padding,
                widgetPoint.x - padding,
                newY + lineHeight * 0.5
              );
            }
            else {
              // If there are other characters on the same line as the widget,
              // avoid drawing a backwards line.
              if (guidelineOffsetDetail.textStartOffset != guidelineOffsetDetail.offset) {
                return;
              }
              // Edge case where we draw a backwards line to clarify
              // that the node is still a child even though the line is in
              // the wrong direction. This is mainly for debugging but could help
              // users fix broken UI.
              // We draw this line so it is inbetween the lines of text so it
              // doesn't get in the way.
              final int loopBackLength = 6;

              //              int endX = start.x + charWidth  * (childIndent -1) - padding - loopBackLength;
              final int endX = widgetPoint.x - padding;
              LinePainter2D.paint(
                g2d,
                start.x + INDENT_GUIDE_DELTA,
                newY,
                endX,
                newY
              );
              LinePainter2D.paint(
                g2d,
                endX,
                newY,
                endX,
                newY + lineHeight * 0.5
              );
              LinePainter2D.paint(
                g2d,
                endX,
                newY + lineHeight * 0.5,
                endX + loopBackLength,
                newY + lineHeight * 0.5
              );
            }
          }
        }

        final List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForLine(i);
        int logicalLineHeight = softWraps.size() * lineHeight;
        if (i > startLine + lineShift) {
          logicalLineHeight += lineHeight; // We assume that initial 'y' value points just below the target line.
        }
        if (!softWraps.isEmpty() && softWraps.get(0).getIndentInColumns() < indentColumn) {
          if (y < newY || i > startLine + lineShift) { // There is a possible case that soft wrap is located on indent start line.
            drawVerticalLineHelper(g2d, lineColor, start.x, y, newY + lineHeight, childLines);
          }
          newY += logicalLineHeight;
          y = newY;
        }
        else {
          newY += logicalLineHeight;
        }

        final FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(i));
        if (foldRegion != null && foldRegion.getEndOffset() < doc.getTextLength()) {
          i = doc.getLineNumber(foldRegion.getEndOffset());
        }
      }

      if (childLines != null && iChildLine < childLines.size() && splitY == -1) {
        // Clipped rectangle is all within the main body.
        splitY = maxY;
      }
      if (y < maxY) {
        if (splitY != -1) {
          drawVerticalLineHelper(g2d, lineColor, start.x, y, splitY, childLines);
          g2d.setColor(pastBlockColor);
          g2d.drawLine(start.x + INDENT_GUIDE_DELTA, (int)splitY + 1, start.x + INDENT_GUIDE_DELTA, maxY);
        }
        else {
          g2d.setColor(pastBlockColor);
          g2d.drawLine(start.x + INDENT_GUIDE_DELTA, y, start.x + INDENT_GUIDE_DELTA, maxY);
        }
      }
      g2d.dispose();
    }