diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp index 1b316bea55f..6516490942d 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.cpp +++ b/src/plugins/clangformat/clangformatbaseindenter.cpp @@ -39,9 +39,6 @@ namespace ClangFormat { static void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style, ReplacementsToKeep replacementsToKeep) { - if (replacementsToKeep == ReplacementsToKeep::All) - return; - style.MaxEmptyLinesToKeep = 2; style.SortIncludes = false; style.SortUsingDeclarations = false; @@ -341,48 +338,49 @@ void ClangFormatBaseIndenter::reindent(const QTextCursor &cursor, indent(cursor, QChar::Null, cursorPositionInEditor); } -TextEditor::Replacements ClangFormatBaseIndenter::format(const QTextCursor &cursor, - int cursorPositionInEditor) +TextEditor::Replacements ClangFormatBaseIndenter::format( + const TextEditor::RangesInLines &rangesInLines) { - int utf8Offset; - int utf8Length; + if (rangesInLines.empty()) + return TextEditor::Replacements(); + + int utf8Offset = -1; QTextBlock block; const QByteArray buffer = m_doc->toPlainText().toUtf8(); - if (cursor.hasSelection()) { - block = m_doc->findBlock(cursor.selectionStart()); - const QTextBlock end = m_doc->findBlock(cursor.selectionEnd()); - utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); - QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements();); - utf8Length = selectedLines(m_doc, block, end).toUtf8().size(); - } else { - block = cursor.block(); - utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); - QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements();); + std::vector ranges; + ranges.reserve(rangesInLines.size()); - utf8Length = block.text().toUtf8().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); + } } - const TextEditor::Replacements toReplace = replacements(buffer, - utf8Offset, - utf8Length, - block, - cursorPositionInEditor, - ReplacementsToKeep::All, - QChar::Null); + 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(block, + utf8Offset, + buffer, + clangReplacements); applyReplacements(block, toReplace); return toReplace; } -TextEditor::Replacements ClangFormatBaseIndenter::format( - const QTextCursor &cursor, - const TextEditor::TabSettings & /*tabSettings*/, - int cursorPositionInEditor) -{ - return format(cursor, cursorPositionInEditor); -} - void ClangFormatBaseIndenter::indentBeforeCursor(const QTextBlock &block, const QChar &typedChar, int cursorPositionInEditor) @@ -535,10 +533,19 @@ void ClangFormatBaseIndenter::formatOrIndent(const QTextCursor &cursor, const TextEditor::TabSettings & /*tabSettings*/, int cursorPositionInEditor) { - if (formatCodeInsteadOfIndent()) - format(cursor, cursorPositionInEditor); - else + if (formatCodeInsteadOfIndent()) { + QTextBlock start; + QTextBlock end; + if (cursor.hasSelection()) { + start = m_doc->findBlock(cursor.selectionStart()); + end = m_doc->findBlock(cursor.selectionEnd()); + } else { + start = end = cursor.block(); + } + format({{start.blockNumber() + 1, end.blockNumber() + 1}}); + } else { indent(cursor, QChar::Null, cursorPositionInEditor); + } } clang::format::FormatStyle ClangFormatBaseIndenter::styleForFile() const @@ -580,6 +587,8 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer const QChar &typedChar, bool secondTry) const { + QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return TextEditor::Replacements()); + clang::format::FormatStyle style = styleForFile(); int originalOffsetUtf8 = utf8Offset; @@ -593,24 +602,21 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer = block.text().left(cursorPositionInEditor - block.position()).toUtf8().size(); } - int extraEmptySpaceOffset = 0; int rangeStart = 0; - if (replacementsToKeep != ReplacementsToKeep::All) { - if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) - rangeStart = formattingRangeStart(block, buffer, lastSaveRevision()); + if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) + rangeStart = formattingRangeStart(block, buffer, lastSaveRevision()); - extraEmptySpaceOffset = previousEmptyLinesLength(block); - utf8Offset -= extraEmptySpaceOffset; - buffer.remove(utf8Offset, extraEmptySpaceOffset); + int extraEmptySpaceOffset = previousEmptyLinesLength(block); + utf8Offset -= extraEmptySpaceOffset; + buffer.remove(utf8Offset, extraEmptySpaceOffset); - adjustFormatStyleForLineBreak(style, replacementsToKeep); - modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry); + adjustFormatStyleForLineBreak(style, replacementsToKeep); + modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry); - if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) { - buffer.insert(utf8Offset - 1, " //"); - extraEmptySpaceOffset -= 3; - utf8Offset += 3; - } + if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) { + buffer.insert(utf8Offset - 1, " //"); + extraEmptySpaceOffset -= 3; + utf8Offset += 3; } if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || utf8Offset < rangeStart) diff --git a/src/plugins/clangformat/clangformatbaseindenter.h b/src/plugins/clangformat/clangformatbaseindenter.h index 2017f8ba646..d79c1393515 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.h +++ b/src/plugins/clangformat/clangformatbaseindenter.h @@ -53,9 +53,8 @@ public: void formatOrIndent(const QTextCursor &cursor, const TextEditor::TabSettings &tabSettings, int cursorPositionInEditor = -1) override; - TextEditor::Replacements format(const QTextCursor &cursor, - const TextEditor::TabSettings &tabSettings, - int cursorPositionInEditor = -1) override; + TextEditor::Replacements format( + const TextEditor::RangesInLines &rangesInLines = TextEditor::RangesInLines()) override; void indentBlock(const QTextBlock &block, const QChar &typedChar, @@ -75,7 +74,6 @@ protected: virtual int lastSaveRevision() const { return 0; } private: - TextEditor::Replacements format(const QTextCursor &cursor, int cursorPositionInEditor); void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor); void indentBlock(const QTextBlock &block, const QChar &typedChar, int cursorPositionInEditor); int indentFor(const QTextBlock &block, int cursorPositionInEditor); diff --git a/src/plugins/clangtools/clangfixitsrefactoringchanges.cpp b/src/plugins/clangtools/clangfixitsrefactoringchanges.cpp index 88b17fb42a8..4be95a2744c 100644 --- a/src/plugins/clangtools/clangfixitsrefactoringchanges.cpp +++ b/src/plugins/clangtools/clangfixitsrefactoringchanges.cpp @@ -89,6 +89,10 @@ bool FixitsRefactoringFile::apply() = CppTools::CppCodeStyleSettings::currentProjectTabSettings(); // Apply changes + std::unique_ptr indenter; + QString lastFilename; + ReplacementOperations operationsForFile; + for (int i=0; i < m_replacementOperations.size(); ++i) { ReplacementOperation &op = *m_replacementOperations[i]; if (op.apply) { @@ -103,15 +107,19 @@ bool FixitsRefactoringFile::apply() // Apply QTextDocument *doc = document(op.fileName); - std::unique_ptr indenter(factory->createIndenter(doc)); - indenter->setFileName(Utils::FileName::fromString(op.fileName)); + if (lastFilename != op.fileName) { + if (indenter) + format(*indenter, doc, operationsForFile, i); + operationsForFile.clear(); + indenter = std::unique_ptr(factory->createIndenter(doc)); + indenter->setFileName(Utils::FileName::fromString(op.fileName)); + } QTextCursor cursor(doc); cursor.setPosition(op.pos); cursor.setPosition(op.pos + op.length, QTextCursor::KeepAnchor); cursor.insertText(op.text); - - tryToFormat(*indenter, tabSettings, doc, op, i); + operationsForFile.push_back(&op); } } @@ -130,28 +138,29 @@ bool FixitsRefactoringFile::apply() return true; } -void FixitsRefactoringFile::tryToFormat(TextEditor::Indenter &indenter, - const TextEditor::TabSettings &tabSettings, - QTextDocument *doc, - const ReplacementOperation &op, - int currentIndex) +void FixitsRefactoringFile::format(TextEditor::Indenter &indenter, + QTextDocument *doc, + const ReplacementOperations &operationsForFile, + int firstOperationIndex) { - QTextCursor cursor(doc); - cursor.beginEditBlock(); - cursor.setPosition(op.pos); - cursor.movePosition(QTextCursor::Right, - QTextCursor::KeepAnchor, - op.text.length()); - const Replacements replacements = indenter.format(cursor, tabSettings); - cursor.endEditBlock(); + if (operationsForFile.isEmpty()) + return; + + TextEditor::RangesInLines ranges; + for (int i = 0; i < operationsForFile.size(); ++i) { + const ReplacementOperation &op = *operationsForFile.at(i); + const int start = doc->findBlock(op.pos).blockNumber() + 1; + const int end = doc->findBlock(op.pos + op.length).blockNumber() + 1; + ranges.push_back({start, end}); + } + const Replacements replacements = indenter.format(ranges); if (replacements.empty()) return; - if (hasIntersection(op.fileName, replacements, currentIndex + 1)) - doc->undo(&cursor); - else - shiftAffectedReplacements(op.fileName, replacements, currentIndex + 1); + shiftAffectedReplacements(operationsForFile.front()->fileName, + replacements, + firstOperationIndex + 1); } QTextDocument *FixitsRefactoringFile::document(const QString &filePath) const diff --git a/src/plugins/clangtools/clangfixitsrefactoringchanges.h b/src/plugins/clangtools/clangfixitsrefactoringchanges.h index c1fcf38c092..cf715b34872 100644 --- a/src/plugins/clangtools/clangfixitsrefactoringchanges.h +++ b/src/plugins/clangtools/clangfixitsrefactoringchanges.h @@ -67,11 +67,10 @@ private: QTextDocument *document(const QString &filePath) const; void shiftAffectedReplacements(const ReplacementOperation &op, int startIndex); - void tryToFormat(TextEditor::Indenter &indenter, - const TextEditor::TabSettings &tabSettings, - QTextDocument *doc, - const ReplacementOperation &op, - int currentIndex); + void format(TextEditor::Indenter &indenter, + QTextDocument *doc, + const ReplacementOperations &operationsForFile, + int firstOperationIndex); void shiftAffectedReplacements(const QString &fileName, const TextEditor::Replacements &replacements, int startIndex); diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index c6f2b9edc88..8a584915103 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -446,46 +446,39 @@ TextEditor::TabSettings CppEditorDocument::tabSettings() const return indenter()->tabSettings().value_or(TextEditor::TextDocument::tabSettings()); } -static int formatRange(QTextDocument *doc, - TextEditor::Indenter *indenter, - std::pair editedRange, - const TextEditor::TabSettings &tabSettings) -{ - QTextCursor cursor(doc); - cursor.setPosition(editedRange.first); - cursor.setPosition(editedRange.second, QTextCursor::KeepAnchor); - const int oldBlockCount = doc->blockCount(); - indenter->format(cursor, tabSettings); - return doc->blockCount() - oldBlockCount; -} - bool CppEditorDocument::save(QString *errorString, const QString &fileName, bool autoSave) { if (indenter()->formatOnSave() && !autoSave) { - QTextCursor cursor(document()); - cursor.joinPreviousEditBlock(); auto *layout = qobject_cast(document()->documentLayout()); const int documentRevision = layout->lastSaveRevision; - std::pair editedRange; + TextEditor::RangesInLines editedRanges; + TextEditor::RangeInLines lastRange{-1, -1}; for (int i = 0; i < document()->blockCount(); ++i) { const QTextBlock block = document()->findBlockByNumber(i); if (block.revision() == documentRevision) { - if (editedRange.first != -1) - i += formatRange(document(), indenter(), editedRange, tabSettings()); + if (lastRange.startLine != -1) + editedRanges.push_back(lastRange); - editedRange = std::make_pair(-1, -1); + lastRange.startLine = lastRange.endLine = -1; continue; } // block.revision() != documentRevision - if (editedRange.first == -1) - editedRange.first = block.position(); - editedRange.second = block.position() + block.length(); + if (lastRange.startLine == -1) + lastRange.startLine = block.blockNumber() + 1; + lastRange.endLine = block.blockNumber() + 1; + } + + if (lastRange.startLine != -1) + editedRanges.push_back(lastRange); + + if (!editedRanges.empty()) { + QTextCursor cursor(document()); + cursor.joinPreviousEditBlock(); + indenter()->format(editedRanges); + cursor.endEditBlock(); } - if (editedRange.first != -1) - formatRange(document(), indenter(), editedRange, tabSettings()); - cursor.endEditBlock(); } return TextEditor::TextDocument::save(errorString, fileName, autoSave); diff --git a/src/plugins/texteditor/indenter.h b/src/plugins/texteditor/indenter.h index 8a663be9037..31dda468008 100644 --- a/src/plugins/texteditor/indenter.h +++ b/src/plugins/texteditor/indenter.h @@ -58,6 +58,15 @@ public: using Replacements = std::vector; +class RangeInLines +{ +public: + int startLine; + int endLine; +}; + +using RangesInLines = std::vector; + class Indenter { public: @@ -91,9 +100,7 @@ public: } // By default just calls indent with default settings. - virtual Replacements format(const QTextCursor &/*cursor*/, - const TabSettings &/*tabSettings*/, - int /*cursorPositionInEditor*/ = -1) + virtual Replacements format(const RangesInLines & /*rangesInLines*/ = RangesInLines()) { return Replacements(); } diff --git a/tests/unit/unittest/clangformat-test.cpp b/tests/unit/unittest/clangformat-test.cpp index 8b64fb19149..6015a4f5b8a 100644 --- a/tests/unit/unittest/clangformat-test.cpp +++ b/tests/unit/unittest/clangformat-test.cpp @@ -415,7 +415,7 @@ TEST_F(ClangFormat, FormatBasicFile) "int a;", "}"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("int main()", "{", @@ -430,7 +430,7 @@ TEST_F(ClangFormat, FormatEmptyLine) "", "}"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("int main() {}")); } @@ -441,7 +441,7 @@ TEST_F(ClangFormat, FormatLambda) "", "});"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("int b = foo([]() {", "", @@ -454,7 +454,7 @@ TEST_F(ClangFormat, FormatInitializerListInArguments) "args,", "{1, 2});"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("foo(arg1, args, {1, 2});")); } @@ -466,7 +466,7 @@ TEST_F(ClangFormat, FormatFunctionArgumentLambdaWithScope) "", "});"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("foo([]() {", @@ -481,7 +481,7 @@ TEST_F(ClangFormat, FormatScopeAsFunctionArgument) "", "});"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("foo({", @@ -494,7 +494,7 @@ TEST_F(ClangFormat, FormatStructuredBinding) insertLines({"auto [a,", "b] = c;"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("auto [a, b] = c;")); } @@ -504,7 +504,7 @@ TEST_F(ClangFormat, FormatStringLiteralContinuation) insertLines({"foo(bar, \"foo\"", "\"bar\");"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("foo(bar,", " \"foo\"", @@ -517,7 +517,7 @@ TEST_F(ClangFormat, FormatTemplateparameters) "B,", "C>"}); - indenter.format(cursor, TextEditor::TabSettings()); + indenter.format(cursor); ASSERT_THAT(documentLines(), ElementsAre("using Alias = Template")); }