ClangFormat: Refactor code

Reorder some functions, remove code duplication.

Change-Id: I33e118e567dee7db622bbc99e6a7c500db54e5c5
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2019-02-19 14:30:52 +01:00
parent 064f43fecf
commit 118c126f98
4 changed files with 319 additions and 317 deletions

View File

@@ -165,5 +165,27 @@ int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffe
return utf8Offset; return utf8Offset;
} }
LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset)
{
Utils::LineColumn lineColumn;
lineColumn.line = static_cast<int>(
std::count(utf8Buffer.begin(), utf8Buffer.begin() + utf8Offset, '\n'))
+ 1;
const int startOfLineOffset = utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1;
lineColumn.column = QString::fromUtf8(
utf8Buffer.mid(startOfLineOffset, utf8Offset - startOfLineOffset))
.length()
+ 1;
return lineColumn;
}
QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset)
{
const int lineStartUtf8Offset = utf8Buffer.lastIndexOf('\n', currentUtf8Offset - 1) + 1;
const int lineEndUtf8Offset = utf8Buffer.indexOf('\n', currentUtf8Offset);
return QString::fromUtf8(
utf8Buffer.mid(lineStartUtf8Offset, lineEndUtf8Offset - lineStartUtf8Offset));
}
} // Text } // Text
} // Utils } // Utils

View File

@@ -59,5 +59,9 @@ QTCREATOR_UTILS_EXPORT int utf8NthLineOffset(const QTextDocument *textDocument,
const QByteArray &buffer, const QByteArray &buffer,
int line); int line);
QTCREATOR_UTILS_EXPORT LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset);
QTCREATOR_UTILS_EXPORT QString utf16LineTextInUtf8Buffer(const QByteArray &utf8Buffer,
int currentUtf8Offset);
} // Text } // Text
} // Utils } // Utils

View File

@@ -36,8 +36,9 @@
namespace ClangFormat { namespace ClangFormat {
static void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style, namespace {
ReplacementsToKeep replacementsToKeep) void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style,
ReplacementsToKeep replacementsToKeep)
{ {
style.MaxEmptyLinesToKeep = 100; style.MaxEmptyLinesToKeep = 100;
style.SortIncludes = false; style.SortIncludes = false;
@@ -56,28 +57,30 @@ static void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style,
#endif #endif
} }
static llvm::StringRef clearExtraNewline(llvm::StringRef text) llvm::StringRef clearExtraNewline(llvm::StringRef text)
{ {
while (text.startswith("\n\n")) while (text.startswith("\n\n"))
text = text.drop_front(); text = text.drop_front();
return text; return text;
} }
static clang::tooling::Replacements filteredReplacements( clang::tooling::Replacements filteredReplacements(const QByteArray &buffer,
const QByteArray &buffer, const clang::tooling::Replacements &replacements,
const clang::tooling::Replacements &replacements, int utf8Offset,
int utf8Offset, int utf8Length,
int utf8Length, ReplacementsToKeep replacementsToKeep)
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());
const bool replacementDoesNotMatchRestriction
= replacementOffset >= utf8Offset + utf8Length // Skip everything after.
|| (replacementsToKeep == ReplacementsToKeep::OnlyIndent if (replacementOffset >= utf8Offset + utf8Length)
&& (replacementOffset < utf8Offset - 1 || buffer.at(replacementOffset) != '\n')); return filtered;
if (replacementDoesNotMatchRestriction)
const bool isNotIndentOrInRange = replacementOffset < utf8Offset - 1
|| buffer.at(replacementOffset) != '\n';
if (isNotIndentOrInRange && replacementsToKeep == ReplacementsToKeep::OnlyIndent)
continue; continue;
llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent
@@ -102,17 +105,10 @@ static clang::tooling::Replacements filteredReplacements(
return filtered; return filtered;
} }
static void trimFirstNonEmptyBlock(const QTextBlock &currentBlock) void trimRHSWhitespace(const QTextBlock &block)
{ {
QTextBlock prevBlock = currentBlock.previous(); const QString initialText = block.text();
while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty()) if (!initialText.rbegin()->isSpace())
prevBlock = prevBlock.previous();
if (prevBlock.text().trimmed().isEmpty())
return;
const QString initialText = prevBlock.text();
if (!initialText.at(initialText.size() - 1).isSpace())
return; return;
auto lastNonSpace = std::find_if_not(initialText.rbegin(), auto lastNonSpace = std::find_if_not(initialText.rbegin(),
@@ -120,7 +116,7 @@ static void trimFirstNonEmptyBlock(const QTextBlock &currentBlock)
[](const QChar &letter) { return letter.isSpace(); }); [](const QChar &letter) { return letter.isSpace(); });
const int extraSpaceCount = static_cast<int>(std::distance(initialText.rbegin(), lastNonSpace)); const int extraSpaceCount = static_cast<int>(std::distance(initialText.rbegin(), lastNonSpace));
QTextCursor cursor(prevBlock); QTextCursor cursor(block);
cursor.beginEditBlock(); cursor.beginEditBlock();
cursor.movePosition(QTextCursor::Right, cursor.movePosition(QTextCursor::Right,
QTextCursor::MoveAnchor, QTextCursor::MoveAnchor,
@@ -130,20 +126,9 @@ static void trimFirstNonEmptyBlock(const QTextBlock &currentBlock)
cursor.endEditBlock(); cursor.endEditBlock();
} }
// Returns the total langth of previous lines with pure whitespace. // Add extra text in case of the empty line or the line starting with ')'.
static int previousEmptyLinesLength(const QTextBlock &currentBlock) // Track such extra pieces of text in isInsideModifiedLine().
{ int forceIndentWithExtraText(QByteArray &buffer, const QTextBlock &block, bool secondTry)
int length{0};
QTextBlock prevBlock = currentBlock.previous();
while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty()) {
length += prevBlock.text().length() + 1;
prevBlock = prevBlock.previous();
}
return length;
}
static int modifyToIndentEmptyLines(QByteArray &buffer, const QTextBlock &block, bool secondTry)
{ {
const QString blockText = block.text(); const QString blockText = block.text();
int firstNonWhitespace = Utils::indexOf(blockText, int firstNonWhitespace = Utils::indexOf(blockText,
@@ -195,48 +180,32 @@ static int modifyToIndentEmptyLines(QByteArray &buffer, const QTextBlock &block,
return extraLength; return extraLength;
} }
static Utils::LineColumn utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset) bool isInsideModifiedLine(const QString &originalLine, const QString &modifiedLine, int column)
{ {
Utils::LineColumn lineColumn; // Detect the cases when we have inserted extra text into the line to get the indentation.
lineColumn.line = std::count(utf8Buffer.begin(), utf8Buffer.begin() + utf8Offset, '\n') + 1;
lineColumn.column = utf8Offset - utf8Buffer.lastIndexOf('\n', utf8Offset - 1);
return lineColumn;
}
static QString utf16LineLengthInUtf8Buffer(const QByteArray &utf8Buffer, int currentUtf8Offset)
{
const int lineStartUtf8Offset = utf8Buffer.lastIndexOf('\n', currentUtf8Offset - 1) + 1;
const int lineEndUtf8Offset = utf8Buffer.indexOf('\n', currentUtf8Offset);
return QString::fromUtf8(
utf8Buffer.mid(lineStartUtf8Offset, lineEndUtf8Offset - lineStartUtf8Offset));
}
static bool isInsideModifiedLine(const QString &originalLine,
const QString &modifiedLine,
int column)
{
// Track the cases when we have inserted extra text into the line to get the indentation.
return originalLine.length() < modifiedLine.length() && column != modifiedLine.length() + 1 return originalLine.length() < modifiedLine.length() && column != modifiedLine.length() + 1
&& (column > originalLine.length() || originalLine.trimmed().isEmpty() && (column > originalLine.length() || originalLine.trimmed().isEmpty()
|| !modifiedLine.startsWith(originalLine)); || !modifiedLine.startsWith(originalLine));
} }
static TextEditor::Replacements utf16Replacements(const QTextDocument *doc, TextEditor::Replacements utf16Replacements(const QTextDocument *doc,
const QByteArray &utf8Buffer, const QByteArray &utf8Buffer,
const clang::tooling::Replacements &replacements) const clang::tooling::Replacements &replacements)
{ {
TextEditor::Replacements convertedReplacements; TextEditor::Replacements convertedReplacements;
convertedReplacements.reserve(replacements.size()); convertedReplacements.reserve(replacements.size());
for (const clang::tooling::Replacement &replacement : replacements) { for (const clang::tooling::Replacement &replacement : replacements) {
Utils::LineColumn lineColUtf16 = utf16LineColumn(utf8Buffer, Utils::LineColumn lineColUtf16 = Utils::Text::utf16LineColumn(utf8Buffer,
static_cast<int>(replacement.getOffset())); static_cast<int>(
replacement.getOffset()));
if (!lineColUtf16.isValid()) if (!lineColUtf16.isValid())
continue; continue;
const QString lineText = doc->findBlockByNumber(lineColUtf16.line - 1).text(); const QString lineText = doc->findBlockByNumber(lineColUtf16.line - 1).text();
const QString bufferLineText = utf16LineLengthInUtf8Buffer(utf8Buffer, const QString bufferLineText
replacement.getOffset()); = Utils::Text::utf16LineTextInUtf8Buffer(utf8Buffer,
static_cast<int>(replacement.getOffset()));
if (isInsideModifiedLine(lineText, bufferLineText, lineColUtf16.column)) if (isInsideModifiedLine(lineText, bufferLineText, lineColUtf16.column))
continue; continue;
@@ -257,7 +226,7 @@ static TextEditor::Replacements utf16Replacements(const QTextDocument *doc,
return convertedReplacements; return convertedReplacements;
} }
static void applyReplacements(QTextDocument *doc, const TextEditor::Replacements &replacements) void applyReplacements(QTextDocument *doc, const TextEditor::Replacements &replacements)
{ {
if (replacements.empty()) if (replacements.empty())
return; return;
@@ -277,9 +246,7 @@ static void applyReplacements(QTextDocument *doc, const TextEditor::Replacements
} }
} }
static QString selectedLines(QTextDocument *doc, QString selectedLines(QTextDocument *doc, const QTextBlock &startBlock, const QTextBlock &endBlock)
const QTextBlock &startBlock,
const QTextBlock &endBlock)
{ {
return Utils::Text::textAt(QTextCursor(doc), return Utils::Text::textAt(QTextCursor(doc),
startBlock.position(), startBlock.position(),
@@ -288,19 +255,231 @@ static QString selectedLines(QTextDocument *doc,
- startBlock.position() - 1)); - startBlock.position() - 1));
} }
int indentationForBlock(const TextEditor::Replacements &toReplace,
const QByteArray &buffer,
const QTextBlock &currentBlock)
{
const int utf8Offset = Utils::Text::utf8NthLineOffset(currentBlock.document(),
buffer,
currentBlock.blockNumber() + 1);
auto replacementIt = std::find_if(toReplace.begin(),
toReplace.end(),
[utf8Offset](const TextEditor::Replacement &replacement) {
return replacement.offset == utf8Offset - 1;
});
if (replacementIt == toReplace.end())
return -1;
int afterLineBreak = replacementIt->text.lastIndexOf('\n');
afterLineBreak = (afterLineBreak < 0) ? 0 : afterLineBreak + 1;
return static_cast<int>(replacementIt->text.size() - afterLineBreak);
}
bool doNotIndentInContext(QTextDocument *doc, int pos)
{
const QChar character = doc->characterAt(pos);
const QTextBlock currentBlock = doc->findBlock(pos);
const QString text = currentBlock.text().left(pos - currentBlock.position());
// NOTE: check if "<<" and ">>" always work correctly.
switch (character.toLatin1()) {
default:
break;
case ':':
// Do not indent when it's the first ':' and it's not the 'case' line.
if (text.contains(QLatin1String("case")) || text.contains(QLatin1String("default"))
|| text.contains(QLatin1String("public")) || text.contains(QLatin1String("private"))
|| text.contains(QLatin1String("protected")) || text.contains(QLatin1String("signals"))
|| text.contains(QLatin1String("Q_SIGNALS"))) {
return false;
}
if (pos > 0 && doc->characterAt(pos - 1) != ':')
return true;
break;
}
return false;
}
QTextBlock reverseFindLastEmptyBlock(QTextBlock start)
{
if (start.position() > 0) {
start = start.previous();
while (start.position() > 0 && start.text().trimmed().isEmpty())
start = start.previous();
if (!start.text().trimmed().isEmpty())
start = start.next();
}
return start;
}
int formattingRangeStart(const QTextBlock &currentBlock,
const QByteArray &buffer,
int documentRevision)
{
QTextBlock prevBlock = currentBlock.previous();
while ((prevBlock.position() > 0 || prevBlock.length() > 0)
&& prevBlock.revision() != documentRevision) {
// 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);
}
} // namespace
ClangFormatBaseIndenter::ClangFormatBaseIndenter(QTextDocument *doc) ClangFormatBaseIndenter::ClangFormatBaseIndenter(QTextDocument *doc)
: TextEditor::Indenter(doc) : TextEditor::Indenter(doc)
{} {}
TextEditor::IndentationForBlock ClangFormatBaseIndenter::indentationForBlocks( TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer,
const QVector<QTextBlock> &blocks, const QTextBlock &startBlock,
const TextEditor::TabSettings & /*tabSettings*/, const QTextBlock &endBlock,
int cursorPositionInEditor) ReplacementsToKeep replacementsToKeep,
const QChar &typedChar,
bool secondTry) const
{ {
TextEditor::IndentationForBlock ret; QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return TextEditor::Replacements());
for (QTextBlock block : blocks)
ret.insert(block.blockNumber(), indentFor(block, cursorPositionInEditor)); clang::format::FormatStyle style = styleForFile();
return ret; QByteArray originalBuffer = buffer;
int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, startBlock.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return TextEditor::Replacements(););
int utf8Length = selectedLines(m_doc, startBlock, endBlock).toUtf8().size();
int rangeStart = 0;
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision());
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) {
buffer.insert(utf8Offset - 1, " //");
utf8Offset += 3;
}
adjustFormatStyleForLineBreak(style, replacementsToKeep);
if (typedChar == QChar::Null) {
for (int index = startBlock.blockNumber(); index <= endBlock.blockNumber(); ++index) {
utf8Length += forceIndentWithExtraText(buffer,
m_doc->findBlockByNumber(index),
secondTry);
}
}
if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || 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,
buffer.data(),
ranges,
m_fileName.toString().toStdString(),
&status);
clang::tooling::Replacements filtered;
if (status.FormatComplete) {
filtered = filteredReplacements(buffer,
clangReplacements,
utf8Offset,
utf8Length,
replacementsToKeep);
}
const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent
&& typedChar == QChar::Null && !secondTry;
if (canTryAgain && filtered.empty()) {
return replacements(originalBuffer,
startBlock,
endBlock,
replacementsToKeep,
typedChar,
true);
}
return utf16Replacements(m_doc, buffer, filtered);
}
TextEditor::Replacements ClangFormatBaseIndenter::format(
const TextEditor::RangesInLines &rangesInLines)
{
if (rangesInLines.empty())
return TextEditor::Replacements();
const QByteArray buffer = m_doc->toPlainText().toUtf8();
std::vector<clang::tooling::Range> ranges;
ranges.reserve(rangesInLines.size());
for (auto &range : rangesInLines) {
const int utf8StartOffset = Utils::Text::utf8NthLineOffset(m_doc, buffer, range.startLine);
int utf8RangeLength = m_doc->findBlockByNumber(range.endLine - 1).text().toUtf8().size();
if (range.endLine > range.startLine) {
utf8RangeLength += Utils::Text::utf8NthLineOffset(m_doc, buffer, range.endLine)
- utf8StartOffset;
}
ranges.emplace_back(static_cast<unsigned int>(utf8StartOffset),
static_cast<unsigned int>(utf8RangeLength));
}
clang::format::FormattingAttemptStatus status;
const clang::tooling::Replacements clangReplacements
= reformat(styleForFile(), buffer.data(), ranges, m_fileName.toString().toStdString(), &status);
const TextEditor::Replacements toReplace = utf16Replacements(m_doc, buffer, clangReplacements);
applyReplacements(m_doc, toReplace);
return toReplace;
}
TextEditor::Replacements ClangFormatBaseIndenter::indentsFor(QTextBlock startBlock,
const QTextBlock &endBlock,
const QByteArray &buffer,
const QChar &typedChar,
int cursorPositionInEditor)
{
if (typedChar != QChar::Null && cursorPositionInEditor > 0
&& m_doc->characterAt(cursorPositionInEditor - 1) == typedChar
&& doNotIndentInContext(m_doc, cursorPositionInEditor - 1)) {
return TextEditor::Replacements();
}
startBlock = reverseFindLastEmptyBlock(startBlock);
const int startBlockPosition = startBlock.position();
if (startBlock.position() > 0) {
trimRHSWhitespace(startBlock.previous());
if (cursorPositionInEditor >= 0)
cursorPositionInEditor += startBlock.position() - startBlockPosition;
}
ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent;
if (formatWhileTyping()
&& (cursorPositionInEditor == -1 || cursorPositionInEditor >= startBlockPosition)
&& (typedChar == QChar::Null || typedChar == ';' || typedChar == '}')) {
// 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.
// Format only before newline or complete statement not to break code.
replacementsToKeep = ReplacementsToKeep::IndentAndBefore;
}
return replacements(buffer,
startBlock,
endBlock,
replacementsToKeep,
typedChar);
}
void ClangFormatBaseIndenter::indentBlocks(const QTextBlock &startBlock,
const QTextBlock &endBlock,
const QChar &typedChar,
int cursorPositionInEditor)
{
const QByteArray buffer = m_doc->toPlainText().toUtf8();
applyReplacements(m_doc,
indentsFor(startBlock, endBlock, buffer, typedChar, cursorPositionInEditor));
} }
void ClangFormatBaseIndenter::indent(const QTextCursor &cursor, void ClangFormatBaseIndenter::indent(const QTextCursor &cursor,
@@ -332,126 +511,6 @@ void ClangFormatBaseIndenter::reindent(const QTextCursor &cursor,
indent(cursor, QChar::Null, cursorPositionInEditor); indent(cursor, QChar::Null, cursorPositionInEditor);
} }
TextEditor::Replacements ClangFormatBaseIndenter::format(
const TextEditor::RangesInLines &rangesInLines)
{
if (rangesInLines.empty())
return TextEditor::Replacements();
int utf8Offset = -1;
QTextBlock block;
const QByteArray buffer = m_doc->toPlainText().toUtf8();
std::vector<clang::tooling::Range> ranges;
ranges.reserve(rangesInLines.size());
for (auto &range : rangesInLines) {
const int utf8StartOffset = Utils::Text::utf8NthLineOffset(m_doc, buffer, range.startLine);
const QTextBlock end = m_doc->findBlockByNumber(range.endLine - 1);
int utf8RangeLength = end.text().toUtf8().size();
if (range.endLine > range.startLine) {
utf8RangeLength += Utils::Text::utf8NthLineOffset(m_doc, buffer, range.endLine)
- utf8StartOffset;
}
ranges.emplace_back(static_cast<unsigned int>(utf8StartOffset),
static_cast<unsigned int>(utf8RangeLength));
if (utf8Offset < 0) {
utf8Offset = utf8StartOffset;
block = m_doc->findBlockByNumber(range.startLine - 1);
}
}
clang::format::FormatStyle style = styleForFile();
clang::format::FormattingAttemptStatus status;
const clang::tooling::Replacements clangReplacements
= reformat(style, buffer.data(), ranges, m_fileName.toString().toStdString(), &status);
const TextEditor::Replacements toReplace = utf16Replacements(m_doc, buffer, clangReplacements);
applyReplacements(m_doc, toReplace);
return toReplace;
}
static bool doNotIndentInContext(QTextDocument *doc, int pos)
{
const QChar character = doc->characterAt(pos);
const QTextBlock currentBlock = doc->findBlock(pos);
const QString text = currentBlock.text().left(pos - currentBlock.position());
// NOTE: check if "<<" and ">>" always work correctly.
switch (character.toLatin1()) {
default:
break;
case ':':
// Do not indent when it's the first ':' and it's not the 'case' line.
if (text.contains(QLatin1String("case")) || text.contains(QLatin1String("default"))
|| text.contains(QLatin1String("public")) || text.contains(QLatin1String("private"))
|| text.contains(QLatin1String("protected")) || text.contains(QLatin1String("signals"))
|| text.contains(QLatin1String("Q_SIGNALS"))) {
return false;
}
if (pos > 0 && doc->characterAt(pos - 1) != ':')
return true;
break;
}
return false;
}
void ClangFormatBaseIndenter::indentBlocks(QTextBlock startBlock,
const QTextBlock &endBlock,
const QChar &typedChar,
int cursorPositionInEditor)
{
if (typedChar != QChar::Null && cursorPositionInEditor > 0
&& m_doc->characterAt(cursorPositionInEditor - 1) == typedChar
&& doNotIndentInContext(m_doc, cursorPositionInEditor - 1)) {
return;
}
if (startBlock.position() > 0) {
startBlock = startBlock.previous();
while (startBlock.position() > 0 && startBlock.text().trimmed().isEmpty())
startBlock = startBlock.previous();
if (!startBlock.text().trimmed().isEmpty()) {
startBlock = startBlock.next();
}
}
const int startBlockPosition = startBlock.position();
trimFirstNonEmptyBlock(startBlock);
if (cursorPositionInEditor >= 0)
cursorPositionInEditor += startBlock.position() - startBlockPosition;
ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent;
if (formatWhileTyping()
&& (cursorPositionInEditor == -1 || cursorPositionInEditor >= startBlockPosition)
&& (typedChar == QChar::Null || typedChar == ';' || typedChar == '}')) {
// 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.
// Format only before newline or complete statement not to break code.
replacementsToKeep = ReplacementsToKeep::IndentAndBefore;
}
const QByteArray buffer = m_doc->toPlainText().toUtf8();
const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc,
buffer,
startBlock.blockNumber() + 1);
QTC_ASSERT(utf8Offset >= 0, return;);
const int utf8Length = selectedLines(m_doc, startBlock, endBlock).toUtf8().size();
applyReplacements(m_doc,
replacements(buffer,
utf8Offset,
utf8Length,
startBlock,
endBlock,
replacementsToKeep,
typedChar));
}
void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block, void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
const QChar &typedChar, const QChar &typedChar,
const TextEditor::TabSettings & /*tabSettings*/, const TextEditor::TabSettings & /*tabSettings*/,
@@ -460,35 +519,40 @@ void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
indentBlocks(block, block, typedChar, cursorPositionInEditor); indentBlocks(block, block, typedChar, cursorPositionInEditor);
} }
int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int /*cursorPositionInEditor*/)
{
trimFirstNonEmptyBlock(block);
const QByteArray buffer = m_doc->toPlainText().toUtf8();
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,
block,
ReplacementsToKeep::OnlyIndent);
if (toReplace.empty())
return -1;
const TextEditor::Replacement &replacement = toReplace.front();
int afterLineBreak = replacement.text.lastIndexOf('\n');
afterLineBreak = (afterLineBreak < 0) ? 0 : afterLineBreak + 1;
return static_cast<int>(replacement.text.size() - afterLineBreak);
}
int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int ClangFormatBaseIndenter::indentFor(const QTextBlock &block,
const TextEditor::TabSettings & /*tabSettings*/, const TextEditor::TabSettings & /*tabSettings*/,
int cursorPositionInEditor) int cursorPositionInEditor)
{ {
return indentFor(block, cursorPositionInEditor); const QByteArray buffer = m_doc->toPlainText().toUtf8();
TextEditor::Replacements toReplace = indentsFor(block,
block,
buffer,
QChar::Null,
cursorPositionInEditor);
if (toReplace.empty())
return -1;
return indentationForBlock(toReplace, buffer, block);
}
TextEditor::IndentationForBlock ClangFormatBaseIndenter::indentationForBlocks(
const QVector<QTextBlock> &blocks,
const TextEditor::TabSettings & /*tabSettings*/,
int cursorPositionInEditor)
{
TextEditor::IndentationForBlock ret;
if (blocks.isEmpty())
return ret;
const QByteArray buffer = m_doc->toPlainText().toUtf8();
TextEditor::Replacements toReplace = indentsFor(blocks.front(),
blocks.back(),
buffer,
QChar::Null,
cursorPositionInEditor);
for (const QTextBlock &block : blocks)
ret.insert(block.blockNumber(), indentationForBlock(toReplace, buffer, block));
return ret;
} }
bool ClangFormatBaseIndenter::isElectricCharacter(const QChar &ch) const bool ClangFormatBaseIndenter::isElectricCharacter(const QChar &ch) const
@@ -541,94 +605,4 @@ 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) {
// 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 &startBlock,
const QTextBlock &endBlock,
ReplacementsToKeep replacementsToKeep,
const QChar &typedChar,
bool secondTry) const
{
QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return TextEditor::Replacements());
clang::format::FormatStyle style = styleForFile();
int originalOffsetUtf8 = utf8Offset;
int originalLengthUtf8 = utf8Length;
QByteArray originalBuffer = buffer;
int rangeStart = 0;
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision());
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) {
buffer.insert(utf8Offset - 1, " //");
utf8Offset += 3;
}
adjustFormatStyleForLineBreak(style, replacementsToKeep);
if (typedChar == QChar::Null) {
for (int index = startBlock.blockNumber(); index <= endBlock.blockNumber(); ++index) {
utf8Length += modifyToIndentEmptyLines(buffer,
m_doc->findBlockByNumber(index),
secondTry);
}
}
if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || 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,
buffer.data(),
ranges,
m_fileName.toString().toStdString(),
&status);
clang::tooling::Replacements filtered;
if (status.FormatComplete) {
filtered = filteredReplacements(buffer,
clangReplacements,
utf8Offset,
utf8Length,
replacementsToKeep);
}
const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent
&& typedChar == QChar::Null && !secondTry;
if (canTryAgain && filtered.empty()) {
return replacements(originalBuffer,
originalOffsetUtf8,
originalLengthUtf8,
startBlock,
endBlock,
replacementsToKeep,
typedChar,
true);
}
return utf16Replacements(m_doc, buffer, filtered);
}
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -75,14 +75,16 @@ protected:
private: private:
void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor); void indent(const QTextCursor &cursor, const QChar &typedChar, int cursorPositionInEditor);
void indentBlocks(QTextBlock startBlock, void indentBlocks(const QTextBlock &startBlock,
const QTextBlock &endBlock, const QTextBlock &endBlock,
const QChar &typedChar, const QChar &typedChar,
int cursorPositionInEditor); int cursorPositionInEditor);
int indentFor(const QTextBlock &block, int cursorPositionInEditor); TextEditor::Replacements indentsFor(QTextBlock startBlock,
const QTextBlock &endBlock,
const QByteArray &buffer,
const QChar &typedChar,
int cursorPositionInEditor);
TextEditor::Replacements replacements(QByteArray buffer, TextEditor::Replacements replacements(QByteArray buffer,
int utf8Offset,
int utf8Length,
const QTextBlock &startBlock, const QTextBlock &startBlock,
const QTextBlock &endBlock, const QTextBlock &endBlock,
ReplacementsToKeep replacementsToKeep, ReplacementsToKeep replacementsToKeep,