diff --git a/src/libs/utils/textutils.cpp b/src/libs/utils/textutils.cpp index 192c68bcbd3..c05e6cce84e 100644 --- a/src/libs/utils/textutils.cpp +++ b/src/libs/utils/textutils.cpp @@ -140,5 +140,20 @@ QTextCursor wordStartCursor(const QTextCursor &textCursor) return cursor; } +int utf8NthLineOffset(const QTextDocument *textDocument, const QByteArray &buffer, int line) +{ + if (textDocument->characterCount() == buffer.size() + 1) + return textDocument->findBlockByNumber(line - 1).position(); + + int pos = 0; + for (int count = 0; count < line - 1; ++count) { + pos = buffer.indexOf('\n', pos); + if (pos == -1) + return -1; + ++pos; + } + return pos; +} + } // Text } // Utils diff --git a/src/libs/utils/textutils.h b/src/libs/utils/textutils.h index d5414e31949..01d765e7867 100644 --- a/src/libs/utils/textutils.h +++ b/src/libs/utils/textutils.h @@ -55,6 +55,10 @@ QTCREATOR_UTILS_EXPORT QTextCursor flippedCursor(const QTextCursor &cursor); QTCREATOR_UTILS_EXPORT QTextCursor wordStartCursor(const QTextCursor &cursor); +QTCREATOR_UTILS_EXPORT int utf8NthLineOffset(const QTextDocument *textDocument, + const QByteArray &buffer, + int line); + template void moveToPrevChar(CharacterProvider &provider, QTextCursor &cursor) { diff --git a/src/plugins/clangformat/clangformat.pro b/src/plugins/clangformat/clangformat.pro index c2e2edd49f5..74331b9e5f6 100644 --- a/src/plugins/clangformat/clangformat.pro +++ b/src/plugins/clangformat/clangformat.pro @@ -24,7 +24,8 @@ HEADERS = \ clangformatconfigwidget.h \ clangformatindenter.h \ clangformatplugin.h \ - clangformatconstants.h + clangformatconstants.h \ + clangformatutils.h FORMS += \ clangformatconfigwidget.ui diff --git a/src/plugins/clangformat/clangformat.qbs b/src/plugins/clangformat/clangformat.qbs index f470951e9d1..37bb4c1f647 100644 --- a/src/plugins/clangformat/clangformat.qbs +++ b/src/plugins/clangformat/clangformat.qbs @@ -27,10 +27,11 @@ QtcPlugin { "clangformatconfigwidget.cpp", "clangformatconfigwidget.h", "clangformatconfigwidget.ui", + "clangformatconstants.h", "clangformatindenter.cpp", "clangformatindenter.h", "clangformatplugin.cpp", "clangformatplugin.h", - "clangformatconstants.h", + "clangformatutils.h", ] } diff --git a/src/plugins/clangformat/clangformatconfigwidget.cpp b/src/plugins/clangformat/clangformatconfigwidget.cpp index 60d58909e3a..d2daa3adf7a 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.cpp +++ b/src/plugins/clangformat/clangformatconfigwidget.cpp @@ -25,6 +25,8 @@ #include "clangformatconfigwidget.h" + +#include "clangformatutils.h" #include "ui_clangformatconfigwidget.h" #include @@ -40,23 +42,6 @@ using namespace ProjectExplorer; namespace ClangFormat { -namespace Internal { - -static void createGlobalClangFormatFileIfNeeded(const QString &settingsDir) -{ - const QString fileName = settingsDir + "/.clang-format"; - if (QFile::exists(fileName)) - return; - - QFile file(fileName); - if (!file.open(QFile::WriteOnly)) - return; - - const clang::format::FormatStyle defaultStyle = clang::format::getLLVMStyle(); - const std::string configuration = clang::format::configurationAsText(defaultStyle); - file.write(configuration.c_str()); - file.close(); -} static void readTable(QTableWidget *table, std::istringstream &stream) { @@ -137,50 +122,75 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje { m_ui->setupUi(this); - std::string testFilePath; + initialize(); +} + +void ClangFormatConfigWidget::initialize() +{ + m_ui->projectHasClangFormat->show(); + m_ui->clangFormatOptionsTable->show(); + m_ui->applyButton->show(); + if (m_project && !m_project->projectDirectory().appendPath(".clang-format").exists()) { - m_ui->projectHasClangFormat->setText("No .clang-format file for the project"); + m_ui->projectHasClangFormat->setText(tr("No .clang-format file for the project.")); m_ui->clangFormatOptionsTable->hide(); m_ui->applyButton->hide(); + + connect(m_ui->createFileButton, &QPushButton::clicked, + this, [this]() { + createStyleFileIfNeeded(m_project->projectDirectory()); + initialize(); + }); return; } + m_ui->createFileButton->hide(); + + std::string testFilePath; if (m_project) { + m_ui->projectHasClangFormat->hide(); testFilePath = m_project->projectDirectory().appendPath("t.cpp").toString().toStdString(); connect(m_ui->applyButton, &QPushButton::clicked, this, &ClangFormatConfigWidget::apply); } else { + const Project *currentProject = SessionManager::startupProject(); + if (!currentProject + || !currentProject->projectDirectory().appendPath(".clang-format").exists()) { + m_ui->projectHasClangFormat->hide(); + } else { + m_ui->projectHasClangFormat->setText( + tr(" Current project has its own .clang-format file " + "and can be configured in Projects > Clang Format.")); + } const QString settingsDir = Core::ICore::userResourcePath(); - createGlobalClangFormatFileIfNeeded(settingsDir); + createStyleFileIfNeeded(Utils::FileName::fromString(settingsDir)); testFilePath = settingsDir.toStdString() + "/t.cpp"; m_ui->applyButton->hide(); } + fillTable(testFilePath); +} + +void ClangFormatConfigWidget::fillTable(const std::string &testFilePath) +{ llvm::Expected formatStyle = - clang::format::getStyle("file", testFilePath, "LLVM", ""); - if (!formatStyle) - return; - - const std::string configText = clang::format::configurationAsText(*formatStyle); - std::istringstream stream(configText); - - readTable(m_ui->clangFormatOptionsTable, stream); - - if (m_project) { - m_ui->projectHasClangFormat->hide(); - return; + clang::format::getStyle("file", testFilePath, "LLVM"); + std::string configText; + bool brokenConfig = false; + if (!formatStyle) { + handleAllErrors(formatStyle.takeError(), [](const llvm::ErrorInfoBase &) { + // do nothing + }); + configText = clang::format::configurationAsText(clang::format::getLLVMStyle()); + brokenConfig = true; + } else { + configText = clang::format::configurationAsText(*formatStyle); } - const Project *currentProject = SessionManager::startupProject(); - if (!currentProject || !currentProject->projectDirectory().appendPath(".clang-format").exists()) - m_ui->projectHasClangFormat->hide(); + std::istringstream stream(configText); + readTable(m_ui->clangFormatOptionsTable, stream); + if (brokenConfig) + apply(); - connect(SessionManager::instance(), &SessionManager::startupProjectChanged, - this, [this](ProjectExplorer::Project *project) { - if (project && project->projectDirectory().appendPath(".clang-format").exists()) - m_ui->projectHasClangFormat->show(); - else - m_ui->projectHasClangFormat->hide(); - }); } ClangFormatConfigWidget::~ClangFormatConfigWidget() = default; @@ -201,5 +211,4 @@ void ClangFormatConfigWidget::apply() file.close(); } -} // namespace Internal } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatconfigwidget.h b/src/plugins/clangformat/clangformatconfigwidget.h index 004e97128c0..5694bd820b7 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.h +++ b/src/plugins/clangformat/clangformatconfigwidget.h @@ -32,7 +32,6 @@ namespace ProjectExplorer { class Project; } namespace ClangFormat { -namespace Internal { namespace Ui { class ClangFormatConfigWidget; @@ -49,9 +48,11 @@ public: void apply(); private: + void initialize(); + void fillTable(const std::string &testFilePath); + ProjectExplorer::Project *m_project; std::unique_ptr m_ui; }; -} // namespace Internal } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatconfigwidget.ui b/src/plugins/clangformat/clangformatconfigwidget.ui index e3efbea1098..41774e133d0 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.ui +++ b/src/plugins/clangformat/clangformatconfigwidget.ui @@ -1,7 +1,7 @@ - ClangFormat::Internal::ClangFormatConfigWidget - + ClangFormat::ClangFormatConfigWidget + 0 @@ -29,7 +29,7 @@ - Current project has its own .clang-format file and can be configured in Projects -> ClangFormat. + @@ -38,6 +38,13 @@ + + + + Create Clang Format Configuration File + + + diff --git a/src/plugins/clangformat/clangformatindenter.cpp b/src/plugins/clangformat/clangformatindenter.cpp index 3164f4ad003..f6cc910be84 100644 --- a/src/plugins/clangformat/clangformatindenter.cpp +++ b/src/plugins/clangformat/clangformatindenter.cpp @@ -25,16 +25,21 @@ #include "clangformatindenter.h" +#include "clangformatutils.h" + #include #include #include +#include #include #include #include #include #include +#include +#include #include @@ -42,6 +47,8 @@ #include #include +#include + using namespace clang; using namespace format; using namespace llvm; @@ -50,67 +57,35 @@ using namespace ProjectExplorer; using namespace TextEditor; namespace ClangFormat { -namespace Internal { namespace { -void adjustFormatStyleForLineBreak(format::FormatStyle &style, - int length, - int prevBlockSize, - bool prevBlockEndsWithPunctuation) +void adjustFormatStyleForLineBreak(format::FormatStyle &style) { - if (length > 0) - style.ColumnLimit = prevBlockSize; - style.AlwaysBreakBeforeMultilineStrings = true; -#if LLVM_VERSION_MAJOR >= 7 - style.AlwaysBreakTemplateDeclarations = FormatStyle::BTDS_Yes; -#else - style.AlwaysBreakTemplateDeclarations = true; + style.DisableFormat = false; + style.ColumnLimit = 0; +#ifdef KEEP_LINE_BREAKS_FOR_NON_EMPTY_LINES_BACKPORTED + style.KeepLineBreaksForNonEmptyLines = true; #endif - - style.AllowAllParametersOfDeclarationOnNextLine = true; - style.AllowShortBlocksOnASingleLine = true; - style.AllowShortCaseLabelsOnASingleLine = true; - style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Empty; - style.AllowShortIfStatementsOnASingleLine = true; - style.AllowShortLoopsOnASingleLine = true; - if (prevBlockEndsWithPunctuation) { - style.BreakBeforeBinaryOperators = FormatStyle::BOS_None; - style.BreakBeforeTernaryOperators = false; - style.BreakConstructorInitializers = FormatStyle::BCIS_AfterColon; - } else { - style.BreakBeforeBinaryOperators = FormatStyle::BOS_All; - style.BreakBeforeTernaryOperators = true; - style.BreakConstructorInitializers = FormatStyle::BCIS_BeforeComma; - } + style.MaxEmptyLinesToKeep = 2; } Replacements filteredReplacements(const Replacements &replacements, - unsigned int offset, - unsigned int lengthForFilter, - int extraOffsetToAdd, - int prevBlockLength) + int offset, + int lengthForFilter, + int extraOffsetToAdd) { Replacements filtered; for (const Replacement &replacement : replacements) { - unsigned int replacementOffset = replacement.getOffset(); + int replacementOffset = static_cast(replacement.getOffset()); if (replacementOffset > offset + lengthForFilter) break; - if (offset > static_cast(prevBlockLength) - && replacementOffset < offset - static_cast(prevBlockLength)) - continue; - - if (lengthForFilter == 0 && replacement.getReplacementText().find('\n') == std::string::npos - && filtered.empty()) { - continue; - } - if (replacementOffset + 1 >= offset) - replacementOffset += static_cast(extraOffsetToAdd); + replacementOffset += extraOffsetToAdd; Error error = filtered.add(Replacement(replacement.getFilePath(), - replacementOffset, + static_cast(replacementOffset), replacement.getLength(), replacement.getReplacementText())); // Throws if error is not checked. @@ -120,147 +95,58 @@ Replacements filteredReplacements(const Replacements &replacements, return filtered; } -std::string assumedFilePath() +Utils::FileName styleConfigPath() { const Project *project = SessionManager::startupProject(); if (project && project->projectDirectory().appendPath(".clang-format").exists()) - return project->projectDirectory().appendPath("test.cpp").toString().toStdString(); + return project->projectDirectory(); - return QString(Core::ICore::userResourcePath() + "/test.cpp").toStdString(); + return Utils::FileName::fromString(Core::ICore::userResourcePath()); } -FormatStyle formatStyle() +FormatStyle formatStyle(Utils::FileName styleConfigPath) { - Expected style = format::getStyle("file", assumedFilePath(), "none", ""); + createStyleFileIfNeeded(styleConfigPath); + + Expected style = format::getStyle( + "file", styleConfigPath.appendPath("test.cpp").toString().toStdString(), "LLVM"); if (style) return *style; - return FormatStyle(); + + handleAllErrors(style.takeError(), [](const ErrorInfoBase &) { + // do nothing + }); + + return format::getLLVMStyle(); } -Replacements replacements(const std::string &buffer, - unsigned int offset, - unsigned int length, - bool blockFormatting = false, - const QChar &typedChar = QChar::Null, - int extraOffsetToAdd = 0, - int prevBlockLength = 1, - bool prevBlockEndsWithPunctuation = false) -{ - FormatStyle style = formatStyle(); - - if (blockFormatting && typedChar == QChar::Null) - adjustFormatStyleForLineBreak(style, length, prevBlockLength, prevBlockEndsWithPunctuation); - - std::vector ranges{{offset, length}}; - FormattingAttemptStatus status; - - Replacements replacements = reformat(style, buffer, ranges, assumedFilePath(), &status); - - if (!status.FormatComplete) - Replacements(); - - unsigned int lengthForFilter = 0; - if (!blockFormatting) - lengthForFilter = length; - - return filteredReplacements(replacements, - offset, - lengthForFilter, - extraOffsetToAdd, - prevBlockLength); -} - -void applyReplacements(QTextDocument *doc, - const std::string &stdStrBuffer, - const tooling::Replacements &replacements, - int totalShift) -{ - if (replacements.empty()) - return; - - QTextCursor editCursor(doc); - int fullOffsetDiff = 0; - for (const Replacement &replacement : replacements) { - const int utf16Offset - = QString::fromStdString(stdStrBuffer.substr(0, replacement.getOffset())).length() - + totalShift + fullOffsetDiff; - const int utf16Length = QString::fromStdString(stdStrBuffer.substr(replacement.getOffset(), - replacement.getLength())) - .length(); - const QString replacementText = QString::fromStdString(replacement.getReplacementText()); - - editCursor.beginEditBlock(); - editCursor.setPosition(utf16Offset); - editCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, utf16Length); - editCursor.removeSelectedText(); - editCursor.insertText(replacementText); - editCursor.endEditBlock(); - fullOffsetDiff += replacementText.length() - utf16Length; - } -} - -// Returns offset shift. -int modifyToIndentEmptyLines(QString &buffer, int &offset, int &length, const QTextBlock &block) -{ - //This extra text works for the most cases. - QString extraText("a;"); - - const QString blockText = block.text().trimmed(); - // Search for previous character - QTextBlock prevBlock = block.previous(); - while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty()) - prevBlock = prevBlock.previous(); - if (prevBlock.text().endsWith(',')) - extraText = "int a,"; - - const bool closingParenBlock = blockText.startsWith(')'); - if (closingParenBlock) { - if (prevBlock.text().endsWith(',')) - extraText = "int a"; - else - extraText = "&& a"; - } - - if (length == 0 || closingParenBlock) { - length += extraText.length(); - buffer.insert(offset, extraText); - } - - if (blockText.startsWith('}')) { - buffer.insert(offset - 1, extraText); - offset += extraText.size(); - return extraText.size(); - } - - return 0; -} - -// Returns first non-empty block (searches from current block backwards). -QTextBlock clearFirstNonEmptyBlockFromExtraSpaces(const QTextBlock ¤tBlock) +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 prevBlock; + return; const QString initialText = prevBlock.text(); - if (!initialText.at(initialText.length() - 1).isSpace()) - return prevBlock; + if (!initialText.at(initialText.size() - 1).isSpace()) + return; + + int extraSpaceCount = 1; + for (int i = initialText.size() - 2; i >= 0; --i) { + if (!initialText.at(i).isSpace()) + break; + ++extraSpaceCount; + } QTextCursor cursor(prevBlock); cursor.beginEditBlock(); - cursor.movePosition(QTextCursor::EndOfBlock); - cursor.movePosition(QTextCursor::Left); - - while (cursor.positionInBlock() >= 0 && initialText.at(cursor.positionInBlock()).isSpace()) - cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); - if (cursor.hasSelection()) - cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, + initialText.size() - extraSpaceCount); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, extraSpaceCount); cursor.removeSelectedText(); cursor.endEditBlock(); - return prevBlock; } // Returns the total langth of previous lines with pure whitespace. @@ -276,42 +162,175 @@ int previousEmptyLinesLength(const QTextBlock ¤tBlock) return length; } -static constexpr const int MinCharactersBeforeCurrentInBuffer = 200; -static constexpr const int MaxCharactersBeforeCurrentInBuffer = 500; - -int startOfIndentationBuffer(const QString &buffer, int start) +void modifyToIndentEmptyLines(QByteArray &buffer, int &offset, int &length, const QTextBlock &block) { - if (start < MaxCharactersBeforeCurrentInBuffer) - return 0; + const QString blockText = block.text().trimmed(); + const bool closingParenBlock = blockText.startsWith(')'); + if (length != 0 && !closingParenBlock) + return; - auto it = buffer.cbegin() + (start - MinCharactersBeforeCurrentInBuffer); - for (; it != buffer.cbegin() + (start - MaxCharactersBeforeCurrentInBuffer); --it) { - if (*it == '{') { - // Find the start of it's line. - for (auto inner = it; - inner != buffer.cbegin() + (start - MaxCharactersBeforeCurrentInBuffer); - --inner) { - if (*inner == '\n') - return inner + 1 - buffer.cbegin(); - } - break; - } + //This extra text works for the most cases. + QByteArray extraText("a;"); + + // Search for previous character + QTextBlock prevBlock = block.previous(); + while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty()) + prevBlock = prevBlock.previous(); + if (prevBlock.text().endsWith(',')) + extraText = "int a,"; + + if (closingParenBlock) { + if (prevBlock.text().endsWith(',')) + extraText = "int a"; + else + extraText = "&& a"; } - return it - buffer.cbegin(); + length += extraText.length(); + buffer.insert(offset, extraText); } -int nextEndingScopePosition(const QString &buffer, int start) -{ - if (start >= buffer.size() - 1) - return buffer.size() - 1; +static const int kMaxLinesFromCurrentBlock = 200; - for (auto it = buffer.cbegin() + (start + 1); it != buffer.cend(); ++it) { - if (*it == '}') - return it - buffer.cbegin(); +Replacements replacements(QByteArray buffer, + int utf8Offset, + int utf8Length, + const QTextBlock *block = nullptr, + const QChar &typedChar = QChar::Null) +{ + Utils::FileName stylePath = styleConfigPath(); + FormatStyle style = formatStyle(stylePath); + + int extraOffset = 0; + if (block) { + if (block->blockNumber() > kMaxLinesFromCurrentBlock) { + extraOffset = Utils::Text::utf8NthLineOffset( + block->document(), buffer, block->blockNumber() - kMaxLinesFromCurrentBlock); + } + buffer = buffer.mid(extraOffset, + std::min(buffer.size(), utf8Offset + kMaxLinesFromCurrentBlock) + - extraOffset); + utf8Offset -= extraOffset; + + const int emptySpaceLength = previousEmptyLinesLength(*block); + utf8Offset -= emptySpaceLength; + buffer.remove(utf8Offset, emptySpaceLength); + + extraOffset += emptySpaceLength; + + adjustFormatStyleForLineBreak(style); + if (typedChar == QChar::Null) + modifyToIndentEmptyLines(buffer, utf8Offset, utf8Length, *block); } - return buffer.size() - 1; + std::vector ranges{{static_cast(utf8Offset), + static_cast(utf8Length)}}; + FormattingAttemptStatus status; + + const std::string assumedFilePath + = stylePath.appendPath("test.cpp").toString().toStdString(); + Replacements replacements = reformat(style, buffer.data(), ranges, assumedFilePath, &status); + + if (!status.FormatComplete) + Replacements(); + + int lengthForFilter = 0; + if (block == nullptr) + lengthForFilter = utf8Length; + + return filteredReplacements(replacements, + utf8Offset, + lengthForFilter, + extraOffset); +} + +Utils::LineColumn utf16LineColumn(const QTextBlock &block, + int blockOffsetUtf8, + const QByteArray &utf8Buffer, + int utf8Offset) +{ + if (utf8Offset < blockOffsetUtf8 - 1) + return Utils::LineColumn(); + + if (utf8Offset == blockOffsetUtf8 - 1) { + const int lineStart = utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1; + const QByteArray lineText = utf8Buffer.mid(lineStart, utf8Offset - lineStart); + return Utils::LineColumn(block.blockNumber(), QString::fromUtf8(lineText).size() + 1); + } + + int pos = blockOffsetUtf8; + int prevPos = pos; + int line = block.blockNumber(); // Start with previous line. + while (pos != -1 && pos <= utf8Offset) { + // Find the first pos which comes after offset and take the previous line. + ++line; + prevPos = pos; + pos = utf8Buffer.indexOf('\n', pos); + if (pos != -1) + ++pos; + } + + const QByteArray lineText = utf8Buffer.mid(prevPos, utf8Offset - prevPos); + return Utils::LineColumn(line, QString::fromUtf8(lineText).size() + 1); +} + +tooling::Replacements utf16Replacements(const QTextBlock &block, + int blockOffsetUtf8, + const QByteArray &utf8Buffer, + const tooling::Replacements &replacements) +{ + tooling::Replacements convertedReplacements; + for (const Replacement &replacement : replacements) { + const Utils::LineColumn lineColUtf16 = utf16LineColumn( + block, blockOffsetUtf8, utf8Buffer, static_cast(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(replacement.getOffset()), + static_cast(replacement.getLength()))).size(); + Error error = convertedReplacements.add( + Replacement(replacement.getFilePath(), + static_cast(utf16Offset), + static_cast(utf16Length), + replacement.getReplacementText())); + // Throws if error is not checked. + if (error) + break; + } + + return convertedReplacements; +} + +void applyReplacements(const QTextBlock &block, + int blockOffsetUtf8, + const QByteArray &utf8Buffer, + const tooling::Replacements &replacements) +{ + if (replacements.empty()) + return; + + tooling::Replacements convertedReplacements = utf16Replacements(block, + blockOffsetUtf8, + utf8Buffer, + replacements); + + int fullOffsetShift = 0; + QTextCursor editCursor(block); + for (const Replacement &replacement : convertedReplacements) { + const QString replacementString = QString::fromStdString(replacement.getReplacementText()); + const int replacementLength = static_cast(replacement.getLength()); + editCursor.beginEditBlock(); + editCursor.setPosition(static_cast(replacement.getOffset()) + fullOffsetShift); + editCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, + replacementLength); + editCursor.removeSelectedText(); + editCursor.insertText(replacementString); + editCursor.endEditBlock(); + fullOffsetShift += replacementString.length() - replacementLength; + } } } // anonymous namespace @@ -342,30 +361,34 @@ void ClangFormatIndenter::indent(QTextDocument *doc, bool autoTriggered) { if (typedChar == QChar::Null && (cursor.hasSelection() || !autoTriggered)) { - TextEditorWidget *editor = TextEditorWidget::currentTextEditorWidget(); - int offset; - int length; + int utf8Offset; + int utf8Length; + const QByteArray buffer = doc->toPlainText().toUtf8(); if (cursor.hasSelection()) { const QTextBlock start = doc->findBlock(cursor.selectionStart()); const QTextBlock end = doc->findBlock(cursor.selectionEnd()); - offset = start.position(); - length = std::max(0, end.position() + end.length() - start.position() - 1); + utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, start.blockNumber() + 1); + QTC_ASSERT(utf8Offset >= 0, return;); + utf8Length = + Utils::Text::textAt( + QTextCursor(doc), + start.position(), + std::max(0, end.position() + end.length() - start.position() - 1)) + .toUtf8().size(); + applyReplacements(start, + utf8Offset, + buffer, + replacements(buffer, utf8Offset, utf8Length)); } else { const QTextBlock block = cursor.block(); - offset = block.position(); - length = std::max(0, block.length() - 1); + utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, block.blockNumber() + 1); + QTC_ASSERT(utf8Offset >= 0, return;); + utf8Length = block.text().toUtf8().size(); + applyReplacements(block, + utf8Offset, + buffer, + replacements(buffer, utf8Offset, utf8Length)); } - QString buffer = editor->toPlainText(); - const int totalShift = startOfIndentationBuffer(buffer, offset); - const int cutAtPos = nextEndingScopePosition(buffer, offset + length) + 1; - buffer = buffer.mid(totalShift, cutAtPos - totalShift); - offset -= totalShift; - const std::string stdStrBefore = buffer.left(offset).toStdString(); - const std::string stdStrBuffer = stdStrBefore + buffer.mid(offset).toStdString(); - applyReplacements(doc, - stdStrBuffer, - replacements(stdStrBuffer, stdStrBefore.length(), length), - totalShift); } else { indentBlock(doc, cursor.block(), typedChar, tabSettings); } @@ -389,46 +412,16 @@ void ClangFormatIndenter::indentBlock(QTextDocument *doc, if (!editor) return; - const QTextBlock prevBlock = clearFirstNonEmptyBlockFromExtraSpaces(block); + trimFirstNonEmptyBlock(block); + const QByteArray buffer = doc->toPlainText().toUtf8(); + const int utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, block.blockNumber() + 1); + QTC_ASSERT(utf8Offset >= 0, return;); + const int utf8Length = block.text().toUtf8().size(); - int offset = block.position(); - int length = std::max(0, block.length() - 1); - QString buffer = editor->toPlainText(); - - int emptySpaceLength = previousEmptyLinesLength(block); - offset -= emptySpaceLength; - buffer.remove(offset, emptySpaceLength); - - int extraPrevBlockLength{0}; - int prevBlockTextLength{1}; - bool prevBlockEndsWithPunctuation = false; - if (typedChar == QChar::Null) { - extraPrevBlockLength = modifyToIndentEmptyLines(buffer, offset, length, block); - - const QString prevBlockText = prevBlock.text(); - prevBlockEndsWithPunctuation = prevBlockText.size() > 0 - ? prevBlockText.at(prevBlockText.size() - 1).isPunct() - : false; - prevBlockTextLength = prevBlockText.size() + 1; - } - - const int totalShift = startOfIndentationBuffer(buffer, offset); - const int cutAtPos = nextEndingScopePosition(buffer, offset + length) + 1; - buffer = buffer.mid(totalShift, cutAtPos - totalShift); - offset -= totalShift; - const std::string stdStrBefore = buffer.left(offset).toStdString(); - const std::string stdStrBuffer = stdStrBefore + buffer.mid(offset).toStdString(); - applyReplacements(doc, - stdStrBuffer, - replacements(stdStrBuffer, - stdStrBefore.length(), - length, - true, - typedChar, - emptySpaceLength - extraPrevBlockLength, - prevBlockTextLength + extraPrevBlockLength, - prevBlockEndsWithPunctuation), - totalShift); + applyReplacements(block, + utf8Offset, + buffer, + replacements(buffer, utf8Offset, utf8Length, &block, typedChar)); } int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::TabSettings &) @@ -437,36 +430,14 @@ int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::Ta if (!editor) return -1; - const QTextBlock prevBlock = clearFirstNonEmptyBlockFromExtraSpaces(block); + trimFirstNonEmptyBlock(block); + const QTextDocument *doc = block.document(); + const QByteArray buffer = doc->toPlainText().toUtf8(); + const int utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, block.blockNumber() + 1); + QTC_ASSERT(utf8Offset >= 0, return 0;); + const int utf8Length = block.text().toUtf8().size(); - int offset = block.position(); - int length = std::max(0, block.length() - 1); - QString buffer = editor->toPlainText(); - - int emptySpaceLength = previousEmptyLinesLength(block); - offset -= emptySpaceLength; - buffer.replace(offset, emptySpaceLength, ""); - int extraPrevBlockLength = modifyToIndentEmptyLines(buffer, offset, length, block); - - const QString prevBlockText = prevBlock.text(); - bool prevBlockEndsWithPunctuation = prevBlockText.size() > 0 - ? prevBlockText.at(prevBlockText.size() - 1).isPunct() - : false; - - const int totalShift = startOfIndentationBuffer(buffer, offset); - const int cutAtPos = nextEndingScopePosition(buffer, offset + length) + 1; - buffer = buffer.mid(totalShift, cutAtPos - totalShift); - offset -= totalShift; - const std::string stdStrBefore = buffer.left(offset).toStdString(); - const std::string stdStrBuffer = stdStrBefore + buffer.mid(offset).toStdString(); - Replacements toReplace = replacements(stdStrBuffer, - stdStrBefore.length(), - length, - true, - QChar::Null, - emptySpaceLength - extraPrevBlockLength, - prevBlockText.size() + extraPrevBlockLength + 1, - prevBlockEndsWithPunctuation); + Replacements toReplace = replacements(buffer, utf8Offset, utf8Length, &block); if (toReplace.empty()) return -1; @@ -481,7 +452,7 @@ int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::Ta TabSettings ClangFormatIndenter::tabSettings() const { - FormatStyle style = formatStyle(); + FormatStyle style = formatStyle(styleConfigPath()); TabSettings tabSettings; switch (style.UseTab) { @@ -495,8 +466,8 @@ TabSettings ClangFormatIndenter::tabSettings() const tabSettings.m_tabPolicy = TabSettings::MixedTabPolicy; } - tabSettings.m_tabSize = style.TabWidth; - tabSettings.m_indentSize = style.IndentWidth; + tabSettings.m_tabSize = static_cast(style.TabWidth); + tabSettings.m_indentSize = static_cast(style.IndentWidth); if (style.AlignAfterOpenBracket) tabSettings.m_continuationAlignBehavior = TabSettings::ContinuationAlignWithSpaces; @@ -506,5 +477,4 @@ TabSettings ClangFormatIndenter::tabSettings() const return tabSettings; } -} // namespace Internal } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatindenter.h b/src/plugins/clangformat/clangformatindenter.h index 00dde55c160..dfe3b5b95f3 100644 --- a/src/plugins/clangformat/clangformatindenter.h +++ b/src/plugins/clangformat/clangformatindenter.h @@ -28,7 +28,6 @@ #include namespace ClangFormat { -namespace Internal { class ClangFormatIndenter final : public TextEditor::Indenter { @@ -42,9 +41,9 @@ public: const QTextCursor &cursor, const TextEditor::TabSettings &tabSettings) override; void indentBlock(QTextDocument *doc, - const QTextBlock &block, - const QChar &typedChar, - const TextEditor::TabSettings &tabSettings) override; + const QTextBlock &block, + const QChar &typedChar, + const TextEditor::TabSettings &tabSettings) override; int indentFor(const QTextBlock &block, const TextEditor::TabSettings &tabSettings) override; bool isElectricCharacter(const QChar &ch) const override; @@ -53,5 +52,4 @@ public: TextEditor::TabSettings tabSettings() const override; }; -} // namespace Internal } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatplugin.cpp b/src/plugins/clangformat/clangformatplugin.cpp index 3da6cbfa08e..f421b686aca 100644 --- a/src/plugins/clangformat/clangformatplugin.cpp +++ b/src/plugins/clangformat/clangformatplugin.cpp @@ -45,6 +45,8 @@ #include #include +#include + #include #include #include @@ -56,7 +58,6 @@ using namespace ProjectExplorer; namespace ClangFormat { -namespace Internal { class ClangFormatOptionsPage : public Core::IOptionsPage { @@ -98,7 +99,7 @@ bool ClangFormatPlugin::initialize(const QStringList &arguments, QString *errorS { Q_UNUSED(arguments); Q_UNUSED(errorString); - +#ifdef KEEP_LINE_BREAKS_FOR_NON_EMPTY_LINES_BACKPORTED m_optionsPage = std::make_unique(); auto panelFactory = new ProjectPanelFactory(); @@ -112,9 +113,8 @@ bool ClangFormatPlugin::initialize(const QStringList &arguments, QString *errorS CppTools::CppModelManager::instance()->setCppIndenterCreator([]() { return new ClangFormatIndenter(); }); - +#endif return true; } -} // namespace Internal } // namespace ClangFormat diff --git a/src/plugins/clangformat/clangformatplugin.h b/src/plugins/clangformat/clangformatplugin.h index e86be645893..00b59cd950e 100644 --- a/src/plugins/clangformat/clangformatplugin.h +++ b/src/plugins/clangformat/clangformatplugin.h @@ -30,7 +30,6 @@ #include namespace ClangFormat { -namespace Internal { class ClangFormatOptionsPage; @@ -50,5 +49,4 @@ private: std::unique_ptr m_optionsPage; }; -} // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangformat/clangformatutils.h b/src/plugins/clangformat/clangformatutils.h new file mode 100644 index 00000000000..ad4e21f8899 --- /dev/null +++ b/src/plugins/clangformat/clangformatutils.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include + +#include + +namespace ClangFormat { + +inline void createStyleFileIfNeeded(Utils::FileName styleConfigPath) +{ + const QString configFile = styleConfigPath.appendPath(".clang-format").toString(); + if (QFile::exists(configFile)) + return; + + clang::format::FormatStyle newStyle = clang::format::getLLVMStyle(); + std::fstream newStyleFile(configFile.toStdString(), std::fstream::out); + if (newStyleFile.is_open()) { + newStyleFile << clang::format::configurationAsText(newStyle); + newStyleFile.close(); + } +} + +} diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 89823a7c126..3141a126208 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -2474,6 +2474,7 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) e->accept(); if (extraBlocks > 0) { + cursor.joinPreviousEditBlock(); const int cursorPosition = cursor.position(); QTextCursor ensureVisible = cursor; while (extraBlocks > 0) { @@ -2493,6 +2494,7 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e) } setTextCursor(ensureVisible); cursor.setPosition(cursorPosition); + cursor.endEditBlock(); } setTextCursor(cursor);