Пример #1
0
  public void testRemoveHugeLogicalLineThatLaysBeforeSoftWrappedLines() throws IOException {
    String text =
        "short line\n"
            + "this is a long line that is expected to be soft wrapped into more than one or even two visual lines\n"
            + "1. just a line that is long enough to be soft wrapped\n"
            + "2. just a line that is long enough to be soft wrapped\n"
            + "3. just a line that is long enough to be soft wrapped\n"
            + "4. just a line that is long enough to be soft wrapped";

    init(15, text);
    Document document = myEditor.getDocument();
    int start = document.getLineStartOffset(1);
    int end = document.getLineEndOffset(1) + 1;
    int visualLinesToRemove = getSoftWrapModel().getSoftWrapsForLine(1).size() + 1;

    List<VisualPosition> positionsBefore = new ArrayList<VisualPosition>();
    for (int i = end; i < text.length(); i++) {
      positionsBefore.add(myEditor.offsetToVisualPosition(i));
    }
    Collections.reverse(positionsBefore);

    myEditor.getSelectionModel().setSelection(start, end);
    delete();

    // Check that all remembered positions are just shifted to expected number of visual lines.
    for (int i = start; i < document.getTextLength(); i++) {
      VisualPosition position = positionsBefore.remove(positionsBefore.size() - 1);
      assertEquals(
          new VisualPosition(position.line - visualLinesToRemove, position.column),
          myEditor.offsetToVisualPosition(i));
    }
  }
Пример #2
0
  private VerticalInfo createVerticalInfo(LogicalPosition position) {
    Document document = myEditor.getDocument();
    int logicalLine = position.line;
    if (logicalLine >= document.getLineCount()) {
      logicalLine = Math.max(0, document.getLineCount() - 1);
    }
    int startOffset = document.getLineStartOffset(logicalLine);
    int endOffset = document.getLineEndOffset(logicalLine);

    // There is a possible case that active logical line is represented on multiple lines due to
    // soft wraps processing.
    // We want to highlight those visual lines as 'active' then, so, we calculate 'y' position for
    // the logical line start
    // and height in accordance with the number of occupied visual lines.
    VisualPosition visualPosition =
        myEditor.offsetToVisualPosition(document.getLineStartOffset(logicalLine));
    int y = myEditor.visualPositionToXY(visualPosition).y;
    int lineHeight = myEditor.getLineHeight();
    int height = lineHeight;
    List<? extends SoftWrap> softWraps =
        myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset);
    for (SoftWrap softWrap : softWraps) {
      height += StringUtil.countNewLines(softWrap.getText()) * lineHeight;
    }

    return new VerticalInfo(y, height);
  }
  public void testPastingInsideSelection() throws IOException {
    String text =
        "this is line number 0\n"
            + "this is line number 1\n"
            + "this is line number 2\n"
            + "this is line number 3\n"
            + "this is line number 4\n"
            + "this is line number 5\n"
            + "this is line number 6\n"
            + "this is the last line";

    init(100, text);
    int lineToSelect = 4;
    myEditor.getCaretModel().moveToOffset(text.indexOf("number " + lineToSelect));
    Document document = myEditor.getDocument();

    int startOffset = document.getLineStartOffset(lineToSelect);
    int endOffset = document.getLineEndOffset(lineToSelect);
    myEditor.getSelectionModel().setSelection(startOffset, endOffset);

    VisualPosition positionBefore =
        myEditor.offsetToVisualPosition(document.getLineStartOffset(lineToSelect + 1));
    List<SoftWrap> softWrapsBefore = new ArrayList<>(getSoftWrapModel().getRegisteredSoftWraps());

    copy();
    paste();

    assertEquals(
        positionBefore,
        myEditor.offsetToVisualPosition(document.getLineStartOffset(lineToSelect + 1)));
    assertEquals(softWrapsBefore, getSoftWrapModel().getRegisteredSoftWraps());
  }
Пример #4
0
  public CaretModelImpl(EditorImpl editor) {
    myEditor = editor;
    myLogicalCaret = new LogicalPosition(0, 0);
    myVisibleCaret = new VisualPosition(0, 0);
    myCaretInfo = new VerticalInfo(0, 0);
    myOffset = 0;
    myVisualLineStart = 0;
    Document doc = editor.getDocument();
    myVisualLineEnd =
        doc.getLineCount() > 1
            ? doc.getLineStartOffset(1)
            : doc.getLineCount() == 0 ? 0 : doc.getLineEndOffset(0);
    DocumentBulkUpdateListener bulkUpdateListener =
        new DocumentBulkUpdateListener() {
          @Override
          public void updateStarted(Document doc) {
            if (doc != myEditor.getDocument()) return;
            savedBeforeBulkCaretMarker = doc.createRangeMarker(myOffset, myOffset);
          }

          @Override
          public void updateFinished(Document doc) {
            if (doc != myEditor.getDocument() || myIsInUpdate) return;
            if (savedBeforeBulkCaretMarker != null && savedBeforeBulkCaretMarker.isValid()) {
              moveToOffset(savedBeforeBulkCaretMarker.getStartOffset());
            }
            releaseBulkCaretMarker();
          }
        };
    ApplicationManager.getApplication()
        .getMessageBus()
        .connect(this)
        .subscribe(DocumentBulkUpdateListener.TOPIC, bulkUpdateListener);
  }
 private boolean isLineEmpty(final int line) {
   final CharSequence chars = myDocument.getCharsSequence();
   int start = myDocument.getLineStartOffset(line);
   int end = Math.min(myDocument.getLineEndOffset(line), myDocument.getTextLength() - 1);
   for (int i = start; i <= end; i++) {
     if (!Character.isWhitespace(chars.charAt(i))) return false;
   }
   return true;
 }
Пример #6
0
 private boolean isBlankLine(int line, CharSequence chars) {
   Document document = myDocument;
   if (document == null) {
     return true;
   }
   int startOffset = document.getLineStartOffset(line);
   int endOffset = document.getLineEndOffset(line);
   return CharArrayUtil.shiftForward(chars, startOffset, endOffset, " \t")
       >= myDocument.getLineEndOffset(line);
 }
 private static boolean lineContainsNonSpaces(final Document document, final int line) {
   if (line >= document.getLineCount()) {
     return false;
   }
   int lineStartOffset = document.getLineStartOffset(line);
   int lineEndOffset = document.getLineEndOffset(line);
   @NonNls
   String text =
       document.getCharsSequence().subSequence(lineStartOffset, lineEndOffset).toString();
   return text.trim().length() != 0;
 }
  public static int insertStringAtCaret(
      Editor editor,
      @NotNull String s,
      boolean toProcessOverwriteMode,
      boolean toMoveCaret,
      int caretShift) {
    final SelectionModel selectionModel = editor.getSelectionModel();
    if (selectionModel.hasSelection()) {
      editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart(), true);
    }

    // There is a possible case that particular soft wraps become hard wraps if the caret is located
    // at soft wrap-introduced virtual
    // space, hence, we need to give editor a chance to react accordingly.
    editor.getSoftWrapModel().beforeDocumentChangeAtCaret();
    int oldOffset = editor.getCaretModel().getOffset();

    String filler = calcStringToFillVirtualSpace(editor);
    if (filler.length() > 0) {
      s = filler + s;
    }

    Document document = editor.getDocument();
    if (editor.isInsertMode() || !toProcessOverwriteMode) {
      if (selectionModel.hasSelection()) {
        oldOffset = selectionModel.getSelectionStart();
        document.replaceString(
            selectionModel.getSelectionStart(), selectionModel.getSelectionEnd(), s);
      } else {
        document.insertString(oldOffset, s);
      }
    } else {
      deleteSelectedText(editor);
      int lineNumber = editor.getCaretModel().getLogicalPosition().line;
      if (lineNumber >= document.getLineCount()) {
        return insertStringAtCaret(editor, s, false, toMoveCaret);
      }

      int endOffset = document.getLineEndOffset(lineNumber);
      document.replaceString(oldOffset, Math.min(endOffset, oldOffset + s.length()), s);
    }

    int offset = oldOffset + filler.length() + caretShift;
    if (toMoveCaret) {
      editor.getCaretModel().moveToOffset(offset, true);
      editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
      selectionModel.removeSelection();
    } else if (editor.getCaretModel().getOffset()
        != oldOffset) { // handling the case when caret model tracks document changes
      editor.getCaretModel().moveToOffset(oldOffset);
    }

    return offset;
  }
 @Nullable
 private static String getPropertyName(@NotNull Document document, int line) {
   int startOffset = document.getLineStartOffset(line);
   int endOffset =
       StringUtil.indexOf(
           document.getCharsSequence(), '=', startOffset, document.getLineEndOffset(line));
   if (endOffset <= startOffset) {
     return null;
   }
   String propertyName =
       document.getCharsSequence().subSequence(startOffset, endOffset).toString().trim();
   return propertyName.isEmpty() ? null : propertyName;
 }
  /**
   * Calculates difference in columns between current editor caret position and end of the logical
   * line fragment displayed on a current visual line.
   *
   * @param editor target editor
   * @return difference in columns between current editor caret position and end of the logical line
   *     fragment displayed on a current visual line
   */
  public static int calcAfterLineEnd(Editor editor) {
    Document document = editor.getDocument();
    CaretModel caretModel = editor.getCaretModel();
    LogicalPosition logicalPosition = caretModel.getLogicalPosition();
    int lineNumber = logicalPosition.line;
    int columnNumber = logicalPosition.column;
    if (lineNumber >= document.getLineCount()) {
      return columnNumber;
    }

    int caretOffset = caretModel.getOffset();
    int anchorLineEndOffset = document.getLineEndOffset(lineNumber);
    List<? extends SoftWrap> softWraps =
        editor.getSoftWrapModel().getSoftWrapsForLine(logicalPosition.line);
    for (SoftWrap softWrap : softWraps) {
      if (!editor.getSoftWrapModel().isVisible(softWrap)) {
        continue;
      }

      int softWrapOffset = softWrap.getStart();
      if (softWrapOffset == caretOffset) {
        // There are two possible situations:
        //     *) caret is located on a visual line before soft wrap-introduced line feed;
        //     *) caret is located on a visual line after soft wrap-introduced line feed;
        VisualPosition position = editor.offsetToVisualPosition(caretOffset - 1);
        VisualPosition visualCaret = caretModel.getVisualPosition();
        if (position.line == visualCaret.line) {
          return visualCaret.column - position.column - 1;
        }
      }
      if (softWrapOffset > caretOffset) {
        anchorLineEndOffset = softWrapOffset;
        break;
      }

      // Same offset corresponds to all soft wrap-introduced symbols, however, current method should
      // behave differently in
      // situations when the caret is located just before the soft wrap and at the next visual line.
      if (softWrapOffset == caretOffset) {
        boolean visuallyBeforeSoftWrap =
            caretModel.getVisualPosition().line < editor.offsetToVisualPosition(caretOffset).line;
        if (visuallyBeforeSoftWrap) {
          anchorLineEndOffset = softWrapOffset;
          break;
        }
      }
    }

    int lineEndColumnNumber = editor.offsetToLogicalPosition(anchorLineEndOffset).column;
    return columnNumber - lineEndColumnNumber;
  }
Пример #11
0
 @Override
 @NotNull
 public List<? extends SoftWrap> getSoftWrapsForLine(int documentLine) {
   if (!isSoftWrappingEnabled() || documentLine < 0) {
     return Collections.emptyList();
   }
   Document document = myEditor.getDocument();
   int start = document.getLineStartOffset(documentLine);
   int end = document.getLineEndOffset(documentLine);
   return getSoftWrapsForRange(
       start,
       end
           + 1 /* it's theoretically possible that soft wrap is registered just before the line feed,
                * hence, we add '1' here assuming that end line offset points to line feed symbol */);
 }
  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;
  }
  @Nullable
  private Commenter findCommenter(final int line) {
    final FileType fileType = myFile.getFileType();
    if (fileType instanceof AbstractFileType) {
      return ((AbstractFileType) fileType).getCommenter();
    }

    int lineStartOffset = myDocument.getLineStartOffset(line);
    int lineEndOffset = myDocument.getLineEndOffset(line) - 1;
    final CharSequence charSequence = myDocument.getCharsSequence();
    lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t");
    lineEndOffset =
        CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t");
    final Language lineStartLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineStartOffset);
    final Language lineEndLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineEndOffset);
    return CommentByBlockCommentHandler.getCommenter(
        myFile, myEditor, lineStartLanguage, lineEndLanguage);
  }
  private static void logInitial(
      @NotNull Editor editor,
      @NotNull int[] startOffsets,
      @NotNull int[] endOffsets,
      int indentSymbolsToStrip,
      int firstLineStartOffset) {
    if (!Registry.is("editor.richcopy.debug")) {
      return;
    }

    StringBuilder buffer = new StringBuilder();
    Document document = editor.getDocument();
    CharSequence text = document.getCharsSequence();
    for (int i = 0; i < startOffsets.length; i++) {
      int start = startOffsets[i];
      int lineStart = document.getLineStartOffset(document.getLineNumber(start));
      int end = endOffsets[i];
      int lineEnd = document.getLineEndOffset(document.getLineNumber(end));
      buffer
          .append("    region #")
          .append(i)
          .append(": ")
          .append(start)
          .append('-')
          .append(end)
          .append(", text at range ")
          .append(lineStart)
          .append('-')
          .append(lineEnd)
          .append(": \n'")
          .append(text.subSequence(lineStart, lineEnd))
          .append("'\n");
    }
    if (buffer.length() > 0) {
      buffer.setLength(buffer.length() - 1);
    }
    LOG.info(
        String.format(
            "Preparing syntax-aware text. Given: %s selection, indent symbols to strip=%d, first line start offset=%d, selected text:%n%s",
            startOffsets.length > 1 ? "block" : "regular",
            indentSymbolsToStrip,
            firstLineStartOffset,
            buffer));
  }
  public static String getNewText(PsiElement elt) {
    Project project = elt.getProject();
    PsiFile psiFile = getContainingFile(elt);

    final Document doc = PsiDocumentManager.getInstance(project).getDocument(psiFile);
    if (doc == null) return null;

    final ImplementationTextSelectioner implementationTextSelectioner =
        LanguageImplementationTextSelectioner.INSTANCE.forLanguage(elt.getLanguage());
    int start = implementationTextSelectioner.getTextStartOffset(elt);
    final int end = implementationTextSelectioner.getTextEndOffset(elt);

    final int lineStart = doc.getLineStartOffset(doc.getLineNumber(start));
    final int lineEnd =
        end < doc.getTextLength()
            ? doc.getLineEndOffset(doc.getLineNumber(end))
            : doc.getTextLength();
    return doc.getCharsSequence().subSequence(lineStart, lineEnd).toString();
  }
  private int yPositionToOffset(int y, boolean beginLine) {
    if (myEditorScrollbarTop == -1 || myEditorTargetHeight == -1) {
      recalcEditorDimensions();
    }
    final int safeY = Math.max(0, y - myEditorScrollbarTop);
    VisualPosition visual;
    if (myEditorSourceHeight < myEditorTargetHeight) {
      visual = myEditor.xyToVisualPosition(new Point(0, safeY));
    } else {
      float fraction = Math.max(0, Math.min(1, safeY / (float) myEditorTargetHeight));
      final int lineCount = myEditorSourceHeight / myEditor.getLineHeight();
      visual = new VisualPosition((int) (fraction * lineCount), 0);
    }
    int line = myEditor.visualToLogicalPosition(visual).line;
    Document document = myEditor.getDocument();
    if (line < 0) return 0;
    if (line >= document.getLineCount()) return document.getTextLength();

    return beginLine ? document.getLineStartOffset(line) : document.getLineEndOffset(line);
  }
  public void doClick(final MouseEvent e, final int width) {
    RangeHighlighter marker = getNearestRangeHighlighter(e, width);
    if (marker == null) return;
    int offset = marker.getStartOffset();

    final Document doc = myEditor.getDocument();
    if (doc.getLineCount() > 0) {
      // Necessary to expand folded block even if navigating just before one
      // Very useful when navigating to first unused import statement.
      int lineEnd = doc.getLineEndOffset(doc.getLineNumber(offset));
      myEditor.getCaretModel().moveToOffset(lineEnd);
    }

    myEditor.getCaretModel().moveToOffset(offset);
    myEditor.getSelectionModel().removeSelection();
    ScrollingModel scrollingModel = myEditor.getScrollingModel();
    scrollingModel.disableAnimation();
    scrollingModel.scrollToCaret(ScrollType.CENTER);
    scrollingModel.enableAnimation();
    fireErrorMarkerClicked(marker, e);
  }
 private static Pair<Integer /* start offset to use */, Integer /* indent symbols to strip */>
     calcIndentSymbolsToStrip(@NotNull Document document, int startOffset, int endOffset) {
   int startLine = document.getLineNumber(startOffset);
   int endLine = document.getLineNumber(endOffset);
   CharSequence text = document.getCharsSequence();
   int maximumCommonIndent = Integer.MAX_VALUE;
   int firstLineStart = startOffset;
   int firstLineEnd = startOffset;
   for (int line = startLine; line <= endLine; line++) {
     int lineStartOffset = document.getLineStartOffset(line);
     int lineEndOffset = document.getLineEndOffset(line);
     if (line == startLine) {
       firstLineStart = lineStartOffset;
       firstLineEnd = lineEndOffset;
     }
     int nonWsOffset = lineEndOffset;
     for (int i = lineStartOffset;
         i < lineEndOffset && (i - lineStartOffset) < maximumCommonIndent && i < endOffset;
         i++) {
       char c = text.charAt(i);
       if (c != ' ' && c != '\t') {
         nonWsOffset = i;
         break;
       }
     }
     if (nonWsOffset >= lineEndOffset) {
       continue; // Blank line
     }
     int indent = nonWsOffset - lineStartOffset;
     maximumCommonIndent = Math.min(maximumCommonIndent, indent);
     if (maximumCommonIndent == 0) {
       break;
     }
   }
   int startOffsetToUse =
       Math.min(firstLineEnd, Math.max(startOffset, firstLineStart + maximumCommonIndent));
   return Pair.create(startOffsetToUse, maximumCommonIndent);
 }
 private void paintHighlighterAfterEndOfLine(Graphics2D g, RangeHighlighterEx highlighter) {
   if (!highlighter.isAfterEndOfLine()) {
     return;
   }
   int startOffset = highlighter.getStartOffset();
   int lineEndOffset = myDocument.getLineEndOffset(myDocument.getLineNumber(startOffset));
   if (myEditor.getFoldingModel().isOffsetCollapsed(lineEndOffset)) return;
   Point lineEnd = myView.offsetToXY(lineEndOffset, true, false);
   int x = lineEnd.x;
   int y = lineEnd.y;
   TextAttributes attributes = highlighter.getTextAttributes();
   paintBackground(g, attributes, x, y, myView.getPlainSpaceWidth());
   if (attributes != null
       && hasTextEffect(attributes.getEffectColor(), attributes.getEffectType())) {
     paintTextEffect(
         g,
         x,
         x + myView.getPlainSpaceWidth() - 1,
         y + myView.getAscent(),
         attributes.getEffectColor(),
         attributes.getEffectType());
   }
 }
    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 paintBorderEffect(
      Graphics2D g,
      ClipDetector clipDetector,
      int startOffset,
      int endOffset,
      TextAttributes attributes) {
    if (!clipDetector.rangeCanBeVisible(startOffset, endOffset)) return;
    int startLine = myDocument.getLineNumber(startOffset);
    int endLine = myDocument.getLineNumber(endOffset);
    if (startLine + 1 == endLine
        && startOffset == myDocument.getLineStartOffset(startLine)
        && endOffset == myDocument.getLineStartOffset(endLine)) {
      // special case of line highlighters
      endLine--;
      endOffset = myDocument.getLineEndOffset(endLine);
    }

    boolean rounded = attributes.getEffectType() == EffectType.ROUNDED_BOX;
    int lineHeight = myView.getLineHeight() - 1;
    g.setColor(attributes.getEffectColor());
    VisualPosition startPosition = myView.offsetToVisualPosition(startOffset, true, false);
    VisualPosition endPosition = myView.offsetToVisualPosition(endOffset, false, true);
    if (startPosition.line == endPosition.line) {
      int y = myView.visualLineToY(startPosition.line);
      TFloatArrayList ranges = adjustedLogicalRangeToVisualRanges(startOffset, endOffset);
      for (int i = 0; i < ranges.size() - 1; i += 2) {
        int startX = (int) ranges.get(i);
        int endX = (int) ranges.get(i + 1);
        if (rounded) {
          UIUtil.drawRectPickedOut(g, startX, y, endX - startX, lineHeight);
        } else {
          g.drawRect(startX, y, endX - startX, lineHeight);
        }
      }
    } else {
      int maxWidth = myView.getMaxWidthInLineRange(startPosition.line, endPosition.line) - 1;
      TFloatArrayList leadingRanges =
          adjustedLogicalRangeToVisualRanges(
              startOffset,
              myView.visualPositionToOffset(
                  new VisualPosition(startPosition.line, Integer.MAX_VALUE, true)));
      TFloatArrayList trailingRanges =
          adjustedLogicalRangeToVisualRanges(
              myView.visualPositionToOffset(new VisualPosition(endPosition.line, 0)), endOffset);
      if (!leadingRanges.isEmpty() && !trailingRanges.isEmpty()) {
        boolean containsInnerLines = endPosition.line > startPosition.line + 1;
        int leadingTopY = myView.visualLineToY(startPosition.line);
        int leadingBottomY = leadingTopY + lineHeight;
        int trailingTopY = myView.visualLineToY(endPosition.line);
        int trailingBottomY = trailingTopY + lineHeight;
        float start = 0;
        float end = 0;
        float leftGap = leadingRanges.get(0) - (containsInnerLines ? 0 : trailingRanges.get(0));
        int adjustY =
            leftGap == 0 ? 2 : leftGap > 0 ? 1 : 0; // avoiding 1-pixel gap between aligned lines
        for (int i = 0; i < leadingRanges.size() - 1; i += 2) {
          start = leadingRanges.get(i);
          end = leadingRanges.get(i + 1);
          if (i > 0) {
            drawLine(g, leadingRanges.get(i - 1), leadingBottomY, start, leadingBottomY, rounded);
          }
          drawLine(g, start, leadingBottomY + (i == 0 ? adjustY : 0), start, leadingTopY, rounded);
          if ((i + 2) < leadingRanges.size()) {
            drawLine(g, start, leadingTopY, end, leadingTopY, rounded);
            drawLine(g, end, leadingTopY, end, leadingBottomY, rounded);
          }
        }
        end = Math.max(end, maxWidth);
        drawLine(g, start, leadingTopY, end, leadingTopY, rounded);
        drawLine(g, end, leadingTopY, end, trailingTopY - 1, rounded);
        float targetX = trailingRanges.get(trailingRanges.size() - 1);
        drawLine(g, end, trailingTopY - 1, targetX, trailingTopY - 1, rounded);
        adjustY =
            end == targetX
                ? -2
                : -1; // for lastX == targetX we need to avoid a gap when rounding is used
        for (int i = trailingRanges.size() - 2; i >= 0; i -= 2) {
          start = trailingRanges.get(i);
          end = trailingRanges.get(i + 1);

          drawLine(g, end, trailingTopY + (i == 0 ? adjustY : 0), end, trailingBottomY, rounded);
          drawLine(g, end, trailingBottomY, start, trailingBottomY, rounded);
          drawLine(g, start, trailingBottomY, start, trailingTopY, rounded);
          if (i > 0) {
            drawLine(g, start, trailingTopY, trailingRanges.get(i - 1), trailingTopY, rounded);
          }
        }
        float lastX = start;
        if (containsInnerLines) {
          if (start > 0) {
            drawLine(g, start, trailingTopY, start, trailingTopY - 1, rounded);
            drawLine(g, start, trailingTopY - 1, 0, trailingTopY - 1, rounded);
            drawLine(g, 0, trailingTopY - 1, 0, leadingBottomY + 1, rounded);
          } else {
            drawLine(g, start, trailingTopY, 0, leadingBottomY + 1, rounded);
          }
          lastX = 0;
        }
        targetX = leadingRanges.get(0);
        if (lastX < targetX) {
          drawLine(g, lastX, leadingBottomY + 1, targetX, leadingBottomY + 1, rounded);
        } else {
          drawLine(g, lastX, leadingBottomY + 1, lastX, leadingBottomY, rounded);
          drawLine(g, lastX, leadingBottomY, targetX, leadingBottomY, rounded);
        }
      }
    }
  }
  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 doWrapLongLinesIfNecessary(
      @NotNull final Editor editor,
      @NotNull final Project project,
      @NotNull Document document,
      int startOffset,
      int endOffset) {
    // Normalization.
    int startOffsetToUse = Math.min(document.getTextLength(), Math.max(0, startOffset));
    int endOffsetToUse = Math.min(document.getTextLength(), Math.max(0, endOffset));

    LineWrapPositionStrategy strategy = LanguageLineWrapPositionStrategy.INSTANCE.forEditor(editor);
    CharSequence text = document.getCharsSequence();
    int startLine = document.getLineNumber(startOffsetToUse);
    int endLine = document.getLineNumber(Math.max(0, endOffsetToUse - 1));
    int maxLine = Math.min(document.getLineCount(), endLine + 1);
    int tabSize = EditorUtil.getTabSize(editor);
    if (tabSize <= 0) {
      tabSize = 1;
    }
    int spaceSize = EditorUtil.getSpaceWidth(Font.PLAIN, editor);
    int[] shifts = new int[2];
    // shifts[0] - lines shift.
    // shift[1] - offset shift.

    for (int line = startLine; line < maxLine; line++) {
      int startLineOffset = document.getLineStartOffset(line);
      int endLineOffset = document.getLineEndOffset(line);
      final int preferredWrapPosition =
          calculatePreferredWrapPosition(
              editor, text, tabSize, spaceSize, startLineOffset, endLineOffset, endOffsetToUse);

      if (preferredWrapPosition < 0 || preferredWrapPosition >= endLineOffset) {
        continue;
      }
      if (preferredWrapPosition >= endOffsetToUse) {
        return;
      }

      // We know that current line exceeds right margin if control flow reaches this place, so, wrap
      // it.
      int wrapOffset =
          strategy.calculateWrapPosition(
              document,
              editor.getProject(),
              Math.max(startLineOffset, startOffsetToUse),
              Math.min(endLineOffset, endOffsetToUse),
              preferredWrapPosition,
              false,
              false);
      if (wrapOffset < 0 // No appropriate wrap position is found.
          // No point in splitting line when its left part contains only white spaces, example:
          //    line start -> |                   | <- right margin
          //                  |   aaaaaaaaaaaaaaaa|aaaaaaaaaaaaaaaaaaaa() <- don't want to wrap this
          // line even if it exceeds right margin
          || CharArrayUtil.shiftBackward(text, startLineOffset, wrapOffset - 1, " \t")
              < startLineOffset) {
        continue;
      }

      // Move caret to the target position and emulate pressing <enter>.
      editor.getCaretModel().moveToOffset(wrapOffset);
      emulateEnter(editor, project, shifts);

      // We know that number of lines is just increased, hence, update the data accordingly.
      maxLine += shifts[0];
      endOffsetToUse += shifts[1];
    }
  }
  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 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);
      }
    }
  }
  public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
    myProject = project;
    myFile = file.getViewProvider().getPsi(file.getViewProvider().getBaseLanguage());
    myEditor = editor;

    PsiElement context = myFile.getContext();

    if (context != null && (context.textContains('\'') || context.textContains('\"'))) {
      String s = context.getText();
      if (StringUtil.startsWith(s, "\"") || StringUtil.startsWith(s, "\'")) {
        myFile = context.getContainingFile();
        myEditor = editor instanceof EditorWindow ? ((EditorWindow) editor).getDelegate() : editor;
      }
    }

    myDocument = myEditor.getDocument();
    if (!FileDocumentManager.getInstance().requestWriting(myDocument, project)) {
      return;
    }

    PsiDocumentManager.getInstance(project).commitDocument(myDocument);

    FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.comment.line");

    myCodeStyleManager = CodeStyleManager.getInstance(myProject);

    final SelectionModel selectionModel = myEditor.getSelectionModel();

    boolean hasSelection = selectionModel.hasSelection();
    myStartOffset = selectionModel.getSelectionStart();
    myEndOffset = selectionModel.getSelectionEnd();

    FoldRegion fold = myEditor.getFoldingModel().getCollapsedRegionAtOffset(myStartOffset);
    if (fold != null
        && fold.shouldNeverExpand()
        && fold.getStartOffset() == myStartOffset
        && fold.getEndOffset() == myEndOffset) {
      // Foldings that never expand are automatically selected, so the fact it is selected must not
      // interfer with commenter's logic
      hasSelection = false;
    }

    if (myDocument.getTextLength() == 0) return;

    while (true) {
      int lastLineEnd = myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset));
      FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(lastLineEnd);
      if (collapsedAt != null) {
        final int endOffset = collapsedAt.getEndOffset();
        if (endOffset <= myEndOffset) {
          break;
        }
        myEndOffset = endOffset;
      } else {
        break;
      }
    }

    boolean wholeLinesSelected =
        !hasSelection
            || myStartOffset
                    == myDocument.getLineStartOffset(myDocument.getLineNumber(myStartOffset))
                && myEndOffset
                    == myDocument.getLineEndOffset(myDocument.getLineNumber(myEndOffset - 1)) + 1;

    boolean startingNewLineComment =
        !hasSelection
            && isLineEmpty(myDocument.getLineNumber(myStartOffset))
            && !Comparing.equal(
                IdeActions.ACTION_COMMENT_LINE,
                ActionManagerEx.getInstanceEx().getPrevPreformedActionId());
    doComment();

    if (startingNewLineComment) {
      final Commenter commenter = myCommenters[0];
      if (commenter != null) {
        String prefix;
        if (commenter instanceof SelfManagingCommenter) {
          prefix =
              ((SelfManagingCommenter) commenter)
                  .getCommentPrefix(
                      myStartLine,
                      myDocument,
                      myCommenterStateMap.get((SelfManagingCommenter) commenter));
          if (prefix == null) prefix = ""; // TODO
        } else {
          prefix = commenter.getLineCommentPrefix();
          if (prefix == null) prefix = commenter.getBlockCommentPrefix();
        }

        int lineStart = myDocument.getLineStartOffset(myStartLine);
        lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t");
        lineStart += prefix.length();
        lineStart = CharArrayUtil.shiftForward(myDocument.getCharsSequence(), lineStart, " \t");
        if (lineStart > myDocument.getTextLength()) lineStart = myDocument.getTextLength();
        myEditor.getCaretModel().moveToOffset(lineStart);
        myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
      }
    } else {
      if (!hasSelection) {
        // Don't tweak caret position if we're already located on the last document line.
        LogicalPosition position = myEditor.getCaretModel().getLogicalPosition();
        if (position.line < myDocument.getLineCount() - 1) {
          int verticalShift =
              1
                  + myEditor.getSoftWrapModel().getSoftWrapsForLine(position.line).size()
                  - position.softWrapLinesOnCurrentLogicalLine;
          myEditor.getCaretModel().moveCaretRelatively(0, verticalShift, false, false, true);
        }
      } else {
        if (wholeLinesSelected) {
          selectionModel.setSelection(myStartOffset, selectionModel.getSelectionEnd());
        }
      }
    }
  }
Пример #27
0
  private void moveToLogicalPosition(LogicalPosition pos, boolean locateBeforeSoftWrap) {
    assertIsDispatchThread();
    myDesiredX = -1;
    validateCallContext();
    int column = pos.column;
    int line = pos.line;
    int softWrapLinesBefore = pos.softWrapLinesBeforeCurrentLogicalLine;
    int softWrapLinesCurrent = pos.softWrapLinesOnCurrentLogicalLine;
    int softWrapColumns = pos.softWrapColumnDiff;

    Document doc = myEditor.getDocument();

    if (column < 0) {
      column = 0;
      softWrapColumns = 0;
    }
    if (line < 0) {
      line = 0;
      softWrapLinesBefore = 0;
      softWrapLinesCurrent = 0;
    }

    int lineCount = doc.getLineCount();
    if (lineCount == 0) {
      line = 0;
    } else if (line > lineCount - 1) {
      line = lineCount - 1;
      softWrapLinesBefore = 0;
      softWrapLinesCurrent = 0;
    }

    EditorSettings editorSettings = myEditor.getSettings();

    if (!editorSettings.isVirtualSpace()
        && line < lineCount
        && !myEditor.getSelectionModel().hasBlockSelection()) {
      int lineEndOffset = doc.getLineEndOffset(line);
      int lineEndColumnNumber = myEditor.offsetToLogicalPosition(lineEndOffset).column;
      if (column > lineEndColumnNumber) {
        column = lineEndColumnNumber;
        if (softWrapColumns != 0) {
          softWrapColumns -= column - lineEndColumnNumber;
        }
      }
    }

    ((FoldingModelImpl) myEditor.getFoldingModel()).flushCaretPosition();

    VerticalInfo oldInfo = myCaretInfo;
    LogicalPosition oldCaretPosition = myLogicalCaret;

    LogicalPosition logicalPositionToUse;
    if (pos.visualPositionAware) {
      logicalPositionToUse =
          new LogicalPosition(
              line,
              column,
              softWrapLinesBefore,
              softWrapLinesCurrent,
              softWrapColumns,
              pos.foldedLines,
              pos.foldingColumnDiff);
    } else {
      logicalPositionToUse = new LogicalPosition(line, column);
    }
    setCurrentLogicalCaret(logicalPositionToUse);

    final int offset = myEditor.logicalPositionToOffset(myLogicalCaret);

    FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(offset);

    if (collapsedAt != null && offset > collapsedAt.getStartOffset()) {
      Runnable runnable =
          new Runnable() {
            public void run() {
              FoldRegion[] allCollapsedAt =
                  ((FoldingModelImpl) myEditor.getFoldingModel()).fetchCollapsedAt(offset);
              for (FoldRegion foldRange : allCollapsedAt) {
                foldRange.setExpanded(true);
              }
            }
          };

      myEditor.getFoldingModel().runBatchFoldingOperation(runnable);
    }

    myEditor.setLastColumnNumber(myLogicalCaret.column);
    myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret);

    myOffset = myEditor.logicalPositionToOffset(myLogicalCaret);
    LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength());

    myVisualLineStart =
        myEditor.logicalPositionToOffset(
            myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0)));
    myVisualLineEnd =
        myEditor.logicalPositionToOffset(
            myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0)));

    myEditor.updateCaretCursor();
    requestRepaint(oldInfo);

    if (locateBeforeSoftWrap && SoftWrapHelper.isCaretAfterSoftWrap(myEditor)) {
      int lineToUse = myVisibleCaret.line - 1;
      if (lineToUse >= 0) {
        moveToVisualPosition(
            new VisualPosition(
                lineToUse, EditorUtil.getLastVisualLineColumnNumber(myEditor, lineToUse)));
        return;
      }
    }

    if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) {
      CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret);
      for (CaretListener listener : myCaretListeners) {
        listener.caretPositionChanged(event);
      }
    }
  }
Пример #28
0
        @Override
        @SuppressWarnings({"AssignmentToForLoopParameter"})
        public void paint(
            @NotNull Editor editor, @NotNull RangeHighlighter highlighter, @NotNull Graphics g) {
          int startOffset = highlighter.getStartOffset();
          final Document doc = highlighter.getDocument();
          if (startOffset >= doc.getTextLength()) return;

          final int endOffset = highlighter.getEndOffset();
          final int endLine = doc.getLineNumber(endOffset);

          int off;
          int startLine = doc.getLineNumber(startOffset);
          IndentGuideDescriptor descriptor =
              editor.getIndentsModel().getDescriptor(startLine, endLine);

          final CharSequence chars = doc.getCharsSequence();
          do {
            int start = doc.getLineStartOffset(startLine);
            int end = doc.getLineEndOffset(startLine);
            off = CharArrayUtil.shiftForward(chars, start, end, " \t");
            startLine--;
          } while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n');

          final VisualPosition startPosition = editor.offsetToVisualPosition(off);
          int indentColumn = startPosition.column;

          // It's considered that indent guide can cross not only white space but comments, javadocs
          // etc. Hence, there is a possible
          // case that the first indent guide line is, say, single-line comment where comment
          // symbols ('//') are located at the first
          // visual column. We need to calculate correct indent guide column then.
          int lineShift = 1;
          if (indentColumn <= 0 && descriptor != null) {
            indentColumn = descriptor.indentLevel;
            lineShift = 0;
          }
          if (indentColumn <= 0) return;

          final FoldingModel foldingModel = editor.getFoldingModel();
          if (foldingModel.isOffsetCollapsed(off)) return;

          final FoldRegion headerRegion =
              foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off)));
          final FoldRegion tailRegion =
              foldingModel.getCollapsedRegionAtOffset(
                  doc.getLineStartOffset(doc.getLineNumber(endOffset)));

          if (tailRegion != null && tailRegion == headerRegion) return;

          final boolean selected;
          final IndentGuideDescriptor guide = editor.getIndentsModel().getCaretIndentGuide();
          if (guide != null) {
            final CaretModel caretModel = editor.getCaretModel();
            final int caretOffset = caretModel.getOffset();
            selected =
                caretOffset >= off
                    && caretOffset < endOffset
                    && caretModel.getLogicalPosition().column == indentColumn;
          } else {
            selected = false;
          }

          Point start =
              editor.visualPositionToXY(
                  new VisualPosition(startPosition.line + lineShift, indentColumn));
          final VisualPosition endPosition = editor.offsetToVisualPosition(endOffset);
          Point end =
              editor.visualPositionToXY(new VisualPosition(endPosition.line, endPosition.column));
          int maxY = end.y;
          if (endPosition.line == editor.offsetToVisualPosition(doc.getTextLength()).line) {
            maxY += editor.getLineHeight();
          }

          Rectangle clip = g.getClipBounds();
          if (clip != null) {
            if (clip.y >= maxY || clip.y + clip.height <= start.y) {
              return;
            }
            maxY = Math.min(maxY, clip.y + clip.height);
          }

          final EditorColorsScheme scheme = editor.getColorsScheme();
          g.setColor(
              selected
                  ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR)
                  : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR));

          // There is a possible case that indent line intersects soft wrap-introduced text.
          // Example:
          //     this is a long line <soft-wrap>
          // that| is soft-wrapped
          //     |
          //     | <- vertical indent
          //
          // Also it's possible that no additional intersections are added because of soft wrap:
          //     this is a long line <soft-wrap>
          //     |   that is soft-wrapped
          //     |
          //     | <- vertical indent
          // We want to use the following approach then:
          //     1. Show only active indent if it crosses soft wrap-introduced text;
          //     2. Show indent as is if it doesn't intersect with soft wrap-introduced text;
          if (selected) {
            g.drawLine(start.x + 2, start.y, start.x + 2, maxY);
          } else {
            int y = start.y;
            int newY = start.y;
            SoftWrapModel softWrapModel = editor.getSoftWrapModel();
            int lineHeight = editor.getLineHeight();
            for (int i = Math.max(0, startLine + lineShift); i < endLine && newY < maxY; i++) {
              List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForLine(i);
              int logicalLineHeight = softWraps.size() * lineHeight;
              if (i > startLine + lineShift) {
                logicalLineHeight +=
                    lineHeight; // We assume that initial 'y' value points just below the target
                // line.
              }
              if (!softWraps.isEmpty() && softWraps.get(0).getIndentInColumns() < indentColumn) {
                if (y < newY
                    || i
                        > startLine
                            + lineShift) { // There is a possible case that soft wrap is located on
                  // indent start line.
                  g.drawLine(start.x + 2, y, start.x + 2, newY + lineHeight);
                }
                newY += logicalLineHeight;
                y = newY;
              } else {
                newY += logicalLineHeight;
              }

              FoldRegion foldRegion =
                  foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(i));
              if (foldRegion != null && foldRegion.getEndOffset() < doc.getTextLength()) {
                i = doc.getLineNumber(foldRegion.getEndOffset());
              }
            }

            if (y < maxY) {
              g.drawLine(start.x + 2, y, start.x + 2, maxY);
            }
          }
        }
  @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());
      }
    }
  }