@Override
 public boolean equals(final TextAttributes attributes1, final TextAttributes attributes2) {
   Color effectColor = attributes1.getEffectColor();
   EffectType effectType = attributes1.getEffectType();
   return effectColor != null
       && effectColor.equals(attributes2.getEffectColor())
       && (EffectType.BOXED == effectType || EffectType.ROUNDED_BOX == effectType)
       && effectType == attributes2.getEffectType();
 }
 private static TextAttributes mergeAttributes(TextAttributes primary, TextAttributes secondary) {
   if (primary == null) return secondary;
   if (secondary == null) return primary;
   return new TextAttributes(
       primary.getForegroundColor() == null
           ? secondary.getForegroundColor()
           : primary.getForegroundColor(),
       primary.getBackgroundColor() == null
           ? secondary.getBackgroundColor()
           : primary.getBackgroundColor(),
       primary.getEffectColor() == null ? secondary.getEffectColor() : primary.getEffectColor(),
       primary.getEffectType() == null ? secondary.getEffectType() : primary.getEffectType(),
       primary.getFontType() == Font.PLAIN ? secondary.getFontType() : primary.getFontType());
 }
  private void highlightInjectedSyntax(
      @NotNull PsiFile injectedPsi, @NotNull HighlightInfoHolder holder) {
    List<Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange>>
        tokens = InjectedLanguageUtil.getHighlightTokens(injectedPsi);
    if (tokens == null) return;

    final Language injectedLanguage = injectedPsi.getLanguage();
    Project project = injectedPsi.getProject();
    SyntaxHighlighter syntaxHighlighter =
        SyntaxHighlighterFactory.getSyntaxHighlighter(
            injectedLanguage, project, injectedPsi.getVirtualFile());
    final TextAttributes defaultAttrs = myGlobalScheme.getAttributes(HighlighterColors.TEXT);

    for (Trinity<IElementType, SmartPsiElementPointer<PsiLanguageInjectionHost>, TextRange> token :
        tokens) {
      ProgressManager.checkCanceled();
      IElementType tokenType = token.getFirst();
      PsiLanguageInjectionHost injectionHost = token.getSecond().getElement();
      if (injectionHost == null) continue;
      TextRange textRange = token.getThird();
      TextAttributesKey[] keys = syntaxHighlighter.getTokenHighlights(tokenType);
      if (textRange.getLength() == 0) continue;

      TextRange annRange = textRange.shiftRight(injectionHost.getTextRange().getStartOffset());
      // force attribute colors to override host' ones
      TextAttributes attributes = null;
      for (TextAttributesKey key : keys) {
        TextAttributes attrs2 = myGlobalScheme.getAttributes(key);
        if (attrs2 != null) {
          attributes = attributes == null ? attrs2 : TextAttributes.merge(attributes, attrs2);
        }
      }
      TextAttributes forcedAttributes;
      if (attributes == null || attributes.isEmpty() || attributes.equals(defaultAttrs)) {
        forcedAttributes = TextAttributes.ERASE_MARKER;
      } else {
        HighlightInfo info =
            HighlightInfo.newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT)
                .range(annRange)
                .textAttributes(TextAttributes.ERASE_MARKER)
                .createUnconditionally();
        holder.add(info);

        forcedAttributes =
            new TextAttributes(
                attributes.getForegroundColor(),
                attributes.getBackgroundColor(),
                attributes.getEffectColor(),
                attributes.getEffectType(),
                attributes.getFontType());
      }

      HighlightInfo info =
          HighlightInfo.newHighlightInfo(HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT)
              .range(annRange)
              .textAttributes(forcedAttributes)
              .createUnconditionally();
      holder.add(info);
    }
  }
 private void paintBorder(RangeHighlighterEx rangeHighlighter, TextAttributes textAttributes) {
   paintBorder(
       textAttributes.getEffectColor(),
       rangeHighlighter.getAffectedAreaStartOffset(),
       rangeHighlighter.getAffectedAreaEndOffset(),
       textAttributes.getEffectType());
 }
 /** Patches attributes to be visible under debugger active line */
 @SuppressWarnings("UseJBColor")
 public static TextAttributes patchAttributesColor(
     TextAttributes attributes, @NotNull TextRange range, @NotNull Editor editor) {
   if (attributes.getForegroundColor() == null && attributes.getEffectColor() == null)
     return attributes;
   MarkupModel model =
       DocumentMarkupModel.forDocument(editor.getDocument(), editor.getProject(), false);
   if (model != null) {
     if (!((MarkupModelEx) model)
         .processRangeHighlightersOverlappingWith(
             range.getStartOffset(),
             range.getEndOffset(),
             highlighter -> {
               if (highlighter.isValid()
                   && highlighter.getTargetArea() == HighlighterTargetArea.LINES_IN_RANGE) {
                 TextAttributes textAttributes = highlighter.getTextAttributes();
                 if (textAttributes != null) {
                   Color color = textAttributes.getBackgroundColor();
                   return !(color != null
                       && color.getBlue() > 128
                       && color.getRed() < 128
                       && color.getGreen() < 128);
                 }
               }
               return true;
             })) {
       TextAttributes clone = attributes.clone();
       clone.setForegroundColor(Color.orange);
       clone.setEffectColor(Color.orange);
       return clone;
     }
   }
   return attributes;
 }
 private void paintHighlighterAfterEndOfLine(Graphics2D g, RangeHighlighterEx highlighter) {
   if (!highlighter.isAfterEndOfLine()) {
     return;
   }
   int startOffset = highlighter.getStartOffset();
   int lineEndOffset = myDocument.getLineEndOffset(myDocument.getLineNumber(startOffset));
   if (myEditor.getFoldingModel().isOffsetCollapsed(lineEndOffset)) return;
   Point lineEnd = myView.offsetToXY(lineEndOffset, true, false);
   int x = lineEnd.x;
   int y = lineEnd.y;
   TextAttributes attributes = highlighter.getTextAttributes();
   paintBackground(g, attributes, x, y, myView.getPlainSpaceWidth());
   if (attributes != null
       && hasTextEffect(attributes.getEffectColor(), attributes.getEffectType())) {
     paintTextEffect(
         g,
         x,
         x + myView.getPlainSpaceWidth() - 1,
         y + myView.getAscent(),
         attributes.getEffectColor(),
         attributes.getEffectType());
   }
 }
 private static boolean isBorder(TextAttributes textAttributes) {
   return textAttributes != null
       && textAttributes.getEffectColor() != null
       && (EffectType.BOXED == textAttributes.getEffectType()
           || EffectType.ROUNDED_BOX == textAttributes.getEffectType());
 }
  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 static boolean isBorder(TextAttributes attributes) {
   return attributes != null
       && (attributes.getEffectType() == EffectType.BOXED
           || attributes.getEffectType() == EffectType.ROUNDED_BOX)
       && attributes.getEffectColor() != null;
 }