private static void doCommitTransaction(
      @NotNull Document document, @NotNull DocumentChangeTransaction documentChangeTransaction) {
    DocumentEx ex = (DocumentEx) document;
    ex.suppressGuardedExceptions();
    try {
      boolean isReadOnly = !document.isWritable();
      ex.setReadOnly(false);
      final Set<Pair<MutableTextRange, StringBuffer>> affectedFragments =
          documentChangeTransaction.getAffectedFragments();
      for (final Pair<MutableTextRange, StringBuffer> pair : affectedFragments) {
        final StringBuffer replaceBuffer = pair.getSecond();
        final MutableTextRange range = pair.getFirst();
        if (replaceBuffer.length() == 0) {
          ex.deleteString(range.getStartOffset(), range.getEndOffset());
        } else if (range.getLength() == 0) {
          ex.insertString(range.getStartOffset(), replaceBuffer);
        } else {
          ex.replaceString(range.getStartOffset(), range.getEndOffset(), replaceBuffer);
        }
      }

      ex.setReadOnly(isReadOnly);
      // if(documentChangeTransaction.getChangeScope() != null) {
      //
      // LOG.assertTrue(document.getText().equals(documentChangeTransaction.getChangeScope().getText()),
      //                 "Psi to document synchronization failed (send to IK)");
      // }
    } finally {
      ex.unSuppressGuardedExceptions();
    }
  }
 public void testUpdatingCaretPositionAfterBulkMode() throws Exception {
   initText("a<caret>bc");
   DocumentEx document = (DocumentEx) myEditor.getDocument();
   document.setInBulkUpdate(true);
   document.insertString(
       0, "\n "); // we're changing number of visual lines, and invalidating text layout for caret
   // line
   document.setInBulkUpdate(false);
   checkResultByText("\n a<caret>bc");
 }
 @Override
 public void insertString(final int offset, @NotNull CharSequence s) {
   synchronized (myLock) {
     LOG.assertTrue(offset >= myShreds.get(0).getPrefix().length(), myShreds.get(0).getPrefix());
     LOG.assertTrue(
         offset <= getTextLength() - myShreds.get(myShreds.size() - 1).getSuffix().length(),
         myShreds.get(myShreds.size() - 1).getSuffix());
   }
   if (isOneLine()) {
     s = StringUtil.replace(s.toString(), "\n", "");
   }
   myDelegate.insertString(injectedToHost(offset), s);
 }
    @SuppressWarnings("AssignmentToForLoopParameter")
    @Override
    public void replace(
        @NotNull ArrangementEntryWrapper<E> newWrapper,
        @NotNull ArrangementEntryWrapper<E> oldWrapper,
        @Nullable ArrangementEntryWrapper<E> previous,
        @NotNull Context<E> context) {
      // Calculate blank lines before the arrangement.
      int blankLinesBefore = oldWrapper.getBlankLinesBefore();

      ArrangementEntryWrapper<E> parentWrapper = oldWrapper.getParent();
      int desiredBlankLinesNumber =
          context.rearranger.getBlankLines(
              context.settings,
              parentWrapper == null ? null : parentWrapper.getEntry(),
              previous == null ? null : previous.getEntry(),
              newWrapper.getEntry());
      if ((desiredBlankLinesNumber < 0 || desiredBlankLinesNumber == blankLinesBefore)
          && newWrapper.equals(oldWrapper)) {
        return;
      }

      int lineFeedsDiff = desiredBlankLinesNumber - blankLinesBefore;
      int insertionOffset = oldWrapper.getStartOffset();
      if (oldWrapper.getStartOffset() > newWrapper.getStartOffset()) {
        insertionOffset -= newWrapper.getEndOffset() - newWrapper.getStartOffset();
      }
      if (newWrapper.getStartOffset() != oldWrapper.getStartOffset()
          || !newWrapper.equals(oldWrapper)) {
        context.addMoveInfo(
            newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset());
        myDocument.moveText(
            newWrapper.getStartOffset(), newWrapper.getEndOffset(), oldWrapper.getStartOffset());
        for (int i = myWrappers.size() - 1; i >= 0; i--) {
          ArrangementEntryWrapper<E> w = myWrappers.get(i);
          if (w == newWrapper) {
            continue;
          }
          if (w.getStartOffset() >= oldWrapper.getStartOffset()
              && w.getStartOffset() < newWrapper.getStartOffset()) {
            w.applyShift(newWrapper.getEndOffset() - newWrapper.getStartOffset());
          } else if (w.getStartOffset() < oldWrapper.getStartOffset()
              && w.getStartOffset() > newWrapper.getStartOffset()) {
            w.applyShift(newWrapper.getStartOffset() - newWrapper.getEndOffset());
          }
        }
      }

      if (desiredBlankLinesNumber >= 0 && lineFeedsDiff > 0) {
        myDocument.insertString(insertionOffset, StringUtil.repeat("\n", lineFeedsDiff));
        shiftOffsets(lineFeedsDiff, insertionOffset);
      }

      if (desiredBlankLinesNumber >= 0 && lineFeedsDiff < 0) {
        // Cut exceeding blank lines.
        int replacementStartOffset = getBlankLineOffset(-lineFeedsDiff, insertionOffset);
        myDocument.deleteString(replacementStartOffset, insertionOffset);
        shiftOffsets(replacementStartOffset - insertionOffset, insertionOffset);
      }

      // Update wrapper ranges.
      if (desiredBlankLinesNumber < 0 || lineFeedsDiff == 0 || parentWrapper == null) {
        return;
      }

      Deque<ArrangementEntryWrapper<E>> parents = new ArrayDeque<ArrangementEntryWrapper<E>>();
      do {
        parents.add(parentWrapper);
        parentWrapper.setEndOffset(parentWrapper.getEndOffset() + lineFeedsDiff);
        parentWrapper = parentWrapper.getParent();
      } while (parentWrapper != null);

      while (!parents.isEmpty()) {
        for (ArrangementEntryWrapper<E> wrapper = parents.removeLast().getNext();
            wrapper != null;
            wrapper = wrapper.getNext()) {
          wrapper.applyShift(lineFeedsDiff);
        }
      }
    }