2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2019 The Qt Company Ltd.
|
2022-12-21 10:12:09 +01:00
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2019-01-16 09:37:54 +01:00
|
|
|
|
|
|
|
|
#include "clangformatbaseindenter.h"
|
2022-01-11 13:18:29 +01:00
|
|
|
#include "clangformatutils.h"
|
2022-12-09 10:26:46 +01:00
|
|
|
#include "llvmfilesystem.h"
|
2022-05-06 12:18:28 +02:00
|
|
|
|
2022-04-08 15:12:30 +02:00
|
|
|
#include <coreplugin/icore.h>
|
2022-12-13 08:42:07 +01:00
|
|
|
|
2023-06-07 16:17:29 +02:00
|
|
|
#include <cppeditor/cppcodestylepreferences.h>
|
2023-06-30 15:16:28 +02:00
|
|
|
#include <cppeditor/cpptoolssettings.h>
|
2023-06-07 16:17:29 +02:00
|
|
|
|
2022-05-06 12:18:28 +02:00
|
|
|
#include <projectexplorer/editorconfiguration.h>
|
|
|
|
|
#include <projectexplorer/project.h>
|
2023-02-14 15:47:22 +01:00
|
|
|
#include <projectexplorer/projectmanager.h>
|
2022-12-13 08:42:07 +01:00
|
|
|
|
2022-04-08 15:12:30 +02:00
|
|
|
#include <texteditor/icodestylepreferences.h>
|
2023-06-07 16:17:29 +02:00
|
|
|
#include <texteditor/tabsettings.h>
|
2022-05-06 12:18:28 +02:00
|
|
|
#include <texteditor/texteditorsettings.h>
|
2019-01-16 09:37:54 +01:00
|
|
|
|
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/qtcassert.h>
|
2019-03-12 11:55:43 +01:00
|
|
|
#include <utils/textutils.h>
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-03-06 12:47:45 +01:00
|
|
|
#include <QDebug>
|
2019-03-12 11:55:43 +01:00
|
|
|
#include <QTextDocument>
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2023-06-07 16:17:29 +02:00
|
|
|
#include <clang/Tooling/Core/Replacement.h>
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
namespace ClangFormat {
|
|
|
|
|
|
2023-02-06 10:27:27 +01:00
|
|
|
Internal::LlvmFileSystemAdapter llvmFileSystemAdapter = {};
|
2022-12-09 10:26:46 +01:00
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
namespace {
|
|
|
|
|
void adjustFormatStyleForLineBreak(clang::format::FormatStyle &style,
|
|
|
|
|
ReplacementsToKeep replacementsToKeep)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-19 12:46:38 +01:00
|
|
|
style.MaxEmptyLinesToKeep = 100;
|
2021-03-08 21:56:06 +01:00
|
|
|
#if LLVM_VERSION_MAJOR >= 13
|
|
|
|
|
style.SortIncludes = clang::format::FormatStyle::SI_Never;
|
|
|
|
|
#else
|
2019-02-08 12:31:53 +01:00
|
|
|
style.SortIncludes = false;
|
2021-03-08 21:56:06 +01:00
|
|
|
#endif
|
2023-01-31 08:11:29 +01:00
|
|
|
#if LLVM_VERSION_MAJOR >= 16
|
|
|
|
|
style.SortUsingDeclarations = clang::format::FormatStyle::SUD_Never;
|
|
|
|
|
#else
|
2019-02-08 12:31:53 +01:00
|
|
|
style.SortUsingDeclarations = false;
|
2023-01-31 08:11:29 +01:00
|
|
|
#endif
|
2019-02-08 12:31:53 +01:00
|
|
|
|
|
|
|
|
// This is a separate pass, don't do it unless it's the full formatting.
|
|
|
|
|
style.FixNamespaceComments = false;
|
2022-11-04 07:18:50 +01:00
|
|
|
#if LLVM_VERSION_MAJOR >= 16
|
|
|
|
|
style.AlignTrailingComments = {clang::format::FormatStyle::TCAS_Never, 0};
|
|
|
|
|
#else
|
2019-04-01 13:37:43 +02:00
|
|
|
style.AlignTrailingComments = false;
|
2022-11-04 07:18:50 +01:00
|
|
|
#endif
|
2019-02-08 12:31:53 +01:00
|
|
|
|
|
|
|
|
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
style.ColumnLimit = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
llvm::StringRef clearExtraNewline(llvm::StringRef text)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
|
|
|
|
while (text.startswith("\n\n"))
|
|
|
|
|
text = text.drop_front();
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
clang::tooling::Replacements filteredReplacements(const QByteArray &buffer,
|
|
|
|
|
const clang::tooling::Replacements &replacements,
|
|
|
|
|
int utf8Offset,
|
|
|
|
|
int utf8Length,
|
|
|
|
|
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-02-19 14:30:52 +01:00
|
|
|
|
|
|
|
|
// Skip everything after.
|
|
|
|
|
if (replacementOffset >= utf8Offset + utf8Length)
|
|
|
|
|
return filtered;
|
|
|
|
|
|
|
|
|
|
const bool isNotIndentOrInRange = replacementOffset < utf8Offset - 1
|
|
|
|
|
|| buffer.at(replacementOffset) != '\n';
|
|
|
|
|
if (isNotIndentOrInRange && replacementsToKeep == ReplacementsToKeep::OnlyIndent)
|
2019-01-16 09:37:54 +01:00
|
|
|
continue;
|
|
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
llvm::StringRef text = replacementsToKeep == ReplacementsToKeep::OnlyIndent
|
|
|
|
|
? clearExtraNewline(replacement.getReplacementText())
|
|
|
|
|
: replacement.getReplacementText();
|
2021-07-05 11:31:29 +02:00
|
|
|
if (replacementsToKeep == ReplacementsToKeep::OnlyIndent && int(text.count('\n'))
|
|
|
|
|
!= buffer.mid(replacementOffset, replacement.getLength()).count('\n')) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
void trimRHSWhitespace(const QTextBlock &block)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-19 14:30:52 +01:00
|
|
|
const QString initialText = block.text();
|
|
|
|
|
if (!initialText.rbegin()->isSpace())
|
2019-01-16 09:37:54 +01:00
|
|
|
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));
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
QTextCursor cursor(block);
|
2019-01-16 09:37:54 +01:00
|
|
|
cursor.movePosition(QTextCursor::Right,
|
|
|
|
|
QTextCursor::MoveAnchor,
|
|
|
|
|
initialText.size() - extraSpaceCount);
|
|
|
|
|
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, extraSpaceCount);
|
|
|
|
|
cursor.removeSelectedText();
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-11 11:10:12 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-05 16:38:51 +02:00
|
|
|
QTextBlock reverseFindLastBlockWithSymbol(QTextBlock start, QChar ch)
|
|
|
|
|
{
|
|
|
|
|
if (start.position() > 0) {
|
|
|
|
|
start = start.previous();
|
|
|
|
|
while (start.position() > 0 && !start.text().contains(ch))
|
|
|
|
|
start = start.previous();
|
|
|
|
|
}
|
|
|
|
|
return start;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-01 13:37:43 +02:00
|
|
|
enum class CharacterContext {
|
|
|
|
|
AfterComma,
|
|
|
|
|
LastAfterComma,
|
|
|
|
|
NewStatementOrContinuation,
|
|
|
|
|
IfOrElseWithoutScope,
|
|
|
|
|
Unknown
|
|
|
|
|
};
|
2019-03-07 12:29:10 +01:00
|
|
|
|
2019-03-11 11:10:12 +01:00
|
|
|
QChar findFirstNonWhitespaceCharacter(const QTextBlock ¤tBlock)
|
2019-03-07 12:29:10 +01:00
|
|
|
{
|
2019-03-11 11:10:12 +01:00
|
|
|
const QTextDocument *doc = currentBlock.document();
|
|
|
|
|
int currentPos = currentBlock.position();
|
|
|
|
|
while (currentPos < doc->characterCount() && doc->characterAt(currentPos).isSpace())
|
|
|
|
|
++currentPos;
|
|
|
|
|
return currentPos < doc->characterCount() ? doc->characterAt(currentPos) : QChar::Null;
|
|
|
|
|
}
|
2019-03-07 12:29:10 +01:00
|
|
|
|
2019-04-01 13:37:43 +02:00
|
|
|
int findMatchingOpeningParen(const QTextBlock &blockEndingWithClosingParen)
|
|
|
|
|
{
|
|
|
|
|
const QTextDocument *doc = blockEndingWithClosingParen.document();
|
|
|
|
|
int currentPos = blockEndingWithClosingParen.position()
|
|
|
|
|
+ blockEndingWithClosingParen.text().lastIndexOf(')');
|
|
|
|
|
int parenBalance = 1;
|
|
|
|
|
|
|
|
|
|
while (currentPos > 0 && parenBalance > 0) {
|
|
|
|
|
--currentPos;
|
|
|
|
|
if (doc->characterAt(currentPos) == ')')
|
|
|
|
|
++parenBalance;
|
|
|
|
|
if (doc->characterAt(currentPos) == '(')
|
|
|
|
|
--parenBalance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parenBalance == 0)
|
|
|
|
|
return currentPos;
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool comesDirectlyAfterIf(const QTextDocument *doc, int pos)
|
|
|
|
|
{
|
|
|
|
|
--pos;
|
|
|
|
|
while (pos > 0 && doc->characterAt(pos).isSpace())
|
|
|
|
|
--pos;
|
|
|
|
|
return pos > 0 && doc->characterAt(pos) == 'f' && doc->characterAt(pos - 1) == 'i';
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-05 16:38:51 +02:00
|
|
|
CharacterContext characterContext(const QTextBlock ¤tBlock)
|
2019-03-11 11:10:12 +01:00
|
|
|
{
|
2022-07-05 16:38:51 +02:00
|
|
|
QTextBlock previousNonEmptyBlock = reverseFindLastEmptyBlock(currentBlock);
|
|
|
|
|
if (previousNonEmptyBlock.position() > 0)
|
|
|
|
|
previousNonEmptyBlock = previousNonEmptyBlock.previous();
|
|
|
|
|
|
2019-03-11 11:10:12 +01:00
|
|
|
const QString prevLineText = previousNonEmptyBlock.text().trimmed();
|
2019-04-01 13:37:43 +02:00
|
|
|
if (prevLineText.isEmpty())
|
|
|
|
|
return CharacterContext::NewStatementOrContinuation;
|
|
|
|
|
|
2019-03-12 10:27:19 +01:00
|
|
|
const QChar firstNonWhitespaceChar = findFirstNonWhitespaceCharacter(currentBlock);
|
2019-03-11 11:10:12 +01:00
|
|
|
if (prevLineText.endsWith(',')) {
|
2022-07-05 16:38:51 +02:00
|
|
|
if (firstNonWhitespaceChar == '}') {
|
|
|
|
|
if (reverseFindLastBlockWithSymbol(currentBlock, '{').text().trimmed().last(1) == '{')
|
|
|
|
|
return CharacterContext::NewStatementOrContinuation;
|
2019-03-11 11:10:12 +01:00
|
|
|
return CharacterContext::LastAfterComma;
|
2022-07-05 16:38:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (firstNonWhitespaceChar == ')') {
|
|
|
|
|
if (reverseFindLastBlockWithSymbol(currentBlock, '(').text().trimmed().last(1) == '(')
|
|
|
|
|
return CharacterContext::NewStatementOrContinuation;
|
|
|
|
|
return CharacterContext::LastAfterComma;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-11 11:10:12 +01:00
|
|
|
return CharacterContext::AfterComma;
|
2019-03-07 12:29:10 +01:00
|
|
|
}
|
|
|
|
|
|
2019-04-01 13:37:43 +02:00
|
|
|
if (prevLineText.endsWith("else"))
|
|
|
|
|
return CharacterContext::IfOrElseWithoutScope;
|
|
|
|
|
if (prevLineText.endsWith(')')) {
|
|
|
|
|
const int pos = findMatchingOpeningParen(previousNonEmptyBlock);
|
|
|
|
|
if (pos >= 0 && comesDirectlyAfterIf(previousNonEmptyBlock.document(), pos))
|
|
|
|
|
return CharacterContext::IfOrElseWithoutScope;
|
2019-03-12 10:27:19 +01:00
|
|
|
}
|
2019-03-07 12:29:10 +01:00
|
|
|
|
2019-04-01 13:37:43 +02:00
|
|
|
return CharacterContext::NewStatementOrContinuation;
|
2019-03-07 12:29:10 +01:00
|
|
|
}
|
|
|
|
|
|
2019-03-12 10:27:19 +01:00
|
|
|
bool nextBlockExistsAndEmpty(const QTextBlock ¤tBlock)
|
|
|
|
|
{
|
|
|
|
|
QTextBlock nextBlock = currentBlock.next();
|
|
|
|
|
if (!nextBlock.isValid() || nextBlock.position() == currentBlock.position())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return nextBlock.text().trimmed().isEmpty();
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-12 11:55:43 +01:00
|
|
|
QByteArray dummyTextForContext(CharacterContext context, bool closingBraceBlock)
|
|
|
|
|
{
|
2019-04-01 13:37:43 +02:00
|
|
|
if (closingBraceBlock && context == CharacterContext::NewStatementOrContinuation)
|
2019-03-12 11:55:43 +01:00
|
|
|
return QByteArray();
|
|
|
|
|
|
|
|
|
|
switch (context) {
|
|
|
|
|
case CharacterContext::AfterComma:
|
|
|
|
|
return "a,";
|
|
|
|
|
case CharacterContext::LastAfterComma:
|
2019-04-01 13:37:43 +02:00
|
|
|
return "a";
|
|
|
|
|
case CharacterContext::IfOrElseWithoutScope:
|
|
|
|
|
return ";";
|
|
|
|
|
case CharacterContext::NewStatementOrContinuation:
|
2022-10-24 16:39:20 +02:00
|
|
|
return "/*//*/";
|
2019-03-12 23:06:57 +02:00
|
|
|
case CharacterContext::Unknown:
|
|
|
|
|
default:
|
|
|
|
|
QTC_ASSERT(false, return "";);
|
2019-03-12 11:55:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
// Add extra text in case of the empty line or the line starting with ')'.
|
2019-04-01 13:37:43 +02:00
|
|
|
// Track such extra pieces of text in isInsideDummyTextInLine().
|
2019-03-06 14:05:31 +01:00
|
|
|
int forceIndentWithExtraText(QByteArray &buffer,
|
2019-03-11 11:10:12 +01:00
|
|
|
CharacterContext &charContext,
|
2019-03-06 14:05:31 +01:00
|
|
|
const QTextBlock &block,
|
|
|
|
|
bool secondTry)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2021-02-18 13:57:29 +01:00
|
|
|
if (!block.isValid())
|
|
|
|
|
return 0;
|
|
|
|
|
|
2022-07-05 16:38:51 +02:00
|
|
|
auto tmpcharContext = characterContext(block);
|
|
|
|
|
if (charContext == CharacterContext::LastAfterComma
|
|
|
|
|
&& tmpcharContext == CharacterContext::LastAfterComma) {
|
|
|
|
|
charContext = CharacterContext::AfterComma;
|
|
|
|
|
} else {
|
|
|
|
|
charContext = tmpcharContext;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-28 08:13:33 +01:00
|
|
|
const QString blockText = block.text();
|
|
|
|
|
int firstNonWhitespace = Utils::indexOf(blockText,
|
|
|
|
|
[](const QChar &ch) { return !ch.isSpace(); });
|
2019-02-19 12:46:38 +01:00
|
|
|
int utf8Offset = Utils::Text::utf8NthLineOffset(block.document(),
|
|
|
|
|
buffer,
|
|
|
|
|
block.blockNumber() + 1);
|
2019-02-20 08:43:29 +01:00
|
|
|
if (firstNonWhitespace >= 0)
|
2019-02-18 15:56:50 +01:00
|
|
|
utf8Offset += firstNonWhitespace;
|
2019-02-19 12:46:38 +01:00
|
|
|
else
|
|
|
|
|
utf8Offset += blockText.length();
|
2019-01-28 08:13:33 +01:00
|
|
|
|
|
|
|
|
const bool closingParenBlock = firstNonWhitespace >= 0
|
|
|
|
|
&& blockText.at(firstNonWhitespace) == ')';
|
2019-03-11 12:30:10 +01:00
|
|
|
const bool closingBraceBlock = firstNonWhitespace >= 0
|
|
|
|
|
&& blockText.at(firstNonWhitespace) == '}';
|
2019-03-07 12:29:10 +01:00
|
|
|
|
2019-02-19 12:46:38 +01:00
|
|
|
int extraLength = 0;
|
2019-03-12 10:27:19 +01:00
|
|
|
QByteArray dummyText;
|
|
|
|
|
if (firstNonWhitespace < 0 && charContext != CharacterContext::Unknown
|
|
|
|
|
&& nextBlockExistsAndEmpty(block)) {
|
|
|
|
|
// If the next line is also empty it's safer to use a comment line.
|
|
|
|
|
dummyText = "//";
|
|
|
|
|
} else if (firstNonWhitespace < 0 || closingParenBlock || closingBraceBlock) {
|
2019-03-12 11:55:43 +01:00
|
|
|
dummyText = dummyTextForContext(charContext, closingBraceBlock);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2021-02-18 13:57:29 +01:00
|
|
|
// A comment at the end of the line appears to prevent clang-format from removing line breaks.
|
2022-10-24 16:39:20 +02:00
|
|
|
if (dummyText == "/*//*/" || dummyText.isEmpty()) {
|
2021-02-18 13:57:29 +01:00
|
|
|
if (block.previous().isValid()) {
|
|
|
|
|
const int prevEndOffset = Utils::Text::utf8NthLineOffset(block.document(), buffer,
|
|
|
|
|
block.blockNumber()) + block.previous().text().length();
|
2021-07-05 13:25:29 +02:00
|
|
|
buffer.insert(prevEndOffset, " //");
|
|
|
|
|
extraLength += 3;
|
2021-02-18 13:57:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
buffer.insert(utf8Offset + extraLength, dummyText);
|
2019-03-12 10:27:19 +01:00
|
|
|
extraLength += dummyText.length();
|
|
|
|
|
|
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
|
2019-03-07 12:29:10 +01:00
|
|
|
// unclosed parenthesis.
|
2019-01-16 09:37:54 +01:00
|
|
|
// TODO: Does it help to add different endings depending on the context?
|
|
|
|
|
buffer.insert(nextLinePos, ')');
|
2019-02-19 12:46:38 +01:00
|
|
|
extraLength += 1;
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
}
|
2019-02-19 12:46:38 +01:00
|
|
|
|
|
|
|
|
return extraLength;
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-04-01 13:37:43 +02:00
|
|
|
bool isInsideDummyTextInLine(const QString &originalLine, const QString &modifiedLine, int column)
|
2019-02-19 12:46:38 +01:00
|
|
|
{
|
2019-02-19 14:30:52 +01:00
|
|
|
// Detect the cases when we have inserted extra text into the line to get the indentation.
|
2019-02-19 12:46:38 +01:00
|
|
|
return originalLine.length() < modifiedLine.length() && column != modifiedLine.length() + 1
|
|
|
|
|
&& (column > originalLine.length() || originalLine.trimmed().isEmpty()
|
|
|
|
|
|| !modifiedLine.startsWith(originalLine));
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 13:53:04 +02:00
|
|
|
static Utils::Text::Position utf16LineColumn(const QByteArray &utf8Buffer, int utf8Offset)
|
|
|
|
|
{
|
|
|
|
|
Utils::Text::Position position;
|
|
|
|
|
position.line = static_cast<int>(std::count(utf8Buffer.begin(),
|
|
|
|
|
utf8Buffer.begin() + utf8Offset, '\n')) + 1;
|
|
|
|
|
const int startOfLineOffset = utf8Offset ? (utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1)
|
|
|
|
|
: 0;
|
|
|
|
|
position.column = QString::fromUtf8(utf8Buffer.mid(startOfLineOffset,
|
|
|
|
|
utf8Offset - startOfLineOffset)).length();
|
|
|
|
|
return position;
|
|
|
|
|
}
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet convertReplacements(const QTextDocument *doc,
|
|
|
|
|
const QByteArray &utf8Buffer,
|
|
|
|
|
const clang::tooling::Replacements &replacements)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet convertedReplacements;
|
2019-02-19 12:46:38 +01:00
|
|
|
|
2019-01-16 09:37:54 +01:00
|
|
|
for (const clang::tooling::Replacement &replacement : replacements) {
|
2023-05-11 13:53:04 +02:00
|
|
|
Utils::Text::Position lineColUtf16 = utf16LineColumn(
|
|
|
|
|
utf8Buffer, static_cast<int>(replacement.getOffset()));
|
2019-01-16 09:37:54 +01:00
|
|
|
if (!lineColUtf16.isValid())
|
|
|
|
|
continue;
|
2019-02-19 12:46:38 +01:00
|
|
|
|
|
|
|
|
const QString lineText = doc->findBlockByNumber(lineColUtf16.line - 1).text();
|
2019-02-19 14:30:52 +01:00
|
|
|
const QString bufferLineText
|
|
|
|
|
= Utils::Text::utf16LineTextInUtf8Buffer(utf8Buffer,
|
|
|
|
|
static_cast<int>(replacement.getOffset()));
|
2023-05-11 14:16:57 +02:00
|
|
|
if (isInsideDummyTextInLine(lineText, bufferLineText, lineColUtf16.column + 1))
|
2019-02-19 12:46:38 +01:00
|
|
|
continue;
|
|
|
|
|
|
2023-05-11 14:16:57 +02:00
|
|
|
lineColUtf16.column = std::min(lineColUtf16.column, int(lineText.length()));
|
2023-06-30 16:03:52 +02:00
|
|
|
int utf16Offset = Utils::Text::positionInText(doc,
|
|
|
|
|
lineColUtf16.line,
|
|
|
|
|
lineColUtf16.column + 1);
|
|
|
|
|
int utf16Length = QString::fromUtf8(
|
|
|
|
|
utf8Buffer.mid(static_cast<int>(replacement.getOffset()),
|
|
|
|
|
static_cast<int>(replacement.getLength())))
|
|
|
|
|
.size();
|
|
|
|
|
|
|
|
|
|
QString replacementText = QString::fromStdString(replacement.getReplacementText().str());
|
|
|
|
|
auto sameCharAt = [&](int replacementOffset) {
|
|
|
|
|
if (replacementText.size() <= replacementOffset || replacementOffset < 0)
|
|
|
|
|
return false;
|
|
|
|
|
const QChar docChar = doc->characterAt(utf16Offset + replacementOffset);
|
|
|
|
|
const QChar replacementChar = replacementText.at(replacementOffset);
|
|
|
|
|
return docChar == replacementChar
|
|
|
|
|
|| (docChar == QChar::ParagraphSeparator && replacementChar == '\n');
|
|
|
|
|
};
|
|
|
|
|
// remove identical prefix from replacement text
|
|
|
|
|
while (sameCharAt(0)) {
|
|
|
|
|
++utf16Offset;
|
|
|
|
|
--utf16Length;
|
|
|
|
|
replacementText = replacementText.mid(1);
|
|
|
|
|
}
|
|
|
|
|
// remove identical suffix from replacement text
|
|
|
|
|
while (sameCharAt(utf16Length - 1)) {
|
|
|
|
|
--utf16Length;
|
|
|
|
|
replacementText.chop(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!replacementText.isEmpty() || utf16Length > 0)
|
2023-07-04 10:05:33 +02:00
|
|
|
convertedReplacements.replace(utf16Offset, utf16Offset + utf16Length, replacementText);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return convertedReplacements;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
QString selectedLines(QTextDocument *doc, const QTextBlock &startBlock, const QTextBlock &endBlock)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-19 12:46:38 +01:00
|
|
|
return Utils::Text::textAt(QTextCursor(doc),
|
|
|
|
|
startBlock.position(),
|
|
|
|
|
std::max(0,
|
|
|
|
|
endBlock.position() + endBlock.length()
|
|
|
|
|
- startBlock.position() - 1));
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-04 10:05:33 +02:00
|
|
|
int indentationForBlock(const Utils::ChangeSet &toReplace,
|
2019-02-19 14:30:52 +01:00
|
|
|
const QByteArray &buffer,
|
|
|
|
|
const QTextBlock ¤tBlock)
|
|
|
|
|
{
|
|
|
|
|
const int utf8Offset = Utils::Text::utf8NthLineOffset(currentBlock.document(),
|
|
|
|
|
buffer,
|
|
|
|
|
currentBlock.blockNumber() + 1);
|
2023-07-04 10:05:33 +02:00
|
|
|
auto ops = toReplace.operationList();
|
|
|
|
|
|
|
|
|
|
auto replacementIt
|
|
|
|
|
= std::find_if(ops.begin(), ops.end(), [utf8Offset](const Utils::ChangeSet::EditOp &op) {
|
|
|
|
|
QTC_ASSERT(op.type == Utils::ChangeSet::EditOp::Replace, return false);
|
|
|
|
|
return op.pos1 == utf8Offset - 1;
|
|
|
|
|
});
|
|
|
|
|
if (replacementIt == ops.end())
|
2019-02-19 14:30:52 +01:00
|
|
|
return -1;
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
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)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-19 14:30:52 +01:00
|
|
|
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;
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
int formattingRangeStart(const QTextBlock ¤tBlock,
|
|
|
|
|
const QByteArray &buffer,
|
|
|
|
|
int documentRevision)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-19 14:30:52 +01:00
|
|
|
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);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
2019-02-19 14:30:52 +01:00
|
|
|
} // namespace
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
ClangFormatBaseIndenter::ClangFormatBaseIndenter(QTextDocument *doc)
|
|
|
|
|
: TextEditor::Indenter(doc)
|
|
|
|
|
{}
|
|
|
|
|
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet ClangFormatBaseIndenter::replacements(QByteArray buffer,
|
|
|
|
|
const QTextBlock &startBlock,
|
|
|
|
|
const QTextBlock &endBlock,
|
|
|
|
|
int cursorPositionInEditor,
|
|
|
|
|
ReplacementsToKeep replacementsToKeep,
|
|
|
|
|
const QChar &typedChar,
|
|
|
|
|
bool secondTry) const
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2023-07-04 10:05:33 +02:00
|
|
|
QTC_ASSERT(replacementsToKeep != ReplacementsToKeep::All, return Utils::ChangeSet());
|
2022-12-16 10:31:20 +01:00
|
|
|
QTC_ASSERT(!m_fileName.isEmpty(), return {});
|
2019-02-19 14:30:52 +01:00
|
|
|
|
|
|
|
|
QByteArray originalBuffer = buffer;
|
|
|
|
|
int utf8Offset = Utils::Text::utf8NthLineOffset(m_doc, buffer, startBlock.blockNumber() + 1);
|
2023-07-04 10:05:33 +02:00
|
|
|
QTC_ASSERT(utf8Offset >= 0, return Utils::ChangeSet(););
|
2019-02-19 14:30:52 +01:00
|
|
|
int utf8Length = selectedLines(m_doc, startBlock, endBlock).toUtf8().size();
|
|
|
|
|
|
|
|
|
|
int rangeStart = 0;
|
|
|
|
|
if (replacementsToKeep == ReplacementsToKeep::IndentAndBefore)
|
|
|
|
|
rangeStart = formattingRangeStart(startBlock, buffer, lastSaveRevision());
|
|
|
|
|
|
2022-12-16 10:31:20 +01:00
|
|
|
clang::format::FormatStyle style = styleForFile();
|
2019-02-19 14:30:52 +01:00
|
|
|
adjustFormatStyleForLineBreak(style, replacementsToKeep);
|
2019-03-05 17:16:29 +01:00
|
|
|
if (replacementsToKeep == ReplacementsToKeep::OnlyIndent) {
|
2019-03-11 11:10:12 +01:00
|
|
|
CharacterContext currentCharContext = CharacterContext::Unknown;
|
2019-03-06 14:05:31 +01:00
|
|
|
// Iterate backwards to reuse the same dummy text for all empty lines.
|
|
|
|
|
for (int index = endBlock.blockNumber(); index >= startBlock.blockNumber(); --index) {
|
2019-02-19 14:30:52 +01:00
|
|
|
utf8Length += forceIndentWithExtraText(buffer,
|
2019-03-11 11:10:12 +01:00
|
|
|
currentCharContext,
|
2019-03-05 17:16:29 +01:00
|
|
|
m_doc->findBlockByNumber(index),
|
2019-02-19 14:30:52 +01:00
|
|
|
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;
|
2022-12-08 16:28:51 +01:00
|
|
|
clang::tooling::Replacements clangReplacements = clang::format::reformat(
|
|
|
|
|
style, buffer.data(), ranges, m_fileName.toFSPathString().toStdString(), &status);
|
2019-02-19 14:30:52 +01:00
|
|
|
|
|
|
|
|
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,
|
2019-02-20 08:43:29 +01:00
|
|
|
cursorPositionInEditor,
|
2019-02-19 14:30:52 +01:00
|
|
|
replacementsToKeep,
|
|
|
|
|
typedChar,
|
|
|
|
|
true);
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-04 10:05:33 +02:00
|
|
|
return convertReplacements(m_doc, buffer, filtered);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::EditOperations ClangFormatBaseIndenter::format(const TextEditor::RangesInLines &rangesInLines,
|
|
|
|
|
FormattingMode mode)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2023-06-30 15:16:28 +02:00
|
|
|
bool doFormatting = mode == FormattingMode::Forced || formatCodeInsteadOfIndent();
|
|
|
|
|
#ifdef WITH_TESTS
|
2023-07-26 13:31:30 +02:00
|
|
|
doFormatting = doFormatting || CppEditor::CppToolsSettings::cppCodeStyle()
|
2023-06-30 15:16:28 +02:00
|
|
|
->codeStyleSettings().forceFormatting;
|
|
|
|
|
#endif
|
|
|
|
|
if (!doFormatting)
|
|
|
|
|
return {};
|
|
|
|
|
|
2022-12-16 10:31:20 +01:00
|
|
|
QTC_ASSERT(!m_fileName.isEmpty(), return {});
|
2019-02-13 14:17:21 +01:00
|
|
|
if (rangesInLines.empty())
|
2023-07-04 10:05:33 +02:00
|
|
|
return {};
|
2019-02-13 14:17:21 +01:00
|
|
|
|
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);
|
2019-02-19 14:30:52 +01:00
|
|
|
int utf8RangeLength = m_doc->findBlockByNumber(range.endLine - 1).text().toUtf8().size();
|
2019-02-13 14:17:21 +01:00
|
|
|
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-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-03-06 12:47:45 +01:00
|
|
|
clang::format::FormatStyle style = styleForFile();
|
2022-12-08 16:28:51 +01:00
|
|
|
const std::string assumedFileName = m_fileName.toFSPathString().toStdString();
|
2019-03-06 12:47:45 +01:00
|
|
|
clang::tooling::Replacements clangReplacements = clang::format::sortIncludes(style,
|
|
|
|
|
buffer.data(),
|
|
|
|
|
ranges,
|
|
|
|
|
assumedFileName);
|
|
|
|
|
auto changedCode = clang::tooling::applyAllReplacements(buffer.data(), clangReplacements);
|
|
|
|
|
QTC_ASSERT(changedCode, {
|
|
|
|
|
qDebug() << QString::fromStdString(llvm::toString(changedCode.takeError()));
|
2023-07-04 10:05:33 +02:00
|
|
|
return {};
|
2019-03-06 12:47:45 +01:00
|
|
|
});
|
|
|
|
|
ranges = clang::tooling::calculateRangesAfterReplacements(clangReplacements, ranges);
|
|
|
|
|
|
2019-02-13 14:17:21 +01:00
|
|
|
clang::format::FormattingAttemptStatus status;
|
2022-12-08 16:28:51 +01:00
|
|
|
const clang::tooling::Replacements formatReplacements = clang::format::reformat(style,
|
|
|
|
|
*changedCode,
|
|
|
|
|
ranges,
|
|
|
|
|
assumedFileName,
|
|
|
|
|
&status);
|
2019-03-06 12:47:45 +01:00
|
|
|
clangReplacements = clangReplacements.merge(formatReplacements);
|
|
|
|
|
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet changeSet = convertReplacements(m_doc, buffer, clangReplacements);
|
|
|
|
|
const Utils::EditOperations editOperations = changeSet.operationList();
|
|
|
|
|
changeSet.apply(m_doc);
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2023-07-04 10:05:33 +02:00
|
|
|
return editOperations;
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet ClangFormatBaseIndenter::indentsFor(QTextBlock startBlock,
|
2020-01-08 10:43:13 +01:00
|
|
|
const QTextBlock &endBlock,
|
|
|
|
|
const QChar &typedChar,
|
2022-09-14 09:11:44 +02:00
|
|
|
int cursorPositionInEditor,
|
|
|
|
|
bool trimTrailingWhitespace)
|
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)) {
|
2023-07-04 10:05:33 +02:00
|
|
|
return Utils::ChangeSet();
|
2019-02-08 09:48:49 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
startBlock = reverseFindLastEmptyBlock(startBlock);
|
|
|
|
|
const int startBlockPosition = startBlock.position();
|
2022-09-14 09:11:44 +02:00
|
|
|
if (trimTrailingWhitespace && startBlockPosition > 0) {
|
2019-02-19 14:30:52 +01:00
|
|
|
trimRHSWhitespace(startBlock.previous());
|
|
|
|
|
if (cursorPositionInEditor >= 0)
|
|
|
|
|
cursorPositionInEditor += startBlock.position() - startBlockPosition;
|
2019-02-19 12:46:38 +01:00
|
|
|
}
|
|
|
|
|
|
2019-03-06 14:05:31 +01:00
|
|
|
const QByteArray buffer = m_doc->toPlainText().toUtf8();
|
|
|
|
|
|
2019-02-18 15:56:50 +01:00
|
|
|
ReplacementsToKeep replacementsToKeep = ReplacementsToKeep::OnlyIndent;
|
2022-06-28 16:17:02 +02:00
|
|
|
if (formatWhileTyping()
|
2022-06-29 09:59:58 +00:00
|
|
|
&& (cursorPositionInEditor == -1 || cursorPositionInEditor >= startBlockPosition)
|
|
|
|
|
&& (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 condition matches automatically.
|
|
|
|
|
|
|
|
|
|
// Format only before complete statement not to break code.
|
|
|
|
|
replacementsToKeep = ReplacementsToKeep::IndentAndBefore;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
return replacements(buffer,
|
|
|
|
|
startBlock,
|
|
|
|
|
endBlock,
|
2019-02-20 08:43:29 +01:00
|
|
|
cursorPositionInEditor,
|
2019-02-19 14:30:52 +01:00
|
|
|
replacementsToKeep,
|
|
|
|
|
typedChar);
|
|
|
|
|
}
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
void ClangFormatBaseIndenter::indentBlocks(const QTextBlock &startBlock,
|
|
|
|
|
const QTextBlock &endBlock,
|
|
|
|
|
const QChar &typedChar,
|
|
|
|
|
int cursorPositionInEditor)
|
|
|
|
|
{
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet changeset = indentsFor(startBlock, endBlock, typedChar, cursorPositionInEditor);
|
|
|
|
|
changeset.apply(m_doc);
|
2019-02-19 14:30:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangFormatBaseIndenter::indent(const QTextCursor &cursor,
|
|
|
|
|
const QChar &typedChar,
|
|
|
|
|
int cursorPositionInEditor)
|
|
|
|
|
{
|
|
|
|
|
if (cursor.hasSelection()) {
|
|
|
|
|
indentBlocks(m_doc->findBlock(cursor.selectionStart()),
|
|
|
|
|
m_doc->findBlock(cursor.selectionEnd()),
|
|
|
|
|
typedChar,
|
|
|
|
|
cursorPositionInEditor);
|
|
|
|
|
} else {
|
|
|
|
|
indentBlocks(cursor.block(), cursor.block(), typedChar, cursorPositionInEditor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangFormatBaseIndenter::indent(const QTextCursor &cursor,
|
|
|
|
|
const QChar &typedChar,
|
|
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
|
|
|
|
{
|
|
|
|
|
indent(cursor, typedChar, cursorPositionInEditor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangFormatBaseIndenter::reindent(const QTextCursor &cursor,
|
|
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
|
|
|
|
{
|
|
|
|
|
indent(cursor, QChar::Null, cursorPositionInEditor);
|
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-19 14:30:52 +01:00
|
|
|
int ClangFormatBaseIndenter::indentFor(const QTextBlock &block,
|
2022-09-20 10:58:18 +02:00
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
2019-02-19 14:30:52 +01:00
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet toReplace
|
|
|
|
|
= indentsFor(block, block, QChar::Null, cursorPositionInEditor, false);
|
|
|
|
|
if (toReplace.isEmpty())
|
2019-01-16 09:37:54 +01:00
|
|
|
return -1;
|
|
|
|
|
|
2019-03-06 14:05:31 +01:00
|
|
|
const QByteArray buffer = m_doc->toPlainText().toUtf8();
|
2019-02-19 14:30:52 +01:00
|
|
|
return indentationForBlock(toReplace, buffer, block);
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
2019-02-19 14:30:52 +01:00
|
|
|
TextEditor::IndentationForBlock ClangFormatBaseIndenter::indentationForBlocks(
|
|
|
|
|
const QVector<QTextBlock> &blocks,
|
|
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
int cursorPositionInEditor)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2019-02-19 14:30:52 +01:00
|
|
|
TextEditor::IndentationForBlock ret;
|
|
|
|
|
if (blocks.isEmpty())
|
|
|
|
|
return ret;
|
2023-07-04 10:05:33 +02:00
|
|
|
Utils::ChangeSet toReplace = indentsFor(blocks.front(),
|
2020-01-08 10:43:13 +01:00
|
|
|
blocks.back(),
|
|
|
|
|
QChar::Null,
|
|
|
|
|
cursorPositionInEditor);
|
2019-02-19 14:30:52 +01:00
|
|
|
|
2019-03-06 14:05:31 +01:00
|
|
|
const QByteArray buffer = m_doc->toPlainText().toUtf8();
|
2019-02-19 14:30:52 +01:00
|
|
|
for (const QTextBlock &block : blocks)
|
|
|
|
|
ret.insert(block.blockNumber(), indentationForBlock(toReplace, buffer, block));
|
|
|
|
|
return ret;
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-26 10:30:00 +02:00
|
|
|
std::optional<int> ClangFormat::ClangFormatBaseIndenter::margin() const
|
2021-01-24 17:11:02 +01:00
|
|
|
{
|
|
|
|
|
return styleForFile().ColumnLimit;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-12 12:43:16 +01:00
|
|
|
void ClangFormatBaseIndenter::autoIndent(const QTextCursor &cursor,
|
|
|
|
|
const TextEditor::TabSettings & /*tabSettings*/,
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2023-01-10 14:29:49 +01:00
|
|
|
clang::format::FormatStyle overrideStyle(const Utils::FilePath &fileName)
|
2019-01-16 09:37:54 +01:00
|
|
|
{
|
2023-01-10 14:29:49 +01:00
|
|
|
const ProjectExplorer::Project *projectForFile
|
2023-02-14 15:47:22 +01:00
|
|
|
= ProjectExplorer::ProjectManager::projectForFile(fileName);
|
2023-01-10 14:29:49 +01:00
|
|
|
|
2022-05-06 12:18:28 +02:00
|
|
|
const TextEditor::ICodeStylePreferences *preferences
|
|
|
|
|
= projectForFile
|
|
|
|
|
? projectForFile->editorConfiguration()->codeStyle("Cpp")->currentPreferences()
|
|
|
|
|
: TextEditor::TextEditorSettings::codeStyle("Cpp")->currentPreferences();
|
|
|
|
|
|
2022-12-13 08:42:07 +01:00
|
|
|
Utils::FilePath filePath = filePathToCurrentSettings(preferences);
|
2022-05-06 12:18:28 +02:00
|
|
|
|
2022-12-13 08:42:07 +01:00
|
|
|
if (!filePath.exists())
|
2023-06-07 16:17:29 +02:00
|
|
|
return currentQtStyle(preferences);
|
2022-05-06 12:18:28 +02:00
|
|
|
|
2022-12-13 08:42:07 +01:00
|
|
|
clang::format::FormatStyle currentSettingsStyle;
|
|
|
|
|
currentSettingsStyle.Language = clang::format::FormatStyle::LK_Cpp;
|
|
|
|
|
const std::error_code error = clang::format::parseConfiguration(filePath.fileContents()
|
|
|
|
|
.value_or(QByteArray())
|
|
|
|
|
.toStdString(),
|
|
|
|
|
¤tSettingsStyle);
|
|
|
|
|
QTC_ASSERT(error.value() == static_cast<int>(clang::format::ParseError::Success),
|
2023-06-07 16:17:29 +02:00
|
|
|
return currentQtStyle(preferences));
|
2022-05-06 12:18:28 +02:00
|
|
|
|
2022-12-13 08:42:07 +01:00
|
|
|
return currentSettingsStyle;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-12 11:57:26 +02:00
|
|
|
static std::chrono::milliseconds getCacheTimeout()
|
2022-12-13 08:42:07 +01:00
|
|
|
{
|
2023-05-12 11:57:26 +02:00
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
bool ok = false;
|
|
|
|
|
const int envCacheTimeout = qEnvironmentVariableIntValue("CLANG_FORMAT_CACHE_TIMEOUT", &ok);
|
|
|
|
|
return ok ? std::chrono::milliseconds(envCacheTimeout) : 1s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const clang::format::FormatStyle &ClangFormatBaseIndenter::styleForFile() const
|
|
|
|
|
{
|
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
|
static const std::chrono::milliseconds cacheTimeout = getCacheTimeout();
|
|
|
|
|
|
|
|
|
|
QDateTime time = QDateTime::currentDateTime();
|
|
|
|
|
if (m_cachedStyle.expirationTime > time && !(m_cachedStyle.style == clang::format::getNoStyle()))
|
|
|
|
|
return m_cachedStyle.style;
|
|
|
|
|
|
|
|
|
|
if (getCurrentOverriddenSettings(m_fileName)) {
|
|
|
|
|
clang::format::FormatStyle style = overrideStyle(m_fileName);
|
|
|
|
|
m_cachedStyle.setCache(style, cacheTimeout);
|
|
|
|
|
return m_cachedStyle.style;
|
|
|
|
|
}
|
2022-12-13 08:42:07 +01:00
|
|
|
|
|
|
|
|
llvm::Expected<clang::format::FormatStyle> styleFromProjectFolder
|
|
|
|
|
= clang::format::getStyle("file",
|
|
|
|
|
m_fileName.toFSPathString().toStdString(),
|
|
|
|
|
"none",
|
|
|
|
|
"",
|
|
|
|
|
&llvmFileSystemAdapter);
|
2022-04-08 15:12:30 +02:00
|
|
|
|
2023-01-09 13:04:24 +01:00
|
|
|
if (styleFromProjectFolder && !(*styleFromProjectFolder == clang::format::getNoStyle())) {
|
2022-05-06 12:18:28 +02:00
|
|
|
addQtcStatementMacros(*styleFromProjectFolder);
|
2023-05-12 11:57:26 +02:00
|
|
|
m_cachedStyle.setCache(*styleFromProjectFolder, cacheTimeout);
|
|
|
|
|
return m_cachedStyle.style;
|
2022-01-11 13:18:29 +01:00
|
|
|
}
|
2019-01-16 09:37:54 +01:00
|
|
|
|
2022-05-06 12:18:28 +02:00
|
|
|
handleAllErrors(styleFromProjectFolder.takeError(), [](const llvm::ErrorInfoBase &) {
|
2019-01-16 09:37:54 +01:00
|
|
|
// do nothing
|
|
|
|
|
});
|
|
|
|
|
|
2023-05-12 11:57:26 +02:00
|
|
|
|
|
|
|
|
m_cachedStyle.setCache(qtcStyle(), 0ms);
|
|
|
|
|
return m_cachedStyle.style;
|
2019-01-16 09:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace ClangFormat
|