@Override
  public Result preprocessEnter(
      @NotNull final PsiFile file,
      @NotNull final Editor editor,
      @NotNull final Ref<Integer> caretOffsetRef,
      @NotNull final Ref<Integer> caretAdvance,
      @NotNull final DataContext dataContext,
      final EditorActionHandler originalHandler) {
    int caretOffset = caretOffsetRef.get().intValue();
    PsiElement psiAtOffset = file.findElementAt(caretOffset);
    if (psiAtOffset != null && psiAtOffset.getTextOffset() < caretOffset) {
      ASTNode token = psiAtOffset.getNode();
      Document document = editor.getDocument();
      CharSequence text = document.getText();
      final Language language = psiAtOffset.getLanguage();
      final Commenter languageCommenter = LanguageCommenters.INSTANCE.forLanguage(language);
      final CodeDocumentationAwareCommenter commenter =
          languageCommenter instanceof CodeDocumentationAwareCommenter
              ? (CodeDocumentationAwareCommenter) languageCommenter
              : null;
      if (commenter != null && token.getElementType() == commenter.getLineCommentTokenType()) {
        final int offset = CharArrayUtil.shiftForward(text, caretOffset, " \t");

        if (offset < document.getTextLength() && text.charAt(offset) != '\n') {
          String prefix = commenter.getLineCommentPrefix();
          assert prefix != null : "Line Comment type is set but Line Comment Prefix is null!";
          if (!StringUtil.startsWith(text, offset, prefix)) {
            if (text.charAt(caretOffset) != ' ' && !prefix.endsWith(" ")) {
              prefix += " ";
            }
            document.insertString(caretOffset, prefix);
            return Result.Default;
          } else {
            int afterPrefix = offset + prefix.length();
            if (afterPrefix < document.getTextLength() && text.charAt(afterPrefix) != ' ') {
              document.insertString(afterPrefix, " ");
              // caretAdvance.set(0);
            }
            caretOffsetRef.set(offset);
          }
          return Result.Default;
        }
      }
    }
    return Result.Continue;
  }
Example #2
0
  /**
   * We want to treat comments specially in a way to skip comment prefix on line indent calculation.
   *
   * <p>Example:
   *
   * <pre>
   *   if (true) {
   *     int i1;
   * //    int i2;
   *     int i3;
   *   }
   * </pre>
   *
   * We want to use 'int i2;' start offset as the third line indent (though it has non-white space
   * comment prefix (//) at the first column.
   *
   * <p>This method tries to parse comment prefix for the language implied by the given comment
   * type. It uses {@link #NO_COMMENT_INFO_MARKER} as an indicator that that information is
   * unavailable
   *
   * @param commentType target comment type
   * @return prefix of the comment denoted by the given type if any; {@link #NO_COMMENT_INFO_MARKER}
   *     otherwise
   */
  @NotNull
  private static String getCommentPrefix(@NotNull IElementType commentType) {
    Commenter c = LanguageCommenters.INSTANCE.forLanguage(commentType.getLanguage());
    if (!(c instanceof CodeDocumentationAwareCommenter)) {
      COMMENT_PREFIXES.put(commentType, NO_COMMENT_INFO_MARKER);
      return NO_COMMENT_INFO_MARKER;
    }
    CodeDocumentationAwareCommenter commenter = (CodeDocumentationAwareCommenter) c;

    IElementType lineCommentType = commenter.getLineCommentTokenType();
    String lineCommentPrefix = commenter.getLineCommentPrefix();
    if (lineCommentType != null) {
      COMMENT_PREFIXES.put(
          lineCommentType, lineCommentPrefix == null ? NO_COMMENT_INFO_MARKER : lineCommentPrefix);
    }

    IElementType blockCommentType = commenter.getBlockCommentTokenType();
    String blockCommentPrefix = commenter.getBlockCommentPrefix();
    if (blockCommentType != null) {
      COMMENT_PREFIXES.put(
          blockCommentType,
          blockCommentPrefix == null ? NO_COMMENT_INFO_MARKER : blockCommentPrefix);
    }

    IElementType docCommentType = commenter.getDocumentationCommentTokenType();
    String docCommentPrefix = commenter.getDocumentationCommentPrefix();
    if (docCommentType != null) {
      COMMENT_PREFIXES.put(
          docCommentType, docCommentPrefix == null ? NO_COMMENT_INFO_MARKER : docCommentPrefix);
    }

    COMMENT_PREFIXES.putIfAbsent(commentType, NO_COMMENT_INFO_MARKER);
    return COMMENT_PREFIXES.get(commentType);
  }
  /**
   * Generates a comment if possible.
   *
   * <p>It's assumed that this method {@link PsiDocumentManager#commitDocument(Document) syncs} all
   * PSI-document changes during the processing.
   *
   * @param anchor target element for which a comment should be generated
   * @param editor target editor
   * @param commenter commenter to use
   * @param project current project
   */
  private static void generateComment(
      @NotNull PsiElement anchor,
      @NotNull Editor editor,
      @NotNull CodeDocumentationProvider documentationProvider,
      @NotNull CodeDocumentationAwareCommenter commenter,
      @NotNull Project project) {
    Document document = editor.getDocument();
    int commentStartOffset = anchor.getTextRange().getStartOffset();
    int lineStartOffset = document.getLineStartOffset(document.getLineNumber(commentStartOffset));
    if (lineStartOffset > 0 && lineStartOffset < commentStartOffset) {
      // Example:
      //    void test1() {
      //    }
      //    void test2() {
      //       <offset>
      //    }
      // We want to insert the comment at the start of the line where 'test2()' is declared.
      int nonWhiteSpaceOffset =
          CharArrayUtil.shiftBackward(document.getCharsSequence(), commentStartOffset - 1, " \t");
      commentStartOffset = Math.max(nonWhiteSpaceOffset, lineStartOffset);
    }

    int commentBodyRelativeOffset = 0;
    int caretOffsetToSet = -1;
    StringBuilder buffer = new StringBuilder();
    String commentPrefix = commenter.getDocumentationCommentPrefix();
    if (commentPrefix != null) {
      buffer.append(commentPrefix).append("\n");
      commentBodyRelativeOffset += commentPrefix.length() + 1;
    }

    String linePrefix = commenter.getDocumentationCommentLinePrefix();
    if (linePrefix != null) {
      buffer.append(linePrefix);
      commentBodyRelativeOffset += linePrefix.length();
      caretOffsetToSet = commentStartOffset + commentBodyRelativeOffset;
    }
    buffer.append("\n");
    commentBodyRelativeOffset++;

    String commentSuffix = commenter.getDocumentationCommentSuffix();
    if (commentSuffix != null) {
      buffer.append(commentSuffix).append("\n");
    }

    if (buffer.length() <= 0) {
      return;
    }

    document.insertString(commentStartOffset, buffer);
    PsiDocumentManager docManager = PsiDocumentManager.getInstance(project);
    docManager.commitDocument(document);

    Pair<PsiElement, PsiComment> pair = documentationProvider.parseContext(anchor);
    if (pair == null || pair.second == null) {
      return;
    }

    String stub = documentationProvider.generateDocumentationContentStub(pair.second);
    CaretModel caretModel = editor.getCaretModel();
    if (stub != null) {
      int insertionOffset = commentStartOffset + commentBodyRelativeOffset;
      // if (CodeStyleSettingsManager.getSettings(project).JD_ADD_BLANK_AFTER_DESCRIPTION) {
      //  buffer.setLength(0);
      //  if (linePrefix != null) {
      //    buffer.append(linePrefix);
      //  }
      //  buffer.append("\n");
      //  buffer.append(stub);
      //  stub = buffer.toString();
      // }
      document.insertString(insertionOffset, stub);
      docManager.commitDocument(document);
      pair = documentationProvider.parseContext(anchor);
    }

    if (caretOffsetToSet >= 0) {
      caretModel.moveToOffset(caretOffsetToSet);
    }

    if (pair == null || pair.second == null) {
      return;
    }

    int start = Math.min(calcStartReformatOffset(pair.first), calcStartReformatOffset(pair.second));
    int end = pair.second.getTextRange().getEndOffset();

    CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
    codeStyleManager.reformatText(anchor.getContainingFile(), start, end);

    int caretOffset = caretModel.getOffset();
    if (caretOffset > 0 && caretOffset <= document.getTextLength()) {
      char c = document.getCharsSequence().charAt(caretOffset - 1);
      if (!StringUtil.isWhiteSpace(c)) {
        document.insertString(caretOffset, " ");
        caretModel.moveToOffset(caretOffset + 1);
      }
    }
  }
    private boolean insertDocAsterisk(
        int lineStart,
        boolean docAsterisk,
        boolean previousLineIndentUsed,
        CodeDocumentationAwareCommenter commenter) {
      PsiElement atLineStart = myFile.findElementAt(lineStart);
      if (atLineStart == null) return false;

      final String linePrefix = commenter.getDocumentationCommentLinePrefix();
      final String docPrefix = commenter.getDocumentationCommentPrefix();

      final String text = atLineStart.getText();
      final TextRange textRange = atLineStart.getTextRange();

      if (text.equals(linePrefix)
          || text.equals(docPrefix)
          || docPrefix != null
              && text.regionMatches(
                  lineStart - textRange.getStartOffset(), docPrefix, 0, docPrefix.length())
          || linePrefix != null
              && text.regionMatches(
                  lineStart - textRange.getStartOffset(), linePrefix, 0, linePrefix.length())) {
        PsiElement element = myFile.findElementAt(myOffset);
        if (element == null) return false;

        PsiComment comment =
            element instanceof PsiComment
                ? (PsiComment) element
                : PsiTreeUtil.getParentOfType(element, PsiComment.class, false);
        if (comment != null) {
          int commentEnd = comment.getTextRange().getEndOffset();
          if (myOffset >= commentEnd) {
            docAsterisk = false;
          } else {
            removeTrailingSpaces(myDocument, myOffset);
            String toInsert =
                previousLineIndentUsed
                    ? "*"
                    : CodeDocumentationUtil.createDocCommentLine("", getProject(), commenter);
            myDocument.insertString(myOffset, toInsert);
            PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
          }
        } else {
          docAsterisk = false;
        }
      } else if (linePrefix != null
          && atLineStart instanceof PsiComment
          && ((PsiComment) atLineStart).getTokenType() == commenter.getBlockCommentTokenType()) {
        // Check if C-Style comment already uses asterisks.
        boolean usesAstersk = false;
        int commentLine = myDocument.getLineNumber(textRange.getStartOffset());
        if (commentLine < myDocument.getLineCount() - 1 && textRange.getEndOffset() >= myOffset) {
          int nextLineOffset = myDocument.getLineStartOffset(commentLine + 1);
          if (nextLineOffset < textRange.getEndOffset()) {
            final CharSequence chars = myDocument.getCharsSequence();
            nextLineOffset = CharArrayUtil.shiftForward(chars, nextLineOffset, " \t");
            usesAstersk = CharArrayUtil.regionMatches(chars, nextLineOffset, linePrefix);
          }
        }
        if (usesAstersk) {
          removeTrailingSpaces(myDocument, myOffset);
          myDocument.insertString(myOffset, linePrefix + " ");
          PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
        }
        docAsterisk = usesAstersk;
      } else {
        docAsterisk = false;
      }
      return docAsterisk;
    }
    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 static boolean isDocComment(
     final PsiElement element, final CodeDocumentationAwareCommenter commenter) {
   if (!(element instanceof PsiComment)) return false;
   PsiComment comment = (PsiComment) element;
   return commenter.isDocumentationComment(comment);
 }
  private static boolean isCommentComplete(
      PsiComment comment, CodeDocumentationAwareCommenter commenter, Editor editor) {
    for (CommentCompleteHandler handler :
        Extensions.getExtensions(CommentCompleteHandler.EP_NAME)) {
      if (handler.isApplicable(comment, commenter)) {
        return handler.isCommentComplete(comment, commenter, editor);
      }
    }

    String commentText = comment.getText();
    final boolean docComment = isDocComment(comment, commenter);
    final String expectedCommentEnd =
        docComment ? commenter.getDocumentationCommentSuffix() : commenter.getBlockCommentSuffix();
    if (!commentText.endsWith(expectedCommentEnd)) return false;

    final PsiFile containingFile = comment.getContainingFile();
    final Language language = containingFile.getLanguage();
    ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.forLanguage(language);
    if (parserDefinition == null) {
      return true;
    }
    Lexer lexer = parserDefinition.createLexer(containingFile.getProject());
    final String commentPrefix =
        docComment ? commenter.getDocumentationCommentPrefix() : commenter.getBlockCommentPrefix();
    lexer.start(
        commentText, commentPrefix == null ? 0 : commentPrefix.length(), commentText.length());
    QuoteHandler fileTypeHandler = TypedHandler.getQuoteHandler(containingFile, editor);
    JavaLikeQuoteHandler javaLikeQuoteHandler =
        fileTypeHandler instanceof JavaLikeQuoteHandler
            ? (JavaLikeQuoteHandler) fileTypeHandler
            : null;

    while (true) {
      IElementType tokenType = lexer.getTokenType();
      if (tokenType == null) {
        return false;
      }

      if (javaLikeQuoteHandler != null
          && javaLikeQuoteHandler.getStringTokenTypes() != null
          && javaLikeQuoteHandler.getStringTokenTypes().contains(tokenType)) {
        String text = commentText.substring(lexer.getTokenStart(), lexer.getTokenEnd());
        int endOffset = comment.getTextRange().getEndOffset();

        if (text.endsWith(expectedCommentEnd)
            && endOffset < containingFile.getTextLength()
            && containingFile.getText().charAt(endOffset) == '\n') {
          return true;
        }
      }
      if (tokenType == commenter.getDocumentationCommentTokenType()
          || tokenType == commenter.getBlockCommentTokenType()) {
        return false;
      }
      if (tokenType == commenter.getLineCommentTokenType()
          && lexer.getTokenText().contains(commentPrefix)) {
        return false;
      }
      if (lexer.getTokenEnd() == commentText.length()) {
        if (tokenType == commenter.getLineCommentTokenType()) {
          String prefix = commenter.getLineCommentPrefix();
          lexer.start(
              commentText,
              lexer.getTokenStart() + (prefix == null ? 0 : prefix.length()),
              commentText.length());
          lexer.advance();
          continue;
        } else if (isInvalidPsi(comment)) {
          return false;
        }
        return true;
      }
      lexer.advance();
    }
  }