Clang: Significantly improve ClangFormat plugin usability

What's new:
1. New LibFormat option is used to prevent lines shrink,
   which allows to drop most of tricks used before for that purpose.
2. Cached UTF-8 source code is used to improve performance
3. Improved error handling.
4. Slightly improved UI.

Change-Id: I4605200fa103167369a40650b2e1ad2c61e8133b
Reviewed-by: Marco Bubke <marco.bubke@qt.io>
This commit is contained in:
Ivan Donchevskii
2018-10-11 14:11:43 +02:00
parent 6f73b3b175
commit cafd5ced1a
13 changed files with 396 additions and 339 deletions

View File

@@ -140,5 +140,20 @@ QTextCursor wordStartCursor(const QTextCursor &textCursor)
return cursor; 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 } // Text
} // Utils } // Utils

View File

@@ -55,6 +55,10 @@ QTCREATOR_UTILS_EXPORT QTextCursor flippedCursor(const QTextCursor &cursor);
QTCREATOR_UTILS_EXPORT QTextCursor wordStartCursor(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 <class CharacterProvider> template <class CharacterProvider>
void moveToPrevChar(CharacterProvider &provider, QTextCursor &cursor) void moveToPrevChar(CharacterProvider &provider, QTextCursor &cursor)
{ {

View File

@@ -24,7 +24,8 @@ HEADERS = \
clangformatconfigwidget.h \ clangformatconfigwidget.h \
clangformatindenter.h \ clangformatindenter.h \
clangformatplugin.h \ clangformatplugin.h \
clangformatconstants.h clangformatconstants.h \
clangformatutils.h
FORMS += \ FORMS += \
clangformatconfigwidget.ui clangformatconfigwidget.ui

View File

@@ -27,10 +27,11 @@ QtcPlugin {
"clangformatconfigwidget.cpp", "clangformatconfigwidget.cpp",
"clangformatconfigwidget.h", "clangformatconfigwidget.h",
"clangformatconfigwidget.ui", "clangformatconfigwidget.ui",
"clangformatconstants.h",
"clangformatindenter.cpp", "clangformatindenter.cpp",
"clangformatindenter.h", "clangformatindenter.h",
"clangformatplugin.cpp", "clangformatplugin.cpp",
"clangformatplugin.h", "clangformatplugin.h",
"clangformatconstants.h", "clangformatutils.h",
] ]
} }

View File

@@ -25,6 +25,8 @@
#include "clangformatconfigwidget.h" #include "clangformatconfigwidget.h"
#include "clangformatutils.h"
#include "ui_clangformatconfigwidget.h" #include "ui_clangformatconfigwidget.h"
#include <clang/Format/Format.h> #include <clang/Format/Format.h>
@@ -40,23 +42,6 @@
using namespace ProjectExplorer; using namespace ProjectExplorer;
namespace ClangFormat { 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) static void readTable(QTableWidget *table, std::istringstream &stream)
{ {
@@ -137,50 +122,75 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(ProjectExplorer::Project *proje
{ {
m_ui->setupUi(this); 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()) { 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->clangFormatOptionsTable->hide();
m_ui->applyButton->hide(); m_ui->applyButton->hide();
connect(m_ui->createFileButton, &QPushButton::clicked,
this, [this]() {
createStyleFileIfNeeded(m_project->projectDirectory());
initialize();
});
return; return;
} }
m_ui->createFileButton->hide();
std::string testFilePath;
if (m_project) { if (m_project) {
m_ui->projectHasClangFormat->hide();
testFilePath = m_project->projectDirectory().appendPath("t.cpp").toString().toStdString(); testFilePath = m_project->projectDirectory().appendPath("t.cpp").toString().toStdString();
connect(m_ui->applyButton, &QPushButton::clicked, this, &ClangFormatConfigWidget::apply); connect(m_ui->applyButton, &QPushButton::clicked, this, &ClangFormatConfigWidget::apply);
} else { } 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(); const QString settingsDir = Core::ICore::userResourcePath();
createGlobalClangFormatFileIfNeeded(settingsDir); createStyleFileIfNeeded(Utils::FileName::fromString(settingsDir));
testFilePath = settingsDir.toStdString() + "/t.cpp"; testFilePath = settingsDir.toStdString() + "/t.cpp";
m_ui->applyButton->hide(); m_ui->applyButton->hide();
} }
fillTable(testFilePath);
}
void ClangFormatConfigWidget::fillTable(const std::string &testFilePath)
{
llvm::Expected<clang::format::FormatStyle> formatStyle = llvm::Expected<clang::format::FormatStyle> formatStyle =
clang::format::getStyle("file", testFilePath, "LLVM", ""); clang::format::getStyle("file", testFilePath, "LLVM");
if (!formatStyle) std::string configText;
return; bool brokenConfig = false;
if (!formatStyle) {
const std::string configText = clang::format::configurationAsText(*formatStyle); handleAllErrors(formatStyle.takeError(), [](const llvm::ErrorInfoBase &) {
std::istringstream stream(configText); // do nothing
});
readTable(m_ui->clangFormatOptionsTable, stream); configText = clang::format::configurationAsText(clang::format::getLLVMStyle());
brokenConfig = true;
if (m_project) { } else {
m_ui->projectHasClangFormat->hide(); configText = clang::format::configurationAsText(*formatStyle);
return;
} }
const Project *currentProject = SessionManager::startupProject(); std::istringstream stream(configText);
if (!currentProject || !currentProject->projectDirectory().appendPath(".clang-format").exists()) readTable(m_ui->clangFormatOptionsTable, stream);
m_ui->projectHasClangFormat->hide(); 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; ClangFormatConfigWidget::~ClangFormatConfigWidget() = default;
@@ -201,5 +211,4 @@ void ClangFormatConfigWidget::apply()
file.close(); file.close();
} }
} // namespace Internal
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -32,7 +32,6 @@
namespace ProjectExplorer { class Project; } namespace ProjectExplorer { class Project; }
namespace ClangFormat { namespace ClangFormat {
namespace Internal {
namespace Ui { namespace Ui {
class ClangFormatConfigWidget; class ClangFormatConfigWidget;
@@ -49,9 +48,11 @@ public:
void apply(); void apply();
private: private:
void initialize();
void fillTable(const std::string &testFilePath);
ProjectExplorer::Project *m_project; ProjectExplorer::Project *m_project;
std::unique_ptr<Ui::ClangFormatConfigWidget> m_ui; std::unique_ptr<Ui::ClangFormatConfigWidget> m_ui;
}; };
} // namespace Internal
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>ClangFormat::Internal::ClangFormatConfigWidget</class> <class>ClangFormat::ClangFormatConfigWidget</class>
<widget class="QWidget" name="ClangFormat::Internal::ClangFormatConfigWidget"> <widget class="QWidget" name="ClangFormat::ClangFormatConfigWidget">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
@@ -29,7 +29,7 @@
<item> <item>
<widget class="QLabel" name="projectHasClangFormat"> <widget class="QLabel" name="projectHasClangFormat">
<property name="text"> <property name="text">
<string> Current project has its own .clang-format file and can be configured in Projects -&gt; ClangFormat.</string> <string/>
</property> </property>
</widget> </widget>
</item> </item>
@@ -38,6 +38,13 @@
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="createFileButton">
<property name="text">
<string>Create Clang Format Configuration File</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="applyButton"> <widget class="QPushButton" name="applyButton">
<property name="text"> <property name="text">

View File

@@ -25,16 +25,21 @@
#include "clangformatindenter.h" #include "clangformatindenter.h"
#include "clangformatutils.h"
#include <clang/Format/Format.h> #include <clang/Format/Format.h>
#include <clang/Tooling/Core/Replacement.h> #include <clang/Tooling/Core/Replacement.h>
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <cpptools/cppmodelmanager.h>
#include <projectexplorer/project.h> #include <projectexplorer/project.h>
#include <projectexplorer/session.h> #include <projectexplorer/session.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditor.h> #include <texteditor/texteditor.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/textutils.h>
#include <utils/qtcassert.h>
#include <llvm/Config/llvm-config.h> #include <llvm/Config/llvm-config.h>
@@ -42,6 +47,8 @@
#include <QFileInfo> #include <QFileInfo>
#include <QTextBlock> #include <QTextBlock>
#include <fstream>
using namespace clang; using namespace clang;
using namespace format; using namespace format;
using namespace llvm; using namespace llvm;
@@ -50,67 +57,35 @@ using namespace ProjectExplorer;
using namespace TextEditor; using namespace TextEditor;
namespace ClangFormat { namespace ClangFormat {
namespace Internal {
namespace { namespace {
void adjustFormatStyleForLineBreak(format::FormatStyle &style, void adjustFormatStyleForLineBreak(format::FormatStyle &style)
int length,
int prevBlockSize,
bool prevBlockEndsWithPunctuation)
{ {
if (length > 0) style.DisableFormat = false;
style.ColumnLimit = prevBlockSize; style.ColumnLimit = 0;
style.AlwaysBreakBeforeMultilineStrings = true; #ifdef KEEP_LINE_BREAKS_FOR_NON_EMPTY_LINES_BACKPORTED
#if LLVM_VERSION_MAJOR >= 7 style.KeepLineBreaksForNonEmptyLines = true;
style.AlwaysBreakTemplateDeclarations = FormatStyle::BTDS_Yes;
#else
style.AlwaysBreakTemplateDeclarations = true;
#endif #endif
style.MaxEmptyLinesToKeep = 2;
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;
}
} }
Replacements filteredReplacements(const Replacements &replacements, Replacements filteredReplacements(const Replacements &replacements,
unsigned int offset, int offset,
unsigned int lengthForFilter, int lengthForFilter,
int extraOffsetToAdd, int extraOffsetToAdd)
int prevBlockLength)
{ {
Replacements filtered; Replacements filtered;
for (const Replacement &replacement : replacements) { for (const Replacement &replacement : replacements) {
unsigned int replacementOffset = replacement.getOffset(); int replacementOffset = static_cast<int>(replacement.getOffset());
if (replacementOffset > offset + lengthForFilter) if (replacementOffset > offset + lengthForFilter)
break; break;
if (offset > static_cast<unsigned int>(prevBlockLength)
&& replacementOffset < offset - static_cast<unsigned int>(prevBlockLength))
continue;
if (lengthForFilter == 0 && replacement.getReplacementText().find('\n') == std::string::npos
&& filtered.empty()) {
continue;
}
if (replacementOffset + 1 >= offset) if (replacementOffset + 1 >= offset)
replacementOffset += static_cast<unsigned int>(extraOffsetToAdd); replacementOffset += extraOffsetToAdd;
Error error = filtered.add(Replacement(replacement.getFilePath(), Error error = filtered.add(Replacement(replacement.getFilePath(),
replacementOffset, static_cast<unsigned int>(replacementOffset),
replacement.getLength(), replacement.getLength(),
replacement.getReplacementText())); replacement.getReplacementText()));
// Throws if error is not checked. // Throws if error is not checked.
@@ -120,147 +95,58 @@ Replacements filteredReplacements(const Replacements &replacements,
return filtered; return filtered;
} }
std::string assumedFilePath() Utils::FileName styleConfigPath()
{ {
const Project *project = SessionManager::startupProject(); const Project *project = SessionManager::startupProject();
if (project && project->projectDirectory().appendPath(".clang-format").exists()) 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<FormatStyle> style = format::getStyle("file", assumedFilePath(), "none", ""); createStyleFileIfNeeded(styleConfigPath);
Expected<FormatStyle> style = format::getStyle(
"file", styleConfigPath.appendPath("test.cpp").toString().toStdString(), "LLVM");
if (style) if (style)
return *style; return *style;
return FormatStyle();
handleAllErrors(style.takeError(), [](const ErrorInfoBase &) {
// do nothing
});
return format::getLLVMStyle();
} }
Replacements replacements(const std::string &buffer, void trimFirstNonEmptyBlock(const QTextBlock &currentBlock)
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<Range> 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 &currentBlock)
{ {
QTextBlock prevBlock = currentBlock.previous(); QTextBlock prevBlock = currentBlock.previous();
while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty()) while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty())
prevBlock = prevBlock.previous(); prevBlock = prevBlock.previous();
if (prevBlock.text().trimmed().isEmpty()) if (prevBlock.text().trimmed().isEmpty())
return prevBlock; return;
const QString initialText = prevBlock.text(); const QString initialText = prevBlock.text();
if (!initialText.at(initialText.length() - 1).isSpace()) if (!initialText.at(initialText.size() - 1).isSpace())
return prevBlock; return;
int extraSpaceCount = 1;
for (int i = initialText.size() - 2; i >= 0; --i) {
if (!initialText.at(i).isSpace())
break;
++extraSpaceCount;
}
QTextCursor cursor(prevBlock); QTextCursor cursor(prevBlock);
cursor.beginEditBlock(); cursor.beginEditBlock();
cursor.movePosition(QTextCursor::EndOfBlock); cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor,
cursor.movePosition(QTextCursor::Left); initialText.size() - extraSpaceCount);
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, extraSpaceCount);
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.removeSelectedText(); cursor.removeSelectedText();
cursor.endEditBlock(); cursor.endEditBlock();
return prevBlock;
} }
// Returns the total langth of previous lines with pure whitespace. // Returns the total langth of previous lines with pure whitespace.
@@ -276,42 +162,175 @@ int previousEmptyLinesLength(const QTextBlock &currentBlock)
return length; return length;
} }
static constexpr const int MinCharactersBeforeCurrentInBuffer = 200; void modifyToIndentEmptyLines(QByteArray &buffer, int &offset, int &length, const QTextBlock &block)
static constexpr const int MaxCharactersBeforeCurrentInBuffer = 500;
int startOfIndentationBuffer(const QString &buffer, int start)
{ {
if (start < MaxCharactersBeforeCurrentInBuffer) const QString blockText = block.text().trimmed();
return 0; const bool closingParenBlock = blockText.startsWith(')');
if (length != 0 && !closingParenBlock)
return;
auto it = buffer.cbegin() + (start - MinCharactersBeforeCurrentInBuffer); //This extra text works for the most cases.
for (; it != buffer.cbegin() + (start - MaxCharactersBeforeCurrentInBuffer); --it) { QByteArray extraText("a;");
if (*it == '{') {
// Find the start of it's line. // Search for previous character
for (auto inner = it; QTextBlock prevBlock = block.previous();
inner != buffer.cbegin() + (start - MaxCharactersBeforeCurrentInBuffer); while (prevBlock.position() > 0 && prevBlock.text().trimmed().isEmpty())
--inner) { prevBlock = prevBlock.previous();
if (*inner == '\n') if (prevBlock.text().endsWith(','))
return inner + 1 - buffer.cbegin(); extraText = "int a,";
}
break; 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) static const int kMaxLinesFromCurrentBlock = 200;
{
if (start >= buffer.size() - 1)
return buffer.size() - 1;
for (auto it = buffer.cbegin() + (start + 1); it != buffer.cend(); ++it) { Replacements replacements(QByteArray buffer,
if (*it == '}') int utf8Offset,
return it - buffer.cbegin(); 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<Range> ranges{{static_cast<unsigned int>(utf8Offset),
static_cast<unsigned int>(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<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();
Error error = convertedReplacements.add(
Replacement(replacement.getFilePath(),
static_cast<unsigned int>(utf16Offset),
static_cast<unsigned int>(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<int>(replacement.getLength());
editCursor.beginEditBlock();
editCursor.setPosition(static_cast<int>(replacement.getOffset()) + fullOffsetShift);
editCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
replacementLength);
editCursor.removeSelectedText();
editCursor.insertText(replacementString);
editCursor.endEditBlock();
fullOffsetShift += replacementString.length() - replacementLength;
}
} }
} // anonymous namespace } // anonymous namespace
@@ -342,30 +361,34 @@ void ClangFormatIndenter::indent(QTextDocument *doc,
bool autoTriggered) bool autoTriggered)
{ {
if (typedChar == QChar::Null && (cursor.hasSelection() || !autoTriggered)) { if (typedChar == QChar::Null && (cursor.hasSelection() || !autoTriggered)) {
TextEditorWidget *editor = TextEditorWidget::currentTextEditorWidget(); int utf8Offset;
int offset; int utf8Length;
int length; const QByteArray buffer = doc->toPlainText().toUtf8();
if (cursor.hasSelection()) { if (cursor.hasSelection()) {
const QTextBlock start = doc->findBlock(cursor.selectionStart()); const QTextBlock start = doc->findBlock(cursor.selectionStart());
const QTextBlock end = doc->findBlock(cursor.selectionEnd()); const QTextBlock end = doc->findBlock(cursor.selectionEnd());
offset = start.position(); utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, start.blockNumber() + 1);
length = std::max(0, end.position() + end.length() - start.position() - 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 { } else {
const QTextBlock block = cursor.block(); const QTextBlock block = cursor.block();
offset = block.position(); utf8Offset = Utils::Text::utf8NthLineOffset(doc, buffer, block.blockNumber() + 1);
length = std::max(0, block.length() - 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 { } else {
indentBlock(doc, cursor.block(), typedChar, tabSettings); indentBlock(doc, cursor.block(), typedChar, tabSettings);
} }
@@ -389,46 +412,16 @@ void ClangFormatIndenter::indentBlock(QTextDocument *doc,
if (!editor) if (!editor)
return; 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(); applyReplacements(block,
int length = std::max(0, block.length() - 1); utf8Offset,
QString buffer = editor->toPlainText(); buffer,
replacements(buffer, utf8Offset, utf8Length, &block, typedChar));
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);
} }
int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::TabSettings &) int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::TabSettings &)
@@ -437,36 +430,14 @@ int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::Ta
if (!editor) if (!editor)
return -1; 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(); Replacements toReplace = replacements(buffer, utf8Offset, utf8Length, &block);
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);
if (toReplace.empty()) if (toReplace.empty())
return -1; return -1;
@@ -481,7 +452,7 @@ int ClangFormatIndenter::indentFor(const QTextBlock &block, const TextEditor::Ta
TabSettings ClangFormatIndenter::tabSettings() const TabSettings ClangFormatIndenter::tabSettings() const
{ {
FormatStyle style = formatStyle(); FormatStyle style = formatStyle(styleConfigPath());
TabSettings tabSettings; TabSettings tabSettings;
switch (style.UseTab) { switch (style.UseTab) {
@@ -495,8 +466,8 @@ TabSettings ClangFormatIndenter::tabSettings() const
tabSettings.m_tabPolicy = TabSettings::MixedTabPolicy; tabSettings.m_tabPolicy = TabSettings::MixedTabPolicy;
} }
tabSettings.m_tabSize = style.TabWidth; tabSettings.m_tabSize = static_cast<int>(style.TabWidth);
tabSettings.m_indentSize = style.IndentWidth; tabSettings.m_indentSize = static_cast<int>(style.IndentWidth);
if (style.AlignAfterOpenBracket) if (style.AlignAfterOpenBracket)
tabSettings.m_continuationAlignBehavior = TabSettings::ContinuationAlignWithSpaces; tabSettings.m_continuationAlignBehavior = TabSettings::ContinuationAlignWithSpaces;
@@ -506,5 +477,4 @@ TabSettings ClangFormatIndenter::tabSettings() const
return tabSettings; return tabSettings;
} }
} // namespace Internal
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -28,7 +28,6 @@
#include <texteditor/indenter.h> #include <texteditor/indenter.h>
namespace ClangFormat { namespace ClangFormat {
namespace Internal {
class ClangFormatIndenter final : public TextEditor::Indenter class ClangFormatIndenter final : public TextEditor::Indenter
{ {
@@ -53,5 +52,4 @@ public:
TextEditor::TabSettings tabSettings() const override; TextEditor::TabSettings tabSettings() const override;
}; };
} // namespace Internal
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -45,6 +45,8 @@
#include <projectexplorer/projectpanelfactory.h> #include <projectexplorer/projectpanelfactory.h>
#include <projectexplorer/target.h> #include <projectexplorer/target.h>
#include <clang/Format/Format.h>
#include <QAction> #include <QAction>
#include <QDebug> #include <QDebug>
#include <QMainWindow> #include <QMainWindow>
@@ -56,7 +58,6 @@
using namespace ProjectExplorer; using namespace ProjectExplorer;
namespace ClangFormat { namespace ClangFormat {
namespace Internal {
class ClangFormatOptionsPage : public Core::IOptionsPage class ClangFormatOptionsPage : public Core::IOptionsPage
{ {
@@ -98,7 +99,7 @@ bool ClangFormatPlugin::initialize(const QStringList &arguments, QString *errorS
{ {
Q_UNUSED(arguments); Q_UNUSED(arguments);
Q_UNUSED(errorString); Q_UNUSED(errorString);
#ifdef KEEP_LINE_BREAKS_FOR_NON_EMPTY_LINES_BACKPORTED
m_optionsPage = std::make_unique<ClangFormatOptionsPage>(); m_optionsPage = std::make_unique<ClangFormatOptionsPage>();
auto panelFactory = new ProjectPanelFactory(); auto panelFactory = new ProjectPanelFactory();
@@ -112,9 +113,8 @@ bool ClangFormatPlugin::initialize(const QStringList &arguments, QString *errorS
CppTools::CppModelManager::instance()->setCppIndenterCreator([]() { CppTools::CppModelManager::instance()->setCppIndenterCreator([]() {
return new ClangFormatIndenter(); return new ClangFormatIndenter();
}); });
#endif
return true; return true;
} }
} // namespace Internal
} // namespace ClangFormat } // namespace ClangFormat

View File

@@ -30,7 +30,6 @@
#include <memory> #include <memory>
namespace ClangFormat { namespace ClangFormat {
namespace Internal {
class ClangFormatOptionsPage; class ClangFormatOptionsPage;
@@ -50,5 +49,4 @@ private:
std::unique_ptr<ClangFormatOptionsPage> m_optionsPage; std::unique_ptr<ClangFormatOptionsPage> m_optionsPage;
}; };
} // namespace Internal
} // namespace ClangTools } // namespace ClangTools

View File

@@ -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 <utils/fileutils.h>
#include <clang/Format/Format.h>
#include <QFile>
#include <fstream>
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();
}
}
}

View File

@@ -2474,6 +2474,7 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
e->accept(); e->accept();
if (extraBlocks > 0) { if (extraBlocks > 0) {
cursor.joinPreviousEditBlock();
const int cursorPosition = cursor.position(); const int cursorPosition = cursor.position();
QTextCursor ensureVisible = cursor; QTextCursor ensureVisible = cursor;
while (extraBlocks > 0) { while (extraBlocks > 0) {
@@ -2493,6 +2494,7 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
} }
setTextCursor(ensureVisible); setTextCursor(ensureVisible);
cursor.setPosition(cursorPosition); cursor.setPosition(cursorPosition);
cursor.endEditBlock();
} }
setTextCursor(cursor); setTextCursor(cursor);