private static void assertBeforeCommit(
      Document document,
      PsiFile file,
      TextBlock textBlock,
      CharSequence chars,
      String oldPsiText,
      FileElement myTreeElementBeingReparsedSoItWontBeCollected) {
    int startOffset = textBlock.getStartOffset();
    int psiEndOffset = textBlock.getPsiEndOffset();
    if (oldPsiText != null) {
      @NonNls String msg = "PSI/document inconsistency before reparse: ";
      if (startOffset >= oldPsiText.length()) {
        msg += "startOffset=" + oldPsiText + " while text length is " + oldPsiText.length() + "; ";
        startOffset = oldPsiText.length();
      }

      String psiPrefix = oldPsiText.substring(0, startOffset);
      String docPrefix = chars.subSequence(0, startOffset).toString();
      String psiSuffix = oldPsiText.substring(psiEndOffset);
      String docSuffix = chars.subSequence(textBlock.getTextEndOffset(), chars.length()).toString();
      if (!psiPrefix.equals(docPrefix) || !psiSuffix.equals(docSuffix)) {
        if (!psiPrefix.equals(docPrefix)) {
          msg = msg + "psiPrefix=" + psiPrefix + "; docPrefix=" + docPrefix + ";";
        }
        if (!psiSuffix.equals(docSuffix)) {
          msg = msg + "psiSuffix=" + psiSuffix + "; docSuffix=" + docSuffix + ";";
        }
        throw new AssertionError(msg);
      }
    } else if (document.getTextLength() - textBlock.getTextEndOffset()
        != myTreeElementBeingReparsedSoItWontBeCollected.getTextLength() - psiEndOffset) {
      throw new AssertionError("PSI/document inconsistency before reparse: file=" + file);
    }
  }
  @Nullable("returns runnable to execute under write action in AWT to finish the commit")
  private Processor<Document> doCommit(
      @NotNull final Document document,
      @NotNull final PsiFile file,
      @NotNull ProgressIndicator indicator,
      final boolean synchronously,
      @NotNull PsiDocumentManager documentManager) {
    ((PsiDocumentManagerImpl) documentManager).clearTreeHardRef(document);
    final TextBlock textBlock = TextBlock.get(file);
    if (textBlock.isEmpty()) return null;
    final long startPsiModificationTimeStamp = file.getModificationStamp();
    final long startDocModificationTimeStamp = document.getModificationStamp();
    final FileElement myTreeElementBeingReparsedSoItWontBeCollected =
        ((PsiFileImpl) file).calcTreeElement();
    if (textBlock.isEmpty())
      return null; // if tree was just loaded above textBlock will be cleared by contentsLoaded
    final CharSequence chars = document.getCharsSequence();
    final Boolean data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY);
    if (data != null) {
      document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null);
      file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data);
    }
    final String oldPsiText =
        ApplicationManagerEx.getApplicationEx().isInternal()
                && !ApplicationManagerEx.getApplicationEx().isUnitTestMode()
            ? myTreeElementBeingReparsedSoItWontBeCollected.getText()
            : null;
    int startOffset;
    int endOffset;
    int lengthShift;
    if (file.getViewProvider().supportsIncrementalReparse(file.getLanguage())) {
      startOffset = textBlock.getStartOffset();
      int psiEndOffset = textBlock.getPsiEndOffset();
      endOffset = psiEndOffset;
      lengthShift = textBlock.getTextEndOffset() - psiEndOffset;
    } else {
      startOffset = 0;
      endOffset = document.getTextLength();
      lengthShift =
          document.getTextLength() - myTreeElementBeingReparsedSoItWontBeCollected.getTextLength();
    }
    assertBeforeCommit(
        document,
        file,
        textBlock,
        chars,
        oldPsiText,
        myTreeElementBeingReparsedSoItWontBeCollected);
    BlockSupport blockSupport = BlockSupport.getInstance(file.getProject());
    final DiffLog diffLog =
        blockSupport.reparseRange(file, startOffset, endOffset, lengthShift, chars, indicator);

    return new Processor<Document>() {
      @Override
      public boolean process(Document document) {
        ApplicationManager.getApplication().assertWriteAccessAllowed();
        log(
            "Finishing",
            document,
            synchronously,
            document.getModificationStamp(),
            startDocModificationTimeStamp);
        // if (file.getModificationStamp() != startPsiModificationTimeStamp) return; // optimistic
        // locking failed
        if (document.getModificationStamp() != startDocModificationTimeStamp) {
          return false; // optimistic locking failed
        }

        try {
          textBlock.performAtomically(
              new Runnable() {
                @Override
                public void run() {
                  CodeStyleManager.getInstance(file.getProject())
                      .performActionWithFormatterDisabled(
                          new Runnable() {
                            @Override
                            public void run() {
                              synchronized (PsiLock.LOCK) {
                                doActualPsiChange(file, diffLog);
                              }
                            }
                          });
                }
              });

          assertAfterCommit(
              document, file, oldPsiText, myTreeElementBeingReparsedSoItWontBeCollected);
        } finally {
          textBlock.clear();
          SmartPointerManagerImpl.synchronizePointers(file);
        }

        return true;
      }
    };
  }