ClangFormat: Simplify the dummy text for empty line heuristics

The text to fill the empty line mostly depends not on the
fact of being inside parenthesis or not but rather on the
last preceding meaningful character.

Let's check for this character and sometimes for the following
one to better understand the current context and pick the
proper dummy text.

With this behavior improvement we can better indent empty lines
inside initializer lists with empty lines inside.

Change-Id: Id2f27454ef56dfdf8c15b5efb14c4d09242908a9
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2019-03-11 11:10:12 +01:00
parent 2d3e8ef77b
commit 5006bfb157
2 changed files with 65 additions and 61 deletions

View File

@@ -127,48 +127,51 @@ void trimRHSWhitespace(const QTextBlock &block)
cursor.endEditBlock(); cursor.endEditBlock();
} }
// We don't need other types so far. QTextBlock reverseFindLastEmptyBlock(QTextBlock start)
enum class CharacterType { OpeningParen, OpeningBrace, Invalid };
CharacterType firstOpeningParenOrBraceBeforeBlock(const QTextBlock &block)
{ {
if (block.text().trimmed().startsWith(')')) if (start.position() > 0) {
return CharacterType::OpeningParen; start = start.previous();
while (start.position() > 0 && start.text().trimmed().isEmpty())
start = start.previous();
if (!start.text().trimmed().isEmpty())
start = start.next();
}
return start;
}
QTextCursor cursor(block); enum class CharacterContext { AfterComma, LastAfterComma, NewStatement, Continuation, Unknown };
const QTextDocument *doc = block.document();
cursor.movePosition(QTextCursor::PreviousCharacter); QChar findFirstNonWhitespaceCharacter(const QTextBlock &currentBlock)
QChar currentChar = doc->characterAt(cursor.position()); {
const QTextDocument *doc = currentBlock.document();
int currentPos = currentBlock.position();
while (currentPos < doc->characterCount() && doc->characterAt(currentPos).isSpace())
++currentPos;
return currentPos < doc->characterCount() ? doc->characterAt(currentPos) : QChar::Null;
}
int parenCount = 0; CharacterContext characterContext(const QTextBlock &currentBlock,
int braceCount = 0; const QTextBlock &previousNonEmptyBlock)
{
while (cursor.position() > 0 && parenCount <= 0 && braceCount <= 0) { const QString prevLineText = previousNonEmptyBlock.text().trimmed();
cursor.movePosition(QTextCursor::PreviousCharacter); if (prevLineText.endsWith(',')) {
currentChar = doc->characterAt(cursor.position()); const QChar firstNonWhitespaceChar = findFirstNonWhitespaceCharacter(currentBlock);
if (currentChar == '(') // We don't need to add comma in case it's the last argument.
++parenCount; if (firstNonWhitespaceChar == '}' || firstNonWhitespaceChar == ')')
else if (currentChar == ')') return CharacterContext::LastAfterComma;
--parenCount; return CharacterContext::AfterComma;
else if (currentChar == '{')
++braceCount;
else if (currentChar == '}')
--braceCount;
} }
if (braceCount > 0) if (prevLineText.endsWith(';') || prevLineText.endsWith('{') || prevLineText.endsWith('}'))
return CharacterType::OpeningBrace; return CharacterContext::NewStatement;
if (parenCount > 0)
return CharacterType::OpeningParen;
return CharacterType::Invalid; return CharacterContext::Continuation;
} }
// Add extra text in case of the empty line or the line starting with ')'. // Add extra text in case of the empty line or the line starting with ')'.
// Track such extra pieces of text in isInsideModifiedLine(). // Track such extra pieces of text in isInsideModifiedLine().
int forceIndentWithExtraText(QByteArray &buffer, int forceIndentWithExtraText(QByteArray &buffer,
QByteArray &dummyText, CharacterContext &charContext,
const QTextBlock &block, const QTextBlock &block,
bool secondTry) bool secondTry)
{ {
@@ -188,24 +191,24 @@ int forceIndentWithExtraText(QByteArray &buffer,
int extraLength = 0; int extraLength = 0;
if (firstNonWhitespace < 0 || closingParenBlock) { if (firstNonWhitespace < 0 || closingParenBlock) {
if (dummyText.isEmpty()) { if (charContext == CharacterContext::LastAfterComma) {
const CharacterType charType = firstOpeningParenOrBraceBeforeBlock(block); charContext = CharacterContext::AfterComma;
} else if (charContext == CharacterContext::Unknown) {
QTextBlock lastBlock = reverseFindLastEmptyBlock(block);
if (lastBlock.position() > 0)
lastBlock = lastBlock.previous();
// If we don't know yet the dummy text, let's guess it and use for this line and before. // If we don't know yet the dummy text, let's guess it and use for this line and before.
if (charType != CharacterType::OpeningParen) { charContext = characterContext(block, lastBlock);
// Use the complete statement if we are not inside parenthesis. }
dummyText = "a;a;";
} else { QByteArray dummyText;
// Search for previous character if (charContext == CharacterContext::NewStatement) {
QTextBlock prevBlock = block.previous(); dummyText = "a;a;";
bool prevBlockIsEmpty = prevBlock.position() > 0 } else if (charContext == CharacterContext::AfterComma) {
&& prevBlock.text().trimmed().isEmpty(); dummyText = "&& a,";
while (prevBlockIsEmpty) { } else {
prevBlock = prevBlock.previous(); dummyText = "&& a";
prevBlockIsEmpty = prevBlock.position() > 0
&& prevBlock.text().trimmed().isEmpty();
}
dummyText = prevBlock.text().endsWith(',') ? "&& a," : "&& a";
}
} }
buffer.insert(utf8Offset, dummyText); buffer.insert(utf8Offset, dummyText);
@@ -349,18 +352,6 @@ bool doNotIndentInContext(QTextDocument *doc, int pos)
return false; return false;
} }
QTextBlock reverseFindLastEmptyBlock(QTextBlock start)
{
if (start.position() > 0) {
start = start.previous();
while (start.position() > 0 && start.text().trimmed().isEmpty())
start = start.previous();
if (!start.text().trimmed().isEmpty())
start = start.next();
}
return start;
}
int formattingRangeStart(const QTextBlock &currentBlock, int formattingRangeStart(const QTextBlock &currentBlock,
const QByteArray &buffer, const QByteArray &buffer,
int documentRevision) int documentRevision)
@@ -405,11 +396,11 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
adjustFormatStyleForLineBreak(style, replacementsToKeep); adjustFormatStyleForLineBreak(style, replacementsToKeep);
if (replacementsToKeep == ReplacementsToKeep::OnlyIndent) { if (replacementsToKeep == ReplacementsToKeep::OnlyIndent) {
QByteArray dummyText; CharacterContext currentCharContext = CharacterContext::Unknown;
// Iterate backwards to reuse the same dummy text for all empty lines. // Iterate backwards to reuse the same dummy text for all empty lines.
for (int index = endBlock.blockNumber(); index >= startBlock.blockNumber(); --index) { for (int index = endBlock.blockNumber(); index >= startBlock.blockNumber(); --index) {
utf8Length += forceIndentWithExtraText(buffer, utf8Length += forceIndentWithExtraText(buffer,
dummyText, currentCharContext,
m_doc->findBlockByNumber(index), m_doc->findBlockByNumber(index),
secondTry); secondTry);
} }

View File

@@ -404,6 +404,19 @@ TEST_F(ClangFormat, IndentEmptyLineInsideParantheses)
" && b)")); " && b)"));
} }
TEST_F(ClangFormat, EmptyLineInInitializerList)
{
insertLines({"Bar foo{a,",
"",
"};"});
indenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings());
ASSERT_THAT(documentLines(), ElementsAre("Bar foo{a,",
" ",
"};"));
}
TEST_F(ClangFormat, IndentFunctionBodyButNotFormatBeforeIt) TEST_F(ClangFormat, IndentFunctionBodyButNotFormatBeforeIt)
{ {
insertLines({"int foo(int a, int b,", insertLines({"int foo(int a, int b,",