clang-format: Support nested block formatting with ColumnLimit=0.

llvm-svn: 235580
This commit is contained in:
Daniel Jasper 2015-04-23 09:23:17 +00:00
parent 5fd24c673e
commit 289afc071e
4 changed files with 118 additions and 31 deletions

View File

@ -839,12 +839,26 @@ void ContinuationIndenter::moveStatePastScopeOpener(LineState &State,
(Current.PackingKind == PPK_OnePerLine ||
(!BinPackInconclusiveFunctions &&
Current.PackingKind == PPK_Inconclusive)));
// If this '[' opens an ObjC call, determine whether all parameters fit
// into one line and put one per line if they don't.
if (Current.is(TT_ObjCMethodExpr) && Style.ColumnLimit != 0 &&
getLengthToMatchingParen(Current) + State.Column >
if (Current.is(TT_ObjCMethodExpr) && Current.MatchingParen) {
if (Style.ColumnLimit) {
// If this '[' opens an ObjC call, determine whether all parameters fit
// into one line and put one per line if they don't.
if (getLengthToMatchingParen(Current) + State.Column >
getColumnLimit(State))
BreakBeforeParameter = true;
BreakBeforeParameter = true;
} else {
// For ColumnLimit = 0, we have to figure out whether there is or has to
// be a line break within this call.
for (const FormatToken *Tok = &Current;
Tok && Tok != Current.MatchingParen; Tok = Tok->Next) {
if (Tok->MustBreakBefore ||
(Tok->CanBreakBefore && Tok->NewlinesBefore > 0)) {
BreakBeforeParameter = true;
break;
}
}
}
}
}
bool NoLineBreak = State.Stack.back().NoLineBreak ||
(Current.is(TT_TemplateOpener) &&

View File

@ -303,7 +303,9 @@ private:
class NoColumnLimitFormatter {
public:
NoColumnLimitFormatter(ContinuationIndenter *Indenter) : Indenter(Indenter) {}
NoColumnLimitFormatter(ContinuationIndenter *Indenter,
UnwrappedLineFormatter *Formatter)
: Indenter(Indenter), Formatter(Formatter) {}
/// \brief Formats the line starting at \p State, simply keeping all of the
/// input's line breaking decisions.
@ -314,12 +316,15 @@ public:
bool Newline =
Indenter->mustBreak(State) ||
(Indenter->canBreak(State) && State.NextToken->NewlinesBefore > 0);
unsigned Penalty = 0;
Formatter->formatChildren(State, Newline, /*DryRun=*/false, Penalty);
Indenter->addTokenToState(State, Newline, /*DryRun=*/false);
}
}
private:
ContinuationIndenter *Indenter;
UnwrappedLineFormatter *Formatter;
};
@ -426,8 +431,7 @@ UnwrappedLineFormatter::format(const SmallVectorImpl<AnnotatedLine *> &Lines,
Indenter->addTokenToState(State, /*Newline=*/false, DryRun);
}
} else if (Style.ColumnLimit == 0) {
// FIXME: Implement nested blocks for ColumnLimit = 0.
NoColumnLimitFormatter Formatter(Indenter);
NoColumnLimitFormatter Formatter(Indenter, this);
if (!DryRun)
Formatter.format(Indent, &TheLine);
} else {

View File

@ -40,6 +40,30 @@ public:
unsigned format(const SmallVectorImpl<AnnotatedLine *> &Lines, bool DryRun,
int AdditionalIndent = 0, bool FixBadIndentation = false);
/// \brief If the \p State's next token is an r_brace closing a nested block,
/// format the nested block before it.
///
/// Returns \c true if all children could be placed successfully and adapts
/// \p Penalty as well as \p State. If \p DryRun is false, also directly
/// creates changes using \c Whitespaces.
///
/// The crucial idea here is that children always get formatted upon
/// encountering the closing brace right after the nested block. Now, if we
/// are currently trying to keep the "}" on the same line (i.e. \p NewLine is
/// \c false), the entire block has to be kept on the same line (which is only
/// possible if it fits on the line, only contains a single statement, etc.
///
/// If \p NewLine is true, we format the nested block on separate lines, i.e.
/// break after the "{", format all lines with correct indentation and the put
/// the closing "}" on yet another new line.
///
/// This enables us to keep the simple structure of the
/// \c UnwrappedLineFormatter, where we only have two options for each token:
/// break or don't break.
bool formatChildren(LineState &State, bool NewLine, bool DryRun,
unsigned &Penalty);
private:
/// \brief Formats an \c AnnotatedLine and returns the penalty.
///
@ -131,29 +155,6 @@ private:
void addNextStateToQueue(unsigned Penalty, StateNode *PreviousNode,
bool NewLine, unsigned *Count, QueueType *Queue);
/// \brief If the \p State's next token is an r_brace closing a nested block,
/// format the nested block before it.
///
/// Returns \c true if all children could be placed successfully and adapts
/// \p Penalty as well as \p State. If \p DryRun is false, also directly
/// creates changes using \c Whitespaces.
///
/// The crucial idea here is that children always get formatted upon
/// encountering the closing brace right after the nested block. Now, if we
/// are currently trying to keep the "}" on the same line (i.e. \p NewLine is
/// \c false), the entire block has to be kept on the same line (which is only
/// possible if it fits on the line, only contains a single statement, etc.
///
/// If \p NewLine is true, we format the nested block on separate lines, i.e.
/// break after the "{", format all lines with correct indentation and the put
/// the closing "}" on yet another new line.
///
/// This enables us to keep the simple structure of the
/// \c UnwrappedLineFormatter, where we only have two options for each token:
/// break or don't break.
bool formatChildren(LineState &State, bool NewLine, bool DryRun,
unsigned &Penalty);
ContinuationIndenter *Indenter;
WhitespaceManager *Whitespaces;
FormatStyle Style;

View File

@ -9789,6 +9789,74 @@ TEST_F(FormatTest, FormatsBlocks) {
FourIndent);
}
TEST_F(FormatTest, FormatsBlocksWithZeroColumnWidth) {
FormatStyle ZeroColumn = getLLVMStyle();
ZeroColumn.ColumnLimit = 0;
verifyFormat("[[SessionService sharedService] "
"loadWindowWithCompletionBlock:^(SessionWindow *window) {\n"
" if (window) {\n"
" [self windowDidLoad:window];\n"
" } else {\n"
" [self errorLoadingWindow];\n"
" }\n"
"}];",
ZeroColumn);
EXPECT_EQ("[[SessionService sharedService]\n"
" loadWindowWithCompletionBlock:^(SessionWindow *window) {\n"
" if (window) {\n"
" [self windowDidLoad:window];\n"
" } else {\n"
" [self errorLoadingWindow];\n"
" }\n"
" }];",
format("[[SessionService sharedService]\n"
"loadWindowWithCompletionBlock:^(SessionWindow *window) {\n"
" if (window) {\n"
" [self windowDidLoad:window];\n"
" } else {\n"
" [self errorLoadingWindow];\n"
" }\n"
"}];",
ZeroColumn));
verifyFormat("[myObject doSomethingWith:arg1\n"
" firstBlock:^(Foo *a) {\n"
" // ...\n"
" int i;\n"
" }\n"
" secondBlock:^(Bar *b) {\n"
" // ...\n"
" int i;\n"
" }\n"
" thirdBlock:^Foo(Bar *b) {\n"
" // ...\n"
" int i;\n"
" }];",
ZeroColumn);
verifyFormat("f(^{\n"
" @autoreleasepool {\n"
" if (a) {\n"
" g();\n"
" }\n"
" }\n"
"});",
ZeroColumn);
verifyFormat("void (^largeBlock)(void) = ^{\n"
" // ...\n"
"};",
ZeroColumn);
ZeroColumn.AllowShortBlocksOnASingleLine = true;
EXPECT_EQ("void (^largeBlock)(void) = ^{ int i; };",
format("void (^largeBlock)(void) = ^{ int i; };",
ZeroColumn));
ZeroColumn.AllowShortBlocksOnASingleLine = false;
EXPECT_EQ("void (^largeBlock)(void) = ^{\n"
" int i;\n"
"};",
format("void (^largeBlock)(void) = ^{ int i; };", ZeroColumn));
}
TEST_F(FormatTest, SupportsCRLF) {
EXPECT_EQ("int a;\r\n"
"int b;\r\n"