diff options
Diffstat (limited to 'src/plugins/clangformat/clangformatindenter.cpp')
-rw-r--r-- | src/plugins/clangformat/clangformatindenter.cpp | 469 |
1 files changed, 8 insertions, 461 deletions
diff --git a/src/plugins/clangformat/clangformatindenter.cpp b/src/plugins/clangformat/clangformatindenter.cpp index e5d14392e4..b52b4e9ce6 100644 --- a/src/plugins/clangformat/clangformatindenter.cpp +++ b/src/plugins/clangformat/clangformatindenter.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -24,479 +24,26 @@ ****************************************************************************/ #include "clangformatindenter.h" - #include "clangformatutils.h" -#include <clang/Format/Format.h> -#include <clang/Tooling/Core/Replacement.h> - -#include <cpptools/cppmodelmanager.h> -#include <texteditor/textdocument.h> -#include <texteditor/texteditor.h> - -#include <utils/hostosinfo.h> -#include <utils/textutils.h> -#include <utils/qtcassert.h> - -#include <llvm/Config/llvm-config.h> - -#include <QDir> -#include <QFileInfo> -#include <QTextBlock> - -#include <fstream> - -using ClangReplacement = clang::tooling::Replacement; -using ClangReplacements = clang::tooling::Replacements; -using QtReplacement = TextEditor::Replacement; -using QtReplacements = TextEditor::Replacements; +#include <texteditor/tabsettings.h> using namespace clang; using namespace format; -using namespace llvm; -using namespace tooling; -using namespace ProjectExplorer; using namespace TextEditor; namespace ClangFormat { -namespace { - -void adjustFormatStyleForLineBreak(format::FormatStyle &style) -{ - style.DisableFormat = false; - style.ColumnLimit = 0; -#ifdef KEEP_LINE_BREAKS_FOR_NON_EMPTY_LINES_BACKPORTED - style.KeepLineBreaksForNonEmptyLines = true; -#endif - style.MaxEmptyLinesToKeep = 2; - style.SortIncludes = false; - style.SortUsingDeclarations = false; -} - -StringRef clearExtraNewline(StringRef text) -{ - while (text.startswith("\n\n")) - text = text.drop_front(); - return text; -} - -ClangReplacements filteredReplacements(const ClangReplacements &replacements, - int offset, - int extraOffsetToAdd, - bool onlyIndention) -{ - ClangReplacements filtered; - for (const ClangReplacement &replacement : replacements) { - int replacementOffset = static_cast<int>(replacement.getOffset()); - if (onlyIndention && replacementOffset != offset - 1) - continue; - - if (replacementOffset + 1 >= offset) - replacementOffset += extraOffsetToAdd; - - StringRef text = onlyIndention ? clearExtraNewline(replacement.getReplacementText()) - : replacement.getReplacementText(); - - Error error = filtered.add(ClangReplacement(replacement.getFilePath(), - static_cast<unsigned int>(replacementOffset), - replacement.getLength(), - text)); - // Throws if error is not checked. - if (error) - break; - } - return filtered; -} - -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(); -} - -void trimCurrentBlock(const QTextBlock ¤tBlock) -{ - if (currentBlock.text().trimmed().isEmpty()) { - // Clear the block containing only spaces - QTextCursor cursor(currentBlock); - cursor.beginEditBlock(); - cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); - cursor.removeSelectedText(); - cursor.endEditBlock(); - } -} - -// Returns the total langth of previous lines with pure whitespace. -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; -} - -void modifyToIndentEmptyLines( - QByteArray &buffer, int offset, int &length, const QTextBlock &block, bool secondTry) -{ - const QString blockText = block.text().trimmed(); - const bool closingParenBlock = blockText.startsWith(')'); - if (blockText.isEmpty() || closingParenBlock) { - //This extra text works for the most cases. - QByteArray dummyText("a;"); - - // 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(); - } - if (prevBlock.text().endsWith(',')) - dummyText = "int a"; - - if (closingParenBlock) { - if (prevBlock.text().endsWith(',')) - dummyText = "int a"; - else - dummyText = "&& a"; - } - - length += dummyText.length(); - buffer.insert(offset, dummyText); - } +ClangFormatIndenter::ClangFormatIndenter(QTextDocument *doc) + : ClangFormatBaseIndenter(doc) +{} - if (secondTry) { - int nextLinePos = buffer.indexOf('\n', offset); - 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, ')'); - length += 1; - } - } -} - -static const int kMaxLinesFromCurrentBlock = 200; - -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. - const int lineStartUtf8Offset = utf8Buffer.lastIndexOf('\n', utf8Offset - 1) + 1; - 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); -} - -QtReplacements utf16Replacements(const QTextBlock &block, - int blockOffsetUtf8, - const QByteArray &utf8Buffer, - const ClangReplacements &replacements) +FormatStyle ClangFormatIndenter::styleForFile() const { - QtReplacements convertedReplacements; - convertedReplacements.reserve(replacements.size()); - for (const ClangReplacement &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; -} - -QtReplacements replacements(const Utils::FileName &fileName, - QByteArray buffer, - int utf8Offset, - int utf8Length, - const QTextBlock &block, - const QChar &typedChar = QChar::Null, - bool onlyIndention = true, - bool secondTry = false) -{ - FormatStyle style = styleForFile(fileName); - - int originalOffsetUtf8 = utf8Offset; - int originalLengthUtf8 = utf8Length; - QByteArray originalBuffer = buffer; - - int extraOffset = 0; - if (onlyIndention) { - if (block.blockNumber() > kMaxLinesFromCurrentBlock) { - extraOffset = Utils::Text::utf8NthLineOffset( - block.document(), buffer, block.blockNumber() - kMaxLinesFromCurrentBlock); - } - int endOffset = Utils::Text::utf8NthLineOffset( - block.document(), buffer, block.blockNumber() + kMaxLinesFromCurrentBlock); - if (endOffset == -1) - endOffset = buffer.size(); - - buffer = buffer.mid(extraOffset, endOffset - 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, secondTry); - } - - std::vector<Range> ranges{{static_cast<unsigned int>(utf8Offset), - static_cast<unsigned int>(utf8Length)}}; - FormattingAttemptStatus status; - - ClangReplacements clangReplacements = reformat(style, - buffer.data(), - ranges, - fileName.toString().toStdString(), - &status); - - if (!status.FormatComplete) - QtReplacements(); - - const ClangReplacements filtered = filteredReplacements(clangReplacements, - utf8Offset, - extraOffset, - onlyIndention); - - const bool canTryAgain = onlyIndention && typedChar == QChar::Null && !secondTry; - if (canTryAgain && filtered.empty()) { - return replacements(fileName, - originalBuffer, - originalOffsetUtf8, - originalLengthUtf8, - block, - typedChar, - onlyIndention, - true); - } - - return utf16Replacements(block, originalOffsetUtf8, originalBuffer, filtered); -} - -void applyReplacements(const QTextBlock &block, const QtReplacements &replacements) -{ - if (replacements.empty()) - return; - - int fullOffsetShift = 0; - QTextCursor editCursor(block); - for (const QtReplacement &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; - } -} - -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; -} - -} // anonymous namespace - -bool ClangFormatIndenter::isElectricCharacter(const QChar &ch) const -{ - switch (ch.toLatin1()) { - case '{': - case '}': - case ':': - case '#': - case '<': - case '>': - case ';': - case '(': - case ')': - case ',': - case '.': - return true; - } - return false; -} - -void ClangFormatIndenter::indent(QTextDocument *doc, - const QTextCursor &cursor, - const QChar &typedChar, - const TabSettings &tabSettings, - bool /*autoTriggered*/) -{ - if (cursor.hasSelection()) { - // Calling currentBlock.next() might be unsafe because we change the document. - // Let's operate with block numbers instead. - const int startNumber = doc->findBlock(cursor.selectionStart()).blockNumber(); - const int endNumber = doc->findBlock(cursor.selectionEnd()).blockNumber(); - for (int currentBlockNumber = startNumber; currentBlockNumber <= endNumber; - ++currentBlockNumber) { - const QTextBlock currentBlock = doc->findBlockByNumber(currentBlockNumber); - if (currentBlock.isValid()) { - const int blocksAmount = doc->blockCount(); - indentBlock(doc, currentBlock, typedChar, tabSettings); - QTC_CHECK(blocksAmount == doc->blockCount() - && "ClangFormat plugin indentation changed the amount of blocks."); - } - } - } else { - indentBlock(doc, cursor.block(), typedChar, tabSettings); - } -} - -QtReplacements ClangFormatIndenter::format(QTextDocument *doc, - const Utils::FileName &fileName, - const QTextCursor &cursor, - const TextEditor::TabSettings & /*tabSettings*/) -{ - int utf8Offset; - int utf8Length; - const QByteArray buffer = doc->toPlainText().toUtf8(); - QTextBlock block = cursor.block(); - if (cursor.hasSelection()) { - block = doc->findBlock(cursor.selectionStart()); - const QTextBlock end = doc->findBlock(cursor.selectionEnd()); - utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, block.blockNumber() + 1); - QTC_ASSERT(utf8Offset >= 0, return QtReplacements();); - utf8Length = selectedLines(doc, block, end).toUtf8().size(); - - } else { - const QTextBlock block = cursor.block(); - utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, block.blockNumber() + 1); - QTC_ASSERT(utf8Offset >= 0, return QtReplacements();); - utf8Length = block.text().toUtf8().size(); - } - - const QtReplacements toReplace - = replacements(fileName, buffer, utf8Offset, utf8Length, block, QChar::Null, false); - applyReplacements(block, toReplace); - - return toReplace; -} - -void ClangFormatIndenter::reindent(QTextDocument *doc, - const QTextCursor &cursor, - const TabSettings &tabSettings) -{ - indent(doc, cursor, QChar::Null, tabSettings); -} - -void ClangFormatIndenter::indentBlock(QTextDocument *doc, - const QTextBlock &block, - const QChar &typedChar, - const TabSettings &tabSettings) -{ - Q_UNUSED(tabSettings); - - TextEditorWidget *editor = TextEditorWidget::currentTextEditorWidget(); - if (!editor) - return; - - const Utils::FileName fileName = editor->textDocument()->filePath(); - trimFirstNonEmptyBlock(block); - trimCurrentBlock(block); - const QByteArray buffer = doc->toPlainText().toUtf8(); - const int utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, block.blockNumber() + 1); - QTC_ASSERT(utf8Offset >= 0, return;); - - applyReplacements(block, - replacements(fileName, buffer, utf8Offset, 0, block, typedChar)); -} - -int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::TabSettings &) -{ - TextEditorWidget *editor = TextEditorWidget::currentTextEditorWidget(); - if (!editor) - return -1; - - const Utils::FileName fileName = editor->textDocument()->filePath(); - trimFirstNonEmptyBlock(block); - trimCurrentBlock(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 QtReplacements toReplace = replacements(fileName, buffer, utf8Offset, 0, block); - - if (toReplace.empty()) - return -1; - - const QtReplacement &replacement = toReplace.front(); - int afterLineBreak = replacement.text.lastIndexOf('\n'); - afterLineBreak = (afterLineBreak < 0) ? 0 : afterLineBreak + 1; - return static_cast<int>(replacement.text.size() - afterLineBreak); + return ClangFormat::styleForFile(m_fileName); } -TabSettings ClangFormatIndenter::tabSettings() const +Utils::optional<TabSettings> ClangFormatIndenter::tabSettings() const { FormatStyle style = currentProjectStyle(); TabSettings tabSettings; |