summaryrefslogtreecommitdiff
path: root/src/plugins/clangformat/clangformatindenter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/clangformat/clangformatindenter.cpp')
-rw-r--r--src/plugins/clangformat/clangformatindenter.cpp469
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 &currentBlock)
-{
- 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 &currentBlock)
-{
- 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 &currentBlock)
-{
- 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;