ClangFormat: Improve mass indentation performance

Do not indent lines separately but call reformat only
once and then extract the proper replacements.

Remove some redundant code in process.

Change-Id: If3a0419fef9fb2ec5422d11343fdc3414f5751b7
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2019-02-18 15:56:50 +01:00
parent 7f461b2e22
commit dcf763c7ee
2 changed files with 61 additions and 98 deletions

View File

@@ -64,9 +64,10 @@ static llvm::StringRef clearExtraNewline(llvm::StringRef text)
} }
static clang::tooling::Replacements filteredReplacements( static clang::tooling::Replacements filteredReplacements(
const QByteArray &buffer,
const clang::tooling::Replacements &replacements, const clang::tooling::Replacements &replacements,
int offset, int utf8Offset,
int utf8LineLengthBeforeCursor, int utf8Length,
int extraEmptySpaceOffset, int extraEmptySpaceOffset,
ReplacementsToKeep replacementsToKeep) ReplacementsToKeep replacementsToKeep)
{ {
@@ -74,14 +75,13 @@ static clang::tooling::Replacements filteredReplacements(
for (const clang::tooling::Replacement &replacement : replacements) { for (const clang::tooling::Replacement &replacement : replacements) {
int replacementOffset = static_cast<int>(replacement.getOffset()); int replacementOffset = static_cast<int>(replacement.getOffset());
const bool replacementDoesNotMatchRestriction const bool replacementDoesNotMatchRestriction
= (replacementsToKeep == ReplacementsToKeep::OnlyIndent = replacementOffset >= utf8Offset + utf8Length
&& replacementOffset != offset - 1) || (replacementsToKeep == ReplacementsToKeep::OnlyIndent
|| (replacementsToKeep == ReplacementsToKeep::IndentAndBefore && (replacementOffset < utf8Offset - 1 || buffer.at(replacementOffset) != '\n'));
&& replacementOffset > offset + utf8LineLengthBeforeCursor - 1);
if (replacementDoesNotMatchRestriction) if (replacementDoesNotMatchRestriction)
continue; continue;
if (replacementOffset >= offset - 1) if (replacementOffset >= utf8Offset - 1)
replacementOffset += extraEmptySpaceOffset; replacementOffset += extraEmptySpaceOffset;
llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent
@@ -148,13 +148,13 @@ static int previousEmptyLinesLength(const QTextBlock &currentBlock)
} }
static void modifyToIndentEmptyLines( static void modifyToIndentEmptyLines(
QByteArray &buffer, int offset, int &length, const QTextBlock &block, bool secondTry) QByteArray &buffer, int utf8Offset, const QTextBlock &block, bool secondTry)
{ {
const QString blockText = block.text(); const QString blockText = block.text();
int firstNonWhitespace = Utils::indexOf(blockText, int firstNonWhitespace = Utils::indexOf(blockText,
[](const QChar &ch) { return !ch.isSpace(); }); [](const QChar &ch) { return !ch.isSpace(); });
if (firstNonWhitespace > 0) if (firstNonWhitespace > 0)
offset += firstNonWhitespace; utf8Offset += firstNonWhitespace;
const bool closingParenBlock = firstNonWhitespace >= 0 const bool closingParenBlock = firstNonWhitespace >= 0
&& blockText.at(firstNonWhitespace) == ')'; && blockText.at(firstNonWhitespace) == ')';
@@ -173,12 +173,11 @@ static void modifyToIndentEmptyLines(
if (closingParenBlock || prevBlock.text().endsWith(',')) if (closingParenBlock || prevBlock.text().endsWith(','))
dummyText = "&& a"; dummyText = "&& a";
length += dummyText.length(); buffer.insert(utf8Offset, dummyText);
buffer.insert(offset, dummyText);
} }
if (secondTry) { if (secondTry) {
int nextLinePos = buffer.indexOf('\n', offset); int nextLinePos = buffer.indexOf('\n', utf8Offset);
if (nextLinePos < 0) if (nextLinePos < 0)
nextLinePos = buffer.size() - 1; nextLinePos = buffer.size() - 1;
@@ -187,7 +186,6 @@ static void modifyToIndentEmptyLines(
// unclosed parentheses. // unclosed parentheses.
// TODO: Does it help to add different endings depending on the context? // TODO: Does it help to add different endings depending on the context?
buffer.insert(nextLinePos, ')'); buffer.insert(nextLinePos, ')');
length += 1;
} }
} }
} }
@@ -248,13 +246,13 @@ static TextEditor::Replacements utf16Replacements(const QTextBlock &block,
return convertedReplacements; return convertedReplacements;
} }
static void applyReplacements(const QTextBlock &block, const TextEditor::Replacements &replacements) static void applyReplacements(QTextDocument *doc, const TextEditor::Replacements &replacements)
{ {
if (replacements.empty()) if (replacements.empty())
return; return;
int fullOffsetShift = 0; int fullOffsetShift = 0;
QTextCursor editCursor(block); QTextCursor editCursor(doc);
for (const TextEditor::Replacement &replacement : replacements) { for (const TextEditor::Replacement &replacement : replacements) {
editCursor.beginEditBlock(); editCursor.beginEditBlock();
editCursor.setPosition(replacement.offset + fullOffsetShift); editCursor.setPosition(replacement.offset + fullOffsetShift);
@@ -302,24 +300,12 @@ void ClangFormatBaseIndenter::indent(const QTextCursor &cursor,
int cursorPositionInEditor) int cursorPositionInEditor)
{ {
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
// Calling currentBlock.next() might be unsafe because we change the document. indentBlocks(m_doc->findBlock(cursor.selectionStart()),
// Let's operate with block numbers instead. m_doc->findBlock(cursor.selectionEnd()),
const int startNumber = m_doc->findBlock(cursor.selectionStart()).blockNumber(); typedChar,
const int endNumber = m_doc->findBlock(cursor.selectionEnd()).blockNumber(); cursorPositionInEditor);
for (int currentBlockNumber = startNumber; currentBlockNumber <= endNumber;
++currentBlockNumber) {
const QTextBlock currentBlock = m_doc->findBlockByNumber(currentBlockNumber);
if (currentBlock.isValid()) {
const int blocksAmount = m_doc->blockCount();
indentBlock(currentBlock, typedChar, cursorPositionInEditor);
// Only blocks before current might be added/removed, so it's safe to modify the index.
if (blocksAmount != m_doc->blockCount())
currentBlockNumber += (m_doc->blockCount() - blocksAmount);
}
}
} else { } else {
indentBlock(cursor.block(), typedChar, cursorPositionInEditor); indentBlocks(cursor.block(), cursor.block(), typedChar, cursorPositionInEditor);
} }
} }
@@ -376,28 +362,11 @@ TextEditor::Replacements ClangFormatBaseIndenter::format(
utf8Offset, utf8Offset,
buffer, buffer,
clangReplacements); clangReplacements);
applyReplacements(block, toReplace); applyReplacements(m_doc, toReplace);
return toReplace; return toReplace;
} }
void ClangFormatBaseIndenter::indentBeforeCursor(const QTextBlock &block,
const QChar &typedChar,
int cursorPositionInEditor)
{
const QByteArray buffer = m_doc->toPlainText().toUtf8();
const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return;);
const TextEditor::Replacements toReplace = replacements(buffer,
utf8Offset,
0,
block,
cursorPositionInEditor,
ReplacementsToKeep::IndentAndBefore,
typedChar);
applyReplacements(block, toReplace);
}
static bool doNotIndentInContext(QTextDocument *doc, int pos) static bool doNotIndentInContext(QTextDocument *doc, int pos)
{ {
const QChar character = doc->characterAt(pos); const QChar character = doc->characterAt(pos);
@@ -423,9 +392,10 @@ static bool doNotIndentInContext(QTextDocument *doc, int pos)
return false; return false;
} }
void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, void ClangFormatBaseIndenter::indentBlocks(const QTextBlock &startBlock,
const QChar &typedChar, const QTextBlock &endBlock,
int cursorPositionInEditor) const QChar &typedChar,
int cursorPositionInEditor)
{ {
if (typedChar != QChar::Null && cursorPositionInEditor > 0 if (typedChar != QChar::Null && cursorPositionInEditor > 0
&& m_doc->characterAt(cursorPositionInEditor - 1) == typedChar && m_doc->characterAt(cursorPositionInEditor - 1) == typedChar
@@ -433,15 +403,14 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
return; return;
} }
const int blockPosition = block.position(); const int startBlockPosition = startBlock.position();
trimFirstNonEmptyBlock(block); trimFirstNonEmptyBlock(startBlock);
if (cursorPositionInEditor >= 0) if (cursorPositionInEditor >= 0)
cursorPositionInEditor += block.position() - blockPosition; cursorPositionInEditor += startBlock.position() - startBlockPosition;
else
cursorPositionInEditor = block.position();
ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent;
if (formatWhileTyping() if (formatWhileTyping()
&& (cursorPositionInEditor == -1 || cursorPositionInEditor >= blockPosition) && (cursorPositionInEditor == -1 || cursorPositionInEditor >= startBlockPosition)
&& (typedChar == QChar::Null || typedChar == ';' || typedChar == '}')) { && (typedChar == QChar::Null || typedChar == ';' || typedChar == '}')) {
// Format before current position only in case the cursor is inside the indented block. // Format before current position only in case the cursor is inside the indented block.
// So if cursor position is less then the block position then the current line is before // So if cursor position is less then the block position then the current line is before
@@ -449,21 +418,23 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
// cursorPositionInEditor == -1 means the consition matches automatically. // cursorPositionInEditor == -1 means the consition matches automatically.
// Format only before newline or complete statement not to break code. // Format only before newline or complete statement not to break code.
indentBeforeCursor(block, typedChar, cursorPositionInEditor); replacementsToKeep = ReplacementsToKeep::IndentAndBefore;
return;
} }
const QByteArray buffer = m_doc->toPlainText().toUtf8(); const QByteArray buffer = m_doc->toPlainText().toUtf8();
const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc,
buffer,
startBlock.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return;); QTC_ASSERT(utf8Offset >= 0, return;);
const int utf8Length = selectedLines(m_doc, startBlock, endBlock).toUtf8().size();
applyReplacements(block, applyReplacements(m_doc,
replacements(buffer, replacements(buffer,
utf8Offset, utf8Offset,
0, utf8Length,
block, startBlock,
cursorPositionInEditor, endBlock,
ReplacementsToKeep::OnlyIndent, replacementsToKeep,
typedChar)); typedChar));
} }
@@ -472,17 +443,12 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
const TextEditor::TabSettings & /*tabSettings*/, const TextEditor::TabSettings & /*tabSettings*/,
int cursorPositionInEditor) int cursorPositionInEditor)
{ {
indentBlock(block, typedChar, cursorPositionInEditor); indentBlocks(block, block, typedChar, cursorPositionInEditor);
} }
int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int cursorPositionInEditor) int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int /*cursorPositionInEditor*/)
{ {
const int blockPosition = block.position();
trimFirstNonEmptyBlock(block); trimFirstNonEmptyBlock(block);
if (cursorPositionInEditor >= 0)
cursorPositionInEditor += block.position() - blockPosition;
else
cursorPositionInEditor = block.position();
const QByteArray buffer = m_doc->toPlainText().toUtf8(); const QByteArray buffer = m_doc->toPlainText().toUtf8();
const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
@@ -492,7 +458,7 @@ int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int cursorPositi
utf8Offset, utf8Offset,
0, 0,
block, block,
cursorPositionInEditor, block,
ReplacementsToKeep::OnlyIndent); ReplacementsToKeep::OnlyIndent);
if (toReplace.empty()) if (toReplace.empty())
@@ -580,8 +546,8 @@ static int formattingRangeStart(const QTextBlock &currentBlock,
TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer, TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer,
int utf8Offset, int utf8Offset,
int utf8Length, int utf8Length,
const QTextBlock &block, const QTextBlock &startBlock,
int cursorPositionInEditor, const QTextBlock &endBlock,
ReplacementsToKeep replacementsToKeep, ReplacementsToKeep replacementsToKeep,
const QChar &typedChar, const QChar &typedChar,
bool secondTry) const bool secondTry) const
@@ -594,23 +560,19 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
int originalLengthUtf8 = utf8Length; int originalLengthUtf8 = utf8Length;
QByteArray originalBuffer = buffer; QByteArray originalBuffer = buffer;
int utf8LineLengthBeforeCursor = 0;
if (cursorPositionInEditor > 0 && typedChar != QChar::Null) {
// Format starting with the electric character if it's present.
utf8LineLengthBeforeCursor
= block.text().left(cursorPositionInEditor - block.position()).toUtf8().size();
}
int rangeStart = 0; int rangeStart = 0;
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
rangeStart = formattingRangeStart(block, buffer, lastSaveRevision()); rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision());
int extraEmptySpaceOffset = previousEmptyLinesLength(block); int extraEmptySpaceOffset = previousEmptyLinesLength(startBlock);
utf8Offset -= extraEmptySpaceOffset; utf8Offset -= extraEmptySpaceOffset;
buffer.remove(utf8Offset, extraEmptySpaceOffset); buffer.remove(utf8Offset, extraEmptySpaceOffset);
adjustFormatStyleForLineBreak(style, replacementsToKeep); adjustFormatStyleForLineBreak(style, replacementsToKeep);
modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry); if (typedChar == QChar::Null && startBlock == endBlock) {
modifyToIndentEmptyLines(buffer, utf8Offset, startBlock, secondTry);
utf8Length = 0;
}
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) { if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) {
buffer.insert(utf8Offset - 1, " //"); buffer.insert(utf8Offset - 1, " //");
@@ -635,9 +597,10 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
clang::tooling::Replacements filtered; clang::tooling::Replacements filtered;
if (status.FormatComplete) { if (status.FormatComplete) {
filtered = filteredReplacements(clangReplacements, filtered = filteredReplacements(buffer,
clangReplacements,
utf8Offset, utf8Offset,
utf8LineLengthBeforeCursor, utf8Length,
extraEmptySpaceOffset, extraEmptySpaceOffset,
replacementsToKeep); replacementsToKeep);
} }
@@ -647,14 +610,14 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
return replacements(originalBuffer, return replacements(originalBuffer,
originalOffsetUtf8, originalOffsetUtf8,
originalLengthUtf8, originalLengthUtf8,
block, startBlock,
cursorPositionInEditor, endBlock,
replacementsToKeep, replacementsToKeep,
typedChar, typedChar,
true); true);
} }
return utf16Replacements(block, originalOffsetUtf8, originalBuffer, filtered); return utf16Replacements(startBlock, originalOffsetUtf8, originalBuffer, filtered);
} }
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -75,16 +75,16 @@ protected:
private: private:
void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor); void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor);
void indentBlock(const QTextBlock &block, const QChar &typedChar, int cursorPositionInEditor); void indentBlocks(const QTextBlock &startBlock,
const QTextBlock &endBlock,
const QChar &typedChar,
int cursorPositionInEditor);
int indentFor(const QTextBlock &block, int cursorPositionInEditor); int indentFor(const QTextBlock &block, int cursorPositionInEditor);
void indentBeforeCursor(const QTextBlock &block,
const QChar &typedChar,
int cursorPositionInEditor);
TextEditor::Replacements replacements(QByteArray buffer, TextEditor::Replacements replacements(QByteArray buffer,
int utf8Offset, int utf8Offset,
int utf8Length, int utf8Length,
const QTextBlock &block, const QTextBlock &startBlock,
int cursorPositionInEditor, const QTextBlock &endBlock,
ReplacementsToKeep replacementsToKeep, ReplacementsToKeep replacementsToKeep,
const QChar &typedChar = QChar::Null, const QChar &typedChar = QChar::Null,
bool secondTry = false) const; bool secondTry = false) const;