Index: lib/Format/BreakableToken.h =================================================================== --- lib/Format/BreakableToken.h +++ lib/Format/BreakableToken.h @@ -135,6 +135,19 @@ virtual unsigned getContentStartColumn(unsigned LineIndex, bool Break) const = 0; + /// Returns additional content indent required for the second line after the + /// content at line \p LineIndex is broken. + /// + /// For example, Javadoc @param annotations require and indent of 4 spaces and + /// in this example getContentIndex(1) returns 4. + /// /** + /// * @param loooooooooooooong line + /// * continuation + /// */ + virtual unsigned getContentIndent(unsigned LineIndex) const { + return 0; + } + /// Returns a range (offset, length) at which to break the line at /// \p LineIndex, if previously broken at \p TailOffset. If possible, do not /// violate \p ColumnLimit, assuming the text starting at \p TailOffset in @@ -146,6 +159,7 @@ /// Emits the previously retrieved \p Split via \p Whitespaces. virtual void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const = 0; /// Returns the number of columns needed to format @@ -210,7 +224,7 @@ Split SplitAfterLastLine, WhitespaceManager &Whitespaces) const { insertBreak(getLineCount() - 1, TailOffset, SplitAfterLastLine, - Whitespaces); + /*ContentIndent=*/0, Whitespaces); } /// Updates the next token of \p State to the next token after this @@ -245,6 +259,7 @@ unsigned ContentStartColumn, llvm::Regex &CommentPragmasRegex) const override; void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const override; void compressWhitespace(unsigned LineIndex, unsigned TailOffset, Split Split, WhitespaceManager &Whitespaces) const override {} @@ -354,7 +369,9 @@ unsigned getRemainingLength(unsigned LineIndex, unsigned Offset, unsigned StartColumn) const override; unsigned getContentStartColumn(unsigned LineIndex, bool Break) const override; + unsigned getContentIndent(unsigned LineIndex) const override; void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const override; Split getReflowSplit(unsigned LineIndex, llvm::Regex &CommentPragmasRegex) const override; @@ -423,6 +440,7 @@ unsigned StartColumn) const override; unsigned getContentStartColumn(unsigned LineIndex, bool Break) const override; void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const override; Split getReflowSplit(unsigned LineIndex, llvm::Regex &CommentPragmasRegex) const override; Index: lib/Format/BreakableToken.cpp =================================================================== --- lib/Format/BreakableToken.cpp +++ lib/Format/BreakableToken.cpp @@ -22,6 +22,11 @@ #include #define DEBUG_TYPE "format-token-breaker" +#define DBG(A) \ + LLVM_DEBUG({ \ + llvm::errs() << __func__ << ":" << __LINE__ << ":" << #A << " = " << A \ + << "\n"; \ + }) namespace clang { namespace format { @@ -235,6 +240,7 @@ void BreakableStringLiteral::insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split, + unsigned ContentIndent, WhitespaceManager &Whitespaces) const { Whitespaces.replaceWhitespaceInToken( Tok, Prefix.size() + TailOffset + Split.first, Split.second, Postfix, @@ -510,8 +516,14 @@ return std::max(0, ContentColumn[LineIndex]); } +unsigned BreakableBlockComment::getContentIndent(unsigned LineIndex) const { + if (Content[LineIndex].startswith("@param")) + return 4; + return 0; +} + void BreakableBlockComment::insertBreak(unsigned LineIndex, unsigned TailOffset, - Split Split, + Split Split, unsigned ContentIndent, WhitespaceManager &Whitespaces) const { StringRef Text = Content[LineIndex].substr(TailOffset); StringRef Prefix = Decoration; @@ -532,10 +544,15 @@ Text.data() - tokenAt(LineIndex).TokenText.data() + Split.first; unsigned CharsToRemove = Split.second; assert(LocalIndentAtLineBreak >= Prefix.size()); + std::string PrefixWithTrailingIndent = Prefix; + for (unsigned I = 0; I < ContentIndent; ++I) + PrefixWithTrailingIndent += " "; + DBG(LocalIndentAtLineBreak); Whitespaces.replaceWhitespaceInToken( - tokenAt(LineIndex), BreakOffsetInToken, CharsToRemove, "", Prefix, - InPPDirective, /*Newlines=*/1, - /*Spaces=*/LocalIndentAtLineBreak - Prefix.size()); + tokenAt(LineIndex), BreakOffsetInToken, CharsToRemove, "", + PrefixWithTrailingIndent, InPPDirective, /*Newlines=*/1, + /*Spaces=*/LocalIndentAtLineBreak + ContentIndent - + PrefixWithTrailingIndent.size()); } BreakableToken::Split @@ -543,8 +560,17 @@ llvm::Regex &CommentPragmasRegex) const { if (!mayReflow(LineIndex, CommentPragmasRegex)) return Split(StringRef::npos, 0); - + + // If we're reflowing into a line with content indent, only reflow the next + // line if its starting whitespace matches the content indent. size_t Trimmed = Content[LineIndex].find_first_not_of(Blanks); + if (LineIndex) { + unsigned PreviousContentIndent = getContentIndent(LineIndex - 1); + if (PreviousContentIndent && Trimmed != StringRef::npos && + Trimmed != PreviousContentIndent) + return Split(StringRef::npos, 0); + } + return Split(0, Trimmed != StringRef::npos ? Trimmed : 0); } @@ -583,7 +609,8 @@ // break length are the same. size_t BreakLength = Lines[0].substr(1).find_first_not_of(Blanks); if (BreakLength != StringRef::npos) - insertBreak(LineIndex, 0, Split(1, BreakLength), Whitespaces); + insertBreak(LineIndex, 0, Split(1, BreakLength), /*ContentIndent=*/0, + Whitespaces); } return; } @@ -754,7 +781,7 @@ void BreakableLineCommentSection::insertBreak( unsigned LineIndex, unsigned TailOffset, Split Split, - WhitespaceManager &Whitespaces) const { + unsigned ContentIndent, WhitespaceManager &Whitespaces) const { StringRef Text = Content[LineIndex].substr(TailOffset); // Compute the offset of the split relative to the beginning of the token // text. Index: lib/Format/ContinuationIndenter.cpp =================================================================== --- lib/Format/ContinuationIndenter.cpp +++ lib/Format/ContinuationIndenter.cpp @@ -22,6 +22,11 @@ #include "llvm/Support/Debug.h" #define DEBUG_TYPE "format-indenter" +#define DBG(A) \ + LLVM_DEBUG({ \ + llvm::errs() << __func__ << ":" << __LINE__ << ":" << #A << " = " << A \ + << "\n"; \ + }) namespace clang { namespace format { @@ -1782,6 +1787,7 @@ if (!DryRun) Token->adaptStartOfLine(0, Whitespaces); + unsigned ContentIndent = 0; unsigned Penalty = 0; LLVM_DEBUG(llvm::dbgs() << "Breaking protruding token at column " << StartColumn << ".\n"); @@ -1903,8 +1909,15 @@ } } LLVM_DEBUG(llvm::dbgs() << " Breaking...\n"); - ContentStartColumn = - Token->getContentStartColumn(LineIndex, /*Break=*/true); + // Update the ContentIndent only if the current line was not reflown with + // the previous line, since in that case the previous line should still + // determine the ContentIndent. + if (!Reflow) ContentIndent = Token->getContentIndent(LineIndex); + DBG(ContentIndent); + ContentStartColumn = ContentIndent + Token->getContentStartColumn( + LineIndex, /*Break=*/true); + DBG(ContentStartColumn); + unsigned NewRemainingTokenColumns = Token->getRemainingLength( LineIndex, TailOffset + Split.first + Split.second, ContentStartColumn); @@ -1921,7 +1934,8 @@ LLVM_DEBUG(llvm::dbgs() << " Breaking at: " << TailOffset + Split.first << ", " << Split.second << "\n"); if (!DryRun) - Token->insertBreak(LineIndex, TailOffset, Split, Whitespaces); + Token->insertBreak(LineIndex, TailOffset, Split, ContentIndent, + Whitespaces); Penalty += NewBreakPenalty; TailOffset += Split.first + Split.second; Index: unittests/Format/FormatTestComments.cpp =================================================================== --- unittests/Format/FormatTestComments.cpp +++ unittests/Format/FormatTestComments.cpp @@ -3105,6 +3105,87 @@ // clang-format on } +TEST_F(FormatTestComments, IndentsLongJavadocAnnotatedLines) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_Java); + Style.ColumnLimit = 60; + FormatStyle Style20 = getGoogleStyle(FormatStyle::LK_Java); + Style20.ColumnLimit = 20; + EXPECT_EQ( + "/**\n" + " * @param x long long long long long long long long long\n" + " * long\n" + " */\n", + format("/**\n" + " * @param x long long long long long long long long long long\n" + " */\n", + Style)); + EXPECT_EQ("/**\n" + " * @param x long long long long long long long long long\n" + " * long long long long long long long long long long\n" + " */\n", + format("/**\n" + " * @param x long long long long long long long long long " + "long long long long long long long long long long\n" + " */\n", + Style)); + EXPECT_EQ("/**\n" + " * @param x long long long long long long long long long\n" + " * long long long long long long long long long long\n" + " * long\n" + " */\n", + format("/**\n" + " * @param x long long long long long long long long long " + "long long long long long long long long long long long\n" + " */\n", + Style)); + EXPECT_EQ( + "/**\n" + " * Sentence that\n" + " * should be broken.\n" + " * @param short\n" + " * keep indentation\n" + " */\n", format( + "/**\n" + " * Sentence that should be broken.\n" + " * @param short\n" + " * keep indentation\n" + " */\n", Style20)); + + EXPECT_EQ("/**\n" + " * @param l1 long1\n" + " * to break\n" + " * @param l2 long2\n" + " * to break\n" + " */\n", + format("/**\n" + " * @param l1 long1 to break\n" + " * @param l2 long2 to break\n" + " */\n", + Style20)); + + EXPECT_EQ("/**\n" + " * @param xx to\n" + " * break\n" + " * no reflow\n" + " */\n", + format("/**\n" + " * @param xx to break\n" + " * no reflow\n" + " */\n", + Style20)); + + EXPECT_EQ("/**\n" + " * @param xx to\n" + " * break yes\n" + " * reflow\n" + " */\n", + format("/**\n" + " * @param xx to break\n" + " * yes reflow\n" + " */\n", + Style20)); +} + } // end namespace } // end namespace format } // end namespace clang Index: unittests/Format/FormatTestJS.cpp =================================================================== --- unittests/Format/FormatTestJS.cpp +++ unittests/Format/FormatTestJS.cpp @@ -2058,8 +2058,8 @@ verifyFormat( "/**\n" " * @param This is a\n" - " * long comment but\n" - " * no type\n" + " * long comment\n" + " * but no type\n" " */", "/**\n" " * @param This is a long comment but no type\n"