@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; }
/** * 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(); } }