@Override
  public void postInvoke() {
    // second pass - determining whether we need to comment or to uncomment
    boolean allLinesCommented = true;
    for (Block block : myBlocks) {
      int startLine = block.startLine;
      int endLine = block.endLine;
      Document document = block.editor.getDocument();
      PsiFile psiFile = block.psiFile;
      block.startOffsets = new int[endLine - startLine + 1];
      block.endOffsets = new int[endLine - startLine + 1];
      block.commenters = new Commenter[endLine - startLine + 1];
      block.commenterStateMap = new HashMap<>();
      CharSequence chars = document.getCharsSequence();

      boolean singleline = startLine == endLine;
      int offset = document.getLineStartOffset(startLine);
      offset = CharArrayUtil.shiftForward(chars, offset, " \t");

      int endOffset = CharArrayUtil.shiftBackward(chars, document.getLineEndOffset(endLine), " \t\n");

      block.blockSuitableCommenter = getBlockSuitableCommenter(psiFile, block.editor, offset, endOffset);
      Language lineStartLanguage = getLineStartLanguage(block.editor, psiFile, startLine);
      CommentStyleSettings commentStyleSettings = CodeStyle.getLanguageSettings(psiFile, lineStartLanguage);
      block.commentWithIndent = !commentStyleSettings.isLineCommentInTheFirstColumn();
      block.addLineSpace = commentStyleSettings.isLineCommentFollowedWithSpace();
      block.addBlockSpace = commentStyleSettings.isBlockCommentIncludesSpace();

      for (int line = startLine; line <= endLine; line++) {
        Commenter commenter = block.blockSuitableCommenter != null ? block.blockSuitableCommenter : findCommenter(block.editor, psiFile, line);
        if (commenter == null || commenter.getLineCommentPrefix() == null
                                 && (commenter.getBlockCommentPrefix() == null || commenter.getBlockCommentSuffix() == null)) {
          block.skip = true;
          break;
        }

        if (commenter instanceof SelfManagingCommenter selfManagingCommenter && block.commenterStateMap.get(commenter) == null) {
          CommenterDataHolder state = selfManagingCommenter.createLineCommentingState(startLine, endLine, document, psiFile);
          if (state == null) state = SelfManagingCommenter.EMPTY_STATE;
          block.commenterStateMap.put(selfManagingCommenter, state);
        }

        block.commenters[line - startLine] = commenter;
        if (allLinesCommented
            && !isLineCommented(block, line, commenter)
            && (singleline || !DocumentUtil.isLineEmpty(document, line))) {
          allLinesCommented = false;
          if (commenter instanceof IndentedCommenter) {
            final Boolean value = ((IndentedCommenter)commenter).forceIndentedLineComment();
            if (value != null) {
              block.commentWithIndent = value;
            }
          }
        }
      }
    }
    boolean moveCarets = true;
    for (Block block : myBlocks) {
      if (block.carets.size() > 1 && block.startLine != block.endLine) {
        moveCarets = false;
        break;
      }
    }
    // third pass - actual change
    Collections.reverse(myBlocks);
    for (Block block : myBlocks) {
      if (!block.skip) {
        if (!allLinesCommented) {
          if (!block.commentWithIndent) {
            doDefaultCommenting(block);
          }
          else {
            doIndentCommenting(block);
          }
        }
        else {
          doUncommenting(block);
        }
      }

      if (!moveCarets || block.caretUpdate == null) {
        continue;
      }
      Document document = block.editor.getDocument();
      for (Caret caret : block.carets) {
        switch (block.caretUpdate) {
          case PUT_AT_COMMENT_START -> {
            final Commenter commenter = block.commenters[0];
            if (commenter != null) {
              String prefix;
              if (commenter instanceof SelfManagingCommenter) {
                prefix = ((SelfManagingCommenter)commenter).getCommentPrefix(block.startLine,
                                                                             document,
                                                                             block.commenterStateMap.get((SelfManagingCommenter)commenter));
                if (prefix == null) prefix = ""; // TODO
              }
              else {
                prefix = commenter.getLineCommentPrefix();
                if (prefix == null) prefix = commenter.getBlockCommentPrefix();
              }

              int lineStart = document.getLineStartOffset(block.startLine);
              lineStart = CharArrayUtil.shiftForward(document.getCharsSequence(), lineStart, " \t");
              lineStart += prefix.length();
              lineStart = CharArrayUtil.shiftForward(document.getCharsSequence(), lineStart, " \t");
              if (block.addBlockSpace &&
                  commenter.getBlockCommentSuffix() != null &&
                  CharArrayUtil.regionMatches(document.getCharsSequence(), lineStart, commenter.getBlockCommentSuffix()) &&
                  StringUtil.isChar(document.getCharsSequence(), lineStart - 1, ' ')) {
                lineStart--;
              }
              if (lineStart > document.getTextLength()) lineStart = document.getTextLength();
              caret.moveToOffset(lineStart);
            }
          }
          case SHIFT_DOWN -> {
            // Don't tweak caret position if we're already located on the last document line.
            LogicalPosition position = caret.getLogicalPosition();
            if (position.line < document.getLineCount() - 1) {
              int verticalShift = 1 + block.editor.getSoftWrapModel().getSoftWrapsForLine(position.line).size()
                                  - EditorUtil.getSoftWrapCountAfterLineStart(block.editor, position);
              caret.moveCaretRelatively(0, verticalShift, false, true);
            }
          }
          case RESTORE_SELECTION ->
            caret.setSelection(document.getLineStartOffset(document.getLineNumber(caret.getSelectionStart())), caret.getSelectionEnd());
        }
      }
    }
  }
@Override
  public void postInvoke()