From 536b733f2978cf81d72cc01572bf6338248aac2d Mon Sep 17 00:00:00 2001 From: Ivan Donchevskii Date: Mon, 28 Jan 2019 08:13:33 +0100 Subject: [PATCH] ClangFormat: Format more code while typing With the extra option "Format while typing" checked try to format text before the current position without breaking the current input. To accomplish that we make proper choices which replacements to apply. The advantage of this change is to decrease the need to manually format code which is just written. Some minor bugs are fixed during the testing of this change. Change-Id: Ibed569042e6c46a881e7a83b1882cf2bb23b3626 Reviewed-by: Leena Miettinen Reviewed-by: Marco Bubke --- .../clangformat/clangformatbaseindenter.cpp | 240 +++++++++++++----- .../clangformat/clangformatbaseindenter.h | 10 +- .../clangformat/clangformatconfigwidget.cpp | 21 +- .../clangformat/clangformatconfigwidget.h | 3 + .../clangformat/clangformatconfigwidget.ui | 7 + .../clangformat/clangformatconstants.h | 1 + .../clangformat/clangformatindenter.cpp | 11 + src/plugins/clangformat/clangformatindenter.h | 2 + .../clangformat/clangformatsettings.cpp | 13 + src/plugins/clangformat/clangformatsettings.h | 4 + tests/unit/unittest/clangformat-test.cpp | 93 +++++++ 11 files changed, 340 insertions(+), 65 deletions(-) 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()",