Index: docs/ClangFormatStyleOptions.rst =================================================================== --- docs/ClangFormatStyleOptions.rst +++ docs/ClangFormatStyleOptions.rst @@ -160,6 +160,17 @@ argument2); \endcode +**AlignConsecutiveAssignments** (``bool``) + If ``true``, aligns consecutive assignments. + + This will align the assignment operators of consecutive lines. This + will result in formattings like + \code + int aaaa = 12; + int b = 23; + int ccc = 23; + \endcode + **AlignEscapedNewlinesLeft** (``bool``) If ``true``, aligns escaped newlines as far left as possible. Otherwise puts them into the right-most column. Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -247,6 +247,17 @@ /// \brief If \c true, aligns trailing comments. bool AlignTrailingComments; + /// \brief If \c true, aligns consecutive assignments. + /// + /// This will align the assignment operators of consecutive lines. This + /// will result in formattings like + /// \code + /// int aaaa = 12; + /// int b = 23; + /// int ccc = 23; + /// \endcode + bool AlignConsecutiveAssignments; + /// \brief If \c true, aligns escaped newlines as far left as possible. /// Otherwise puts them into the right-most column. bool AlignEscapedNewlinesLeft; Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -174,6 +174,7 @@ IO.mapOptional("AlignEscapedNewlinesLeft", Style.AlignEscapedNewlinesLeft); IO.mapOptional("AlignOperands", Style.AlignOperands); IO.mapOptional("AlignTrailingComments", Style.AlignTrailingComments); + IO.mapOptional("AlignConsecutiveAssignments", Style.AlignConsecutiveAssignments); IO.mapOptional("AllowAllParametersOfDeclarationOnNextLine", Style.AllowAllParametersOfDeclarationOnNextLine); IO.mapOptional("AllowShortBlocksOnASingleLine", @@ -329,6 +330,7 @@ LLVMStyle.AlignAfterOpenBracket = true; LLVMStyle.AlignOperands = true; LLVMStyle.AlignTrailingComments = true; + LLVMStyle.AlignConsecutiveAssignments = false; LLVMStyle.AllowAllParametersOfDeclarationOnNextLine = true; LLVMStyle.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_All; LLVMStyle.AllowShortBlocksOnASingleLine = false; Index: lib/Format/WhitespaceManager.h =================================================================== --- lib/Format/WhitespaceManager.h +++ lib/Format/WhitespaceManager.h @@ -106,11 +106,13 @@ /// /// \p StartOfTokenColumn and \p InPPDirective will be used to lay out /// trailing comments and escaped newlines. + /// + /// \p AssignmentToken will be used to align consecutive assignments. Change(bool CreateReplacement, const SourceRange &OriginalWhitespaceRange, unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore, StringRef PreviousLinePostfix, StringRef CurrentLinePrefix, tok::TokenKind Kind, - bool ContinuesPPDirective); + bool ContinuesPPDirective, bool AssignmentToken); bool CreateReplacement; // Changes might be in the middle of a token, so we cannot just keep the @@ -126,6 +128,7 @@ // the \c BreakableToken is still doing its own alignment. tok::TokenKind Kind; bool ContinuesPPDirective; + bool IsAssignmentToken; // The number of nested blocks the token is in. This is used to add tabs // only for the indentation, and not for alignment, when @@ -164,6 +167,16 @@ /// \c EscapedNewlineColumn for the first tokens or token parts in a line. void calculateLineBreakInformation(); + /// \brief Returns true if the FormatToken passed in is an assignment token. + bool isAssignmentToken(tok::TokenKind Kind); + + /// \brief Align consecutive assignments over all \c Changes. + void alignConsecutiveAssignments(); + + /// \brief Align consecutive assignments from change \p Start to change \p End at + /// the specified \p Column. + void alignConsecutiveAssignments(unsigned Start, unsigned End, unsigned Column); + /// \brief Align trailing comments over all \c Changes. void alignTrailingComments(); Index: lib/Format/WhitespaceManager.cpp =================================================================== --- lib/Format/WhitespaceManager.cpp +++ lib/Format/WhitespaceManager.cpp @@ -29,13 +29,15 @@ bool CreateReplacement, const SourceRange &OriginalWhitespaceRange, unsigned IndentLevel, int Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore, StringRef PreviousLinePostfix, - StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective) + StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective, + bool AssignmentToken) : CreateReplacement(CreateReplacement), OriginalWhitespaceRange(OriginalWhitespaceRange), StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore), PreviousLinePostfix(PreviousLinePostfix), CurrentLinePrefix(CurrentLinePrefix), Kind(Kind), - ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel), + ContinuesPPDirective(ContinuesPPDirective), + IsAssignmentToken(AssignmentToken), IndentLevel(IndentLevel), Spaces(Spaces), IsTrailingComment(false), TokenLength(0), PreviousEndOfTokenColumn(0), EscapedNewlineColumn(0), StartOfBlockComment(nullptr), IndentationOffset(0) {} @@ -54,7 +56,8 @@ Tok.Decision = (Newlines > 0) ? FD_Break : FD_Continue; Changes.push_back(Change(true, Tok.WhitespaceRange, IndentLevel, Spaces, StartOfTokenColumn, Newlines, "", "", - Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst)); + Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst, + isAssignmentToken(Tok.Tok.getKind()))); } void WhitespaceManager::addUntouchableToken(const FormatToken &Tok, @@ -64,7 +67,8 @@ Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0, /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore, "", "", Tok.Tok.getKind(), - InPPDirective && !Tok.IsFirst)); + InPPDirective && !Tok.IsFirst, + isAssignmentToken(Tok.Tok.getKind()))); } void WhitespaceManager::replaceWhitespaceInToken( @@ -84,7 +88,7 @@ // calculate the new length of the comment and to calculate the changes // for which to do the alignment when aligning comments. Tok.is(TT_LineComment) && Newlines > 0 ? tok::comment : tok::unknown, - InPPDirective && !Tok.IsFirst)); + InPPDirective && !Tok.IsFirst, isAssignmentToken(Tok.Tok.getKind()))); } const tooling::Replacements &WhitespaceManager::generateReplacements() { @@ -93,6 +97,7 @@ std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr)); calculateLineBreakInformation(); + alignConsecutiveAssignments(); alignTrailingComments(); alignEscapedNewlines(); generateChanges(); @@ -141,6 +146,83 @@ } } +bool WhitespaceManager::isAssignmentToken(tok::TokenKind Kind) { + return (Kind == tok::ampequal || Kind == tok::starequal || + Kind == tok::plusequal || Kind == tok::minusequal || + Kind == tok::slashequal || Kind == tok::percentequal || + Kind == tok::caretequal || Kind == tok::pipeequal || + Kind == tok::equal); +} + +void WhitespaceManager::alignConsecutiveAssignments() { + if (!Style.AlignConsecutiveAssignments) + return; + + unsigned MinColumn = 0; + unsigned StartOfSequence = 0; + bool FoundAssignmentOnLine = false; + unsigned CurrentLine = 0; + for (unsigned i = 0, e = Changes.size(); i != e; ++i) { + if (Changes[i].NewlinesBefore == 1) { + CurrentLine += Changes[i].NewlinesBefore; + if (!FoundAssignmentOnLine && StartOfSequence > 0) { + alignConsecutiveAssignments(StartOfSequence, i, MinColumn); + MinColumn = 0; + StartOfSequence = 0; + } + FoundAssignmentOnLine = false; + } else if (Changes[i].NewlinesBefore > 1) { + CurrentLine += Changes[i].NewlinesBefore; + if (StartOfSequence > 0) { + alignConsecutiveAssignments(StartOfSequence, i, MinColumn); + MinColumn = 0; + StartOfSequence = 0; + } + FoundAssignmentOnLine = false; + } + + if (FoundAssignmentOnLine || !Changes[i].IsAssignmentToken) + continue; + + FoundAssignmentOnLine = true; + if (StartOfSequence == 0) + StartOfSequence = i; + + unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn; + MinColumn = std::max(MinColumn, ChangeMinColumn); + } + + if (StartOfSequence > 0) + alignConsecutiveAssignments(StartOfSequence, Changes.size(), MinColumn); +} + +void WhitespaceManager::alignConsecutiveAssignments(unsigned Start, + unsigned End, + unsigned Column) { + bool AlignedAssignment = false; + int PreviousShift = 0; + for (unsigned i = Start; i != End; ++i) { + int Shift = 0; + if (Changes[i].NewlinesBefore > 0) + AlignedAssignment = false; + if (!AlignedAssignment && Changes[i].IsAssignmentToken) { + Shift = Column - Changes[i].StartOfTokenColumn; + AlignedAssignment = true; + PreviousShift = Shift; + } + assert(Shift >= 0); + Changes[i].Spaces += Shift; + if (i + 1 != Changes.size()) + Changes[i + 1].PreviousEndOfTokenColumn += Shift; + Changes[i].StartOfTokenColumn += Shift; + if (AlignedAssignment) { + Changes[i].StartOfTokenColumn += PreviousShift; + if (i + 1 != Changes.size()) + Changes[i + 1].PreviousEndOfTokenColumn += PreviousShift; + } + } +} + void WhitespaceManager::alignTrailingComments() { unsigned MinColumn = 0; unsigned MaxColumn = UINT_MAX; Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -8347,6 +8347,77 @@ verifyFormat("a or_eq 8;", Spaces); } +TEST_F(FormatTest, AlignConsecutiveAssignments) { + FormatStyle Alignment = getLLVMStyle(); + Alignment.AlignConsecutiveAssignments = false; + verifyFormat("int a = 5;\n" + "int oneTwoThree = 123;", Alignment); + verifyFormat("int a = 5;\n" + "int oneTwoThree = 123;", Alignment); + + Alignment.AlignConsecutiveAssignments = true; + verifyFormat("int a = 5;\n" + "int oneTwoThree = 123;", Alignment); + verifyFormat("int a = 5;\n" + "int oneTwoThree = 123;", Alignment); + verifyFormat("a &= 5;\n" + "bcd *= 5;\n" + "ghtyf += 5;\n" + "dvfvdb -= 5;\n" + "a /= 5;\n" + "vdsvsv %= 5;\n" + "sfdbddfbdfbb ^= 5;\n" + "dvsdsv |= 5;\n" + "int dsvvdvsdvvv = 123;", Alignment); + verifyFormat("int i = 1, j = 10;\n" + "something = 2000;", Alignment); + verifyFormat("something = 2000;\n" + "int i = 1, j = 10;\n", Alignment); + verifyFormat("int a = 5;\n" + "int one = 1;\n" + "method();\n" + "int oneTwoThree = 123;\n" + "int oneTwo = 12;", Alignment); + verifyFormat("int oneTwoThree = 123; // comment\n" + "int oneTwo = 12; // comment", Alignment); + EXPECT_EQ("int a = 5;\n" + "\n" + "int oneTwoThree = 123;", + format("int a = 5;\n" + "\n" + "int oneTwoThree= 123;", Alignment)); + EXPECT_EQ("int a = 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;", + format("int a = 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;", Alignment)); + EXPECT_EQ("int a = 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n" + "int oneTwo = 12;", + format("int a = 5;\n" + "int one = 1;\n" + "\n" + "int oneTwoThree = 123;\n" + "int oneTwo = 12;", Alignment)); + Alignment.AlignEscapedNewlinesLeft = true; + verifyFormat("#define A \\\n" + " int aaaa = 12; \\\n" + " int b = 23; \\\n" + " int ccc = 23; \\\n" + " int dddddddddd = 2345;", Alignment); + Alignment.AlignEscapedNewlinesLeft = false; + verifyFormat("#define A \\\n" + " int aaaa = 12; \\\n" + " int b = 23; \\\n" + " int ccc = 23; \\\n" + " int dddddddddd = 2345;", Alignment); +} + TEST_F(FormatTest, LinuxBraceBreaking) { FormatStyle LinuxBraceStyle = getLLVMStyle(); LinuxBraceStyle.BreakBeforeBraces = FormatStyle::BS_Linux; @@ -8886,6 +8957,7 @@ CHECK_PARSE_BOOL(AlignEscapedNewlinesLeft); CHECK_PARSE_BOOL(AlignOperands); CHECK_PARSE_BOOL(AlignTrailingComments); + CHECK_PARSE_BOOL(AlignConsecutiveAssignments); CHECK_PARSE_BOOL(AllowAllParametersOfDeclarationOnNextLine); CHECK_PARSE_BOOL(AllowShortBlocksOnASingleLine); CHECK_PARSE_BOOL(AllowShortCaseLabelsOnASingleLine);