forked from qt-creator/qt-creator
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:
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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 ¤tBlock)
|
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 ¤tBlock)
|
|||||||
[](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 ¤tBlock)
|
|||||||
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 ¤tBlock)
|
// 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 ¤tBlock)
|
||||||
|
{
|
||||||
|
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 ¤tBlock,
|
||||||
|
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 ¤tBlock,
|
|
||||||
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
|
||||||
|
@@ -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,
|
||||||
|
Reference in New Issue
Block a user