forked from qt-creator/qt-creator
The combined UI was a bit misleading for the user and cause bunch of synchronization problems. The synchronization between old CodeStyle settings and ClangFormat settings was removed. Corresponding classes and functions were also cleared. The behavior now: When "Indenting only" or "Full Formatting" modes are chosen then The ClangFormat settings page is visible and the ClangFormat indenter is used. For "Disable" mode standard CodeStyle pages are shown and standard indenter respectively. Change-Id: Idb4974c68ceb16ef2e55b108043cc6f56f859840 Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
506 lines
16 KiB
C++
506 lines
16 KiB
C++
// Copyright (C) 2018 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "clangformatconfigwidget.h"
|
|
|
|
// the file below was generated by scripts/generateClangFormatChecksLayout.py
|
|
#include "clangformatchecks.h"
|
|
#include "clangformatconstants.h"
|
|
#include "clangformatfile.h"
|
|
#include "clangformatindenter.h"
|
|
#include "clangformattr.h"
|
|
#include "clangformatutils.h"
|
|
|
|
#include <coreplugin/icore.h>
|
|
|
|
#include <cppeditor/cppcodestylepreferences.h>
|
|
#include <cppeditor/cppcodestylesettings.h>
|
|
#include <cppeditor/cppcodestylesettingspage.h>
|
|
#include <cppeditor/cppcodestylesnippets.h>
|
|
#include <cppeditor/cpphighlighter.h>
|
|
#include <cppeditor/cpptoolssettings.h>
|
|
|
|
#include <projectexplorer/editorconfiguration.h>
|
|
#include <projectexplorer/project.h>
|
|
|
|
#include <texteditor/displaysettings.h>
|
|
#include <texteditor/icodestylepreferences.h>
|
|
#include <texteditor/snippets/snippeteditor.h>
|
|
#include <texteditor/textdocument.h>
|
|
#include <texteditor/texteditorsettings.h>
|
|
|
|
#include <utils/guard.h>
|
|
#include <utils/layoutbuilder.h>
|
|
#include <utils/qtcassert.h>
|
|
#include <utils/utilsicons.h>
|
|
|
|
#include <QComboBox>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QPushButton>
|
|
#include <QScrollArea>
|
|
#include <QVBoxLayout>
|
|
|
|
#include <memory>
|
|
#include <sstream>
|
|
|
|
using namespace ProjectExplorer;
|
|
using namespace Utils;
|
|
|
|
namespace ClangFormat {
|
|
|
|
class ClangFormatConfigWidget final : public TextEditor::CodeStyleEditorWidget
|
|
{
|
|
public:
|
|
ClangFormatConfigWidget(TextEditor::ICodeStylePreferences *codeStyle,
|
|
Project *project,
|
|
QWidget *parent);
|
|
void apply() final;
|
|
void finish() final;
|
|
|
|
private:
|
|
bool eventFilter(QObject *object, QEvent *event) final;
|
|
|
|
FilePath globalPath();
|
|
FilePath projectPath();
|
|
void createStyleFileIfNeeded(bool isGlobal);
|
|
void showOrHideWidgets();
|
|
void initChecksAndPreview();
|
|
void connectChecks();
|
|
|
|
void fillTable();
|
|
std::string readFile(const QString &path);
|
|
void saveChanges(QObject *sender);
|
|
|
|
void updatePreview();
|
|
void slotCodeStyleChanged(TextEditor::ICodeStylePreferences *currentPreferences);
|
|
|
|
ProjectExplorer::Project *m_project = nullptr;
|
|
QWidget *m_checksWidget = nullptr;
|
|
QScrollArea *m_checksScrollArea = nullptr;
|
|
TextEditor::SnippetEditorWidget *m_preview = nullptr;
|
|
std::unique_ptr<ClangFormatFile> m_config;
|
|
clang::format::FormatStyle m_style;
|
|
Guard m_ignoreChanges;
|
|
QLabel *m_fallbackConfig;
|
|
QLabel *m_clangVersion;
|
|
QLabel *m_clangWarningText;
|
|
QLabel *m_clangWarningIcon;
|
|
};
|
|
|
|
bool ClangFormatConfigWidget::eventFilter(QObject *object, QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::Wheel && qobject_cast<QComboBox *>(object)) {
|
|
event->ignore();
|
|
return true;
|
|
}
|
|
return QWidget::eventFilter(object, event);
|
|
}
|
|
|
|
ClangFormatConfigWidget::ClangFormatConfigWidget(TextEditor::ICodeStylePreferences *codeStyle,
|
|
Project *project,
|
|
QWidget *parent)
|
|
: CodeStyleEditorWidget(parent)
|
|
{
|
|
m_project = project;
|
|
m_config = std::make_unique<ClangFormatFile>(codeStyle->currentPreferences());
|
|
|
|
m_fallbackConfig = new QLabel(Tr::tr("Clang-Format Style"));
|
|
m_checksScrollArea = new QScrollArea();
|
|
m_checksWidget = new ClangFormatChecks();
|
|
|
|
m_checksScrollArea->setWidget(m_checksWidget);
|
|
m_checksScrollArea->setWidgetResizable(true);
|
|
m_checksWidget->setEnabled(!codeStyle->isReadOnly() && !codeStyle->isTemporarilyReadOnly()
|
|
&& codeStyle->isAdditionalTabVisible());
|
|
|
|
|
|
static const int expectedMajorVersion = 17;
|
|
m_clangVersion = new QLabel(Tr::tr("Current ClangFormat version: %1.").arg(LLVM_VERSION_STRING),
|
|
this);
|
|
m_clangWarningText
|
|
= new QLabel(Tr::tr("The widget was generated for ClangFormat %1. "
|
|
"If you use a different version, the widget may work incorrectly.")
|
|
.arg(expectedMajorVersion),
|
|
this);
|
|
|
|
QPalette palette = m_clangWarningText->palette();
|
|
palette.setColor(QPalette::WindowText, Qt::red);
|
|
m_clangWarningText->setPalette(palette);
|
|
|
|
m_clangWarningIcon = new QLabel(this);
|
|
m_clangWarningIcon->setPixmap(Icons::WARNING.icon().pixmap(16, 16));
|
|
|
|
if (LLVM_VERSION_MAJOR == expectedMajorVersion) {
|
|
m_clangWarningText->hide();
|
|
m_clangWarningIcon->hide();
|
|
}
|
|
|
|
FilePath fileName;
|
|
if (m_project)
|
|
fileName = m_project->projectFilePath().pathAppended("snippet.cpp");
|
|
else
|
|
fileName = Core::ICore::userResourcePath("snippet.cpp");
|
|
|
|
m_preview = new TextEditor::SnippetEditorWidget(this);
|
|
TextEditor::DisplaySettings displaySettings = m_preview->displaySettings();
|
|
displaySettings.m_visualizeWhitespace = true;
|
|
m_preview->setDisplaySettings(displaySettings);
|
|
m_preview->setPlainText(QLatin1String(CppEditor::Constants::DEFAULT_CODE_STYLE_SNIPPETS[0]));
|
|
auto *indenter = new ClangFormatIndenter(m_preview->document());
|
|
indenter->setOverriddenPreferences(codeStyle);
|
|
m_preview->textDocument()->setIndenter(indenter);
|
|
m_preview->textDocument()->setFontSettings(TextEditor::TextEditorSettings::fontSettings());
|
|
m_preview->textDocument()->resetSyntaxHighlighter(
|
|
[] { return new CppEditor::CppHighlighter(); });
|
|
m_preview->textDocument()->indenter()->setFileName(fileName);
|
|
|
|
using namespace Layouting;
|
|
|
|
Column {
|
|
m_fallbackConfig,
|
|
Row {m_clangWarningIcon, m_clangWarningText, st},
|
|
m_clangVersion,
|
|
Row { m_checksScrollArea, m_preview },
|
|
}.attachTo(this);
|
|
|
|
connect(codeStyle, &TextEditor::ICodeStylePreferences::currentPreferencesChanged,
|
|
this, &ClangFormatConfigWidget::slotCodeStyleChanged);
|
|
|
|
slotCodeStyleChanged(codeStyle->currentPreferences());
|
|
|
|
showOrHideWidgets();
|
|
fillTable();
|
|
updatePreview();
|
|
|
|
connectChecks();
|
|
}
|
|
|
|
void ClangFormatConfigWidget::slotCodeStyleChanged(
|
|
TextEditor::ICodeStylePreferences *codeStyle)
|
|
{
|
|
if (!codeStyle)
|
|
return;
|
|
m_config.reset(new ClangFormatFile(codeStyle));
|
|
m_config->setIsReadOnly(codeStyle->isReadOnly());
|
|
m_style = m_config->style();
|
|
|
|
m_checksWidget->setEnabled(!codeStyle->isReadOnly() && !codeStyle->isTemporarilyReadOnly()
|
|
&& codeStyle->isAdditionalTabVisible());
|
|
|
|
fillTable();
|
|
updatePreview();
|
|
}
|
|
|
|
void ClangFormatConfigWidget::connectChecks()
|
|
{
|
|
auto doSaveChanges = [this](QObject *sender) {
|
|
if (!m_ignoreChanges.isLocked())
|
|
saveChanges(sender);
|
|
};
|
|
|
|
for (QObject *child : m_checksWidget->children()) {
|
|
auto comboBox = qobject_cast<QComboBox *>(child);
|
|
if (comboBox != nullptr) {
|
|
connect(comboBox, &QComboBox::currentIndexChanged,
|
|
this, std::bind(doSaveChanges, comboBox));
|
|
comboBox->installEventFilter(this);
|
|
continue;
|
|
}
|
|
|
|
const auto button = qobject_cast<QPushButton *>(child);
|
|
if (button != nullptr)
|
|
connect(button, &QPushButton::clicked, this, std::bind(doSaveChanges, button));
|
|
}
|
|
}
|
|
|
|
static clang::format::FormatStyle constructStyle(const QByteArray &baseStyle = QByteArray())
|
|
{
|
|
if (!baseStyle.isEmpty()) {
|
|
// Try to get the style for this base style.
|
|
llvm::Expected<clang::format::FormatStyle> style
|
|
= clang::format::getStyle(baseStyle.toStdString(), "dummy.cpp", baseStyle.toStdString());
|
|
if (style)
|
|
return *style;
|
|
|
|
handleAllErrors(style.takeError(), [](const llvm::ErrorInfoBase &) {
|
|
// do nothing
|
|
});
|
|
// Fallthrough to the default style.
|
|
}
|
|
return qtcStyle();
|
|
}
|
|
|
|
FilePath ClangFormatConfigWidget::globalPath()
|
|
{
|
|
return Core::ICore::userResourcePath();
|
|
}
|
|
|
|
FilePath ClangFormatConfigWidget::projectPath()
|
|
{
|
|
if (m_project)
|
|
return globalPath().pathAppended("clang-format/" + projectUniqueId(m_project));
|
|
|
|
return {};
|
|
}
|
|
|
|
void ClangFormatConfigWidget::createStyleFileIfNeeded(bool isGlobal)
|
|
{
|
|
const FilePath path = isGlobal ? globalPath() : projectPath();
|
|
const FilePath configFile = path / Constants::SETTINGS_FILE_NAME;
|
|
|
|
if (configFile.exists())
|
|
return;
|
|
|
|
path.ensureWritableDir();
|
|
if (!isGlobal) {
|
|
FilePath possibleProjectConfig = m_project->rootProjectDirectory()
|
|
/ Constants::SETTINGS_FILE_NAME;
|
|
if (possibleProjectConfig.exists()) {
|
|
// Just copy th .clang-format if current project has one.
|
|
possibleProjectConfig.copyFile(configFile);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const std::string config = clang::format::configurationAsText(constructStyle());
|
|
configFile.writeFileContents(QByteArray::fromStdString(config));
|
|
}
|
|
|
|
void ClangFormatConfigWidget::showOrHideWidgets()
|
|
{
|
|
auto verticalLayout = qobject_cast<QVBoxLayout *>(layout());
|
|
QTC_ASSERT(verticalLayout, return);
|
|
|
|
QLayoutItem *lastItem = verticalLayout->itemAt(verticalLayout->count() - 1);
|
|
if (lastItem->spacerItem())
|
|
verticalLayout->removeItem(lastItem);
|
|
|
|
createStyleFileIfNeeded(!m_project);
|
|
m_fallbackConfig->show();
|
|
m_checksScrollArea->show();
|
|
m_preview->show();
|
|
}
|
|
|
|
void ClangFormatConfigWidget::updatePreview()
|
|
{
|
|
QTextCursor cursor(m_preview->document());
|
|
cursor.setPosition(0);
|
|
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
|
|
m_preview->textDocument()->autoFormatOrIndent(cursor);
|
|
}
|
|
|
|
std::string ClangFormatConfigWidget::readFile(const QString &path)
|
|
{
|
|
const std::string defaultStyle = clang::format::configurationAsText(qtcStyle());
|
|
|
|
QFile file(path);
|
|
if (!file.open(QFile::ReadOnly))
|
|
return defaultStyle;
|
|
|
|
const std::string content = file.readAll().toStdString();
|
|
file.close();
|
|
|
|
clang::format::FormatStyle style;
|
|
bool success = parseConfigurationFile(FilePath::fromString(path), style);
|
|
QTC_ASSERT(success, return defaultStyle);
|
|
|
|
addQtcStatementMacros(style);
|
|
std::string settings = clang::format::configurationAsText(style);
|
|
|
|
// Needed workaround because parseConfiguration remove BasedOnStyle field
|
|
// ToDo: standardize this behavior for future
|
|
const size_t index = content.find("BasedOnStyle");
|
|
if (index != std::string::npos) {
|
|
const size_t size = content.find("\n", index) - index;
|
|
const size_t insert_index = settings.find("\n");
|
|
settings.insert(insert_index, "\n" + content.substr(index, size));
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
static std::map<QString, QString> getMapFromString(const QString &text)
|
|
{
|
|
std::map<QString, QString> objectNameMap;
|
|
|
|
QString parentName;
|
|
for (QString line : text.split('\n')) {
|
|
if (line.isEmpty())
|
|
continue;
|
|
|
|
QStringList list = line.split(':');
|
|
QString key = !list.isEmpty() ? list[0] : "";
|
|
QString value = line.mid(key.size() + 1).trimmed();
|
|
|
|
if (line.contains(':') && value.isEmpty()) {
|
|
parentName = key;
|
|
continue;
|
|
}
|
|
|
|
if (!value.isEmpty() && !line.startsWith(" "))
|
|
parentName = "";
|
|
|
|
if (line.startsWith(" - ") || line.startsWith(" ")) {
|
|
line.remove(0, 2);
|
|
if (objectNameMap.find(parentName) == objectNameMap.end())
|
|
objectNameMap[parentName] = line + "\n";
|
|
else
|
|
objectNameMap[parentName] += line + "\n";
|
|
continue;
|
|
}
|
|
|
|
if (line.startsWith(" ")) {
|
|
key.remove(0, 2);
|
|
key = parentName + key;
|
|
objectNameMap.insert(std::make_pair(key, value));
|
|
continue;
|
|
}
|
|
|
|
objectNameMap.insert(std::make_pair(key, value));
|
|
}
|
|
|
|
return objectNameMap;
|
|
}
|
|
|
|
void ClangFormatConfigWidget::fillTable()
|
|
{
|
|
GuardLocker locker(m_ignoreChanges);
|
|
|
|
const QString configText = QString::fromStdString(readFile(m_config->filePath().path()));
|
|
std::map<QString, QString> objectNameMap = getMapFromString(configText);
|
|
|
|
for (QObject *child : m_checksWidget->children()) {
|
|
if (!qobject_cast<QComboBox *>(child) && !qobject_cast<QLineEdit *>(child)
|
|
&& !qobject_cast<QPlainTextEdit *>(child)) {
|
|
continue;
|
|
}
|
|
|
|
if (objectNameMap.find(child->objectName()) == objectNameMap.end())
|
|
continue;
|
|
|
|
if (QPlainTextEdit *plainText = qobject_cast<QPlainTextEdit *>(child)) {
|
|
plainText->setPlainText(objectNameMap[child->objectName()]);
|
|
continue;
|
|
}
|
|
|
|
if (QComboBox *comboBox = qobject_cast<QComboBox *>(child)) {
|
|
if (comboBox->findText(objectNameMap[child->objectName()]) == -1) {
|
|
comboBox->setCurrentIndex(0);
|
|
} else {
|
|
comboBox->setCurrentText(objectNameMap[child->objectName()]);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (QLineEdit *lineEdit = qobject_cast<QLineEdit *>(child)) {
|
|
lineEdit->setText(objectNameMap[child->objectName()]);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClangFormatConfigWidget::saveChanges(QObject *sender)
|
|
{
|
|
if (sender->objectName() == "BasedOnStyle") {
|
|
const auto *basedOnStyle = m_checksWidget->findChild<QComboBox *>("BasedOnStyle");
|
|
m_config->setBasedOnStyle(basedOnStyle->currentText());
|
|
} else {
|
|
QList<ClangFormatFile::Field> fields;
|
|
QString parentName;
|
|
|
|
for (QObject *child : m_checksWidget->children()) {
|
|
if (child->objectName() == "BasedOnStyle")
|
|
continue;
|
|
auto *label = qobject_cast<QLabel *>(child);
|
|
if (!label)
|
|
continue;
|
|
|
|
// reset parent name if label starts without " "
|
|
if (!label->text().startsWith(" "))
|
|
parentName = "";
|
|
|
|
QList<QWidget *> valueWidgets = m_checksWidget->findChildren<QWidget *>(
|
|
parentName + label->text().trimmed());
|
|
|
|
if (valueWidgets.empty()) {
|
|
// Currently BraceWrapping only.
|
|
fields.append({label->text(), ""});
|
|
// save parent name
|
|
parentName = label->text().trimmed();
|
|
continue;
|
|
}
|
|
|
|
QWidget *valueWidget = valueWidgets.first();
|
|
if (valueWidgets.size() > 1) {
|
|
for (QWidget *w : valueWidgets) {
|
|
if (w->objectName() == parentName + label->text().trimmed()) {
|
|
valueWidget = w;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!qobject_cast<QComboBox *>(valueWidget) && !qobject_cast<QLineEdit *>(valueWidget)
|
|
&& !qobject_cast<QPlainTextEdit *>(valueWidget)) {
|
|
continue;
|
|
}
|
|
|
|
auto *plainText = qobject_cast<QPlainTextEdit *>(valueWidget);
|
|
if (plainText) {
|
|
if (plainText->toPlainText().trimmed().isEmpty())
|
|
continue;
|
|
|
|
std::stringstream content;
|
|
QStringList list = plainText->toPlainText().split('\n');
|
|
for (const QString &line : list)
|
|
content << "\n " << line.toStdString();
|
|
|
|
fields.append({label->text(), QString::fromStdString(content.str())});
|
|
} else {
|
|
QString text;
|
|
if (auto *comboBox = qobject_cast<QComboBox *>(valueWidget)) {
|
|
text = comboBox->currentText();
|
|
} else {
|
|
auto *lineEdit = qobject_cast<QLineEdit *>(valueWidget);
|
|
QTC_ASSERT(lineEdit, continue;);
|
|
text = lineEdit->text();
|
|
}
|
|
|
|
if (!text.isEmpty() && text != "Default")
|
|
fields.append({label->text(), text});
|
|
}
|
|
}
|
|
m_config->changeFields(fields);
|
|
}
|
|
|
|
fillTable();
|
|
updatePreview();
|
|
}
|
|
|
|
void ClangFormatConfigWidget::apply()
|
|
{
|
|
if (!m_checksWidget->isVisible() && !m_checksWidget->isEnabled())
|
|
return;
|
|
|
|
m_style = m_config->style();
|
|
}
|
|
|
|
void ClangFormatConfigWidget::finish()
|
|
{
|
|
if (!m_checksWidget->isVisible() && !m_checksWidget->isEnabled())
|
|
return;
|
|
|
|
m_config->setStyle(m_style);
|
|
}
|
|
|
|
TextEditor::CodeStyleEditorWidget *createClangFormatConfigWidget(
|
|
TextEditor::ICodeStylePreferences *codeStyle,
|
|
Project *project,
|
|
QWidget *parent)
|
|
{
|
|
return new ClangFormatConfigWidget(codeStyle, project, parent);
|
|
}
|
|
|
|
} // namespace ClangFormat
|