public void testRangeMarkersAreLazyCreated() throws Exception {
    final Document document = EditorFactory.getInstance().createDocument("[xxxxxxxxxxxxxx]");
    RangeMarker m1 = document.createRangeMarker(2, 4);
    RangeMarker m2 = document.createRangeMarker(2, 4);

    assertEquals(2, ((DocumentImpl) document).getRangeMarkersSize());
    assertEquals(1, ((DocumentImpl) document).getRangeMarkersNodeSize());

    RangeMarker m3 = document.createRangeMarker(2, 5);
    assertEquals(2, ((DocumentImpl) document).getRangeMarkersNodeSize());
    document.deleteString(4, 5);
    assertTrue(m1.isValid());
    assertTrue(m2.isValid());
    assertTrue(m3.isValid());
    assertEquals(1, ((DocumentImpl) document).getRangeMarkersNodeSize());

    m1.setGreedyToLeft(true);
    assertTrue(m1.isValid());
    assertEquals(3, ((DocumentImpl) document).getRangeMarkersSize());
    assertEquals(2, ((DocumentImpl) document).getRangeMarkersNodeSize());

    m3.dispose();
    assertTrue(m1.isValid());
    assertTrue(m2.isValid());
    assertFalse(m3.isValid());
    assertEquals(2, ((DocumentImpl) document).getRangeMarkersSize());
    assertEquals(2, ((DocumentImpl) document).getRangeMarkersNodeSize());
  }
  @NotNull
  private static Document setupFileEditorAndDocument(
      @NotNull String fileName, @NotNull String fileText) throws IOException {
    EncodingProjectManager.getInstance(getProject()).setEncoding(null, CharsetToolkit.UTF8_CHARSET);
    EncodingProjectManager.getInstance(ProjectManager.getInstance().getDefaultProject())
        .setEncoding(null, CharsetToolkit.UTF8_CHARSET);
    PostprocessReformattingAspect.getInstance(ourProject).doPostponedFormatting();
    deleteVFile();
    myVFile = getSourceRoot().createChildData(null, fileName);
    VfsUtil.saveText(myVFile, fileText);
    final FileDocumentManager manager = FileDocumentManager.getInstance();
    final Document document = manager.getDocument(myVFile);
    assertNotNull("Can't create document for '" + fileName + "'", document);
    manager.reloadFromDisk(document);
    document.insertString(0, " ");
    document.deleteString(0, 1);
    myFile = getPsiManager().findFile(myVFile);
    assertNotNull(
        "Can't create PsiFile for '" + fileName + "'. Unknown file type most probably.", myFile);
    assertTrue(myFile.isPhysical());
    myEditor = createEditor(myVFile);
    myVFile.setCharset(CharsetToolkit.UTF8_CHARSET);

    PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
    return document;
  }
 private void uncommentRange(int startOffset, int endOffset, @NotNull Commenter commenter) {
   final String commentedSuffix = commenter.getCommentedBlockCommentSuffix();
   final String commentedPrefix = commenter.getCommentedBlockCommentPrefix();
   final String prefix = commenter.getBlockCommentPrefix();
   final String suffix = commenter.getBlockCommentSuffix();
   if (prefix == null || suffix == null) {
     return;
   }
   if (endOffset >= suffix.length()
       && CharArrayUtil.regionMatches(
           myDocument.getCharsSequence(), endOffset - suffix.length(), suffix)) {
     myDocument.deleteString(endOffset - suffix.length(), endOffset);
   }
   if (commentedPrefix != null && commentedSuffix != null) {
     CommentByBlockCommentHandler.commentNestedComments(
         myDocument, new TextRange(startOffset, endOffset), commenter);
   }
   myDocument.deleteString(startOffset, startOffset + prefix.length());
 }
    private static void removeTrailingSpaces(final Document document, final int startOffset) {
      int endOffset = startOffset;

      final CharSequence charsSequence = document.getCharsSequence();

      for (int i = startOffset; i < charsSequence.length(); i++) {
        final char c = charsSequence.charAt(i);
        endOffset = i;
        if (c == '\n') {
          break;
        }
        if (c != ' ' && c != '\t') {
          return;
        }
      }

      document.deleteString(startOffset, endOffset);
    }
  public void testValidationBug() throws Exception {
    Document document = EditorFactory.getInstance().createDocument("[xxxxxxxxxxxxxx]");
    final Editor editor = EditorFactory.getInstance().createEditor(document);

    try {
      final FoldRegion[] fold = new FoldRegion[1];
      editor
          .getFoldingModel()
          .runBatchFoldingOperation(
              new Runnable() {
                @Override
                public void run() {
                  fold[0] = editor.getFoldingModel().addFoldRegion(0, 2, "");
                }
              });
      RangeMarker marker = document.createRangeMarker(0, 2);
      document.deleteString(1, 2);

      assertTrue(marker.isValid());
      // assertFalse(fold[0].isValid());
    } finally {
      EditorFactory.getInstance().releaseEditor(editor);
    }
  }
  public final void move(Editor editor, final PsiFile file) {
    myMover.beforeMove(editor, myInfo, myIsDown);
    final Document document = editor.getDocument();
    final int start =
        StatementUpDownMover.getLineStartSafeOffset(document, myInfo.toMove.startLine);
    final int end = StatementUpDownMover.getLineStartSafeOffset(document, myInfo.toMove.endLine);
    myInfo.range1 = document.createRangeMarker(start, end);

    String textToInsert = document.getCharsSequence().subSequence(start, end).toString();
    if (!StringUtil.endsWithChar(textToInsert, '\n')) textToInsert += '\n';

    final int start2 = document.getLineStartOffset(myInfo.toMove2.startLine);
    final int end2 = StatementUpDownMover.getLineStartSafeOffset(document, myInfo.toMove2.endLine);
    String textToInsert2 = document.getCharsSequence().subSequence(start2, end2).toString();
    if (!StringUtil.endsWithChar(textToInsert2, '\n')) textToInsert2 += '\n';
    myInfo.range2 = document.createRangeMarker(start2, end2);
    if (myInfo.range1.getStartOffset() < myInfo.range2.getStartOffset()) {
      myInfo.range1.setGreedyToLeft(true);
      myInfo.range1.setGreedyToRight(false);
      myInfo.range2.setGreedyToLeft(true);
      myInfo.range2.setGreedyToRight(true);
    } else {
      myInfo.range1.setGreedyToLeft(true);
      myInfo.range1.setGreedyToRight(true);
      myInfo.range2.setGreedyToLeft(true);
      myInfo.range2.setGreedyToRight(false);
    }

    final CaretModel caretModel = editor.getCaretModel();
    final int caretRelativePos = caretModel.getOffset() - start;
    final SelectionModel selectionModel = editor.getSelectionModel();
    final int selectionStart = selectionModel.getSelectionStart();
    final int selectionEnd = selectionModel.getSelectionEnd();
    final boolean hasSelection = selectionModel.hasSelection();

    // to prevent flicker
    caretModel.moveToOffset(0);

    // There is a possible case that the user performs, say, method move. It's also possible that
    // one (or both) of moved methods
    // are folded. We want to preserve their states then. The problem is that folding processing is
    // based on PSI element pointers
    // and the pointers behave as following during move up/down:
    //     method1() {}
    //     method2() {}
    // Pointer for the fold region from method1 points to 'method2()' now and vice versa (check
    // range markers processing on
    // document change for further information). I.e. information about fold regions statuses holds
    // the data swapped for
    // 'method1' and 'method2'. Hence, we want to apply correct 'collapsed' status.
    FoldRegion topRegion = null;
    FoldRegion bottomRegion = null;
    for (FoldRegion foldRegion : editor.getFoldingModel().getAllFoldRegions()) {
      if (!foldRegion.isValid()
          || (!contains(myInfo.range1, foldRegion) && !contains(myInfo.range2, foldRegion))) {
        continue;
      }
      if (contains(myInfo.range1, foldRegion) && !contains(topRegion, foldRegion)) {
        topRegion = foldRegion;
      } else if (contains(myInfo.range2, foldRegion) && !contains(bottomRegion, foldRegion)) {
        bottomRegion = foldRegion;
      }
    }

    document.insertString(myInfo.range1.getStartOffset(), textToInsert2);
    document.deleteString(
        myInfo.range1.getStartOffset() + textToInsert2.length(), myInfo.range1.getEndOffset());

    document.insertString(myInfo.range2.getStartOffset(), textToInsert);
    int s = myInfo.range2.getStartOffset() + textToInsert.length();
    int e = myInfo.range2.getEndOffset();
    if (e > s) {
      document.deleteString(s, e);
    }

    final Project project = file.getProject();
    PsiDocumentManager.getInstance(project).commitAllDocuments();

    // Swap fold regions status if necessary.
    if (topRegion != null && bottomRegion != null) {
      final FoldRegion finalTopRegion = topRegion;
      final FoldRegion finalBottomRegion = bottomRegion;
      editor
          .getFoldingModel()
          .runBatchFoldingOperation(
              new Runnable() {
                @Override
                public void run() {
                  boolean topExpanded = finalTopRegion.isExpanded();
                  finalTopRegion.setExpanded(finalBottomRegion.isExpanded());
                  finalBottomRegion.setExpanded(topExpanded);
                }
              });
    }
    CodeFoldingManager.getInstance(project).allowFoldingOnCaretLine(editor);

    if (hasSelection) {
      restoreSelection(editor, selectionStart, selectionEnd, start, myInfo.range2.getStartOffset());
    }

    caretModel.moveToOffset(myInfo.range2.getStartOffset() + caretRelativePos);
    if (myInfo.indentTarget) {
      indentLinesIn(editor, file, document, project, myInfo.range2);
    }
    if (myInfo.indentSource) {
      indentLinesIn(editor, file, document, project, myInfo.range1);
    }

    myMover.afterMove(editor, file, myInfo, myIsDown);
    editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
  }
Exemplo n.º 7
0
  private static void indentBlockWithFormatter(
      Project project, Document document, int startOffset, int endOffset, PsiFile file) {

    // Algorithm: the main idea is to process the first line of the pasted block, adjust its indent
    // if necessary, calculate indent
    // adjustment string and apply to each line of the pasted block starting from the second one.
    //
    // We differentiate the following possible states here:
    //   --- pasted block doesn't start new line, i.e. there are non-white space symbols before it
    // at the first line.
    //      Example:
    //         old content [pasted line 1
    //                pasted line 2]
    //      Indent adjustment string is just the first line indent then.
    //
    //   --- pasted block starts with empty line(s)
    //      Example:
    //         old content [
    //            pasted line 1
    //            pasted line 2]
    //      We parse existing indents of the pasted block then, adjust its first non-blank line via
    // formatter and adjust indent
    //      of subsequent pasted lines in order to preserve old indentation.
    //
    //   --- pasted block is located at the new line and starts with white space symbols.
    //       Example:
    //          [   pasted line 1
    //                 pasted line 2]
    //       We parse existing indents of the pasted block then, adjust its first line via formatter
    // and adjust indent of the pasted lines
    //       starting from the second one in order to preserve old indentation.
    //
    //   --- pasted block is located at the new line but doesn't start with white space symbols.
    //       Example:
    //           [pasted line 1
    //         pasted line 2]
    //       We adjust the first line via formatter then and apply first line's indent to all
    // subsequent pasted lines.

    CharSequence chars = document.getCharsSequence();
    final int firstLine = document.getLineNumber(startOffset);
    final int firstLineStart = document.getLineStartOffset(firstLine);

    // There is a possible case that we paste block that ends with new line that is empty or
    // contains only white space symbols.
    // We want to preserve indent for the original document line where paste was performed.
    // Example:
    //   Original:
    //       if (test) {
    //   <caret>    }
    //
    //   Pasting: 'int i = 1;\n'
    //   Expected:
    //       if (test) {
    //           int i = 1;
    //       }
    boolean saveLastLineIndent = false;
    for (int i = endOffset - 1; i >= startOffset; i--) {
      final char c = chars.charAt(i);
      if (c == '\n') {
        saveLastLineIndent = true;
        break;
      }
      if (c != ' ' && c != '\t') {
        break;
      }
    }

    final int lastLine;
    if (saveLastLineIndent) {
      lastLine = document.getLineNumber(endOffset) - 1;
      // Remove white space symbols at the pasted text if any.
      int start = document.getLineStartOffset(lastLine + 1);
      if (start < endOffset) {
        int i = CharArrayUtil.shiftForward(chars, start, " \t");
        if (i > start) {
          i = Math.min(i, endOffset);
          document.deleteString(start, i);
        }
      }

      // Insert white space from the start line of the pasted block.
      int indentToKeepEndOffset =
          Math.min(startOffset, CharArrayUtil.shiftForward(chars, firstLineStart, " \t"));
      if (indentToKeepEndOffset > firstLineStart) {
        document.insertString(start, chars.subSequence(firstLineStart, indentToKeepEndOffset));
      }
    } else {
      lastLine = document.getLineNumber(endOffset);
    }

    final int i = CharArrayUtil.shiftBackward(chars, startOffset - 1, " \t");

    // Handle a situation when pasted block doesn't start a new line.
    if (chars.charAt(startOffset) != '\n' && i > 0 && chars.charAt(i) != '\n') {
      int firstNonWsOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t");
      if (firstNonWsOffset > firstLineStart) {
        CharSequence toInsert = chars.subSequence(firstLineStart, firstNonWsOffset);
        for (int line = firstLine + 1; line <= lastLine; line++) {
          document.insertString(document.getLineStartOffset(line), toInsert);
        }
      }
      return;
    }

    // Sync document and PSI for correct formatting processing.
    PsiDocumentManager.getInstance(project).commitAllDocuments();
    if (file == null) {
      return;
    }
    CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);

    final int j = CharArrayUtil.shiftForward(chars, startOffset, " \t\n");
    if (j >= endOffset) {
      // Pasted text contains white space/line feed symbols only, do nothing.
      return;
    }

    final int anchorLine = document.getLineNumber(j);
    final int anchorLineStart = document.getLineStartOffset(anchorLine);
    codeStyleManager.adjustLineIndent(file, j);

    // Handle situation when pasted block starts with non-white space symbols.
    if (anchorLine == firstLine && j == startOffset) {
      int indentOffset = CharArrayUtil.shiftForward(chars, firstLineStart, " \t");
      if (indentOffset > firstLineStart) {
        CharSequence toInsert = chars.subSequence(firstLineStart, indentOffset);
        for (int line = firstLine + 1; line <= lastLine; line++) {
          document.insertString(document.getLineStartOffset(line), toInsert);
        }
      }
      return;
    }

    // Handle situation when pasted block starts from white space symbols. Assume that the pasted
    // text started at the line start,
    // i.e. correct indentation level is stored at the blocks structure.
    final int firstNonWsOffset = CharArrayUtil.shiftForward(chars, anchorLineStart, " \t");
    final int diff = firstNonWsOffset - j;
    if (diff == 0) {
      return;
    }
    if (diff > 0) {
      CharSequence toInsert = chars.subSequence(anchorLineStart, anchorLineStart + diff);
      for (int line = anchorLine + 1; line <= lastLine; line++) {
        document.insertString(document.getLineStartOffset(line), toInsert);
      }
      return;
    }

    // We've pasted text to the non-first column and exact white space between the line start and
    // caret position on the moment of paste
    // has been removed by formatter during 'adjust line indent'
    // Example:
    //       copied text:
    //                 '   line1
    //                       line2'
    //       after paste:
    //          line start -> '   I   line1
    //                              line2' (I - caret position during 'paste')
    //       formatter removed white space between the line start and caret position, so, current
    // document state is:
    //                        '   line1
    //                              line2'
    if (anchorLine == firstLine && -diff == startOffset - firstLineStart) {
      return;
    }
    if (anchorLine != firstLine || -diff > startOffset - firstLineStart) {
      final int desiredSymbolsToRemove;
      if (anchorLine == firstLine) {
        desiredSymbolsToRemove = -diff - (startOffset - firstLineStart);
      } else {
        desiredSymbolsToRemove = -diff;
      }

      for (int line = anchorLine + 1; line <= lastLine; line++) {
        int currentLineStart = document.getLineStartOffset(line);
        int currentLineIndentOffset = CharArrayUtil.shiftForward(chars, currentLineStart, " \t");
        int symbolsToRemove =
            Math.min(currentLineIndentOffset - currentLineStart, desiredSymbolsToRemove);
        if (symbolsToRemove > 0) {
          document.deleteString(currentLineStart, currentLineStart + symbolsToRemove);
        }
      }
    } else {
      CharSequence toInsert = chars.subSequence(anchorLineStart, diff + startOffset);
      for (int line = anchorLine + 1; line <= lastLine; line++) {
        document.insertString(document.getLineStartOffset(line), toInsert);
      }
    }
  }
    private void generateJavadoc(CodeDocumentationAwareCommenter commenter)
        throws IncorrectOperationException {
      CodeInsightSettings settings = CodeInsightSettings.getInstance();
      StringBuilder buffer = new StringBuilder();
      final String docCommentLinePrefix = commenter.getDocumentationCommentLinePrefix();
      if (docCommentLinePrefix == null) {
        return;
      }

      // There are at least two approaches for completing javadoc in case there is a text between
      // current caret position and line end:
      //     1. Move that tail text below the javadoc. Use-case:
      //         Before:
      //             /**<caret>public void foo() {}
      //         After:
      //             /**
      //              */
      //             public void foo() {}
      //     2. Move the tail text inside the javadoc. Use-case:
      //          Before:
      //             /**This is <caret>javadoc description
      //          After:
      //             /** This is
      //              * javadoc description
      //              */
      // The later is most relevant when we have 'auto wrap when typing reaches right margin' option
      // set, i.e. user starts javadoc
      // and types until right margin is reached. We want the wrapped text tail to be located inside
      // javadoc and continue typing
      // inside it. So, we have a control flow branch below that does the trick.
      buffer.append(docCommentLinePrefix);
      if (DataManager.getInstance()
              .loadFromDataContext(
                  myDataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY)
          == Boolean.TRUE) {
        myDocument.insertString(myOffset, buffer);

        // We create new buffer here because the one referenced by current 'buffer' variable value
        // may be already referenced at another
        // place (e.g. 'undo' processing stuff).
        buffer =
            new StringBuilder(LINE_SEPARATOR).append(commenter.getDocumentationCommentSuffix());
        int line = myDocument.getLineNumber(myOffset);
        myOffset = myDocument.getLineEndOffset(line);
      } else {
        buffer.append(LINE_SEPARATOR);
        buffer.append(commenter.getDocumentationCommentSuffix());
      }

      PsiComment comment = createComment(buffer, settings);
      if (comment == null) {
        return;
      }

      myOffset = comment.getTextRange().getStartOffset();
      CharSequence text = myDocument.getCharsSequence();
      myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, LINE_SEPARATOR);
      myOffset = CharArrayUtil.shiftForward(text, myOffset, LINE_SEPARATOR);
      myOffset = CharArrayUtil.shiftForwardUntil(text, myOffset, docCommentLinePrefix) + 1;
      removeTrailingSpaces(myDocument, myOffset);

      if (!CodeStyleSettingsManager.getSettings(getProject()).JD_LEADING_ASTERISKS_ARE_ENABLED) {
        LOG.assertTrue(
            CharArrayUtil.regionMatches(
                myDocument.getCharsSequence(),
                myOffset - docCommentLinePrefix.length(),
                docCommentLinePrefix));
        myDocument.deleteString(myOffset - docCommentLinePrefix.length(), myOffset);
        myOffset--;
      } else {
        myDocument.insertString(myOffset, " ");
        myOffset++;
      }

      PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
    }
  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);
    }
  }
  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();
    }
  }