forked from qt-creator/qt-creator
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:
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <clang/Tooling/Core/Replacement.h>
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/textutils.h>
|
||||
#include <utils/qtcassert.h>
|
||||
@@ -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<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;
|
||||
|
||||
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<clang::tooling::Range> ranges{
|
||||
{static_cast<unsigned int>(utf8Offset), static_cast<unsigned int>(utf8Length)}};
|
||||
if (replacementsToKeep != ReplacementsToKeep::OnlyBeforeIndent || utf8Offset < rangeStart)
|
||||
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::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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -51,6 +51,9 @@ private:
|
||||
void initialize();
|
||||
void fillTable();
|
||||
|
||||
void hideGlobalCheckboxes();
|
||||
void showGlobalCheckboxes();
|
||||
|
||||
ProjectExplorer::Project *m_project;
|
||||
std::unique_ptr<Ui::ClangFormatConfigWidget> m_ui;
|
||||
};
|
||||
|
@@ -33,6 +33,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="formatWhileTyping">
|
||||
<property name="text">
|
||||
<string>Format while typing</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="projectHasClangFormat">
|
||||
<property name="text">
|
||||
|
@@ -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
|
||||
|
@@ -28,6 +28,7 @@
|
||||
#include "clangformatutils.h"
|
||||
|
||||
#include <texteditor/tabsettings.h>
|
||||
#include <texteditor/textdocumentlayout.h>
|
||||
|
||||
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<TabSettings> ClangFormatIndenter::tabSettings() const
|
||||
{
|
||||
FormatStyle style = currentProjectStyle();
|
||||
@@ -76,4 +82,9 @@ Utils::optional<TabSettings> ClangFormatIndenter::tabSettings() const
|
||||
return tabSettings;
|
||||
}
|
||||
|
||||
int ClangFormatIndenter::lastSaveRevision() const
|
||||
{
|
||||
return qobject_cast<TextEditor::TextDocumentLayout *>(m_doc->documentLayout())->lastSaveRevision;
|
||||
}
|
||||
|
||||
} // namespace ClangFormat
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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<QString> &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()",
|
||||
|
Reference in New Issue
Block a user