diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp index d69498d0473..f5bc876020a 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.cpp +++ b/src/plugins/clangformat/clangformatbaseindenter.cpp @@ -27,6 +27,7 @@ #include +#include #include #include #include @@ -57,20 +58,29 @@ static llvm::StringRef clearExtraNewline(llvm::StringRef text) static clang::tooling::Replacements filteredReplacements( const clang::tooling::Replacements &replacements, int offset, - int extraOffsetToAdd, - bool onlyIndention) + int utf8LineLengthBeforeCursor, + int extraOffsetFromStartOfFile, + int extraEmptySpaceOffset, + ReplacementsToKeep replacementsToKeep) { clang::tooling::Replacements filtered; for (const clang::tooling::Replacement &replacement : replacements) { int replacementOffset = static_cast(replacement.getOffset()); - if (onlyIndention && replacementOffset != offset - 1) + const bool replacementDoesNotMatchRestriction + = (replacementsToKeep == ReplacementsToKeep::OnlyIndent + && replacementOffset != offset - 1) + || (replacementsToKeep == ReplacementsToKeep::OnlyBeforeIndent + && replacementOffset >= offset + utf8LineLengthBeforeCursor - 1); + if (replacementDoesNotMatchRestriction) continue; - if (replacementOffset + 1 >= offset) - replacementOffset += extraOffsetToAdd; + if (replacementOffset >= offset - 1) + replacementOffset += extraEmptySpaceOffset; + replacementOffset += extraOffsetFromStartOfFile; - llvm::StringRef text = onlyIndention ? clearExtraNewline(replacement.getReplacementText()) - : replacement.getReplacementText(); + llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent + ? clearExtraNewline(replacement.getReplacementText()) + : replacement.getReplacementText(); llvm::Error error = filtered.add( clang::tooling::Replacement(replacement.getFilePath(), @@ -78,8 +88,14 @@ static clang::tooling::Replacements filteredReplacements( replacement.getLength(), text)); // Throws if error is not checked. - if (error) + if (error) { + error = llvm::handleErrors(std::move(error), + [](const llvm::ErrorInfoBase &) -> llvm::Error { + return llvm::Error::success(); + }); + QTC_CHECK(!error && "Error must be a \"success\" at this point"); break; + } } return filtered; } @@ -140,9 +156,16 @@ static int previousEmptyLinesLength(const QTextBlock ¤tBlock) static void modifyToIndentEmptyLines( QByteArray &buffer, int offset, int &length, const QTextBlock &block, bool secondTry) { - const QString blockText = block.text().trimmed(); - const bool closingParenBlock = blockText.startsWith(')'); - if (blockText.isEmpty() || closingParenBlock) { + const QString blockText = block.text(); + int firstNonWhitespace = Utils::indexOf(blockText, + [](const QChar &ch) { return !ch.isSpace(); }); + if (firstNonWhitespace > 0) + offset += firstNonWhitespace; + + const bool closingParenBlock = firstNonWhitespace >= 0 + && blockText.at(firstNonWhitespace) == ')'; + + if (firstNonWhitespace < 0 || closingParenBlock) { //This extra text works for the most cases. QByteArray dummyText("a;"); @@ -153,15 +176,8 @@ static void modifyToIndentEmptyLines( prevBlock = prevBlock.previous(); prevBlockIsEmpty = prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty(); } - if (prevBlock.text().endsWith(',')) - dummyText = "int a"; - - if (closingParenBlock) { - if (prevBlock.text().endsWith(',')) - dummyText = "int a"; - else - dummyText = "&& a"; - } + if (closingParenBlock || prevBlock.text().endsWith(',')) + dummyText = "&& a"; length += dummyText.length(); buffer.insert(offset, dummyText); @@ -169,6 +185,9 @@ static void modifyToIndentEmptyLines( if (secondTry) { int nextLinePos = buffer.indexOf('\n', offset); + if (nextLinePos < 0) + nextLinePos = buffer.size() - 1; + if (nextLinePos > 0) { // If first try was not successful try to put ')' in the end of the line to close possibly // unclosed parentheses. @@ -187,7 +206,9 @@ static Utils::LineColumn utf16LineColumn(const QTextBlock &block, int utf8Offset) { // If lastIndexOf('\n') returns -1 then we are fine to add 1 and get 0 offset. - const int lineStartUtf8Offset = utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1; + const int lineStartUtf8Offset = utf8Offset == 0 + ? 0 + : utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1; int line = block.blockNumber() + 1; // Init with the line corresponding the block. if (utf8Offset < blockOffsetUtf8) { @@ -299,8 +320,10 @@ void ClangFormatBaseIndenter::indent(const QTextCursor &cursor, if (currentBlock.isValid()) { const int blocksAmount = m_doc->blockCount(); indentBlock(currentBlock, typedChar, cursorPositionInEditor); - QTC_CHECK(blocksAmount == m_doc->blockCount() - && "ClangFormat plugin indentation changed the amount of blocks."); + + // 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 { @@ -328,24 +351,30 @@ TextEditor::Replacements ClangFormatBaseIndenter::format(const QTextCursor &curs { int utf8Offset; int utf8Length; + QTextBlock block; + const QByteArray buffer = m_doc->toPlainText().toUtf8(); - QTextBlock block = cursor.block(); 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 { - const QTextBlock block = cursor.block(); + block = cursor.block(); utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements();); + utf8Length = block.text().toUtf8().size(); } - const TextEditor::Replacements toReplace - = replacements(buffer, utf8Offset, utf8Length, block, QChar::Null, false); + const TextEditor::Replacements toReplace = replacements(buffer, + utf8Offset, + utf8Length, + block, + cursorPositionInEditor, + ReplacementsToKeep::All, + QChar::Null); applyReplacements(block, toReplace); return toReplace; @@ -359,17 +388,62 @@ TextEditor::Replacements ClangFormatBaseIndenter::format( return format(cursor, cursorPositionInEditor); } +int 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 cursorPositionInEditor;); + const TextEditor::Replacements toReplace = replacements(buffer, + utf8Offset, + 0, + block, + cursorPositionInEditor, + ReplacementsToKeep::OnlyBeforeIndent, + typedChar); + applyReplacements(block, toReplace); + for (const TextEditor::Replacement &replacement : toReplace) + cursorPositionInEditor += replacement.text.length() - replacement.length; + return cursorPositionInEditor; +} + void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, const QChar &typedChar, - int /*cursorPositionInEditor*/) + int cursorPositionInEditor) { - trimFirstNonEmptyBlock(block); - trimCurrentBlock(block); + QTextBlock currentBlock = block; + const int blockPosition = currentBlock.position(); + trimFirstNonEmptyBlock(currentBlock); + + if (formatWhileTyping() + && (cursorPositionInEditor == -1 || cursorPositionInEditor >= blockPosition)) { + // 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. + if (cursorPositionInEditor >= 0) + cursorPositionInEditor += currentBlock.position() - blockPosition; + else + cursorPositionInEditor = currentBlock.position(); + + cursorPositionInEditor = indentBeforeCursor(currentBlock, typedChar, cursorPositionInEditor); + currentBlock = m_doc->findBlock(cursorPositionInEditor); + } + + trimCurrentBlock(currentBlock); const QByteArray buffer = m_doc->toPlainText().toUtf8(); const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); QTC_ASSERT(utf8Offset >= 0, return;); - applyReplacements(block, replacements(buffer, utf8Offset, 0, block, typedChar)); + applyReplacements(currentBlock, + replacements(buffer, + utf8Offset, + 0, + currentBlock, + cursorPositionInEditor, + ReplacementsToKeep::OnlyIndent, + typedChar)); } void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, @@ -380,7 +454,7 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, indentBlock(block, typedChar, cursorPositionInEditor); } -int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int /*cursorPositionInEditor*/) +int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int cursorPositionInEditor) { trimFirstNonEmptyBlock(block); trimCurrentBlock(block); @@ -388,7 +462,12 @@ int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int /*cursorPosi 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); + const TextEditor::Replacements toReplace = replacements(buffer, + utf8Offset, + 0, + block, + cursorPositionInEditor, + ReplacementsToKeep::OnlyIndent); if (toReplace.empty()) return -1; @@ -447,12 +526,30 @@ 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 + && (currentBlock.blockNumber() - prevBlock.blockNumber() < kMaxLinesFromCurrentBlock)) { + // 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 &block, + int cursorPositionInEditor, + ReplacementsToKeep replacementsToKeep, const QChar &typedChar, - bool onlyIndention, bool secondTry) const { clang::format::FormatStyle style = styleForFile(); @@ -461,13 +558,22 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer int originalLengthUtf8 = utf8Length; QByteArray originalBuffer = buffer; - int extraOffset = 0; - if (onlyIndention) { + 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 extraOffsetFromStartOfFile = 0; + int extraEmptySpaceOffset = 0; + int rangeStart = 0; + if (replacementsToKeep != ReplacementsToKeep::All) { if (block.blockNumber() > kMaxLinesFromCurrentBlock) { - extraOffset = Utils::Text::utf8NthLineOffset(block.document(), - buffer, - block.blockNumber() - - kMaxLinesFromCurrentBlock); + extraOffsetFromStartOfFile + = Utils::Text::utf8NthLineOffset(block.document(), + buffer, + block.blockNumber() - kMaxLinesFromCurrentBlock); } int endOffset = Utils::Text::utf8NthLineOffset(block.document(), buffer, @@ -476,22 +582,30 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer if (endOffset == -1) endOffset = buffer.size(); - buffer = buffer.mid(extraOffset, endOffset - extraOffset); - utf8Offset -= extraOffset; - const int emptySpaceLength = previousEmptyLinesLength(block); - utf8Offset -= emptySpaceLength; - buffer.remove(utf8Offset, emptySpaceLength); + if (replacementsToKeep == ReplacementsToKeep::OnlyBeforeIndent) + rangeStart = formattingRangeStart(block, buffer, lastSaveRevision()); - extraOffset += emptySpaceLength; + buffer = buffer.mid(extraOffsetFromStartOfFile, endOffset - extraOffsetFromStartOfFile); + utf8Offset -= extraOffsetFromStartOfFile; + rangeStart -= extraOffsetFromStartOfFile; - adjustFormatStyleForLineBreak(style); - if (typedChar == QChar::Null) - modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry); + extraEmptySpaceOffset = previousEmptyLinesLength(block); + utf8Offset -= extraEmptySpaceOffset; + buffer.remove(utf8Offset, extraEmptySpaceOffset); + + if (replacementsToKeep == ReplacementsToKeep::OnlyIndent) + adjustFormatStyleForLineBreak(style); + modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry); } - std::vector ranges{ - {static_cast(utf8Offset), static_cast(utf8Length)}}; + if (replacementsToKeep != ReplacementsToKeep::OnlyBeforeIndent || 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, @@ -500,21 +614,25 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer m_fileName.toString().toStdString(), &status); - if (!status.FormatComplete) - return TextEditor::Replacements(); - - const clang::tooling::Replacements filtered = filteredReplacements(clangReplacements, - utf8Offset, - extraOffset, - onlyIndention); - const bool canTryAgain = onlyIndention && typedChar == QChar::Null && !secondTry; + clang::tooling::Replacements filtered; + if (status.FormatComplete) { + filtered = filteredReplacements(clangReplacements, + utf8Offset, + utf8LineLengthBeforeCursor, + extraOffsetFromStartOfFile, + extraEmptySpaceOffset, + replacementsToKeep); + } + const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent + && typedChar == QChar::Null && !secondTry; if (canTryAgain && filtered.empty()) { return replacements(originalBuffer, originalOffsetUtf8, originalLengthUtf8, block, + cursorPositionInEditor, + replacementsToKeep, typedChar, - onlyIndention, true); } diff --git a/src/plugins/clangformat/clangformatbaseindenter.h b/src/plugins/clangformat/clangformatbaseindenter.h index 4071422cfde..1a73d9bfbcf 100644 --- a/src/plugins/clangformat/clangformatbaseindenter.h +++ b/src/plugins/clangformat/clangformatbaseindenter.h @@ -31,6 +31,8 @@ namespace ClangFormat { +enum class ReplacementsToKeep { OnlyIndent, OnlyBeforeIndent, All }; + class ClangFormatBaseIndenter : public TextEditor::Indenter { public: @@ -69,18 +71,24 @@ public: protected: virtual clang::format::FormatStyle styleForFile() const; virtual bool formatCodeInsteadOfIndent() const { return false; } + virtual bool formatWhileTyping() const { return false; } + 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); + int indentBeforeCursor(const QTextBlock &block, + const QChar &typedChar, + int cursorPositionInEditor); TextEditor::Replacements replacements(QByteArray buffer, int utf8Offset, int utf8Length, const QTextBlock &block, + int cursorPositionInEditor, + ReplacementsToKeep replacementsToKeep, const QChar &typedChar = QChar::Null, - bool onlyIndention = true, bool secondTry = false) const; }; diff --git a/src/plugins/clangformat/clangformatconfigwidget.cpp b/src/plugins/clangformat/clangformatconfigwidget.cpp index 3d105f55801..bcede8c0101 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.cpp +++ b/src/plugins/clangformat/clangformatconfigwidget.cpp @@ -127,12 +127,27 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje initialize(); } +void ClangFormatConfigWidget::hideGlobalCheckboxes() +{ + m_ui->formatAlways->hide(); + m_ui->formatWhileTyping->hide(); +} + +void ClangFormatConfigWidget::showGlobalCheckboxes() +{ + m_ui->formatAlways->setChecked(ClangFormatSettings::instance().formatCodeInsteadOfIndent()); + m_ui->formatAlways->show(); + + m_ui->formatWhileTyping->setChecked(ClangFormatSettings::instance().formatWhileTyping()); + m_ui->formatWhileTyping->show(); +} + void ClangFormatConfigWidget::initialize() { m_ui->projectHasClangFormat->show(); m_ui->clangFormatOptionsTable->show(); m_ui->applyButton->show(); - m_ui->formatAlways->hide(); + hideGlobalCheckboxes(); QLayoutItem *lastItem = m_ui->verticalLayout->itemAt(m_ui->verticalLayout->count() - 1); if (lastItem->spacerItem()) @@ -171,8 +186,7 @@ void ClangFormatConfigWidget::initialize() "and can be configured in Projects > Code Style > C++.")); } createStyleFileIfNeeded(true); - m_ui->formatAlways->setChecked(ClangFormatSettings::instance().formatCodeInsteadOfIndent()); - m_ui->formatAlways->show(); + showGlobalCheckboxes(); m_ui->applyButton->hide(); } @@ -196,6 +210,7 @@ void ClangFormatConfigWidget::apply() if (!m_project) { ClangFormatSettings &settings = ClangFormatSettings::instance(); settings.setFormatCodeInsteadOfIndent(m_ui->formatAlways->isChecked()); + settings.setFormatWhileTyping(m_ui->formatWhileTyping->isChecked()); settings.write(); } diff --git a/src/plugins/clangformat/clangformatconfigwidget.h b/src/plugins/clangformat/clangformatconfigwidget.h index 5db16e3c967..7d3818b7a52 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.h +++ b/src/plugins/clangformat/clangformatconfigwidget.h @@ -51,6 +51,9 @@ private: void initialize(); void fillTable(); + void hideGlobalCheckboxes(); + void showGlobalCheckboxes(); + ProjectExplorer::Project *m_project; std::unique_ptr m_ui; }; diff --git a/src/plugins/clangformat/clangformatconfigwidget.ui b/src/plugins/clangformat/clangformatconfigwidget.ui index 8fcc87235d8..37f228e97cc 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.ui +++ b/src/plugins/clangformat/clangformatconfigwidget.ui @@ -33,6 +33,13 @@ + + + + Format while typing + + + diff --git a/src/plugins/clangformat/clangformatconstants.h b/src/plugins/clangformat/clangformatconstants.h index aa2389bbfeb..40b43e56407 100644 --- a/src/plugins/clangformat/clangformatconstants.h +++ b/src/plugins/clangformat/clangformatconstants.h @@ -32,5 +32,6 @@ static const char SETTINGS_FILE_ALT_NAME[] = "_clang-format"; static const char SAMPLE_FILE_NAME[] = "test.cpp"; static const char SETTINGS_ID[] = "ClangFormat"; static const char FORMAT_CODE_INSTEAD_OF_INDENT_ID[] = "ClangFormat.FormatCodeInsteadOfIndent"; +static const char FORMAT_WHILE_TYPING_ID[] = "ClangFormat.FormatWhileTyping"; } // namespace Constants } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatindenter.cpp b/src/plugins/clangformat/clangformatindenter.cpp index 1d0400d323f..f1ece90164a 100644 --- a/src/plugins/clangformat/clangformatindenter.cpp +++ b/src/plugins/clangformat/clangformatindenter.cpp @@ -28,6 +28,7 @@ #include "clangformatutils.h" #include +#include using namespace clang; using namespace format; @@ -49,6 +50,11 @@ bool ClangFormatIndenter::formatCodeInsteadOfIndent() const return ClangFormatSettings::instance().formatCodeInsteadOfIndent(); } +bool ClangFormatIndenter::formatWhileTyping() const +{ + return ClangFormatSettings::instance().formatWhileTyping(); +} + Utils::optional ClangFormatIndenter::tabSettings() const { FormatStyle style = currentProjectStyle(); @@ -76,4 +82,9 @@ Utils::optional ClangFormatIndenter::tabSettings() const return tabSettings; } +int ClangFormatIndenter::lastSaveRevision() const +{ + return qobject_cast(m_doc->documentLayout())->lastSaveRevision; +} + } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatindenter.h b/src/plugins/clangformat/clangformatindenter.h index 507ea68f415..dca5f583a64 100644 --- a/src/plugins/clangformat/clangformatindenter.h +++ b/src/plugins/clangformat/clangformatindenter.h @@ -39,7 +39,9 @@ public: private: bool formatCodeInsteadOfIndent() const override; + bool formatWhileTyping() const override; clang::format::FormatStyle styleForFile() const override; + int lastSaveRevision() const override; }; } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatsettings.cpp b/src/plugins/clangformat/clangformatsettings.cpp index 0547bde9604..d8369bf5efd 100644 --- a/src/plugins/clangformat/clangformatsettings.cpp +++ b/src/plugins/clangformat/clangformatsettings.cpp @@ -42,6 +42,8 @@ ClangFormatSettings::ClangFormatSettings() settings->beginGroup(QLatin1String(Constants::SETTINGS_ID)); m_formatCodeInsteadOfIndent = settings->value(QLatin1String(Constants::FORMAT_CODE_INSTEAD_OF_INDENT_ID), false).toBool(); + m_formatWhileTyping = settings->value(QLatin1String(Constants::FORMAT_WHILE_TYPING_ID), false) + .toBool(); settings->endGroup(); } @@ -51,6 +53,7 @@ void ClangFormatSettings::write() const settings->beginGroup(QLatin1String(Constants::SETTINGS_ID)); settings->setValue(QLatin1String(Constants::FORMAT_CODE_INSTEAD_OF_INDENT_ID), m_formatCodeInsteadOfIndent); + settings->setValue(QLatin1String(Constants::FORMAT_WHILE_TYPING_ID), m_formatWhileTyping); settings->endGroup(); } @@ -64,4 +67,14 @@ bool ClangFormatSettings::formatCodeInsteadOfIndent() const return m_formatCodeInsteadOfIndent; } +void ClangFormatSettings::setFormatWhileTyping(bool enable) +{ + m_formatWhileTyping = enable; +} + +bool ClangFormatSettings::formatWhileTyping() const +{ + return m_formatWhileTyping; +} + } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatsettings.h b/src/plugins/clangformat/clangformatsettings.h index 06205932286..4a62eb83035 100644 --- a/src/plugins/clangformat/clangformatsettings.h +++ b/src/plugins/clangformat/clangformatsettings.h @@ -37,8 +37,12 @@ public: void setFormatCodeInsteadOfIndent(bool enable); bool formatCodeInsteadOfIndent() const; + + void setFormatWhileTyping(bool enable); + bool formatWhileTyping() const; private: bool m_formatCodeInsteadOfIndent = false; + bool m_formatWhileTyping = false; }; } // namespace ClangFormat diff --git a/tests/unit/unittest/clangformat-test.cpp b/tests/unit/unittest/clangformat-test.cpp index 771e3159fe5..8b64fb19149 100644 --- a/tests/unit/unittest/clangformat-test.cpp +++ b/tests/unit/unittest/clangformat-test.cpp @@ -51,12 +51,27 @@ public: } }; +class ClangFormatExtendedIndenter : public ClangFormatIndenter +{ +public: + ClangFormatExtendedIndenter(QTextDocument *doc) + : ClangFormatIndenter(doc) + {} + + bool formatWhileTyping() const override + { + return true; + } +}; + class ClangFormat : public ::testing::Test { protected: void SetUp() final { indenter.setFileName(Utils::FileName::fromString(TESTDATA_DIR "/clangformat/test.cpp")); + extendedIndenter.setFileName( + Utils::FileName::fromString(TESTDATA_DIR "/clangformat/test.cpp")); } void insertLines(const std::vector &lines) @@ -85,6 +100,7 @@ protected: QTextDocument doc; ClangFormatIndenter indenter{&doc}; + ClangFormatExtendedIndenter extendedIndenter{&doc}; QTextCursor cursor{&doc}; }; @@ -315,6 +331,83 @@ TEST_F(ClangFormat, NoExtraIndentAfterBraceInitialization) "return 0;")); } +TEST_F(ClangFormat, IndentFunctionBodyAndFormatBeforeIt) +{ + insertLines({"int foo(int a, int b,", + " int c, int d", + " ) {", + "", + "}"}); + + extendedIndenter.indentBlock(doc.findBlockByNumber(3), QChar::Null, TextEditor::TabSettings()); + + ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b, int c, int d)", + "{", + " ", + "}")); +} + +TEST_F(ClangFormat, IndentAfterFunctionBodyAndNotFormatBefore) +{ + insertLines({"int foo(int a, int b, int c, int d)", + "{", + " ", + "}"}); + + extendedIndenter.indentBlock(doc.findBlockByNumber(3), + QChar::Null, + TextEditor::TabSettings(), + doc.characterCount() - 3); + + ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b, int c, int d)", + "{", + " ", + "}")); +} + +TEST_F(ClangFormat, ReformatToEmptyFunction) +{ + insertLines({"int foo(int a, int b, int c, int d)", + "{", + " ", + "}", + ""}); + + extendedIndenter.indentBlock(doc.findBlockByNumber(4), QChar::Null, TextEditor::TabSettings()); + + ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b, int c, int d) {}", + "")); +} + +TEST_F(ClangFormat, ReformatToNonEmptyFunction) +{ + insertLines({"int foo(int a, int b) {", + "", + "}"}); + + extendedIndenter.indentBlock(doc.findBlockByNumber(1), QChar::Null, TextEditor::TabSettings()); + + ASSERT_THAT(documentLines(), ElementsAre("int foo(int a, int b)", + "{", + " ", + "}")); +} + +TEST_F(ClangFormat, IndentIfBodyAndFormatBeforeIt) +{ + insertLines({"if(a && b", + " &&c && d", + " ) {", + "", + "}"}); + + extendedIndenter.indentBlock(doc.findBlockByNumber(3), QChar::Null, TextEditor::TabSettings()); + + ASSERT_THAT(documentLines(), ElementsAre("if (a && b && c && d) {", + " ", + "}")); +} + TEST_F(ClangFormat, FormatBasicFile) { insertLines({"int main()",