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 <riitta-leena.miettinen@qt.io>
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2019-01-28 08:13:33 +01:00
parent 68bce815ac
commit 536b733f29
11 changed files with 340 additions and 65 deletions

View File

@@ -27,6 +27,7 @@
#include <clang/Tooling/Core/Replacement.h> #include <clang/Tooling/Core/Replacement.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/textutils.h> #include <utils/textutils.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -57,19 +58,28 @@ static llvm::StringRef clearExtraNewline(llvm::StringRef text)
static clang::tooling::Replacements filteredReplacements( static clang::tooling::Replacements filteredReplacements(
const clang::tooling::Replacements &replacements, const clang::tooling::Replacements &replacements,
int offset, int offset,
int extraOffsetToAdd, int utf8LineLengthBeforeCursor,
bool onlyIndention) int extraOffsetFromStartOfFile,
int extraEmptySpaceOffset,
ReplacementsToKeep replacementsToKeep)
{ {
clang::tooling::Replacements filtered; clang::tooling::Replacements filtered;
for (const clang::tooling::Replacement &replacement : replacements) { for (const clang::tooling::Replacement &replacement : replacements) {
int replacementOffset = static_cast<int>(replacement.getOffset()); int replacementOffset = static_cast<int>(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; continue;
if (replacementOffset + 1 >= offset) if (replacementOffset >= offset - 1)
replacementOffset += extraOffsetToAdd; replacementOffset += extraEmptySpaceOffset;
replacementOffset += extraOffsetFromStartOfFile;
llvm::StringRef text = onlyIndention ? clearExtraNewline(replacement.getReplacementText()) llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent
? clearExtraNewline(replacement.getReplacementText())
: replacement.getReplacementText(); : replacement.getReplacementText();
llvm::Error error = filtered.add( llvm::Error error = filtered.add(
@@ -78,9 +88,15 @@ static clang::tooling::Replacements filteredReplacements(
replacement.getLength(), replacement.getLength(),
text)); text));
// Throws if error is not checked. // 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; break;
} }
}
return filtered; return filtered;
} }
@@ -140,9 +156,16 @@ static int previousEmptyLinesLength(const QTextBlock &currentBlock)
static void modifyToIndentEmptyLines( static void modifyToIndentEmptyLines(
QByteArray &buffer, int offset, int &length, const QTextBlock &block, bool secondTry) QByteArray &buffer, int offset, int &length, const QTextBlock &block, bool secondTry)
{ {
const QString blockText = block.text().trimmed(); const QString blockText = block.text();
const bool closingParenBlock = blockText.startsWith(')'); int firstNonWhitespace = Utils::indexOf(blockText,
if (blockText.isEmpty() || closingParenBlock) { [](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. //This extra text works for the most cases.
QByteArray dummyText("a;"); QByteArray dummyText("a;");
@@ -153,15 +176,8 @@ static void modifyToIndentEmptyLines(
prevBlock = prevBlock.previous(); prevBlock = prevBlock.previous();
prevBlockIsEmpty = prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty(); prevBlockIsEmpty = prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty();
} }
if (prevBlock.text().endsWith(',')) if (closingParenBlock || prevBlock.text().endsWith(','))
dummyText = "int a";
if (closingParenBlock) {
if (prevBlock.text().endsWith(','))
dummyText = "int a";
else
dummyText = "&& a"; dummyText = "&& a";
}
length += dummyText.length(); length += dummyText.length();
buffer.insert(offset, dummyText); buffer.insert(offset, dummyText);
@@ -169,6 +185,9 @@ static void modifyToIndentEmptyLines(
if (secondTry) { if (secondTry) {
int nextLinePos = buffer.indexOf('\n', offset); int nextLinePos = buffer.indexOf('\n', offset);
if (nextLinePos < 0)
nextLinePos = buffer.size() - 1;
if (nextLinePos > 0) { if (nextLinePos > 0) {
// If first try was not successful try to put ')' in the end of the line to close possibly // If first try was not successful try to put ')' in the end of the line to close possibly
// unclosed parentheses. // unclosed parentheses.
@@ -187,7 +206,9 @@ static Utils::LineColumn utf16LineColumn(const QTextBlock &block,
int utf8Offset) int utf8Offset)
{ {
// If lastIndexOf('\n') returns -1 then we are fine to add 1 and get 0 offset. // 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. int line = block.blockNumber() + 1; // Init with the line corresponding the block.
if (utf8Offset < blockOffsetUtf8) { if (utf8Offset < blockOffsetUtf8) {
@@ -299,8 +320,10 @@ void ClangFormatBaseIndenter::indent(const QTextCursor &cursor,
if (currentBlock.isValid()) { if (currentBlock.isValid()) {
const int blocksAmount = m_doc->blockCount(); const int blocksAmount = m_doc->blockCount();
indentBlock(currentBlock, typedChar, cursorPositionInEditor); 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 { } else {
@@ -328,24 +351,30 @@ TextEditor::Replacements ClangFormatBaseIndenter::format(const QTextCursor &curs
{ {
int utf8Offset; int utf8Offset;
int utf8Length; int utf8Length;
QTextBlock block;
const QByteArray buffer = m_doc->toPlainText().toUtf8(); const QByteArray buffer = m_doc->toPlainText().toUtf8();
QTextBlock block = cursor.block();
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
block = m_doc->findBlock(cursor.selectionStart()); block = m_doc->findBlock(cursor.selectionStart());
const QTextBlock end = m_doc->findBlock(cursor.selectionEnd()); const QTextBlock end = m_doc->findBlock(cursor.selectionEnd());
utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements();); QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements(););
utf8Length = selectedLines(m_doc, block, end).toUtf8().size(); utf8Length = selectedLines(m_doc, block, end).toUtf8().size();
} else { } else {
const QTextBlock block = cursor.block(); block = cursor.block();
utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements();); QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements(););
utf8Length = block.text().toUtf8().size(); utf8Length = block.text().toUtf8().size();
} }
const TextEditor::Replacements toReplace const TextEditor::Replacements toReplace = replacements(buffer,
= replacements(buffer, utf8Offset, utf8Length, block, QChar::Null, false); utf8Offset,
utf8Length,
block,
cursorPositionInEditor,
ReplacementsToKeep::All,
QChar::Null);
applyReplacements(block, toReplace); applyReplacements(block, toReplace);
return toReplace; return toReplace;
@@ -359,17 +388,62 @@ TextEditor::Replacements ClangFormatBaseIndenter::format(
return format(cursor, cursorPositionInEditor); 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, void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
const QChar &typedChar, const QChar &typedChar,
int /*cursorPositionInEditor*/) int cursorPositionInEditor)
{ {
trimFirstNonEmptyBlock(block); QTextBlock currentBlock = block;
trimCurrentBlock(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 QByteArray buffer = m_doc->toPlainText().toUtf8();
const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1); const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return;); 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, void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
@@ -380,7 +454,7 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
indentBlock(block, typedChar, cursorPositionInEditor); indentBlock(block, typedChar, cursorPositionInEditor);
} }
int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int /*cursorPositionInEditor*/) int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int cursorPositionInEditor)
{ {
trimFirstNonEmptyBlock(block); trimFirstNonEmptyBlock(block);
trimCurrentBlock(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); const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, block.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return 0;); 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()) if (toReplace.empty())
return -1; return -1;
@@ -447,12 +526,30 @@ clang::format::FormatStyle ClangFormatBaseIndenter::styleForFile() const
return clang::format::getLLVMStyle(); return clang::format::getLLVMStyle();
} }
static int formattingRangeStart(const QTextBlock &currentBlock,
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, TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer,
int utf8Offset, int utf8Offset,
int utf8Length, int utf8Length,
const QTextBlock &block, const QTextBlock &block,
int cursorPositionInEditor,
ReplacementsToKeep replacementsToKeep,
const QChar &typedChar, const QChar &typedChar,
bool onlyIndention,
bool secondTry) const bool secondTry) const
{ {
clang::format::FormatStyle style = styleForFile(); clang::format::FormatStyle style = styleForFile();
@@ -461,13 +558,22 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
int originalLengthUtf8 = utf8Length; int originalLengthUtf8 = utf8Length;
QByteArray originalBuffer = buffer; QByteArray originalBuffer = buffer;
int extraOffset = 0; int utf8LineLengthBeforeCursor = 0;
if (onlyIndention) { 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) { if (block.blockNumber() > kMaxLinesFromCurrentBlock) {
extraOffset = Utils::Text::utf8NthLineOffset(block.document(), extraOffsetFromStartOfFile
= Utils::Text::utf8NthLineOffset(block.document(),
buffer, buffer,
block.blockNumber() block.blockNumber() - kMaxLinesFromCurrentBlock);
- kMaxLinesFromCurrentBlock);
} }
int endOffset = Utils::Text::utf8NthLineOffset(block.document(), int endOffset = Utils::Text::utf8NthLineOffset(block.document(),
buffer, buffer,
@@ -476,22 +582,30 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
if (endOffset == -1) if (endOffset == -1)
endOffset = buffer.size(); endOffset = buffer.size();
buffer = buffer.mid(extraOffset, endOffset - extraOffset);
utf8Offset -= extraOffset;
const int emptySpaceLength = previousEmptyLinesLength(block); if (replacementsToKeep == ReplacementsToKeep::OnlyBeforeIndent)
utf8Offset -= emptySpaceLength; rangeStart = formattingRangeStart(block, buffer, lastSaveRevision());
buffer.remove(utf8Offset, emptySpaceLength);
extraOffset += emptySpaceLength; buffer = buffer.mid(extraOffsetFromStartOfFile, endOffset - extraOffsetFromStartOfFile);
utf8Offset -= extraOffsetFromStartOfFile;
rangeStart -= extraOffsetFromStartOfFile;
extraEmptySpaceOffset = previousEmptyLinesLength(block);
utf8Offset -= extraEmptySpaceOffset;
buffer.remove(utf8Offset, extraEmptySpaceOffset);
if (replacementsToKeep == ReplacementsToKeep::OnlyIndent)
adjustFormatStyleForLineBreak(style); adjustFormatStyleForLineBreak(style);
if (typedChar == QChar::Null)
modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry); modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, block, secondTry);
} }
std::vector<clang::tooling::Range> ranges{ if (replacementsToKeep != ReplacementsToKeep::OnlyBeforeIndent || utf8Offset < rangeStart)
{static_cast<unsigned int>(utf8Offset), static_cast<unsigned int>(utf8Length)}}; rangeStart = utf8Offset;
unsigned int rangeLength = static_cast<unsigned int>(utf8Offset + utf8Length - rangeStart);
std::vector<clang::tooling::Range> ranges{{static_cast<unsigned int>(rangeStart), rangeLength}};
clang::format::FormattingAttemptStatus status; clang::format::FormattingAttemptStatus status;
clang::tooling::Replacements clangReplacements = reformat(style, clang::tooling::Replacements clangReplacements = reformat(style,
@@ -500,21 +614,25 @@ TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer
m_fileName.toString().toStdString(), m_fileName.toString().toStdString(),
&status); &status);
if (!status.FormatComplete) clang::tooling::Replacements filtered;
return TextEditor::Replacements(); if (status.FormatComplete) {
filtered = filteredReplacements(clangReplacements,
const clang::tooling::Replacements filtered = filteredReplacements(clangReplacements,
utf8Offset, utf8Offset,
extraOffset, utf8LineLengthBeforeCursor,
onlyIndention); extraOffsetFromStartOfFile,
const bool canTryAgain = onlyIndention && typedChar == QChar::Null && !secondTry; extraEmptySpaceOffset,
replacementsToKeep);
}
const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent
&& typedChar == QChar::Null && !secondTry;
if (canTryAgain && filtered.empty()) { if (canTryAgain && filtered.empty()) {
return replacements(originalBuffer, return replacements(originalBuffer,
originalOffsetUtf8, originalOffsetUtf8,
originalLengthUtf8, originalLengthUtf8,
block, block,
cursorPositionInEditor,
replacementsToKeep,
typedChar, typedChar,
onlyIndention,
true); true);
} }

View File

@@ -31,6 +31,8 @@
namespace ClangFormat { namespace ClangFormat {
enum class ReplacementsToKeep { OnlyIndent, OnlyBeforeIndent, All };
class ClangFormatBaseIndenter : public TextEditor::Indenter class ClangFormatBaseIndenter : public TextEditor::Indenter
{ {
public: public:
@@ -69,18 +71,24 @@ public:
protected: protected:
virtual clang::format::FormatStyle styleForFile() const; virtual clang::format::FormatStyle styleForFile() const;
virtual bool formatCodeInsteadOfIndent() const { return false; } virtual bool formatCodeInsteadOfIndent() const { return false; }
virtual bool formatWhileTyping() const { return false; }
virtual int lastSaveRevision() const { return 0; }
private: private:
TextEditor::Replacements format(const QTextCursor &cursor, int cursorPositionInEditor); TextEditor::Replacements format(const QTextCursor &cursor, int cursorPositionInEditor);
void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor); void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor);
void indentBlock(const QTextBlock &block, const QChar &typedChar, int cursorPositionInEditor); void indentBlock(const QTextBlock &block, const QChar &typedChar, int cursorPositionInEditor);
int indentFor(const QTextBlock &block, int cursorPositionInEditor); int indentFor(const QTextBlock &block, int cursorPositionInEditor);
int indentBeforeCursor(const QTextBlock &block,
const QChar &typedChar,
int cursorPositionInEditor);
TextEditor::Replacements replacements(QByteArray buffer, TextEditor::Replacements replacements(QByteArray buffer,
int utf8Offset, int utf8Offset,
int utf8Length, int utf8Length,
const QTextBlock &block, const QTextBlock &block,
int cursorPositionInEditor,
ReplacementsToKeep replacementsToKeep,
const QChar &typedChar = QChar::Null, const QChar &typedChar = QChar::Null,
bool onlyIndention = true,
bool secondTry = false) const; bool secondTry = false) const;
}; };

View File

@@ -127,12 +127,27 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje
initialize(); 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() void ClangFormatConfigWidget::initialize()
{ {
m_ui->projectHasClangFormat->show(); m_ui->projectHasClangFormat->show();
m_ui->clangFormatOptionsTable->show(); m_ui->clangFormatOptionsTable->show();
m_ui->applyButton->show(); m_ui->applyButton->show();
m_ui->formatAlways->hide(); hideGlobalCheckboxes();
QLayoutItem *lastItem = m_ui->verticalLayout->itemAt(m_ui->verticalLayout->count() - 1); QLayoutItem *lastItem = m_ui->verticalLayout->itemAt(m_ui->verticalLayout->count() - 1);
if (lastItem->spacerItem()) if (lastItem->spacerItem())
@@ -171,8 +186,7 @@ void ClangFormatConfigWidget::initialize()
"and can be configured in Projects > Code Style > C++.")); "and can be configured in Projects > Code Style > C++."));
} }
createStyleFileIfNeeded(true); createStyleFileIfNeeded(true);
m_ui->formatAlways->setChecked(ClangFormatSettings::instance().formatCodeInsteadOfIndent()); showGlobalCheckboxes();
m_ui->formatAlways->show();
m_ui->applyButton->hide(); m_ui->applyButton->hide();
} }
@@ -196,6 +210,7 @@ void ClangFormatConfigWidget::apply()
if (!m_project) { if (!m_project) {
ClangFormatSettings &settings = ClangFormatSettings::instance(); ClangFormatSettings &settings = ClangFormatSettings::instance();
settings.setFormatCodeInsteadOfIndent(m_ui->formatAlways->isChecked()); settings.setFormatCodeInsteadOfIndent(m_ui->formatAlways->isChecked());
settings.setFormatWhileTyping(m_ui->formatWhileTyping->isChecked());
settings.write(); settings.write();
} }

View File

@@ -51,6 +51,9 @@ private:
void initialize(); void initialize();
void fillTable(); void fillTable();
void hideGlobalCheckboxes();
void showGlobalCheckboxes();
ProjectExplorer::Project *m_project; ProjectExplorer::Project *m_project;
std::unique_ptr<Ui::ClangFormatConfigWidget> m_ui; std::unique_ptr<Ui::ClangFormatConfigWidget> m_ui;
}; };

View File

@@ -33,6 +33,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="formatWhileTyping">
<property name="text">
<string>Format while typing</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="projectHasClangFormat"> <widget class="QLabel" name="projectHasClangFormat">
<property name="text"> <property name="text">

View File

@@ -32,5 +32,6 @@ static const char SETTINGS_FILE_ALT_NAME[] = "_clang-format";
static const char SAMPLE_FILE_NAME[] = "test.cpp"; static const char SAMPLE_FILE_NAME[] = "test.cpp";
static const char SETTINGS_ID[] = "ClangFormat"; static const char SETTINGS_ID[] = "ClangFormat";
static const char FORMAT_CODE_INSTEAD_OF_INDENT_ID[] = "ClangFormat.FormatCodeInsteadOfIndent"; static const char FORMAT_CODE_INSTEAD_OF_INDENT_ID[] = "ClangFormat.FormatCodeInsteadOfIndent";
static const char FORMAT_WHILE_TYPING_ID[] = "ClangFormat.FormatWhileTyping";
} // namespace Constants } // namespace Constants
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -28,6 +28,7 @@
#include "clangformatutils.h" #include "clangformatutils.h"
#include <texteditor/tabsettings.h> #include <texteditor/tabsettings.h>
#include <texteditor/textdocumentlayout.h>
using namespace clang; using namespace clang;
using namespace format; using namespace format;
@@ -49,6 +50,11 @@ bool ClangFormatIndenter::formatCodeInsteadOfIndent() const
return ClangFormatSettings::instance().formatCodeInsteadOfIndent(); return ClangFormatSettings::instance().formatCodeInsteadOfIndent();
} }
bool ClangFormatIndenter::formatWhileTyping() const
{
return ClangFormatSettings::instance().formatWhileTyping();
}
Utils::optional<TabSettings> ClangFormatIndenter::tabSettings() const Utils::optional<TabSettings> ClangFormatIndenter::tabSettings() const
{ {
FormatStyle style = currentProjectStyle(); FormatStyle style = currentProjectStyle();
@@ -76,4 +82,9 @@ Utils::optional<TabSettings> ClangFormatIndenter::tabSettings() const
return tabSettings; return tabSettings;
} }
int ClangFormatIndenter::lastSaveRevision() const
{
return qobject_cast<TextEditor::TextDocumentLayout *>(m_doc->documentLayout())->lastSaveRevision;
}
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -39,7 +39,9 @@ public:
private: private:
bool formatCodeInsteadOfIndent() const override; bool formatCodeInsteadOfIndent() const override;
bool formatWhileTyping() const override;
clang::format::FormatStyle styleForFile() const override; clang::format::FormatStyle styleForFile() const override;
int lastSaveRevision() const override;
}; };
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -42,6 +42,8 @@ ClangFormatSettings::ClangFormatSettings()
settings->beginGroup(QLatin1String(Constants::SETTINGS_ID)); settings->beginGroup(QLatin1String(Constants::SETTINGS_ID));
m_formatCodeInsteadOfIndent m_formatCodeInsteadOfIndent
= settings->value(QLatin1String(Constants::FORMAT_CODE_INSTEAD_OF_INDENT_ID), false).toBool(); = 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(); settings->endGroup();
} }
@@ -51,6 +53,7 @@ void ClangFormatSettings::write() const
settings->beginGroup(QLatin1String(Constants::SETTINGS_ID)); settings->beginGroup(QLatin1String(Constants::SETTINGS_ID));
settings->setValue(QLatin1String(Constants::FORMAT_CODE_INSTEAD_OF_INDENT_ID), settings->setValue(QLatin1String(Constants::FORMAT_CODE_INSTEAD_OF_INDENT_ID),
m_formatCodeInsteadOfIndent); m_formatCodeInsteadOfIndent);
settings->setValue(QLatin1String(Constants::FORMAT_WHILE_TYPING_ID), m_formatWhileTyping);
settings->endGroup(); settings->endGroup();
} }
@@ -64,4 +67,14 @@ bool ClangFormatSettings::formatCodeInsteadOfIndent() const
return m_formatCodeInsteadOfIndent; return m_formatCodeInsteadOfIndent;
} }
void ClangFormatSettings::setFormatWhileTyping(bool enable)
{
m_formatWhileTyping = enable;
}
bool ClangFormatSettings::formatWhileTyping() const
{
return m_formatWhileTyping;
}
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -37,8 +37,12 @@ public:
void setFormatCodeInsteadOfIndent(bool enable); void setFormatCodeInsteadOfIndent(bool enable);
bool formatCodeInsteadOfIndent() const; bool formatCodeInsteadOfIndent() const;
void setFormatWhileTyping(bool enable);
bool formatWhileTyping() const;
private: private:
bool m_formatCodeInsteadOfIndent = false; bool m_formatCodeInsteadOfIndent = false;
bool m_formatWhileTyping = false;
}; };
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -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 class ClangFormat : public ::testing::Test
{ {
protected: protected:
void SetUp() final void SetUp() final
{ {
indenter.setFileName(Utils::FileName::fromString(TESTDATA_DIR "/clangformat/test.cpp")); 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<QString> &lines) void insertLines(const std::vector<QString> &lines)
@@ -85,6 +100,7 @@ protected:
QTextDocument doc; QTextDocument doc;
ClangFormatIndenter indenter{&doc}; ClangFormatIndenter indenter{&doc};
ClangFormatExtendedIndenter extendedIndenter{&doc};
QTextCursor cursor{&doc}; QTextCursor cursor{&doc};
}; };
@@ -315,6 +331,83 @@ TEST_F(ClangFormat, NoExtraIndentAfterBraceInitialization)
"return 0;")); "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) TEST_F(ClangFormat, FormatBasicFile)
{ {
insertLines({"int main()", insertLines({"int main()",