From 118c126f984debd3a39ea034179622da4d33fe7a Mon Sep 17 00:00:00 2001 From: Ivan Donchevskii Date: Tue, 19 Feb 2019 14:30:52 +0100 Subject: [PATCH] ClangFormat: Refactor code Reorder some functions, remove code duplication. Change-Id: I33e118e567dee7db622bbc99e6a7c500db54e5c5 Reviewed-by: Marco Bubke --- src/libs/utils/textutils.cpp | 22 + src/libs/utils/textutils.h | 4 + .../clangformat/clangformatbaseindenter.cpp | 600 +++++++++--------- .../clangformat/clangformatbaseindenter.h | 10 +- 4 files changed, 319 insertions(+), 317 deletions(-) diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 21fb3caf511..0d8009fef89 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -165,5 +165,27 @@ int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffe return utf8Offset; } +LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset) +{ + Utils::LineColumn lineColumn; + lineColumn.line = static_cast( + std::count(utf8Buffer.begin(), utf8Buffer.begin() + utf8Offset, '\n')) + + 1; + const int startOfLineOffset = utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1; + lineColumn.column = QString::fromUtf8( + utf8Buffer.mid(startOfLineOffset, utf8Offset - startOfLineOffset)) + .length() + + 1; + return lineColumn; +} + +QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset) +{ + const int lineStartUtf8Offset = utf8Buffer.lastIndexOf('\n', currentUtf8Offset - 1) + 1; + const int lineEndUtf8Offset = utf8Buffer.indexOf('\n', currentUtf8Offset); + return QString::fromUtf8( + utf8Buffer.mid(lineStartUtf8Offset, lineEndUtf8Offset - lineStartUtf8Offset)); +} + } // Text } // Utils diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index 895023903ee..fb0efe3f15f 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -59,5 +59,9 @@ QTCREATOR_UTILS_EXPORT int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffer, int line); +QTCREATOR_UTILS_EXPORT LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset); +QTCREATOR_UTILS_EXPORT QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, + int currentUtf8Offset); + } // Text } // Utils diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp index da36731796a..7a87eabcd79 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.cpp +++ b/src/plugins/clangformat/clangformatbaseindenter.cpp @@ -36,8 +36,9 @@ namespace ClangFormat { -static void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style, - ReplacementsToKeep replacementsToKeep) +namespace { +void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style, + ReplacementsToKeep replacementsToKeep) { style.MaxEmptyLinesToKeep = 100; style.SortIncludes = false; @@ -56,28 +57,30 @@ static void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style, #endif } -static llvm::StringRef clearExtraNewline(llvm::StringRef text) +llvm::StringRef clearExtraNewline(llvm::StringRef text) { while (text.startswith("\n\n")) text = text.drop_front(); return text; } -static clang::tooling::Replacements filteredReplacements( - const QByteArray &buffer, - const clang::tooling::Replacements &replacements, - int utf8Offset, - int utf8Length, - ReplacementsToKeep replacementsToKeep) +clang::tooling::Replacements filteredReplacements(const QByteArray &buffer, + const clang::tooling::Replacements &replacements, + int utf8Offset, + int utf8Length, + ReplacementsToKeep replacementsToKeep) { clang::tooling::Replacements filtered; for (const clang::tooling::Replacement &replacement : replacements) { int replacementOffset = static_cast(replacement.getOffset()); - const bool replacementDoesNotMatchRestriction - = replacementOffset >= utf8Offset + utf8Length - || (replacementsToKeep == ReplacementsToKeep::OnlyIndent - && (replacementOffset < utf8Offset - 1 || buffer.at(replacementOffset) != '\n')); - if (replacementDoesNotMatchRestriction) + + // Skip everything after. + if (replacementOffset >= utf8Offset + utf8Length) + return filtered; + + const bool isNotIndentOrInRange = replacementOffset < utf8Offset - 1 + || buffer.at(replacementOffset) != '\n'; + if (isNotIndentOrInRange && replacementsToKeep == ReplacementsToKeep::OnlyIndent) continue; llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent @@ -102,17 +105,10 @@ static clang::tooling::Replacements filteredReplacements( return filtered; } -static void trimFirstNonEmptyBlock(const QTextBlock ¤tBlock) +void trimRHSWhitespace(const QTextBlock &block) { - QTextBlock prevBlock = currentBlock.previous(); - while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty()) - prevBlock = prevBlock.previous(); - - if (prevBlock.text().trimmed().isEmpty()) - return; - - const QString initialText = prevBlock.text(); - if (!initialText.at(initialText.size() - 1).isSpace()) + const QString initialText = block.text(); + if (!initialText.rbegin()->isSpace()) return; auto lastNonSpace = std::find_if_not(initialText.rbegin(), @@ -120,7 +116,7 @@ static void trimFirstNonEmptyBlock(const QTextBlock ¤tBlock) [](const QChar &letter) { return letter.isSpace(); }); const int extraSpaceCount = static_cast(std::distance(initialText.rbegin(), lastNonSpace)); - QTextCursor cursor(prevBlock); + QTextCursor cursor(block); cursor.beginEditBlock(); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, @@ -130,20 +126,9 @@ static void trimFirstNonEmptyBlock(const QTextBlock ¤tBlock) cursor.endEditBlock(); } -// Returns the total langth of previous lines with pure whitespace. -static int previousEmptyLinesLength(const QTextBlock ¤tBlock) -{ - int length{0}; - QTextBlock prevBlock = currentBlock.previous(); - while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty()) { - length += prevBlock.text().length() + 1; - prevBlock = prevBlock.previous(); - } - - return length; -} - -static int modifyToIndentEmptyLines(QByteArray &buffer, const QTextBlock &block, bool secondTry) +// Add extra text in case of the empty line or the line starting with ')'. +// Track such extra pieces of text in isInsideModifiedLine(). +int forceIndentWithExtraText(QByteArray &buffer, const QTextBlock &block, bool secondTry) { const QString blockText = block.text(); int firstNonWhitespace = Utils::indexOf(blockText, @@ -195,48 +180,32 @@ static int modifyToIndentEmptyLines(QByteArray &buffer, const QTextBlock &block, return extraLength; } -static Utils::LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset) +bool isInsideModifiedLine(const QString &originalLine, const QString &modifiedLine, int column) { - Utils::LineColumn lineColumn; - lineColumn.line = std::count(utf8Buffer.begin(), utf8Buffer.begin() + utf8Offset, '\n') + 1; - lineColumn.column = utf8Offset - utf8Buffer.lastIndexOf('\n', utf8Offset - 1); - return lineColumn; -} - -static QString utf16LineLengthInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset) -{ - const int lineStartUtf8Offset = utf8Buffer.lastIndexOf('\n', currentUtf8Offset - 1) + 1; - const int lineEndUtf8Offset = utf8Buffer.indexOf('\n', currentUtf8Offset); - return QString::fromUtf8( - utf8Buffer.mid(lineStartUtf8Offset, lineEndUtf8Offset - lineStartUtf8Offset)); -} - -static bool isInsideModifiedLine(const QString &originalLine, - const QString &modifiedLine, - int column) -{ - // Track the cases when we have inserted extra text into the line to get the indentation. + // Detect the cases when we have inserted extra text into the line to get the indentation. return originalLine.length() < modifiedLine.length() && column != modifiedLine.length() + 1 && (column > originalLine.length() || originalLine.trimmed().isEmpty() || !modifiedLine.startsWith(originalLine)); } -static TextEditor::Replacements utf16Replacements(const QTextDocument *doc, - const QByteArray &utf8Buffer, - const clang::tooling::Replacements &replacements) +TextEditor::Replacements utf16Replacements(const QTextDocument *doc, + const QByteArray &utf8Buffer, + const clang::tooling::Replacements &replacements) { TextEditor::Replacements convertedReplacements; convertedReplacements.reserve(replacements.size()); for (const clang::tooling::Replacement &replacement : replacements) { - Utils::LineColumn lineColUtf16 = utf16LineColumn(utf8Buffer, - static_cast(replacement.getOffset())); + Utils::LineColumn lineColUtf16 = Utils::Text::utf16LineColumn(utf8Buffer, + static_cast( + replacement.getOffset())); if (!lineColUtf16.isValid()) continue; const QString lineText = doc->findBlockByNumber(lineColUtf16.line - 1).text(); - const QString bufferLineText = utf16LineLengthInUtf8Buffer(utf8Buffer, - replacement.getOffset()); + const QString bufferLineText + = Utils::Text::utf16LineTextInUtf8Buffer(utf8Buffer, + static_cast(replacement.getOffset())); if (isInsideModifiedLine(lineText, bufferLineText, lineColUtf16.column)) continue; @@ -257,7 +226,7 @@ static TextEditor::Replacements utf16Replacements(const QTextDocument *doc, return convertedReplacements; } -static void applyReplacements(QTextDocument *doc, const TextEditor::Replacements &replacements) +void applyReplacements(QTextDocument *doc, const TextEditor::Replacements &replacements) { if (replacements.empty()) return; @@ -277,9 +246,7 @@ static void applyReplacements(QTextDocument *doc, const TextEditor::Replacements } } -static QString selectedLines(QTextDocument *doc, - const QTextBlock &startBlock, - const QTextBlock &endBlock) +QString selectedLines(QTextDocument *doc, const QTextBlock &startBlock, const QTextBlock &endBlock) { return Utils::Text::textAt(QTextCursor(doc), startBlock.position(), @@ -288,19 +255,231 @@ static QString selectedLines(QTextDocument *doc, - startBlock.position() - 1)); } +int indentationForBlock(const TextEditor::Replacements &toReplace, + const QByteArray &buffer, + const QTextBlock ¤tBlock) +{ + const int utf8Offset = Utils::Text::utf8NthLineOffset(currentBlock.document(), + buffer, + currentBlock.blockNumber() + 1); + auto replacementIt = std::find_if(toReplace.begin(), + toReplace.end(), + [utf8Offset](const TextEditor::Replacement &replacement) { + return replacement.offset == utf8Offset - 1; + }); + if (replacementIt == toReplace.end()) + return -1; + + int afterLineBreak = replacementIt->text.lastIndexOf('\n'); + afterLineBreak = (afterLineBreak < 0) ? 0 : afterLineBreak + 1; + return static_cast(replacementIt->text.size() - afterLineBreak); +} + +bool doNotIndentInContext(QTextDocument *doc, int pos) +{ + const QChar character = doc->characterAt(pos); + const QTextBlock currentBlock = doc->findBlock(pos); + const QString text = currentBlock.text().left(pos - currentBlock.position()); + // NOTE: check if "<<" and ">>" always work correctly. + switch (character.toLatin1()) { + default: + break; + case ':': + // Do not indent when it's the first ':' and it's not the 'case' line. + if (text.contains(QLatin1String("case")) || text.contains(QLatin1String("default")) + || text.contains(QLatin1String("public")) || text.contains(QLatin1String("private")) + || text.contains(QLatin1String("protected")) || text.contains(QLatin1String("signals")) + || text.contains(QLatin1String("Q_SIGNALS"))) { + return false; + } + if (pos > 0 && doc->characterAt(pos - 1) != ':') + return true; + break; + } + + 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 ¤tBlock, + const QByteArray &buffer, + int documentRevision) +{ + QTextBlock prevBlock = currentBlock.previous(); + while ((prevBlock.position() > 0 || prevBlock.length() > 0) + && prevBlock.revision() != documentRevision) { + // Find the first block with not matching revision. + prevBlock = prevBlock.previous(); + } + if (prevBlock.revision() == documentRevision) + prevBlock = prevBlock.next(); + + return Utils::Text::utf8NthLineOffset(prevBlock.document(), buffer, prevBlock.blockNumber() + 1); +} +} // namespace + ClangFormatBaseIndenter::ClangFormatBaseIndenter(QTextDocument *doc) : TextEditor::Indenter(doc) {} -TextEditor::IndentationForBlock ClangFormatBaseIndenter::indentationForBlocks( - const QVector &blocks, - const TextEditor::TabSettings & /*tabSettings*/, - int cursorPositionInEditor) +TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer, + const QTextBlock &startBlock, + const QTextBlock &endBlock, + ReplacementsToKeep replacementsToKeep, + const QChar &typedChar, + bool secondTry) const { - TextEditor::IndentationForBlock ret; - for (QTextBlock block : blocks) - ret.insert(block.blockNumber(), indentFor(block, cursorPositionInEditor)); - return ret; + QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return TextEditor::Replacements()); + + clang::format::FormatStyle style = styleForFile(); + QByteArray originalBuffer = buffer; + + int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, startBlock.blockNumber() + 1); + QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements();); + int utf8Length = selectedLines(m_doc, startBlock, endBlock).toUtf8().size(); + + int rangeStart = 0; + if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) + rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision()); + + if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) { + buffer.insert(utf8Offset - 1, " //"); + utf8Offset += 3; + } + + adjustFormatStyleForLineBreak(style, replacementsToKeep); + if (typedChar == QChar::Null) { + for (int index = startBlock.blockNumber(); index <= endBlock.blockNumber(); ++index) { + utf8Length += forceIndentWithExtraText(buffer, + m_doc->findBlockByNumber(index), + secondTry); + } + } + + if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || utf8Offset < rangeStart) + rangeStart = utf8Offset; + + unsigned int rangeLength = static_cast(utf8Offset + utf8Length - rangeStart); + std::vector ranges{{static_cast(rangeStart), rangeLength}}; + + clang::format::FormattingAttemptStatus status; + clang::tooling::Replacements clangReplacements = reformat(style, + buffer.data(), + ranges, + m_fileName.toString().toStdString(), + &status); + + clang::tooling::Replacements filtered; + if (status.FormatComplete) { + filtered = filteredReplacements(buffer, + clangReplacements, + utf8Offset, + utf8Length, + replacementsToKeep); + } + const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent + && typedChar == QChar::Null && !secondTry; + if (canTryAgain && filtered.empty()) { + return replacements(originalBuffer, + startBlock, + endBlock, + replacementsToKeep, + typedChar, + true); + } + + return utf16Replacements(m_doc, buffer, filtered); +} + +TextEditor::Replacements ClangFormatBaseIndenter::format( + const TextEditor::RangesInLines &rangesInLines) +{ + if (rangesInLines.empty()) + return TextEditor::Replacements(); + + const QByteArray buffer = m_doc->toPlainText().toUtf8(); + std::vector ranges; + ranges.reserve(rangesInLines.size()); + + for (auto &range : rangesInLines) { + const int utf8StartOffset = Utils::Text::utf8NthLineOffset(m_doc, buffer, range.startLine); + int utf8RangeLength = m_doc->findBlockByNumber(range.endLine - 1).text().toUtf8().size(); + if (range.endLine > range.startLine) { + utf8RangeLength += Utils::Text::utf8NthLineOffset(m_doc, buffer, range.endLine) + - utf8StartOffset; + } + ranges.emplace_back(static_cast(utf8StartOffset), + static_cast(utf8RangeLength)); + } + + clang::format::FormattingAttemptStatus status; + const clang::tooling::Replacements clangReplacements + = reformat(styleForFile(), buffer.data(), ranges, m_fileName.toString().toStdString(), &status); + const TextEditor::Replacements toReplace = utf16Replacements(m_doc, buffer, clangReplacements); + applyReplacements(m_doc, toReplace); + + return toReplace; +} + +TextEditor::Replacements ClangFormatBaseIndenter::indentsFor(QTextBlock startBlock, + const QTextBlock &endBlock, + const QByteArray &buffer, + const QChar &typedChar, + int cursorPositionInEditor) +{ + if (typedChar != QChar::Null && cursorPositionInEditor > 0 + && m_doc->characterAt(cursorPositionInEditor - 1) == typedChar + && doNotIndentInContext(m_doc, cursorPositionInEditor - 1)) { + return TextEditor::Replacements(); + } + + startBlock = reverseFindLastEmptyBlock(startBlock); + const int startBlockPosition = startBlock.position(); + if (startBlock.position() > 0) { + trimRHSWhitespace(startBlock.previous()); + if (cursorPositionInEditor >= 0) + cursorPositionInEditor += startBlock.position() - startBlockPosition; + } + + ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent; + if (formatWhileTyping() + && (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 + // the indented block - don't trigger extra formatting in this case. + // cursorPositionInEditor == -1 means the consition matches automatically. + + // Format only before newline or complete statement not to break code. + replacementsToKeep = ReplacementsToKeep::IndentAndBefore; + } + + return replacements(buffer, + startBlock, + endBlock, + replacementsToKeep, + typedChar); +} + +void ClangFormatBaseIndenter::indentBlocks(const QTextBlock &startBlock, + const QTextBlock &endBlock, + const QChar &typedChar, + int cursorPositionInEditor) +{ + const QByteArray buffer = m_doc->toPlainText().toUtf8(); + applyReplacements(m_doc, + indentsFor(startBlock, endBlock, buffer, typedChar, cursorPositionInEditor)); } void ClangFormatBaseIndenter::indent(const QTextCursor &cursor, @@ -332,126 +511,6 @@ void ClangFormatBaseIndenter::reindent(const QTextCursor &cursor, indent(cursor, QChar::Null, cursorPositionInEditor); } -TextEditor::Replacements ClangFormatBaseIndenter::format( - const TextEditor::RangesInLines &rangesInLines) -{ - if (rangesInLines.empty()) - return TextEditor::Replacements(); - - int utf8Offset = -1; - QTextBlock block; - - const QByteArray buffer = m_doc->toPlainText().toUtf8(); - std::vector ranges; - ranges.reserve(rangesInLines.size()); - - for (auto &range : rangesInLines) { - const int utf8StartOffset = Utils::Text::utf8NthLineOffset(m_doc, buffer, range.startLine); - const QTextBlock end = m_doc->findBlockByNumber(range.endLine - 1); - int utf8RangeLength = end.text().toUtf8().size(); - if (range.endLine > range.startLine) { - utf8RangeLength += Utils::Text::utf8NthLineOffset(m_doc, buffer, range.endLine) - - utf8StartOffset; - } - ranges.emplace_back(static_cast(utf8StartOffset), - static_cast(utf8RangeLength)); - - if (utf8Offset < 0) { - utf8Offset = utf8StartOffset; - block = m_doc->findBlockByNumber(range.startLine - 1); - } - } - - clang::format::FormatStyle style = styleForFile(); - clang::format::FormattingAttemptStatus status; - const clang::tooling::Replacements clangReplacements - = reformat(style, buffer.data(), ranges, m_fileName.toString().toStdString(), &status); - const TextEditor::Replacements toReplace = utf16Replacements(m_doc, buffer, clangReplacements); - applyReplacements(m_doc, toReplace); - - return toReplace; -} - -static bool doNotIndentInContext(QTextDocument *doc, int pos) -{ - const QChar character = doc->characterAt(pos); - const QTextBlock currentBlock = doc->findBlock(pos); - const QString text = currentBlock.text().left(pos - currentBlock.position()); - // NOTE: check if "<<" and ">>" always work correctly. - switch (character.toLatin1()) { - default: - break; - case ':': - // Do not indent when it's the first ':' and it's not the 'case' line. - if (text.contains(QLatin1String("case")) || text.contains(QLatin1String("default")) - || text.contains(QLatin1String("public")) || text.contains(QLatin1String("private")) - || text.contains(QLatin1String("protected")) || text.contains(QLatin1String("signals")) - || text.contains(QLatin1String("Q_SIGNALS"))) { - return false; - } - if (pos > 0 && doc->characterAt(pos - 1) != ':') - return true; - break; - } - - return false; -} - -void ClangFormatBaseIndenter::indentBlocks(QTextBlock startBlock, - const QTextBlock &endBlock, - const QChar &typedChar, - int cursorPositionInEditor) -{ - if (typedChar != QChar::Null && cursorPositionInEditor > 0 - && m_doc->characterAt(cursorPositionInEditor - 1) == typedChar - && doNotIndentInContext(m_doc, cursorPositionInEditor - 1)) { - return; - } - - if (startBlock.position() > 0) { - startBlock = startBlock.previous(); - while (startBlock.position() > 0 && startBlock.text().trimmed().isEmpty()) - startBlock = startBlock.previous(); - if (!startBlock.text().trimmed().isEmpty()) { - startBlock = startBlock.next(); - } - } - - const int startBlockPosition = startBlock.position(); - trimFirstNonEmptyBlock(startBlock); - if (cursorPositionInEditor >= 0) - cursorPositionInEditor += startBlock.position() - startBlockPosition; - - ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent; - if (formatWhileTyping() - && (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 - // the indented block - don't trigger extra formatting in this case. - // cursorPositionInEditor == -1 means the consition matches automatically. - - // Format only before newline or complete statement not to break code. - replacementsToKeep = ReplacementsToKeep::IndentAndBefore; - } - - const QByteArray buffer = m_doc->toPlainText().toUtf8(); - 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(m_doc, - replacements(buffer, - utf8Offset, - utf8Length, - startBlock, - endBlock, - replacementsToKeep, - typedChar)); -} - void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, const QChar &typedChar, const TextEditor::TabSettings & /*tabSettings*/, @@ -460,35 +519,40 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, indentBlocks(block, block, typedChar, cursorPositionInEditor); } -int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int /*cursorPositionInEditor*/) -{ - trimFirstNonEmptyBlock(block); - - const QByteArray buffer = m_doc->toPlainText().toUtf8(); - const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); - QTC_ASSERT(utf8Offset >= 0, return 0;); - - const TextEditor::Replacements toReplace = replacements(buffer, - utf8Offset, - 0, - block, - block, - ReplacementsToKeep::OnlyIndent); - - if (toReplace.empty()) - return -1; - - const TextEditor::Replacement &replacement = toReplace.front(); - int afterLineBreak = replacement.text.lastIndexOf('\n'); - afterLineBreak = (afterLineBreak < 0) ? 0 : afterLineBreak + 1; - return static_cast(replacement.text.size() - afterLineBreak); -} - int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, const TextEditor::TabSettings & /*tabSettings*/, int cursorPositionInEditor) { - return indentFor(block, cursorPositionInEditor); + const QByteArray buffer = m_doc->toPlainText().toUtf8(); + TextEditor::Replacements toReplace = indentsFor(block, + block, + buffer, + QChar::Null, + cursorPositionInEditor); + if (toReplace.empty()) + return -1; + + return indentationForBlock(toReplace, buffer, block); +} + +TextEditor::IndentationForBlock ClangFormatBaseIndenter::indentationForBlocks( + const QVector &blocks, + const TextEditor::TabSettings & /*tabSettings*/, + int cursorPositionInEditor) +{ + TextEditor::IndentationForBlock ret; + if (blocks.isEmpty()) + return ret; + const QByteArray buffer = m_doc->toPlainText().toUtf8(); + TextEditor::Replacements toReplace = indentsFor(blocks.front(), + blocks.back(), + buffer, + QChar::Null, + cursorPositionInEditor); + + for (const QTextBlock &block : blocks) + ret.insert(block.blockNumber(), indentationForBlock(toReplace, buffer, block)); + return ret; } bool ClangFormatBaseIndenter::isElectricCharacter(const QChar &ch) const @@ -541,94 +605,4 @@ clang::format::FormatStyle ClangFormatBaseIndenter::styleForFile() const return clang::format::getLLVMStyle(); } -static int formattingRangeStart(const QTextBlock ¤tBlock, - const QByteArray &buffer, - int documentRevision) -{ - QTextBlock prevBlock = currentBlock.previous(); - while ((prevBlock.position() > 0 || prevBlock.length() > 0) - && prevBlock.revision() != documentRevision) { - // Find the first block with not matching revision. - prevBlock = prevBlock.previous(); - } - if (prevBlock.revision() == documentRevision) - prevBlock = prevBlock.next(); - - return Utils::Text::utf8NthLineOffset(prevBlock.document(), buffer, prevBlock.blockNumber() + 1); -} - -TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer, - int utf8Offset, - int utf8Length, - const QTextBlock &startBlock, - const QTextBlock &endBlock, - ReplacementsToKeep replacementsToKeep, - const QChar &typedChar, - bool secondTry) const -{ - QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return TextEditor::Replacements()); - - clang::format::FormatStyle style = styleForFile(); - - int originalOffsetUtf8 = utf8Offset; - int originalLengthUtf8 = utf8Length; - QByteArray originalBuffer = buffer; - - int rangeStart = 0; - if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) - rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision()); - - if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) { - buffer.insert(utf8Offset - 1, " //"); - utf8Offset += 3; - } - - adjustFormatStyleForLineBreak(style, replacementsToKeep); - if (typedChar == QChar::Null) { - for (int index = startBlock.blockNumber(); index <= endBlock.blockNumber(); ++index) { - utf8Length += modifyToIndentEmptyLines(buffer, - m_doc->findBlockByNumber(index), - secondTry); - } - } - - if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || utf8Offset < rangeStart) - rangeStart = utf8Offset; - - unsigned int rangeLength = static_cast(utf8Offset + utf8Length - rangeStart); - - std::vector ranges{{static_cast(rangeStart), rangeLength}}; - - clang::format::FormattingAttemptStatus status; - - clang::tooling::Replacements clangReplacements = reformat(style, - buffer.data(), - ranges, - m_fileName.toString().toStdString(), - &status); - - clang::tooling::Replacements filtered; - if (status.FormatComplete) { - filtered = filteredReplacements(buffer, - clangReplacements, - utf8Offset, - utf8Length, - replacementsToKeep); - } - const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent - && typedChar == QChar::Null && !secondTry; - if (canTryAgain && filtered.empty()) { - return replacements(originalBuffer, - originalOffsetUtf8, - originalLengthUtf8, - startBlock, - endBlock, - replacementsToKeep, - typedChar, - true); - } - - return utf16Replacements(m_doc, buffer, filtered); -} - } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatbaseindenter.h b/src/plugins/clangformat/clangformatbaseindenter.h index 360f27fbedc..ed63ff6efa3 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.h +++ b/src/plugins/clangformat/clangformatbaseindenter.h @@ -75,14 +75,16 @@ protected: private: void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor); - void indentBlocks(QTextBlock startBlock, + void indentBlocks(const QTextBlock &startBlock, const QTextBlock &endBlock, const QChar &typedChar, int cursorPositionInEditor); - int indentFor(const QTextBlock &block, int cursorPositionInEditor); + TextEditor::Replacements indentsFor(QTextBlock startBlock, + const QTextBlock &endBlock, + const QByteArray &buffer, + const QChar &typedChar, + int cursorPositionInEditor); TextEditor::Replacements replacements(QByteArray buffer, - int utf8Offset, - int utf8Length, const QTextBlock &startBlock, const QTextBlock &endBlock, ReplacementsToKeep replacementsToKeep,