private boolean isLineCommented(
      final int line, final CharSequence chars, final Commenter commenter) {
    boolean commented;
    int lineEndForBlockCommenting = -1;
    int lineStart = myDocument.getLineStartOffset(line);
    lineStart = CharArrayUtil.shiftForward(chars, lineStart, " \t");

    if (commenter instanceof SelfManagingCommenter) {
      final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
      commented =
          selfManagingCommenter.isLineCommented(
              line, lineStart, myDocument, myCommenterStateMap.get(selfManagingCommenter));
    } else {
      String prefix = commenter.getLineCommentPrefix();

      if (prefix != null) {
        commented =
            CharArrayUtil.regionMatches(chars, lineStart, prefix)
                || prefix.endsWith(" ")
                    && CharArrayUtil.regionMatches(chars, lineStart, prefix.trim() + "\n");
      } else {
        prefix = commenter.getBlockCommentPrefix();
        String suffix = commenter.getBlockCommentSuffix();
        final int textLength = myDocument.getTextLength();
        lineEndForBlockCommenting = myDocument.getLineEndOffset(line);
        if (lineEndForBlockCommenting == textLength) {
          final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
          if (shifted < textLength - 1) lineEndForBlockCommenting = shifted;
        } else {
          lineEndForBlockCommenting =
              CharArrayUtil.shiftBackward(chars, lineEndForBlockCommenting, " \t");
        }
        commented =
            lineStart == lineEndForBlockCommenting && myStartLine != myEndLine
                || CharArrayUtil.regionMatches(chars, lineStart, prefix)
                    && CharArrayUtil.regionMatches(
                        chars, lineEndForBlockCommenting - suffix.length(), suffix);
      }
    }

    if (commented) {
      myStartOffsets[line - myStartLine] = lineStart;
      myEndOffsets[line - myStartLine] = lineEndForBlockCommenting;
    }

    return commented;
  }
  private void commentLine(int line, int offset, @Nullable Commenter commenter) {
    if (commenter == null) commenter = findCommenter(line);
    if (commenter == null) return;
    if (commenter instanceof SelfManagingCommenter) {
      final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
      selfManagingCommenter.commentLine(
          line, offset, myDocument, myCommenterStateMap.get(selfManagingCommenter));
      return;
    }

    String prefix = commenter.getLineCommentPrefix();
    if (prefix != null) {
      if (commenter instanceof CommenterWithLineSuffix) {
        int endOffset = myDocument.getLineEndOffset(line);
        endOffset = CharArrayUtil.shiftBackward(myDocument.getCharsSequence(), endOffset, " \t");
        int shiftedStartOffset =
            CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t");
        String lineSuffix = ((CommenterWithLineSuffix) commenter).getLineCommentSuffix();
        if (!CharArrayUtil.regionMatches(
            myDocument.getCharsSequence(), shiftedStartOffset, prefix)) {
          if (!CharArrayUtil.regionMatches(
              myDocument.getCharsSequence(), endOffset - lineSuffix.length(), lineSuffix)) {
            myDocument.insertString(endOffset, lineSuffix);
          }
          myDocument.insertString(offset, prefix);
        }
      } else {
        myDocument.insertString(offset, prefix);
      }
    } else {
      prefix = commenter.getBlockCommentPrefix();
      String suffix = commenter.getBlockCommentSuffix();
      if (prefix == null || suffix == null) return;
      int endOffset = myDocument.getLineEndOffset(line);
      if (endOffset == offset && myStartLine != myEndLine) return;
      final int textLength = myDocument.getTextLength();
      final CharSequence chars = myDocument.getCharsSequence();
      offset = CharArrayUtil.shiftForward(chars, offset, " \t");
      if (endOffset == textLength) {
        final int shifted = CharArrayUtil.shiftBackward(chars, textLength - 1, " \t");
        if (shifted < textLength - 1) endOffset = shifted;
      } else {
        endOffset = CharArrayUtil.shiftBackward(chars, endOffset, " \t");
      }
      if (endOffset < offset || offset == textLength - 1 && line != myDocument.getLineCount() - 1) {
        return;
      }
      final String text = chars.subSequence(offset, endOffset).toString();
      final IntArrayList prefixes = new IntArrayList();
      final IntArrayList suffixes = new IntArrayList();
      final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
      final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
      for (int position = 0; position < text.length(); ) {
        int nearestPrefix = text.indexOf(prefix, position);
        if (nearestPrefix == -1) {
          nearestPrefix = text.length();
        }
        int nearestSuffix = text.indexOf(suffix, position);
        if (nearestSuffix == -1) {
          nearestSuffix = text.length();
        }
        if (Math.min(nearestPrefix, nearestSuffix) == text.length()) {
          break;
        }
        if (nearestPrefix < nearestSuffix) {
          prefixes.add(nearestPrefix);
          position = nearestPrefix + prefix.length();
        } else {
          suffixes.add(nearestSuffix);
          position = nearestSuffix + suffix.length();
        }
      }
      if (!(commentedSuffix == null
          && !suffixes.isEmpty()
          && offset + suffixes.get(suffixes.size() - 1) + suffix.length() >= endOffset)) {
        myDocument.insertString(endOffset, suffix);
      }
      int nearestPrefix = prefixes.size() - 1;
      int nearestSuffix = suffixes.size() - 1;
      while (nearestPrefix >= 0 || nearestSuffix >= 0) {
        if (nearestSuffix == -1
            || nearestPrefix != -1 && prefixes.get(nearestPrefix) > suffixes.get(nearestSuffix)) {
          final int position = prefixes.get(nearestPrefix);
          nearestPrefix--;
          if (commentedPrefix != null) {
            myDocument.replaceString(
                offset + position, offset + position + prefix.length(), commentedPrefix);
          } else if (position != 0) {
            myDocument.insertString(offset + position, suffix);
          }
        } else {
          final int position = suffixes.get(nearestSuffix);
          nearestSuffix--;
          if (commentedSuffix != null) {
            myDocument.replaceString(
                offset + position, offset + position + suffix.length(), commentedSuffix);
          } else if (offset + position + suffix.length() < endOffset) {
            myDocument.insertString(offset + position + suffix.length(), prefix);
          }
        }
      }
      if (!(commentedPrefix == null && !prefixes.isEmpty() && prefixes.get(0) == 0)) {
        myDocument.insertString(offset, prefix);
      }
    }
  }
  private void uncommentLine(int line) {
    Commenter commenter = myCommenters[line - myStartLine];
    if (commenter == null) commenter = findCommenter(line);
    if (commenter == null) return;

    final int startOffset = myStartOffsets[line - myStartLine];

    if (commenter instanceof SelfManagingCommenter) {
      final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
      selfManagingCommenter.uncommentLine(
          line, startOffset, myDocument, myCommenterStateMap.get(selfManagingCommenter));
      return;
    }

    final int endOffset = myEndOffsets[line - myStartLine];
    if (startOffset == endOffset) {
      return;
    }
    String prefix = commenter.getLineCommentPrefix();
    if (prefix != null) {
      CharSequence chars = myDocument.getCharsSequence();

      if (commenter instanceof CommenterWithLineSuffix) {
        CommenterWithLineSuffix commenterWithLineSuffix = (CommenterWithLineSuffix) commenter;
        String suffix = commenterWithLineSuffix.getLineCommentSuffix();

        int theEnd = endOffset > 0 ? endOffset : myDocument.getLineEndOffset(line);
        while (theEnd > startOffset && Character.isWhitespace(chars.charAt(theEnd - 1))) {
          theEnd--;
        }

        String lineText = myDocument.getText(new TextRange(startOffset, theEnd));
        if (lineText.indexOf(suffix) != -1) {
          int start = startOffset + lineText.indexOf(suffix);
          myDocument.deleteString(start, start + suffix.length());
        }
      }

      boolean skipNewLine = false;
      boolean commented =
          CharArrayUtil.regionMatches(chars, startOffset, prefix)
              || (skipNewLine =
                  prefix.endsWith(" ")
                      && CharArrayUtil.regionMatches(chars, startOffset, prefix.trim() + "\n"));
      assert commented;

      int charsToDelete = skipNewLine ? prefix.trim().length() : prefix.length();
      int theEnd = endOffset > 0 ? endOffset : chars.length();
      // if there's exactly one space after line comment prefix and before the text that follows in
      // the same line, delete the space too
      if (startOffset + charsToDelete < theEnd - 1
          && chars.charAt(startOffset + charsToDelete) == ' ') {
        if (startOffset + charsToDelete == theEnd - 2
            || chars.charAt(startOffset + charsToDelete + 1) != ' ') {
          charsToDelete++;
        }
      }
      myDocument.deleteString(startOffset, startOffset + charsToDelete);
      return;
    }
    String text = myDocument.getCharsSequence().subSequence(startOffset, endOffset).toString();

    prefix = commenter.getBlockCommentPrefix();
    final String suffix = commenter.getBlockCommentSuffix();
    if (prefix == null || suffix == null) {
      return;
    }

    IntArrayList prefixes = new IntArrayList();
    IntArrayList suffixes = new IntArrayList();
    for (int position = 0; position < text.length(); ) {
      int prefixPos = text.indexOf(prefix, position);
      if (prefixPos == -1) {
        break;
      }
      prefixes.add(prefixPos);
      position = prefixPos + prefix.length();
      int suffixPos = text.indexOf(suffix, position);
      if (suffixPos == -1) {
        suffixPos = text.length() - suffix.length();
      }
      suffixes.add(suffixPos);
      position = suffixPos + suffix.length();
    }

    assert prefixes.size() == suffixes.size();

    for (int i = prefixes.size() - 1; i >= 0; i--) {
      uncommentRange(
          startOffset + prefixes.get(i),
          Math.min(startOffset + suffixes.get(i) + suffix.length(), endOffset),
          commenter);
    }
  }
  private void doComment() {
    myStartLine = myDocument.getLineNumber(myStartOffset);
    myEndLine = myDocument.getLineNumber(myEndOffset);

    if (myEndLine > myStartLine && myDocument.getLineStartOffset(myEndLine) == myEndOffset) {
      myEndLine--;
    }

    myStartOffsets = new int[myEndLine - myStartLine + 1];
    myEndOffsets = new int[myEndLine - myStartLine + 1];
    myCommenters = new Commenter[myEndLine - myStartLine + 1];
    myCommenterStateMap = new THashMap<SelfManagingCommenter, CommenterDataHolder>();
    CharSequence chars = myDocument.getCharsSequence();

    boolean singleline = myStartLine == myEndLine;
    int offset = myDocument.getLineStartOffset(myStartLine);
    offset = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), offset, " \t");

    final Language languageSuitableForCompleteFragment =
        PsiUtilBase.reallyEvaluateLanguageInRange(
            offset,
            CharArrayUtil.shiftBackward(
                myDocument.getCharsSequence(), myDocument.getLineEndOffset(myEndLine), " \t\n"),
            myFile);

    Commenter blockSuitableCommenter =
        languageSuitableForCompleteFragment == null
            ? LanguageCommenters.INSTANCE.forLanguage(myFile.getLanguage())
            : null;
    if (blockSuitableCommenter == null && myFile.getFileType() instanceof AbstractFileType) {
      blockSuitableCommenter =
          new Commenter() {
            final SyntaxTable mySyntaxTable =
                ((AbstractFileType) myFile.getFileType()).getSyntaxTable();

            @Nullable
            public String getLineCommentPrefix() {
              return mySyntaxTable.getLineComment();
            }

            @Nullable
            public String getBlockCommentPrefix() {
              return mySyntaxTable.getStartComment();
            }

            @Nullable
            public String getBlockCommentSuffix() {
              return mySyntaxTable.getEndComment();
            }

            public String getCommentedBlockCommentPrefix() {
              return null;
            }

            public String getCommentedBlockCommentSuffix() {
              return null;
            }
          };
    }

    boolean allLineCommented = true;
    boolean commentWithIndent =
        !CodeStyleSettingsManager.getSettings(myProject).LINE_COMMENT_AT_FIRST_COLUMN;

    for (int line = myStartLine; line <= myEndLine; line++) {
      Commenter commenter =
          blockSuitableCommenter != null ? blockSuitableCommenter : findCommenter(line);
      if (commenter == null) return;
      if (commenter.getLineCommentPrefix() == null
          && (commenter.getBlockCommentPrefix() == null
              || commenter.getBlockCommentSuffix() == null)) {
        return;
      }

      if (commenter instanceof SelfManagingCommenter
          && myCommenterStateMap.get(commenter) == null) {
        final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
        CommenterDataHolder state =
            selfManagingCommenter.createLineCommentingState(
                myStartLine, myEndLine, myDocument, myFile);
        if (state == null) state = SelfManagingCommenter.EMPTY_STATE;
        myCommenterStateMap.put(selfManagingCommenter, state);
      }

      myCommenters[line - myStartLine] = commenter;
      if (!isLineCommented(line, chars, commenter) && (singleline || !isLineEmpty(line))) {
        allLineCommented = false;
        if (commenter instanceof IndentedCommenter) {
          final Boolean value = ((IndentedCommenter) commenter).forceIndentedLineComment();
          if (value != null) {
            commentWithIndent = value;
          }
        }
        break;
      }
    }

    if (!allLineCommented) {
      if (!commentWithIndent) {
        doDefaultCommenting(blockSuitableCommenter);
      } else {
        doIndentCommenting(blockSuitableCommenter);
      }
    } else {
      for (int line = myEndLine; line >= myStartLine; line--) {
        uncommentLine(line);
        // int offset1 = myStartOffsets[line - myStartLine];
        // int offset2 = myEndOffsets[line - myStartLine];
        // if (offset1 == offset2) continue;
        // Commenter commenter = myCommenters[line - myStartLine];
        // String prefix = commenter.getBlockCommentPrefix();
        // if (prefix == null || !myDocument.getText().substring(offset1,
        // myDocument.getTextLength()).startsWith(prefix)) {
        //  prefix = commenter.getLineCommentPrefix();
        // }
        //
        // String suffix = commenter.getBlockCommentSuffix();
        // if (suffix == null && prefix != null) suffix = "";
        //
        // if (prefix != null && suffix != null) {
        //  final int suffixLen = suffix.length();
        //  final int prefixLen = prefix.length();
        //  if (offset2 >= 0) {
        //    if (!CharArrayUtil.regionMatches(chars, offset1 + prefixLen, prefix)) {
        //      myDocument.deleteString(offset2 - suffixLen, offset2);
        //    }
        //  }
        //  if (offset1 >= 0) {
        //    for (int i = offset2 - suffixLen - 1; i > offset1 + prefixLen; --i) {
        //      if (CharArrayUtil.regionMatches(chars, i, suffix)) {
        //        myDocument.deleteString(i, i + suffixLen);
        //      }
        //      else if (CharArrayUtil.regionMatches(chars, i, prefix)) {
        //        myDocument.deleteString(i, i + prefixLen);
        //      }
        //    }
        //    myDocument.deleteString(offset1, offset1 + prefixLen);
        //  }
        // }
      }
    }
  }
  public void uncommentRange(
      TextRange range, String commentPrefix, String commentSuffix, Commenter commenter) {
    if (commenter instanceof SelfManagingCommenter) {
      final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
      selfManagingCommenter.uncommentBlockComment(
          range.getStartOffset(), range.getEndOffset(), myDocument, mySelfManagedCommenterData);
      return;
    }

    String text =
        myDocument
            .getCharsSequence()
            .subSequence(range.getStartOffset(), range.getEndOffset())
            .toString();
    int startOffset = range.getStartOffset();
    // boolean endsProperly = CharArrayUtil.regionMatches(chars, range.getEndOffset() -
    // commentSuffix.length(), commentSuffix);
    List<Couple<TextRange>> ranges = new ArrayList<>();

    if (commenter instanceof CustomUncommenter) {
      /*
       In case of custom uncommenter, we need to ask it for list of [commentOpen-start,commentOpen-end], [commentClose-start,commentClose-end]
       and shift if according to current offset
      */
      CustomUncommenter customUncommenter = (CustomUncommenter) commenter;
      for (Couple<TextRange> coupleFromCommenter :
          customUncommenter.getCommentRangesToDelete(text)) {
        TextRange openComment = coupleFromCommenter.first.shiftRight(startOffset);
        TextRange closeComment = coupleFromCommenter.second.shiftRight(startOffset);
        ranges.add(Couple.of(openComment, closeComment));
      }
    } else {
      // If commenter is not custom, we need to get this list by our selves
      int position = 0;
      while (true) {
        int start = getNearest(text, commentPrefix, position);
        if (start == text.length()) {
          break;
        }
        position = start;
        int end =
            getNearest(text, commentSuffix, position + commentPrefix.length())
                + commentSuffix.length();
        position = end;
        Couple<TextRange> pair =
            findCommentBlock(
                new TextRange(start + startOffset, end + startOffset),
                commentPrefix,
                commentSuffix);
        ranges.add(pair);
      }
    }

    RangeMarker marker = myDocument.createRangeMarker(range);
    try {
      for (int i = ranges.size() - 1; i >= 0; i--) {
        Couple<TextRange> toDelete = ranges.get(i);
        myDocument.deleteString(toDelete.first.getStartOffset(), toDelete.first.getEndOffset());
        int shift = toDelete.first.getEndOffset() - toDelete.first.getStartOffset();
        myDocument.deleteString(
            toDelete.second.getStartOffset() - shift, toDelete.second.getEndOffset() - shift);
        if (commenter.getCommentedBlockCommentPrefix() != null) {
          commentNestedComments(
              myDocument,
              new TextRange(
                  toDelete.first.getEndOffset() - shift, toDelete.second.getStartOffset() - shift),
              commenter);
        }
      }

      processDocument(myDocument, marker, commenter, false);
    } finally {
      marker.dispose();
    }
  }
  @Override
  public void invoke(
      @NotNull Project project,
      @NotNull Editor editor,
      @NotNull Caret caret,
      @NotNull PsiFile file) {
    if (!CodeInsightUtilBase.prepareEditorForWrite(editor)) return;
    myProject = project;
    myEditor = editor;
    myCaret = caret;
    myFile = file;

    myDocument = editor.getDocument();

    if (!FileDocumentManager.getInstance().requestWriting(myDocument, project)) {
      return;
    }
    FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.block");
    final Commenter commenter = findCommenter(myFile, myEditor, caret);
    if (commenter == null) return;

    final String prefix;
    final String suffix;

    if (commenter instanceof SelfManagingCommenter) {
      final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
      mySelfManagedCommenterData =
          selfManagingCommenter.createBlockCommentingState(
              caret.getSelectionStart(), caret.getSelectionEnd(), myDocument, myFile);

      if (mySelfManagedCommenterData == null) {
        mySelfManagedCommenterData = SelfManagingCommenter.EMPTY_STATE;
      }

      prefix =
          selfManagingCommenter.getBlockCommentPrefix(
              caret.getSelectionStart(), myDocument, mySelfManagedCommenterData);
      suffix =
          selfManagingCommenter.getBlockCommentSuffix(
              caret.getSelectionEnd(), myDocument, mySelfManagedCommenterData);
    } else {
      prefix = commenter.getBlockCommentPrefix();
      suffix = commenter.getBlockCommentSuffix();
    }

    if (prefix == null || suffix == null) return;

    TextRange commentedRange = findCommentedRange(commenter);
    if (commentedRange != null) {
      final int commentStart = commentedRange.getStartOffset();
      final int commentEnd = commentedRange.getEndOffset();
      int selectionStart = commentStart;
      int selectionEnd = commentEnd;
      if (myCaret.hasSelection()) {
        selectionStart = myCaret.getSelectionStart();
        selectionEnd = myCaret.getSelectionEnd();
      }
      if ((commentStart < selectionStart || commentStart >= selectionEnd)
          && (commentEnd <= selectionStart || commentEnd > selectionEnd)) {
        commentRange(selectionStart, selectionEnd, prefix, suffix, commenter);
      } else {
        uncommentRange(commentedRange, trim(prefix), trim(suffix), commenter);
      }
    } else {
      if (myCaret.hasSelection()) {
        int selectionStart = myCaret.getSelectionStart();
        int selectionEnd = myCaret.getSelectionEnd();
        if (commenter instanceof IndentedCommenter) {
          final Boolean value = ((IndentedCommenter) commenter).forceIndentedLineComment();
          if (value != null && value == Boolean.TRUE) {
            selectionStart =
                myDocument.getLineStartOffset(myDocument.getLineNumber(selectionStart));
            selectionEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(selectionEnd));
          }
        }
        commentRange(selectionStart, selectionEnd, prefix, suffix, commenter);
      } else {
        EditorUtil.fillVirtualSpaceUntilCaret(editor);
        int caretOffset = myCaret.getOffset();
        if (commenter instanceof IndentedCommenter) {
          final Boolean value = ((IndentedCommenter) commenter).forceIndentedLineComment();
          if (value != null && value == Boolean.TRUE) {
            final int lineNumber = myDocument.getLineNumber(caretOffset);
            final int start = myDocument.getLineStartOffset(lineNumber);
            final int end = myDocument.getLineEndOffset(lineNumber);
            commentRange(start, end, prefix, suffix, commenter);
            return;
          }
        }
        myDocument.insertString(caretOffset, prefix + suffix);
        myCaret.moveToOffset(caretOffset + prefix.length());
      }
    }
  }
  private TextRange insertNestedComments(
      int startOffset,
      int endOffset,
      String commentPrefix,
      String commentSuffix,
      Commenter commenter) {
    if (commenter instanceof SelfManagingCommenter) {
      final SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;
      return selfManagingCommenter.insertBlockComment(
          startOffset, endOffset, myDocument, mySelfManagedCommenterData);
    }

    String normalizedPrefix = commentPrefix.trim();
    String normalizedSuffix = commentSuffix.trim();
    IntArrayList nestedCommentPrefixes = new IntArrayList();
    IntArrayList nestedCommentSuffixes = new IntArrayList();
    String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
    String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
    CharSequence chars = myDocument.getCharsSequence();
    for (int i = startOffset; i < endOffset; ++i) {
      if (CharArrayUtil.regionMatches(chars, i, normalizedPrefix)) {
        nestedCommentPrefixes.add(i);
      } else {
        if (CharArrayUtil.regionMatches(chars, i, normalizedSuffix)) {
          nestedCommentSuffixes.add(i);
        }
      }
    }
    int shift = 0;
    if (!(commentedSuffix == null
        && !nestedCommentSuffixes.isEmpty()
        && nestedCommentSuffixes.get(nestedCommentSuffixes.size() - 1) + commentSuffix.length()
            == endOffset)) {
      myDocument.insertString(endOffset, commentSuffix);
      shift += commentSuffix.length();
    }

    // process nested comments in back order
    int i = nestedCommentPrefixes.size() - 1;
    int j = nestedCommentSuffixes.size() - 1;
    final TextRange selection = new TextRange(startOffset, endOffset);
    while (i >= 0 && j >= 0) {
      final int prefixIndex = nestedCommentPrefixes.get(i);
      final int suffixIndex = nestedCommentSuffixes.get(j);
      if (prefixIndex > suffixIndex) {
        shift +=
            doBoundCommentingAndGetShift(
                prefixIndex,
                commentedPrefix,
                normalizedPrefix.length(),
                commentSuffix,
                false,
                selection);
        --i;
      } else {
        // if (insertPos < myDocument.getTextLength() &&
        // Character.isWhitespace(myDocument.getCharsSequence().charAt(insertPos))) {
        //  insertPos = suffixIndex + commentSuffix.length();
        // }
        shift +=
            doBoundCommentingAndGetShift(
                suffixIndex,
                commentedSuffix,
                normalizedSuffix.length(),
                commentPrefix,
                true,
                selection);
        --j;
      }
    }
    while (i >= 0) {
      final int prefixIndex = nestedCommentPrefixes.get(i);
      shift +=
          doBoundCommentingAndGetShift(
              prefixIndex,
              commentedPrefix,
              normalizedPrefix.length(),
              commentSuffix,
              false,
              selection);
      --i;
    }
    while (j >= 0) {
      final int suffixIndex = nestedCommentSuffixes.get(j);
      shift +=
          doBoundCommentingAndGetShift(
              suffixIndex,
              commentedSuffix,
              normalizedSuffix.length(),
              commentPrefix,
              true,
              selection);
      --j;
    }
    if (!(commentedPrefix == null
        && !nestedCommentPrefixes.isEmpty()
        && nestedCommentPrefixes.get(0) == startOffset)) {
      myDocument.insertString(startOffset, commentPrefix);
      shift += commentPrefix.length();
    }

    RangeMarker marker = myDocument.createRangeMarker(startOffset, endOffset + shift);
    try {
      return processDocument(myDocument, marker, commenter, true);
    } finally {
      marker.dispose();
    }
  }
  @Nullable
  private TextRange findCommentedRange(final Commenter commenter) {
    final CharSequence text = myDocument.getCharsSequence();
    final FileType fileType = myFile.getFileType();
    if (fileType instanceof CustomSyntaxTableFileType) {
      Lexer lexer =
          new CustomFileTypeLexer(((CustomSyntaxTableFileType) fileType).getSyntaxTable());
      final int caretOffset = myCaret.getOffset();
      int commentStart =
          CharArrayUtil.lastIndexOf(text, commenter.getBlockCommentPrefix(), caretOffset);
      if (commentStart == -1) return null;

      lexer.start(text, commentStart, text.length());
      if (lexer.getTokenType() == CustomHighlighterTokenType.MULTI_LINE_COMMENT
          && lexer.getTokenEnd() >= caretOffset) {
        return new TextRange(commentStart, lexer.getTokenEnd());
      }
      return null;
    }

    final String prefix;
    final String suffix;
    // Custom uncommenter is able to find commented block inside of selected text
    final String selectedText = myCaret.getSelectedText();
    if ((commenter instanceof CustomUncommenter) && selectedText != null) {
      final TextRange commentedRange =
          ((CustomUncommenter) commenter).findMaximumCommentedRange(selectedText);
      if (commentedRange == null) {
        return null;
      }
      // Uncommenter returns range relative to text start, so we need to shift it to make abosolute.
      return commentedRange.shiftRight(myCaret.getSelectionStart());
    }

    if (commenter instanceof SelfManagingCommenter) {
      SelfManagingCommenter selfManagingCommenter = (SelfManagingCommenter) commenter;

      prefix =
          selfManagingCommenter.getBlockCommentPrefix(
              myCaret.getSelectionStart(), myDocument, mySelfManagedCommenterData);
      suffix =
          selfManagingCommenter.getBlockCommentSuffix(
              myCaret.getSelectionEnd(), myDocument, mySelfManagedCommenterData);
    } else {
      prefix = trim(commenter.getBlockCommentPrefix());
      suffix = trim(commenter.getBlockCommentSuffix());
    }
    if (prefix == null || suffix == null) return null;

    TextRange commentedRange;

    if (commenter instanceof SelfManagingCommenter) {
      commentedRange =
          ((SelfManagingCommenter) commenter)
              .getBlockCommentRange(
                  myCaret.getSelectionStart(),
                  myCaret.getSelectionEnd(),
                  myDocument,
                  mySelfManagedCommenterData);
    } else {
      if (!testSelectionForNonComments()) {
        return null;
      }

      commentedRange = getSelectedComments(text, prefix, suffix);
    }
    if (commentedRange == null) {
      PsiElement comment = findCommentAtCaret();
      if (comment != null) {

        String commentText = comment.getText();
        if (commentText.startsWith(prefix) && commentText.endsWith(suffix)) {
          commentedRange = comment.getTextRange();
        }
      }
    }
    return commentedRange;
  }