From dcf763c7ee380b68f8c20e5160493814142c9eac Mon Sep 17 00:00:00 2001 From: Ivan Donchevskii Date: Mon, 18 Feb 2019 15:56:50 +0100 Subject: [PATCH] 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 --- .../clangformat/clangformatbaseindenter.cpp | 147 +++++++----------- .../clangformat/clangformatbaseindenter.h | 12 +- 2 files changed, 61 insertions(+), 98 deletions(-) diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp index 34087553a7f..6d06b058122 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.cpp +++ b/src/plugins/clangformat/clangformatbaseindenter.cpp @@ -64,9 +64,10 @@ static llvm::StringRef clearExtraNewline(llvm::StringRef text) } static clang::tooling::Replacements filteredReplacements( + const QByteArray &buffer, const clang::tooling::Replacements &replacements, - int offset, - int utf8LineLengthBeforeCursor, + int utf8Offset, + int utf8Length, int extraEmptySpaceOffset, ReplacementsToKeep replacementsToKeep) { @@ -74,14 +75,13 @@ static clang::tooling::Replacements filteredReplacements( for (const clang::tooling::Replacement &replacement : replacements) { int replacementOffset = static_cast(replacement.getOffset()); const bool replacementDoesNotMatchRestriction - = (replacementsToKeep == ReplacementsToKeep::OnlyIndent - && replacementOffset != offset - 1) - || (replacementsToKeep == ReplacementsToKeep::IndentAndBefore - && replacementOffset > offset + utf8LineLengthBeforeCursor - 1); + = replacementOffset >= utf8Offset + utf8Length + || (replacementsToKeep == ReplacementsToKeep::OnlyIndent + && (replacementOffset < utf8Offset - 1 || buffer.at(replacementOffset) != '\n')); if (replacementDoesNotMatchRestriction) continue; - if (replacementOffset >= offset - 1) + if (replacementOffset >= utf8Offset - 1) replacementOffset += extraEmptySpaceOffset; llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent @@ -148,13 +148,13 @@ static int previousEmptyLinesLength(const QTextBlock ¤tBlock) } 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(); int firstNonWhitespace = Utils::indexOf(blockText, [](const QChar &ch) { return !ch.isSpace(); }); if (firstNonWhitespace > 0) - offset += firstNonWhitespace; + utf8Offset += firstNonWhitespace; const bool closingParenBlock = firstNonWhitespace >= 0 && blockText.at(firstNonWhitespace) == ')'; @@ -173,12 +173,11 @@ static void modifyToIndentEmptyLines( if (closingParenBlock || prevBlock.text().endsWith(',')) dummyText = "&& a"; - length += dummyText.length(); - buffer.insert(offset, dummyText); + buffer.insert(utf8Offset, dummyText); } if (secondTry) { - int nextLinePos = buffer.indexOf('\n', offset); + int nextLinePos = buffer.indexOf('\n', utf8Offset); if (nextLinePos < 0) nextLinePos = buffer.size() - 1; @@ -187,7 +186,6 @@ static void modifyToIndentEmptyLines( // unclosed parentheses. // TODO: Does it help to add different endings depending on the context? buffer.insert(nextLinePos, ')'); - length += 1; } } } @@ -248,13 +246,13 @@ static TextEditor::Replacements utf16Replacements(const QTextBlock &block, return convertedReplacements; } -static void applyReplacements(const QTextBlock &block, const TextEditor::Replacements &replacements) +static void applyReplacements(QTextDocument *doc, const TextEditor::Replacements &replacements) { if (replacements.empty()) return; int fullOffsetShift = 0; - QTextCursor editCursor(block); + QTextCursor editCursor(doc); for (const TextEditor::Replacement &replacement : replacements) { editCursor.beginEditBlock(); editCursor.setPosition(replacement.offset + fullOffsetShift); @@ -302,24 +300,12 @@ void ClangFormatBaseIndenter::indent(const QTextCursor &cursor, int cursorPositionInEditor) { if (cursor.hasSelection()) { - // Calling currentBlock.next() might be unsafe because we change the document. - // Let's operate with block numbers instead. - const int startNumber = m_doc->findBlock(cursor.selectionStart()).blockNumber(); - const int endNumber = m_doc->findBlock(cursor.selectionEnd()).blockNumber(); - 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); - } - } + indentBlocks(m_doc->findBlock(cursor.selectionStart()), + m_doc->findBlock(cursor.selectionEnd()), + typedChar, + cursorPositionInEditor); } else { - indentBlock(cursor.block(), typedChar, cursorPositionInEditor); + indentBlocks(cursor.block(), cursor.block(), typedChar, cursorPositionInEditor); } } @@ -376,28 +362,11 @@ TextEditor::Replacements ClangFormatBaseIndenter::format( utf8Offset, buffer, clangReplacements); - applyReplacements(block, toReplace); + applyReplacements(m_doc, 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) { const QChar character = doc->characterAt(pos); @@ -423,9 +392,10 @@ static bool doNotIndentInContext(QTextDocument *doc, int pos) return false; } -void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, - const QChar &typedChar, - int cursorPositionInEditor) +void ClangFormatBaseIndenter::indentBlocks(const QTextBlock &startBlock, + const QTextBlock &endBlock, + const QChar &typedChar, + int cursorPositionInEditor) { if (typedChar != QChar::Null && cursorPositionInEditor > 0 && m_doc->characterAt(cursorPositionInEditor - 1) == typedChar @@ -433,15 +403,14 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, return; } - const int blockPosition = block.position(); - trimFirstNonEmptyBlock(block); + const int startBlockPosition = startBlock.position(); + trimFirstNonEmptyBlock(startBlock); if (cursorPositionInEditor >= 0) - cursorPositionInEditor += block.position() - blockPosition; - else - cursorPositionInEditor = block.position(); + cursorPositionInEditor += startBlock.position() - startBlockPosition; + ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent; if (formatWhileTyping() - && (cursorPositionInEditor == -1 || cursorPositionInEditor >= blockPosition) + && (cursorPositionInEditor == -1 || cursorPositionInEditor >= startBlockPosition) && (typedChar == QChar::Null || typedChar == ';' || typedChar == '}')) { // 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 @@ -449,21 +418,23 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, // cursorPositionInEditor == -1 means the consition matches automatically. // Format only before newline or complete statement not to break code. - indentBeforeCursor(block, typedChar, cursorPositionInEditor); - return; + replacementsToKeep = ReplacementsToKeep::IndentAndBefore; } 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;); + const int utf8Length = selectedLines(m_doc, startBlock, endBlock).toUtf8().size(); - applyReplacements(block, + applyReplacements(m_doc, replacements(buffer, utf8Offset, - 0, - block, - cursorPositionInEditor, - ReplacementsToKeep::OnlyIndent, + utf8Length, + startBlock, + endBlock, + replacementsToKeep, typedChar)); } @@ -472,17 +443,12 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, const TextEditor::TabSettings & /*tabSettings*/, 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); - if (cursorPositionInEditor >= 0) - cursorPositionInEditor += block.position() - blockPosition; - else - cursorPositionInEditor = block.position(); const QByteArray buffer = m_doc->toPlainText().toUtf8(); 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, 0, block, - cursorPositionInEditor, + block, ReplacementsToKeep::OnlyIndent); if (toReplace.empty()) @@ -580,8 +546,8 @@ static int formattingRangeStart(const QTextBlock ¤tBlock, TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer, int utf8Offset, int utf8Length, - const QTextBlock &block, - int cursorPositionInEditor, + const QTextBlock &startBlock, + const QTextBlock &endBlock, ReplacementsToKeep replacementsToKeep, const QChar &typedChar, bool secondTry) const @@ -594,23 +560,19 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer int originalLengthUtf8 = utf8Length; 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; if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) - rangeStart = formattingRangeStart(block, buffer, lastSaveRevision()); + rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision()); - int extraEmptySpaceOffset = previousEmptyLinesLength(block); + int extraEmptySpaceOffset = previousEmptyLinesLength(startBlock); utf8Offset -= extraEmptySpaceOffset; buffer.remove(utf8Offset, extraEmptySpaceOffset); 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) { buffer.insert(utf8Offset - 1, " //"); @@ -635,9 +597,10 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer clang::tooling::Replacements filtered; if (status.FormatComplete) { - filtered = filteredReplacements(clangReplacements, + filtered = filteredReplacements(buffer, + clangReplacements, utf8Offset, - utf8LineLengthBeforeCursor, + utf8Length, extraEmptySpaceOffset, replacementsToKeep); } @@ -647,14 +610,14 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer return replacements(originalBuffer, originalOffsetUtf8, originalLengthUtf8, - block, - cursorPositionInEditor, + startBlock, + endBlock, replacementsToKeep, typedChar, true); } - return utf16Replacements(block, originalOffsetUtf8, originalBuffer, filtered); + return utf16Replacements(startBlock, originalOffsetUtf8, originalBuffer, filtered); } } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatbaseindenter.h b/src/plugins/clangformat/clangformatbaseindenter.h index d79c1393515..272cbd648fd 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.h +++ b/src/plugins/clangformat/clangformatbaseindenter.h @@ -75,16 +75,16 @@ protected: private: 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); - void indentBeforeCursor(const QTextBlock &block, - const QChar &typedChar, - int cursorPositionInEditor); TextEditor::Replacements replacements(QByteArray buffer, int utf8Offset, int utf8Length, - const QTextBlock &block, - int cursorPositionInEditor, + const QTextBlock &startBlock, + const QTextBlock &endBlock, ReplacementsToKeep replacementsToKeep, const QChar &typedChar = QChar::Null, bool secondTry = false) const;