Skip to content

[clang-format] Add KeepFormFeed option #113268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions clang/docs/ClangFormatStyleOptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4663,6 +4663,14 @@ the configuration (without a prefix: ``Auto``).
**KeepEmptyLinesAtTheStartOfBlocks** (``Boolean``) :versionbadge:`clang-format 3.7` :ref:`¶ <KeepEmptyLinesAtTheStartOfBlocks>`
This option is deprecated. See ``AtStartOfBlock`` of ``KeepEmptyLines``.

.. _KeepFormFeed:

**KeepFormFeed** (``Boolean``) :versionbadge:`clang-format 20` :ref:`¶ <KeepFormFeed>`
Keep the form feed character if it's immediately preceded and followed by
a newline. Multiple form feeds and newlines within a whitespace range are
replaced with a single newline and form feed followed by the remaining
newlines.

.. _LambdaBodyIndentation:

**LambdaBodyIndentation** (``LambdaBodyIndentationKind``) :versionbadge:`clang-format 13` :ref:`¶ <LambdaBodyIndentation>`
Expand Down
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ clang-format
multi-line comments without touching their contents, renames ``false`` to
``Never``, and ``true`` to ``Always``.
- Adds ``RemoveEmptyLinesInUnwrappedLines`` option.
- Adds ``KeepFormFeed`` option.

libclang
--------
Expand Down
10 changes: 9 additions & 1 deletion clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -3207,6 +3207,13 @@ struct FormatStyle {
/// \version 3.7
// bool KeepEmptyLinesAtTheStartOfBlocks;

/// Keep the form feed character if it's immediately preceded and followed by
/// a newline. Multiple form feeds and newlines within a whitespace range are
/// replaced with a single newline and form feed followed by the remaining
/// newlines.
/// \version 20
bool KeepFormFeed;

/// Indentation logic for lambda bodies.
enum LambdaBodyIndentationKind : int8_t {
/// Align lambda body relative to the lambda signature. This is the default.
Expand Down Expand Up @@ -5222,7 +5229,8 @@ struct FormatStyle {
JavaImportGroups == R.JavaImportGroups &&
JavaScriptQuotes == R.JavaScriptQuotes &&
JavaScriptWrapImports == R.JavaScriptWrapImports &&
KeepEmptyLines == R.KeepEmptyLines && Language == R.Language &&
KeepEmptyLines == R.KeepEmptyLines &&
KeepFormFeed == R.KeepFormFeed && Language == R.Language &&
LambdaBodyIndentation == R.LambdaBodyIndentation &&
LineEnding == R.LineEnding && MacroBlockBegin == R.MacroBlockBegin &&
MacroBlockEnd == R.MacroBlockEnd && Macros == R.Macros &&
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,7 @@ template <> struct MappingTraits<FormatStyle> {
IO.mapOptional("JavaScriptQuotes", Style.JavaScriptQuotes);
IO.mapOptional("JavaScriptWrapImports", Style.JavaScriptWrapImports);
IO.mapOptional("KeepEmptyLines", Style.KeepEmptyLines);
IO.mapOptional("KeepFormFeed", Style.KeepFormFeed);
IO.mapOptional("LambdaBodyIndentation", Style.LambdaBodyIndentation);
IO.mapOptional("LineEnding", Style.LineEnding);
IO.mapOptional("MacroBlockBegin", Style.MacroBlockBegin);
Expand Down Expand Up @@ -1567,6 +1568,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) {
/*AtStartOfBlock=*/true,
/*AtStartOfFile=*/true,
};
LLVMStyle.KeepFormFeed = false;
LLVMStyle.LambdaBodyIndentation = FormatStyle::LBI_Signature;
LLVMStyle.Language = Language;
LLVMStyle.LineEnding = FormatStyle::LE_DeriveLF;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Format/FormatToken.h
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,9 @@ struct FormatToken {
/// Might be function declaration open/closing paren.
bool MightBeFunctionDeclParen = false;

/// Has "\n\f\n" or "\n\f\r\n" before TokenText.
bool HasFormFeedBefore = false;

/// Number of optional braces to be inserted after this token:
/// -1: a single left brace
/// 0: no braces
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/Format/FormatTokenLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,14 @@ FormatToken *FormatTokenLexer::getNextToken() {
Column = 0;
break;
case '\f':
if (!InEscape && !FormatTok->HasFormFeedBefore &&
// The form feed is immediately preceded and followed by a newline.
i > 0 && Text[i - 1] == '\n' &&
((i + 1 < e && Text[i + 1] == '\n') ||
(i + 2 < e && Text[i + 1] == '\r' && Text[i + 2] == '\n'))) {
FormatTok->HasFormFeedBefore = true;
}
[[fallthrough]];
case '\v':
Column = 0;
break;
Expand Down
23 changes: 13 additions & 10 deletions clang/lib/Format/WhitespaceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1674,7 +1674,7 @@ void WhitespaceManager::generateChanges() {
C.PreviousEndOfTokenColumn,
C.EscapedNewlineColumn);
} else {
appendNewlineText(ReplacementText, C.NewlinesBefore);
appendNewlineText(ReplacementText, C);
}
// FIXME: This assert should hold if we computed the column correctly.
// assert((int)C.StartOfTokenColumn >= C.Spaces);
Expand Down Expand Up @@ -1706,15 +1706,18 @@ void WhitespaceManager::storeReplacement(SourceRange Range, StringRef Text) {
}
}

void WhitespaceManager::appendNewlineText(std::string &Text,
unsigned Newlines) {
if (UseCRLF) {
Text.reserve(Text.size() + 2 * Newlines);
for (unsigned i = 0; i < Newlines; ++i)
Text.append("\r\n");
} else {
Text.append(Newlines, '\n');
}
void WhitespaceManager::appendNewlineText(std::string &Text, const Change &C) {
if (C.NewlinesBefore <= 0)
return;

StringRef Newline = UseCRLF ? "\r\n" : "\n";
Text.append(Newline);

if (Style.KeepFormFeed && C.Tok->HasFormFeedBefore)
Text.append("\f");

for (unsigned I = 1; I < C.NewlinesBefore; ++I)
Text.append(Newline);
}

void WhitespaceManager::appendEscapedNewlineText(
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Format/WhitespaceManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class WhitespaceManager {

/// Stores \p Text as the replacement for the whitespace in \p Range.
void storeReplacement(SourceRange Range, StringRef Text);
void appendNewlineText(std::string &Text, unsigned Newlines);
void appendNewlineText(std::string &Text, const Change &C);
void appendEscapedNewlineText(std::string &Text, unsigned Newlines,
unsigned PreviousEndOfTokenColumn,
unsigned EscapedNewlineColumn);
Expand Down
7 changes: 4 additions & 3 deletions clang/unittests/Format/ConfigParseTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,24 +165,25 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
CHECK_PARSE_BOOL(BreakBeforeTernaryOperators);
CHECK_PARSE_BOOL(BreakStringLiterals);
CHECK_PARSE_BOOL(CompactNamespaces);
CHECK_PARSE_BOOL(Cpp11BracedListStyle);
CHECK_PARSE_BOOL(DerivePointerAlignment);
CHECK_PARSE_BOOL_FIELD(DerivePointerAlignment, "DerivePointerBinding");
CHECK_PARSE_BOOL(DisableFormat);
CHECK_PARSE_BOOL(IndentAccessModifiers);
CHECK_PARSE_BOOL(IndentCaseLabels);
CHECK_PARSE_BOOL(IndentCaseBlocks);
CHECK_PARSE_BOOL(IndentCaseLabels);
CHECK_PARSE_BOOL(IndentGotoLabels);
CHECK_PARSE_BOOL_FIELD(IndentRequiresClause, "IndentRequires");
CHECK_PARSE_BOOL(IndentRequiresClause);
CHECK_PARSE_BOOL_FIELD(IndentRequiresClause, "IndentRequires");
CHECK_PARSE_BOOL(IndentWrappedFunctionNames);
CHECK_PARSE_BOOL(InsertBraces);
CHECK_PARSE_BOOL(InsertNewlineAtEOF);
CHECK_PARSE_BOOL_FIELD(KeepEmptyLines.AtEndOfFile, "KeepEmptyLinesAtEOF");
CHECK_PARSE_BOOL_FIELD(KeepEmptyLines.AtStartOfBlock,
"KeepEmptyLinesAtTheStartOfBlocks");
CHECK_PARSE_BOOL(KeepFormFeed);
CHECK_PARSE_BOOL(ObjCSpaceAfterProperty);
CHECK_PARSE_BOOL(ObjCSpaceBeforeProtocolList);
CHECK_PARSE_BOOL(Cpp11BracedListStyle);
CHECK_PARSE_BOOL(RemoveBracesLLVM);
CHECK_PARSE_BOOL(RemoveEmptyLinesInUnwrappedLines);
CHECK_PARSE_BOOL(RemoveSemicolon);
Expand Down
56 changes: 55 additions & 1 deletion clang/unittests/Format/FormatTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28135,7 +28135,7 @@ TEST_F(FormatTest, BreakBinaryOperations) {
Style);
}

TEST_F(FormatTest, RemovesEmptyLinesInUnwrappedLines) {
TEST_F(FormatTest, RemoveEmptyLinesInUnwrappedLines) {
auto Style = getLLVMStyle();
Style.RemoveEmptyLinesInUnwrappedLines = true;

Expand Down Expand Up @@ -28212,6 +28212,60 @@ TEST_F(FormatTest, RemovesEmptyLinesInUnwrappedLines) {
Style);
}

TEST_F(FormatTest, KeepFormFeed) {
auto Style = getLLVMStyle();
Style.KeepFormFeed = true;

constexpr StringRef NoFormFeed{"int i;\n"
"\n"
"void f();"};
verifyFormat(NoFormFeed,
"int i;\n"
" \f\n"
"void f();",
Style);
verifyFormat(NoFormFeed,
"int i;\n"
"\n"
"\fvoid f();",
Style);
verifyFormat(NoFormFeed,
"\fint i;\n"
"\n"
"void f();",
Style);
verifyFormat(NoFormFeed,
"int i;\n"
"\n"
"void f();\f",
Style);

constexpr StringRef FormFeed{"int i;\n"
"\f\n"
"void f();"};
verifyNoChange(FormFeed, Style);

Style.LineEnding = FormatStyle::LE_LF;
verifyFormat(FormFeed,
"int i;\r\n"
"\f\r\n"
"void f();",
Style);

Style.MaxEmptyLinesToKeep = 3;
verifyFormat("int i;\n"
"\f\n"
"\n"
"\n"
"void f();",
"int i;\n"
"\n"
"\f\n"
"\n"
"void f();",
Style);
}

} // namespace
} // namespace test
} // namespace format
Expand Down
Loading