2019-01-16 09:37:54 +01:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "clangformatbaseindenter.h"
|
|
|
|
|
|
|
|
|
|
#include <clang/Tooling/Core/Replacement.h>
|
|
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
#include <utils/algorithm.h>
|
2019-01-16 09:37:54 +01:00
|
|
|
#include <utils/fileutils.h>
|
|
|
|
|
#include <utils/textutils.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
|
|
|
|
|
#include <QTextDocument>
|
|
|
|
|
|
|
|
|
|
namespace ClangFormat {
|
|
|
|
|
|
2019-02-08 12:31:53 +01:00
|
|
|
static void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style,
|
|
|
|
|
ReplacementsToKeep replacementsToKeep)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-08 12:31:53 +01:00
|
|
|
style.MaxEmptyLinesToKeep = 2;
|
|
|
|
|
style.SortIncludes = false;
|
|
|
|
|
style.SortUsingDeclarations = false;
|
|
|
|
|
|
|
|
|
|
// This is a separate pass, don't do it unless it's the full formatting.
|
|
|
|
|
style.FixNamespaceComments = false;
|
|
|
|
|
|
|
|
|
|
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
style.DisableFormat = false;
|
|
|
|
|
style.ColumnLimit = 0;
|
|
|
|
|
#ifdef KEEP_LINE_BREAKS_FOR_NON_EMPTY_LINES_BACKPORTED
|
|
|
|
|
style.KeepLineBreaksForNonEmptyLines = true;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static llvm::StringRef clearExtraNewline(llvm::StringRef text)
|
|
|
|
|
{
|
|
|
|
|
while (text.startswith("\n\n"))
|
|
|
|
|
text = text.drop_front();
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static clang::tooling::Replacements filteredReplacements(
|
2019-02-18 15:56:50 +01:00
|
|
|
const QByteArray &buffer,
|
2019-01-16 09:37:54 +01:00
|
|
|
const clang::tooling::Replacements &replacements,
|
2019-02-18 15:56:50 +01:00
|
|
|
int utf8Offset,
|
|
|
|
|
int utf8Length,
|
2019-01-28 08:13:33 +01:00
|
|
|
int extraEmptySpaceOffset,
|
|
|
|
|
ReplacementsToKeep replacementsToKeep)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
|
|
|
|
clang::tooling::Replacements filtered;
|
|
|
|
|
for (const clang::tooling::Replacement &replacement : replacements) {
|
|
|
|
|
int replacementOffset = static_cast<int>(replacement.getOffset());
|
2019-01-28 08:13:33 +01:00
|
|
|
const bool replacementDoesNotMatchRestriction
|
2019-02-18 15:56:50 +01:00
|
|
|
= replacementOffset >= utf8Offset + utf8Length
|
|
|
|
|
|| (replacementsToKeep == ReplacementsToKeep::OnlyIndent
|
|
|
|
|
&& (replacementOffset < utf8Offset - 1 || buffer.at(replacementOffset) != '\n'));
|
2019-01-28 08:13:33 +01:00
|
|
|
if (replacementDoesNotMatchRestriction)
|
2019-01-16 09:37:54 +01:00
|
|
|
continue;
|
|
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
if (replacementOffset >= utf8Offset - 1)
|
2019-01-28 08:13:33 +01:00
|
|
|
replacementOffset += extraEmptySpaceOffset;
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent
|
|
|
|
|
? clearExtraNewline(replacement.getReplacementText())
|
|
|
|
|
: replacement.getReplacementText();
|
2019-01-16 09:37:54 +01:00
|
|
|
|
|
|
|
|
llvm::Error error = filtered.add(
|
|
|
|
|
clang::tooling::Replacement(replacement.getFilePath(),
|
|
|
|
|
static_cast<unsigned int>(replacementOffset),
|
|
|
|
|
replacement.getLength(),
|
|
|
|
|
text));
|
|
|
|
|
// Throws if error is not checked.
|
2019-01-28 08:13:33 +01:00
|
|
|
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");
|
2019-01-16 09:37:54 +01:00
|
|
|
break;
|
2019-01-28 08:13:33 +01:00
|
|
|
}
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
return filtered;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void trimFirstNonEmptyBlock(const QTextBlock ¤tBlock)
|
|
|
|
|
{
|
|
|
|
|
QTextBlock prevBlock = currentBlock.previous();
|
|
|
|
|
while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty())
|
|
|
|
|
prevBlock = prevBlock.previous();
|
|
|
|
|
|
|
|
|
|
if (prevBlock.text().trimmed().isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const QString initialText = prevBlock.text();
|
|
|
|
|
if (!initialText.at(initialText.size() - 1).isSpace())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto lastNonSpace = std::find_if_not(initialText.rbegin(),
|
|
|
|
|
initialText.rend(),
|
|
|
|
|
[](const QChar &letter) { return letter.isSpace(); });
|
|
|
|
|
const int extraSpaceCount = static_cast<int>(std::distance(initialText.rbegin(), lastNonSpace));
|
|
|
|
|
|
|
|
|
|
QTextCursor cursor(prevBlock);
|
|
|
|
|
cursor.beginEditBlock();
|
|
|
|
|
cursor.movePosition(QTextCursor::Right,
|
|
|
|
|
QTextCursor::MoveAnchor,
|
|
|
|
|
initialText.size() - extraSpaceCount);
|
|
|
|
|
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, extraSpaceCount);
|
|
|
|
|
cursor.removeSelectedText();
|
|
|
|
|
cursor.endEditBlock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns the total langth of previous lines with pure whitespace.
|
|
|
|
|
static int previousEmptyLinesLength(const QTextBlock ¤tBlock)
|
|
|
|
|
{
|
|
|
|
|
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 void modifyToIndentEmptyLines(
|
2019-02-18 15:56:50 +01:00
|
|
|
QByteArray &buffer, int utf8Offset, const QTextBlock &block, bool secondTry)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-01-28 08:13:33 +01:00
|
|
|
const QString blockText = block.text();
|
|
|
|
|
int firstNonWhitespace = Utils::indexOf(blockText,
|
|
|
|
|
[](const QChar &ch) { return !ch.isSpace(); });
|
|
|
|
|
if (firstNonWhitespace > 0)
|
2019-02-18 15:56:50 +01:00
|
|
|
utf8Offset += firstNonWhitespace;
|
2019-01-28 08:13:33 +01:00
|
|
|
|
|
|
|
|
const bool closingParenBlock = firstNonWhitespace >= 0
|
|
|
|
|
&& blockText.at(firstNonWhitespace) == ')';
|
|
|
|
|
|
|
|
|
|
if (firstNonWhitespace < 0 || closingParenBlock) {
|
2019-01-16 09:37:54 +01:00
|
|
|
//This extra text works for the most cases.
|
2019-02-08 12:31:53 +01:00
|
|
|
QByteArray dummyText("a;a;");
|
2019-01-16 09:37:54 +01:00
|
|
|
|
|
|
|
|
// Search for previous character
|
|
|
|
|
QTextBlock prevBlock = block.previous();
|
|
|
|
|
bool prevBlockIsEmpty = prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty();
|
|
|
|
|
while (prevBlockIsEmpty) {
|
|
|
|
|
prevBlock = prevBlock.previous();
|
|
|
|
|
prevBlockIsEmpty = prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty();
|
|
|
|
|
}
|
2019-01-28 08:13:33 +01:00
|
|
|
if (closingParenBlock || prevBlock.text().endsWith(','))
|
|
|
|
|
dummyText = "&& a";
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
buffer.insert(utf8Offset, dummyText);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (secondTry) {
|
2019-02-18 15:56:50 +01:00
|
|
|
int nextLinePos = buffer.indexOf('\n', utf8Offset);
|
2019-01-28 08:13:33 +01:00
|
|
|
if (nextLinePos < 0)
|
|
|
|
|
nextLinePos = buffer.size() - 1;
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
if (nextLinePos > 0) {
|
|
|
|
|
// If first try was not successful try to put ')' in the end of the line to close possibly
|
|
|
|
|
// unclosed parentheses.
|
|
|
|
|
// TODO: Does it help to add different endings depending on the context?
|
|
|
|
|
buffer.insert(nextLinePos, ')');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Utils::LineColumn utf16LineColumn(const QTextBlock &block,
|
|
|
|
|
int blockOffsetUtf8,
|
|
|
|
|
const QByteArray &utf8Buffer,
|
|
|
|
|
int utf8Offset)
|
|
|
|
|
{
|
|
|
|
|
// If lastIndexOf('\n') returns -1 then we are fine to add 1 and get 0 offset.
|
2019-01-28 08:13:33 +01:00
|
|
|
const int lineStartUtf8Offset = utf8Offset == 0
|
|
|
|
|
? 0
|
|
|
|
|
: utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1;
|
2019-01-16 09:37:54 +01:00
|
|
|
int line = block.blockNumber() + 1; // Init with the line corresponding the block.
|
|
|
|
|
|
|
|
|
|
if (utf8Offset < blockOffsetUtf8) {
|
|
|
|
|
line -= static_cast<int>(std::count(utf8Buffer.begin() + lineStartUtf8Offset,
|
|
|
|
|
utf8Buffer.begin() + blockOffsetUtf8,
|
|
|
|
|
'\n'));
|
|
|
|
|
} else {
|
|
|
|
|
line += static_cast<int>(std::count(utf8Buffer.begin() + blockOffsetUtf8,
|
|
|
|
|
utf8Buffer.begin() + lineStartUtf8Offset,
|
|
|
|
|
'\n'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QByteArray lineText = utf8Buffer.mid(lineStartUtf8Offset,
|
|
|
|
|
utf8Offset - lineStartUtf8Offset);
|
|
|
|
|
return Utils::LineColumn(line, QString::fromUtf8(lineText).size() + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static TextEditor::Replacements utf16Replacements(const QTextBlock &block,
|
|
|
|
|
int blockOffsetUtf8,
|
|
|
|
|
const QByteArray &utf8Buffer,
|
|
|
|
|
const clang::tooling::Replacements &replacements)
|
|
|
|
|
{
|
|
|
|
|
TextEditor::Replacements convertedReplacements;
|
|
|
|
|
convertedReplacements.reserve(replacements.size());
|
|
|
|
|
for (const clang::tooling::Replacement &replacement : replacements) {
|
|
|
|
|
const Utils::LineColumn lineColUtf16 = utf16LineColumn(block,
|
|
|
|
|
blockOffsetUtf8,
|
|
|
|
|
utf8Buffer,
|
|
|
|
|
static_cast<int>(
|
|
|
|
|
replacement.getOffset()));
|
|
|
|
|
if (!lineColUtf16.isValid())
|
|
|
|
|
continue;
|
|
|
|
|
const int utf16Offset = Utils::Text::positionInText(block.document(),
|
|
|
|
|
lineColUtf16.line,
|
|
|
|
|
lineColUtf16.column);
|
|
|
|
|
const int utf16Length = QString::fromUtf8(
|
|
|
|
|
utf8Buffer.mid(static_cast<int>(replacement.getOffset()),
|
|
|
|
|
static_cast<int>(replacement.getLength())))
|
|
|
|
|
.size();
|
|
|
|
|
convertedReplacements.emplace_back(utf16Offset,
|
|
|
|
|
utf16Length,
|
|
|
|
|
QString::fromStdString(replacement.getReplacementText()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return convertedReplacements;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
static void applyReplacements(QTextDocument *doc, const TextEditor::Replacements &replacements)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
|
|
|
|
if (replacements.empty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
int fullOffsetShift = 0;
|
2019-02-18 15:56:50 +01:00
|
|
|
QTextCursor editCursor(doc);
|
2019-01-16 09:37:54 +01:00
|
|
|
for (const TextEditor::Replacement &replacement : replacements) {
|
|
|
|
|
editCursor.beginEditBlock();
|
|
|
|
|
editCursor.setPosition(replacement.offset + fullOffsetShift);
|
|
|
|
|
editCursor.movePosition(QTextCursor::NextCharacter,
|
|
|
|
|
QTextCursor::KeepAnchor,
|
|
|
|
|
replacement.length);
|
|
|
|
|
editCursor.removeSelectedText();
|
|
|
|
|
editCursor.insertText(replacement.text);
|
|
|
|
|
editCursor.endEditBlock();
|
|
|
|
|
fullOffsetShift += replacement.text.length() - replacement.length;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString selectedLines(QTextDocument *doc,
|
|
|
|
|
const QTextBlock &startBlock,
|
|
|
|
|
const QTextBlock &endBlock)
|
|
|
|
|
{
|
|
|
|
|
QString text = Utils::Text::textAt(QTextCursor(doc),
|
|
|
|
|
startBlock.position(),
|
|
|
|
|
std::max(0,
|
|
|
|
|
endBlock.position() + endBlock.length()
|
|
|
|
|
- startBlock.position() - 1));
|
|
|
|
|
while (!text.isEmpty() && text.rbegin()->isSpace())
|
|
|
|
|
text.chop(1);
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ClangFormatBaseIndenter::ClangFormatBaseIndenter(QTextDocument *doc)
|
|
|
|
|
: TextEditor::Indenter(doc)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
TextEditor::IndentationForBlock ClangFormatBaseIndenter::indentationForBlocks(
|
2019-01-28 08:11:20 +01:00
|
|
|
const QVector<QTextBlock> &blocks,
|
|
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
|
|
|
|
TextEditor::IndentationForBlock ret;
|
|
|
|
|
for (QTextBlock block : blocks)
|
2019-01-28 08:11:20 +01:00
|
|
|
ret.insert(block.blockNumber(), indentFor(block, cursorPositionInEditor));
|
2019-01-16 09:37:54 +01:00
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 08:11:20 +01:00
|
|
|
void ClangFormatBaseIndenter::indent(const QTextCursor &cursor,
|
|
|
|
|
const QChar &typedChar,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
|
|
|
|
if (cursor.hasSelection()) {
|
2019-02-18 15:56:50 +01:00
|
|
|
indentBlocks(m_doc->findBlock(cursor.selectionStart()),
|
|
|
|
|
m_doc->findBlock(cursor.selectionEnd()),
|
|
|
|
|
typedChar,
|
|
|
|
|
cursorPositionInEditor);
|
2019-01-16 09:37:54 +01:00
|
|
|
} else {
|
2019-02-18 15:56:50 +01:00
|
|
|
indentBlocks(cursor.block(), cursor.block(), typedChar, cursorPositionInEditor);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangFormatBaseIndenter::indent(const QTextCursor &cursor,
|
|
|
|
|
const QChar &typedChar,
|
2019-01-28 08:11:20 +01:00
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-01-28 08:11:20 +01:00
|
|
|
indent(cursor, typedChar, cursorPositionInEditor);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangFormatBaseIndenter::reindent(const QTextCursor &cursor,
|
2019-01-28 08:11:20 +01:00
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-01-28 08:11:20 +01:00
|
|
|
indent(cursor, QChar::Null, cursorPositionInEditor);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-13 14:17:21 +01:00
|
|
|
TextEditor::Replacements ClangFormatBaseIndenter::format(
|
|
|
|
|
const TextEditor::RangesInLines &rangesInLines)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-13 14:17:21 +01:00
|
|
|
if (rangesInLines.empty())
|
|
|
|
|
return TextEditor::Replacements();
|
|
|
|
|
|
|
|
|
|
int utf8Offset = -1;
|
2019-01-28 08:13:33 +01:00
|
|
|
QTextBlock block;
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
const QByteArray buffer = m_doc->toPlainText().toUtf8();
|
2019-02-13 14:17:21 +01:00
|
|
|
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));
|
2019-01-28 08:13:33 +01:00
|
|
|
|
2019-02-13 14:17:21 +01:00
|
|
|
if (utf8Offset < 0) {
|
|
|
|
|
utf8Offset = utf8StartOffset;
|
|
|
|
|
block = m_doc->findBlockByNumber(range.startLine - 1);
|
|
|
|
|
}
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-13 14:17:21 +01:00
|
|
|
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(block,
|
|
|
|
|
utf8Offset,
|
|
|
|
|
buffer,
|
|
|
|
|
clangReplacements);
|
2019-02-18 15:56:50 +01:00
|
|
|
applyReplacements(m_doc, toReplace);
|
2019-01-16 09:37:54 +01:00
|
|
|
|
|
|
|
|
return toReplace;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-08 09:48:49 +01:00
|
|
|
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());
|
2019-02-14 13:02:21 +01:00
|
|
|
// NOTE: check if "<<" and ">>" always work correctly.
|
2019-02-08 09:48:49 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
void ClangFormatBaseIndenter::indentBlocks(const QTextBlock &startBlock,
|
|
|
|
|
const QTextBlock &endBlock,
|
|
|
|
|
const QChar &typedChar,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-08 09:48:49 +01:00
|
|
|
if (typedChar != QChar::Null && cursorPositionInEditor > 0
|
|
|
|
|
&& m_doc->characterAt(cursorPositionInEditor - 1) == typedChar
|
|
|
|
|
&& doNotIndentInContext(m_doc, cursorPositionInEditor - 1)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
const int startBlockPosition = startBlock.position();
|
|
|
|
|
trimFirstNonEmptyBlock(startBlock);
|
2019-02-14 13:02:21 +01:00
|
|
|
if (cursorPositionInEditor >= 0)
|
2019-02-18 15:56:50 +01:00
|
|
|
cursorPositionInEditor += startBlock.position() - startBlockPosition;
|
2019-02-14 13:02:21 +01:00
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent;
|
2019-01-28 08:13:33 +01:00
|
|
|
if (formatWhileTyping()
|
2019-02-18 15:56:50 +01:00
|
|
|
&& (cursorPositionInEditor == -1 || cursorPositionInEditor >= startBlockPosition)
|
2019-02-08 09:48:49 +01:00
|
|
|
&& (typedChar == QChar::Null || typedChar == ';' || typedChar == '}')) {
|
2019-01-28 08:13:33 +01:00
|
|
|
// 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.
|
2019-02-08 09:48:49 +01:00
|
|
|
|
|
|
|
|
// Format only before newline or complete statement not to break code.
|
2019-02-18 15:56:50 +01:00
|
|
|
replacementsToKeep = ReplacementsToKeep::IndentAndBefore;
|
2019-01-28 08:13:33 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
const QByteArray buffer = m_doc->toPlainText().toUtf8();
|
2019-02-18 15:56:50 +01:00
|
|
|
const int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc,
|
|
|
|
|
buffer,
|
|
|
|
|
startBlock.blockNumber() + 1);
|
2019-01-16 09:37:54 +01:00
|
|
|
QTC_ASSERT(utf8Offset >= 0, return;);
|
2019-02-18 15:56:50 +01:00
|
|
|
const int utf8Length = selectedLines(m_doc, startBlock, endBlock).toUtf8().size();
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
applyReplacements(m_doc,
|
2019-01-28 08:13:33 +01:00
|
|
|
replacements(buffer,
|
|
|
|
|
utf8Offset,
|
2019-02-18 15:56:50 +01:00
|
|
|
utf8Length,
|
|
|
|
|
startBlock,
|
|
|
|
|
endBlock,
|
|
|
|
|
replacementsToKeep,
|
2019-01-28 08:13:33 +01:00
|
|
|
typedChar));
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangFormatBaseIndenter::indentBlock(const QTextBlock &block,
|
|
|
|
|
const QChar &typedChar,
|
2019-01-28 08:11:20 +01:00
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-18 15:56:50 +01:00
|
|
|
indentBlocks(block, block, typedChar, cursorPositionInEditor);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
int ClangFormatBaseIndenter::indentFor(const QTextBlock &block, int /*cursorPositionInEditor*/)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
|
|
|
|
trimFirstNonEmptyBlock(block);
|
2019-02-14 13:02:21 +01:00
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
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;);
|
|
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
const TextEditor::Replacements toReplace = replacements(buffer,
|
|
|
|
|
utf8Offset,
|
|
|
|
|
0,
|
|
|
|
|
block,
|
2019-02-18 15:56:50 +01:00
|
|
|
block,
|
2019-01-28 08:13:33 +01:00
|
|
|
ReplacementsToKeep::OnlyIndent);
|
2019-01-16 09:37:54 +01:00
|
|
|
|
|
|
|
|
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,
|
2019-01-28 08:11:20 +01:00
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-01-28 08:11:20 +01:00
|
|
|
return indentFor(block, cursorPositionInEditor);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ClangFormatBaseIndenter::isElectricCharacter(const QChar &ch) const
|
|
|
|
|
{
|
|
|
|
|
switch (ch.toLatin1()) {
|
|
|
|
|
case '{':
|
|
|
|
|
case '}':
|
|
|
|
|
case ':':
|
|
|
|
|
case '#':
|
|
|
|
|
case '<':
|
|
|
|
|
case '>':
|
|
|
|
|
case ';':
|
|
|
|
|
case '(':
|
|
|
|
|
case ')':
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 07:54:05 +01:00
|
|
|
void ClangFormatBaseIndenter::formatOrIndent(const QTextCursor &cursor,
|
|
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
2019-01-28 08:11:20 +01:00
|
|
|
int cursorPositionInEditor)
|
2019-01-28 07:54:05 +01:00
|
|
|
{
|
2019-02-13 14:17:21 +01:00
|
|
|
if (formatCodeInsteadOfIndent()) {
|
|
|
|
|
QTextBlock start;
|
|
|
|
|
QTextBlock end;
|
|
|
|
|
if (cursor.hasSelection()) {
|
|
|
|
|
start = m_doc->findBlock(cursor.selectionStart());
|
|
|
|
|
end = m_doc->findBlock(cursor.selectionEnd());
|
|
|
|
|
} else {
|
|
|
|
|
start = end = cursor.block();
|
|
|
|
|
}
|
|
|
|
|
format({{start.blockNumber() + 1, end.blockNumber() + 1}});
|
|
|
|
|
} else {
|
2019-01-28 08:11:20 +01:00
|
|
|
indent(cursor, QChar::Null, cursorPositionInEditor);
|
2019-02-13 14:17:21 +01:00
|
|
|
}
|
2019-01-28 07:54:05 +01:00
|
|
|
}
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
clang::format::FormatStyle ClangFormatBaseIndenter::styleForFile() const
|
|
|
|
|
{
|
|
|
|
|
llvm::Expected<clang::format::FormatStyle> style
|
|
|
|
|
= clang::format::getStyle("file", m_fileName.toString().toStdString(), "none");
|
|
|
|
|
if (style)
|
|
|
|
|
return *style;
|
|
|
|
|
|
|
|
|
|
handleAllErrors(style.takeError(), [](const llvm::ErrorInfoBase &) {
|
|
|
|
|
// do nothing
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return clang::format::getLLVMStyle();
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
static int formattingRangeStart(const QTextBlock ¤tBlock,
|
|
|
|
|
const QByteArray &buffer,
|
|
|
|
|
int documentRevision)
|
|
|
|
|
{
|
|
|
|
|
QTextBlock prevBlock = currentBlock.previous();
|
|
|
|
|
while ((prevBlock.position() > 0 || prevBlock.length() > 0)
|
2019-02-08 12:31:53 +01:00
|
|
|
&& prevBlock.revision() != documentRevision) {
|
2019-01-28 08:13:33 +01:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
TextEditor::Replacements ClangFormatBaseIndenter::replacements(QByteArray buffer,
|
|
|
|
|
int utf8Offset,
|
|
|
|
|
int utf8Length,
|
2019-02-18 15:56:50 +01:00
|
|
|
const QTextBlock &startBlock,
|
|
|
|
|
const QTextBlock &endBlock,
|
2019-01-28 08:13:33 +01:00
|
|
|
ReplacementsToKeep replacementsToKeep,
|
2019-01-16 09:37:54 +01:00
|
|
|
const QChar &typedChar,
|
|
|
|
|
bool secondTry) const
|
|
|
|
|
{
|
2019-02-13 14:17:21 +01:00
|
|
|
QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return TextEditor::Replacements());
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
clang::format::FormatStyle style = styleForFile();
|
|
|
|
|
|
|
|
|
|
int originalOffsetUtf8 = utf8Offset;
|
|
|
|
|
int originalLengthUtf8 = utf8Length;
|
|
|
|
|
QByteArray originalBuffer = buffer;
|
|
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
int rangeStart = 0;
|
2019-02-13 14:17:21 +01:00
|
|
|
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
|
2019-02-18 15:56:50 +01:00
|
|
|
rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision());
|
2019-01-28 08:13:33 +01:00
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
int extraEmptySpaceOffset = previousEmptyLinesLength(startBlock);
|
2019-02-13 14:17:21 +01:00
|
|
|
utf8Offset -= extraEmptySpaceOffset;
|
|
|
|
|
buffer.remove(utf8Offset, extraEmptySpaceOffset);
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-02-13 14:17:21 +01:00
|
|
|
adjustFormatStyleForLineBreak(style, replacementsToKeep);
|
2019-02-18 15:56:50 +01:00
|
|
|
if (typedChar == QChar::Null && startBlock == endBlock) {
|
|
|
|
|
modifyToIndentEmptyLines(buffer, utf8Offset, startBlock, secondTry);
|
|
|
|
|
utf8Length = 0;
|
|
|
|
|
}
|
2019-02-08 12:31:53 +01:00
|
|
|
|
2019-02-13 14:17:21 +01:00
|
|
|
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore) {
|
|
|
|
|
buffer.insert(utf8Offset - 1, " //");
|
|
|
|
|
extraEmptySpaceOffset -= 3;
|
|
|
|
|
utf8Offset += 3;
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-08 12:31:53 +01:00
|
|
|
if (replacementsToKeep != ReplacementsToKeep::IndentAndBefore || utf8Offset < rangeStart)
|
2019-01-28 08:13:33 +01:00
|
|
|
rangeStart = utf8Offset;
|
|
|
|
|
|
|
|
|
|
unsigned int rangeLength = static_cast<unsigned int>(utf8Offset + utf8Length - rangeStart);
|
|
|
|
|
|
|
|
|
|
std::vector<clang::tooling::Range> ranges{{static_cast<unsigned int>(rangeStart), rangeLength}};
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
clang::format::FormattingAttemptStatus status;
|
|
|
|
|
|
|
|
|
|
clang::tooling::Replacements clangReplacements = reformat(style,
|
|
|
|
|
buffer.data(),
|
|
|
|
|
ranges,
|
|
|
|
|
m_fileName.toString().toStdString(),
|
|
|
|
|
&status);
|
|
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
clang::tooling::Replacements filtered;
|
|
|
|
|
if (status.FormatComplete) {
|
2019-02-18 15:56:50 +01:00
|
|
|
filtered = filteredReplacements(buffer,
|
|
|
|
|
clangReplacements,
|
2019-01-28 08:13:33 +01:00
|
|
|
utf8Offset,
|
2019-02-18 15:56:50 +01:00
|
|
|
utf8Length,
|
2019-01-28 08:13:33 +01:00
|
|
|
extraEmptySpaceOffset,
|
|
|
|
|
replacementsToKeep);
|
|
|
|
|
}
|
|
|
|
|
const bool canTryAgain = replacementsToKeep == ReplacementsToKeep::OnlyIndent
|
|
|
|
|
&& typedChar == QChar::Null && !secondTry;
|
2019-01-16 09:37:54 +01:00
|
|
|
if (canTryAgain && filtered.empty()) {
|
|
|
|
|
return replacements(originalBuffer,
|
|
|
|
|
originalOffsetUtf8,
|
|
|
|
|
originalLengthUtf8,
|
2019-02-18 15:56:50 +01:00
|
|
|
startBlock,
|
|
|
|
|
endBlock,
|
2019-01-28 08:13:33 +01:00
|
|
|
replacementsToKeep,
|
2019-01-16 09:37:54 +01:00
|
|
|
typedChar,
|
|
|
|
|
true);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
return utf16Replacements(startBlock, originalOffsetUtf8, originalBuffer, filtered);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace ClangFormat
|