private void paintWhitespace(
      Graphics2D g,
      CharSequence text,
      float x,
      int y,
      int start,
      int end,
      EditorImpl.LineWhitespacePaintingStrategy whitespacePaintingStrategy,
      VisualLineFragmentsIterator.Fragment fragment) {
    g.setColor(myEditor.getColorsScheme().getColor(EditorColors.WHITESPACES_COLOR));
    boolean isRtl = fragment.isRtl();
    int baseStartOffset = fragment.getStartOffset();
    int startOffset = isRtl ? baseStartOffset - start : baseStartOffset + start;
    for (int i = start; i < end; i++) {
      int charOffset = isRtl ? baseStartOffset - i - 1 : baseStartOffset + i;
      char c = text.charAt(charOffset);
      if (" \t\u3000".indexOf(c) >= 0
          && whitespacePaintingStrategy.showWhitespaceAtOffset(charOffset)) {
        int startX =
            (int)
                fragment.offsetToX(
                    x, startOffset, isRtl ? baseStartOffset - i : baseStartOffset + i);
        int endX =
            (int)
                fragment.offsetToX(
                    x, startOffset, isRtl ? baseStartOffset - i - 1 : baseStartOffset + i + 1);

        if (c == ' ') {
          g.fillRect((startX + endX) / 2, y, 1, 1);
        } else if (c == '\t') {
          endX -= myView.getPlainSpaceWidth() / 4;
          int height = myView.getCharHeight();
          int halfHeight = height / 2;
          int mid = y - halfHeight;
          int top = y - height;
          UIUtil.drawLine(g, startX, mid, endX, mid);
          UIUtil.drawLine(g, endX, y, endX, top);
          g.fillPolygon(
              new int[] {endX - halfHeight, endX - halfHeight, endX},
              new int[] {y, y - height, y - halfHeight},
              3);
        } else if (c == '\u3000') { // ideographic space
          final int charHeight = myView.getCharHeight();
          g.drawRect(startX + 2, y - charHeight, endX - startX - 4, charHeight);
        }
      }
    }
  }
  public static boolean checkConsistency(PsiFile psiFile, Document document) {
    // todo hack
    if (psiFile.getVirtualFile() == null) return true;

    CharSequence editorText = document.getCharsSequence();
    int documentLength = document.getTextLength();
    if (psiFile.textMatches(editorText)) {
      LOG.assertTrue(psiFile.getTextLength() == documentLength);
      return true;
    }

    char[] fileText = psiFile.textToCharArray();
    @SuppressWarnings({"NonConstantStringShouldBeStringBuffer"})
    @NonNls
    String error =
        "File '"
            + psiFile.getName()
            + "' text mismatch after reparse. "
            + "File length="
            + fileText.length
            + "; Doc length="
            + documentLength
            + "\n";
    int i = 0;
    for (; i < documentLength; i++) {
      if (i >= fileText.length) {
        error += "editorText.length > psiText.length i=" + i + "\n";
        break;
      }
      if (i >= editorText.length()) {
        error += "editorText.length > psiText.length i=" + i + "\n";
        break;
      }
      if (editorText.charAt(i) != fileText[i]) {
        error += "first unequal char i=" + i + "\n";
        break;
      }
    }
    // error += "*********************************************" + "\n";
    // if (i <= 500){
    //  error += "Equal part:" + editorText.subSequence(0, i) + "\n";
    // }
    // else{
    //  error += "Equal part start:\n" + editorText.subSequence(0, 200) + "\n";
    //  error += "................................................" + "\n";
    //  error += "................................................" + "\n";
    //  error += "................................................" + "\n";
    //  error += "Equal part end:\n" + editorText.subSequence(i - 200, i) + "\n";
    // }
    error += "*********************************************" + "\n";
    error +=
        "Editor Text tail:("
            + (documentLength - i)
            + ")\n"; // + editorText.subSequence(i, Math.min(i + 300, documentLength)) + "\n";
    error += "*********************************************" + "\n";
    error += "Psi Text tail:(" + (fileText.length - i) + ")\n";
    error += "*********************************************" + "\n";

    if (document instanceof DocumentWindow) {
      error += "doc: '" + document.getText() + "'\n";
      error += "psi: '" + psiFile.getText() + "'\n";
      error += "ast: '" + psiFile.getNode().getText() + "'\n";
      error += psiFile.getLanguage() + "\n";
      PsiElement context =
          InjectedLanguageManager.getInstance(psiFile.getProject()).getInjectionHost(psiFile);
      if (context != null) {
        error += "context: " + context + "; text: '" + context.getText() + "'\n";
        error += "context file: " + context.getContainingFile() + "\n";
      }
      error +=
          "document window ranges: "
              + Arrays.asList(((DocumentWindow) document).getHostRanges())
              + "\n";
    }
    LOG.error(error);
    // document.replaceString(0, documentLength, psiFile.getText());
    return false;
  }
  /**
   * @param hideByAnyKey
   * @param x <code>x</code> coordinate in layered pane coordinate system.
   * @param y <code>y</code> coordinate in layered pane coordinate system.
   */
  @Nullable
  public static LightweightHint showEditorFragmentHintAt(
      Editor editor,
      TextRange range,
      int x,
      int y,
      boolean showUpward,
      boolean showFolding,
      boolean hideByAnyKey) {
    if (ApplicationManager.getApplication().isUnitTestMode()) return null;
    Document document = editor.getDocument();

    int startOffset = range.getStartOffset();
    int startLine = document.getLineNumber(startOffset);
    CharSequence text = document.getCharsSequence();
    // There is a possible case that we have a situation like below:
    //    line 1
    //    line 2 <fragment start>
    //    line 3<fragment end>
    // We don't want to include 'line 2' to the target fragment then.
    boolean incrementLine = false;
    for (int offset = startOffset, max = Math.min(range.getEndOffset(), text.length());
        offset < max;
        offset++) {
      char c = text.charAt(offset);
      incrementLine = StringUtil.isWhiteSpace(c);
      if (!incrementLine || c == '\n') {
        break;
      }
    }
    if (incrementLine) {
      startLine++;
    }

    int endLine =
        Math.min(document.getLineNumber(range.getEndOffset()) + 1, document.getLineCount() - 1);

    // if (editor.logicalPositionToXY(new LogicalPosition(startLine, 0)).y >=
    // editor.logicalPositionToXY(new LogicalPosition(endLine, 0)).y) return null;
    if (startLine >= endLine) return null;

    EditorFragmentComponent fragmentComponent =
        createEditorFragmentComponent(editor, startLine, endLine, showFolding, true);

    if (showUpward) {
      y -= fragmentComponent.getPreferredSize().height + 10;
      y = Math.max(0, y);
    }

    final JComponent c = editor.getComponent();
    x = SwingUtilities.convertPoint(c, new Point(-3, 0), UIUtil.getRootPane(c)).x; // IDEA-68016

    Point p = new Point(x, y);
    LightweightHint hint = new MyComponentHint(fragmentComponent);
    HintManagerImpl.getInstanceImpl()
        .showEditorHint(
            hint,
            editor,
            p,
            (hideByAnyKey ? HintManager.HIDE_BY_ANY_KEY : 0)
                | HintManager.HIDE_BY_TEXT_CHANGE
                | HintManager.HIDE_BY_MOUSEOVER,
            0,
            false,
            new HintHint(editor, p));
    return hint;
  }