public void commentRange(
      int startOffset,
      int endOffset,
      String commentPrefix,
      String commentSuffix,
      Commenter commenter) {
    final CharSequence chars = myDocument.getCharsSequence();
    LogicalPosition caretPosition = myCaret.getLogicalPosition();

    if (startOffset == 0 || chars.charAt(startOffset - 1) == '\n') {
      if (endOffset == myDocument.getTextLength()
          || endOffset > 0 && chars.charAt(endOffset - 1) == '\n') {
        CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(myProject);
        CommonCodeStyleSettings settings =
            CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(myFile.getLanguage());
        String space;
        if (!settings.BLOCK_COMMENT_AT_FIRST_COLUMN) {
          final FileType fileType = myFile.getFileType();
          int line1 = myEditor.offsetToLogicalPosition(startOffset).line;
          int line2 = myEditor.offsetToLogicalPosition(endOffset - 1).line;
          Indent minIndent =
              CommentUtil.getMinLineIndent(myProject, myDocument, line1, line2, fileType);
          if (minIndent == null) {
            minIndent = codeStyleManager.zeroIndent();
          }
          space = codeStyleManager.fillIndent(minIndent, fileType);
        } else {
          space = "";
        }
        final StringBuilder nestingPrefix = new StringBuilder(space).append(commentPrefix);
        if (!commentPrefix.endsWith("\n")) {
          nestingPrefix.append("\n");
        }
        final StringBuilder nestingSuffix = new StringBuilder(space);
        nestingSuffix.append(
            commentSuffix.startsWith("\n") ? commentSuffix.substring(1) : commentSuffix);
        nestingSuffix.append("\n");
        TextRange range =
            insertNestedComments(
                startOffset,
                endOffset,
                nestingPrefix.toString(),
                nestingSuffix.toString(),
                commenter);
        myCaret.setSelection(range.getStartOffset(), range.getEndOffset());
        LogicalPosition pos = new LogicalPosition(caretPosition.line + 1, caretPosition.column);
        myCaret.moveToLogicalPosition(pos);
        return;
      }
    }

    TextRange range =
        insertNestedComments(startOffset, endOffset, commentPrefix, commentSuffix, commenter);
    myCaret.setSelection(range.getStartOffset(), range.getEndOffset());
    LogicalPosition pos =
        new LogicalPosition(caretPosition.line, caretPosition.column + commentPrefix.length());
    myCaret.moveToLogicalPosition(pos);
  }
  private void notifyBatchFoldingProcessingDone(final boolean moveCaretFromCollapsedRegion) {
    rebuild();

    for (FoldingListener listener : myListeners) {
      listener.onFoldProcessingEnd();
    }

    myEditor.updateCaretCursor();
    myEditor.recalculateSizeAndRepaint();
    myEditor.getGutterComponentEx().updateSize();
    myEditor.getGutterComponentEx().repaint();

    for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
      // There is a possible case that caret position is already visual position aware. But visual
      // position depends on number of folded
      // logical lines as well, hence, we can't be sure that target logical position defines correct
      // visual position because fold
      // regions have just changed. Hence, we use 'raw' logical position instead.
      LogicalPosition caretPosition = caret.getLogicalPosition().withoutVisualPositionInfo();
      int caretOffset = myEditor.logicalPositionToOffset(caretPosition);
      int selectionStart = caret.getSelectionStart();
      int selectionEnd = caret.getSelectionEnd();

      LogicalPosition positionToUse = null;
      int offsetToUse = -1;

      FoldRegion collapsed = myFoldTree.fetchOutermost(caretOffset);
      LogicalPosition savedPosition = caret.getUserData(SAVED_CARET_POSITION);
      if (savedPosition != null) {
        int savedOffset = myEditor.logicalPositionToOffset(savedPosition);
        FoldRegion collapsedAtSaved = myFoldTree.fetchOutermost(savedOffset);
        if (collapsedAtSaved == null) {
          positionToUse = savedPosition;
        } else {
          offsetToUse = collapsedAtSaved.getStartOffset();
        }
      }

      if (collapsed != null && positionToUse == null) {
        positionToUse = myEditor.offsetToLogicalPosition(collapsed.getStartOffset());
      }

      if (moveCaretFromCollapsedRegion && caret.isUpToDate()) {
        if (offsetToUse >= 0) {
          caret.moveToOffset(offsetToUse);
        } else if (positionToUse != null) {
          caret.moveToLogicalPosition(positionToUse);
        } else {
          caret.moveToLogicalPosition(caretPosition);
        }
      }

      caret.putUserData(SAVED_CARET_POSITION, savedPosition);

      if (isOffsetInsideCollapsedRegion(selectionStart)
          || isOffsetInsideCollapsedRegion(selectionEnd)) {
        caret.removeSelection();
      } else if (selectionStart < myEditor.getDocument().getTextLength()) {
        caret.setSelection(selectionStart, selectionEnd);
      }
    }

    if (mySavedCaretShift > 0) {
      final ScrollingModel scrollingModel = myEditor.getScrollingModel();
      scrollingModel.disableAnimation();
      scrollingModel.scrollVertically(
          myEditor.visibleLineToY(myEditor.getCaretModel().getVisualPosition().line)
              - mySavedCaretShift);
      scrollingModel.enableAnimation();
    }
  }