diff --git a/src/plugins/clangtools/clangtidyclazyrunner.cpp b/src/plugins/clangtools/clangtidyclazyrunner.cpp index 9ad557f99bc..6b4ff3fac17 100644 --- a/src/plugins/clangtools/clangtidyclazyrunner.cpp +++ b/src/plugins/clangtools/clangtidyclazyrunner.cpp @@ -60,7 +60,7 @@ static QStringList tidyChecksArguments(const ClangDiagnosticConfig diagnosticCon if (tidyMode == ClangDiagnosticConfig::TidyMode::UseDefaultChecks) return {"-config={}", "-checks=-clang-diagnostic-*"}; if (tidyMode == ClangDiagnosticConfig::TidyMode::UseCustomChecks) - return {"-config={}", "-checks=" + diagnosticConfig.clangTidyChecks() + ",-clang-diagnostic-*"}; + return {"-config=" + diagnosticConfig.clangTidyChecksAsJson()}; return {"--warnings-as-errors=-*", "-check=-clang-diagnostic-*"}; } diff --git a/src/plugins/clangtools/diagnosticconfigswidget.cpp b/src/plugins/clangtools/diagnosticconfigswidget.cpp index 700dba7f8cc..2f12dcdf393 100644 --- a/src/plugins/clangtools/diagnosticconfigswidget.cpp +++ b/src/plugins/clangtools/diagnosticconfigswidget.cpp @@ -41,15 +41,86 @@ #include #include +#include #include +#include +#include #include #include +#include +#include +#include using namespace CppTools; namespace ClangTools { namespace Internal { +class TidyOptionsDialog : public QDialog +{ + Q_OBJECT +public: + TidyOptionsDialog(const QString &check, const ClangDiagnosticConfig::TidyCheckOptions &options, + QWidget *parent = nullptr) : QDialog(parent) + { + setWindowTitle(tr("Options for %1").arg(check)); + resize(600, 300); + const auto mainLayout = new QVBoxLayout(this); + const auto widgetLayout = new QHBoxLayout; + mainLayout->addLayout(widgetLayout); + m_optionsWidget.setColumnCount(2); + m_optionsWidget.setHeaderLabels({tr("Option"), tr("Value")}); + const auto addItem = [this](const QString &option, const QString &value) { + const auto item = new QTreeWidgetItem(&m_optionsWidget, QStringList{option, value}); + item->setFlags(item->flags() | Qt::ItemIsEditable); + return item; + }; + for (auto it = options.begin(); it != options.end(); ++it) + addItem(it.key(), it.value()); + m_optionsWidget.resizeColumnToContents(0); + widgetLayout->addWidget(&m_optionsWidget); + const auto buttonLayout = new QVBoxLayout; + widgetLayout->addLayout(buttonLayout); + const auto addButton = new QPushButton(tr("Add Option")); + connect(addButton, &QPushButton::clicked, this, [this, addItem] { + const auto item = addItem(tr(""), {}); + m_optionsWidget.editItem(item); + }); + buttonLayout->addWidget(addButton); + const auto removeButton = new QPushButton(tr("Remove Option")); + connect(removeButton, &QPushButton::clicked, this, [this] { + qDeleteAll(m_optionsWidget.selectedItems()); + }); + const auto toggleRemoveButtonEnabled = [this, removeButton] { + removeButton->setEnabled(!m_optionsWidget.selectionModel()->selectedRows().isEmpty()); + }; + connect(&m_optionsWidget, &QTreeWidget::itemSelectionChanged, + this, [toggleRemoveButtonEnabled] { toggleRemoveButtonEnabled(); }); + toggleRemoveButtonEnabled(); + buttonLayout->addWidget(removeButton); + buttonLayout->addStretch(1); + + const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok + | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + mainLayout->addWidget(buttonBox); + } + + ClangDiagnosticConfig::TidyCheckOptions options() const + { + ClangDiagnosticConfig::TidyCheckOptions opts; + for (int i = 0; i < m_optionsWidget.topLevelItemCount(); ++i) { + const QTreeWidgetItem * const item = m_optionsWidget.topLevelItem(i); + opts.insert(item->text(0), item->text(1)); + } + return opts; + } + +private: + QTreeWidget m_optionsWidget; +}; + namespace ClangTidyPrefixTree { class Node @@ -357,7 +428,6 @@ public: } } -private: QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const final { if (!fullIndex.isValid() || role == Qt::DecorationRole) @@ -380,12 +450,27 @@ private: return BaseChecksTreeModel::data(fullIndex, role); } + if (fullIndex.column() == 2) { + if (hasChildren(fullIndex)) + return {}; + if (role == Qt::DisplayRole) + return tr("Options"); + if (role == Qt::FontRole || role == Qt::ForegroundRole) { + return BaseChecksTreeModel::data(fullIndex.sibling(fullIndex.row(), LinkColumn), + role); + } + return {}; + } + if (role == Qt::DisplayRole) return node->isDir ? (node->name + "*") : node->name; return ProjectExplorer::SelectableFilesModel::data(index, role); } +private: + int columnCount(const QModelIndex &) const final { return 3; } + QModelIndex indexForCheck(const QString &check) const { if (check == "*") return index(0, 0, QModelIndex()); @@ -784,7 +869,29 @@ DiagnosticConfigsWidget::DiagnosticConfigsWidget(const ClangDiagnosticConfigs &c connect(m_tidyChecks->checksPrefixesTree, &QTreeView::clicked, - [model = m_tidyTreeModel.get()](const QModelIndex &index) { openUrl(model, index); }); + [this](const QModelIndex &index) { + if (index.column() == 2) { + if (m_tidyTreeModel->hasChildren(index)) + return; + ClangDiagnosticConfig config = currentConfig(); + QString check; + for (QModelIndex idx = index.siblingAtColumn(0); idx.isValid(); + idx = idx.parent()) { + QString current = m_tidyTreeModel->data(idx).toString(); + if (current.endsWith('*')) + current.chop(1); + check.prepend(current); + } + TidyOptionsDialog dlg(check, config.tidyCheckOptions(check), + m_tidyChecks->checksPrefixesTree); + if (dlg.exec() != QDialog::Accepted) + return; + config.setTidyCheckOptions(check, dlg.options()); + updateConfig(config); + return; + } + openUrl(m_tidyTreeModel.get(), index); + }); connect(m_tidyChecks->plainTextEditButton, &QPushButton::clicked, this, [this]() { const bool readOnly = currentConfig().isReadOnly(); diff --git a/src/plugins/cpptools/clangdiagnosticconfig.cpp b/src/plugins/cpptools/clangdiagnosticconfig.cpp index 3c3758c37a5..c31fc4f8ce5 100644 --- a/src/plugins/cpptools/clangdiagnosticconfig.cpp +++ b/src/plugins/cpptools/clangdiagnosticconfig.cpp @@ -79,6 +79,7 @@ bool ClangDiagnosticConfig::operator==(const ClangDiagnosticConfig &other) const && m_clangOptions == other.m_clangOptions && m_clangTidyMode == other.m_clangTidyMode && m_clangTidyChecks == other.m_clangTidyChecks + && m_tidyChecksOptions == other.m_tidyChecksOptions && m_clazyMode == other.m_clazyMode && m_clazyChecks == other.m_clazyChecks && m_isReadOnly == other.m_isReadOnly @@ -125,6 +126,28 @@ QString ClangDiagnosticConfig::clangTidyChecks() const return m_clangTidyChecks; } +QString ClangDiagnosticConfig::clangTidyChecksAsJson() const +{ + QString jsonString = "{Checks: '" + clangTidyChecks() + + ",-clang-diagnostic-*', CheckOptions: ["; + for (auto it = m_tidyChecksOptions.cbegin(); it != m_tidyChecksOptions.cend(); ++it) { + const int idx = m_clangTidyChecks.indexOf(it.key()); + if (idx == -1) + continue; + if (idx > 0 && m_clangTidyChecks.at(idx - 1) == '-') + continue; + QString optionString; + for (auto optIt = it.value().begin(); optIt != it.value().end(); ++optIt) { + if (!optionString.isEmpty()) + optionString += ','; + optionString += "{key: '" + it.key() + '.' + optIt.key() + + "', value: '" + optIt.value() + "'}"; + } + jsonString += optionString; + } + return jsonString += "]}"; +} + void ClangDiagnosticConfig::setClangTidyChecks(const QString &checks) { m_clangTidyChecks = checks; @@ -135,6 +158,42 @@ bool ClangDiagnosticConfig::isClangTidyEnabled() const return m_clangTidyMode != TidyMode::UseCustomChecks || clangTidyChecks() != "-*"; } +void ClangDiagnosticConfig::setTidyCheckOptions(const QString &check, + const TidyCheckOptions &options) +{ + m_tidyChecksOptions[check] = options; +} + +ClangDiagnosticConfig::TidyCheckOptions +ClangDiagnosticConfig::tidyCheckOptions(const QString &check) const +{ + return m_tidyChecksOptions.value(check); +} + +void ClangDiagnosticConfig::setTidyChecksOptionsFromSettings(const QVariant &options) +{ + const QVariantMap topLevelMap = options.toMap(); + for (auto it = topLevelMap.begin(); it != topLevelMap.end(); ++it) { + const QVariantMap optionsMap = it.value().toMap(); + TidyCheckOptions options; + for (auto optIt = optionsMap.begin(); optIt != optionsMap.end(); ++optIt) + options.insert(optIt.key(), optIt.value().toString()); + m_tidyChecksOptions.insert(it.key(), options); + } +} + +QVariant ClangDiagnosticConfig::tidyChecksOptionsForSettings() const +{ + QVariantMap topLevelMap; + for (auto it = m_tidyChecksOptions.cbegin(); it != m_tidyChecksOptions.cend(); ++it) { + QVariantMap optionsMap; + for (auto optIt = it.value().begin(); optIt != it.value().end(); ++optIt) + optionsMap.insert(optIt.key(), optIt.value()); + topLevelMap.insert(it.key(), optionsMap); + } + return topLevelMap; +} + QString ClangDiagnosticConfig::clazyChecks() const { return m_clazyChecks; @@ -168,6 +227,7 @@ static const char diagnosticConfigDisplayNameKey[] = "displayName"; static const char diagnosticConfigWarningsKey[] = "diagnosticOptions"; static const char useBuildSystemFlagsKey[] = "useBuildSystemFlags"; static const char diagnosticConfigsTidyChecksKey[] = "clangTidyChecks"; +static const char diagnosticConfigsTidyChecksOptionsKey[] = "clangTidyChecksOptions"; static const char diagnosticConfigsTidyModeKey[] = "clangTidyMode"; static const char diagnosticConfigsClazyModeKey[] = "clazyMode"; static const char diagnosticConfigsClazyChecksKey[] = "clazyChecks"; @@ -184,6 +244,7 @@ void diagnosticConfigsToSettings(QSettings *s, const ClangDiagnosticConfigs &con s->setValue(useBuildSystemFlagsKey, config.useBuildSystemWarnings()); s->setValue(diagnosticConfigsTidyModeKey, int(config.clangTidyMode())); s->setValue(diagnosticConfigsTidyChecksKey, config.clangTidyChecks()); + s->setValue(diagnosticConfigsTidyChecksOptionsKey, config.tidyChecksOptionsForSettings()); s->setValue(diagnosticConfigsClazyModeKey, int(config.clazyMode())); s->setValue(diagnosticConfigsClazyChecksKey, config.clazyChecks()); } @@ -210,6 +271,8 @@ ClangDiagnosticConfigs diagnosticConfigsFromSettings(QSettings *s) } else { config.setClangTidyMode(static_cast(tidyModeValue)); config.setClangTidyChecks(s->value(diagnosticConfigsTidyChecksKey).toString()); + config.setTidyChecksOptionsFromSettings( + s->value(diagnosticConfigsTidyChecksOptionsKey)); } config.setClazyMode(static_cast( diff --git a/src/plugins/cpptools/clangdiagnosticconfig.h b/src/plugins/cpptools/clangdiagnosticconfig.h index d442a1a312c..55e66e67df0 100644 --- a/src/plugins/cpptools/clangdiagnosticconfig.h +++ b/src/plugins/cpptools/clangdiagnosticconfig.h @@ -29,6 +29,7 @@ #include +#include #include #include @@ -69,10 +70,17 @@ public: void setClangTidyMode(TidyMode mode); QString clangTidyChecks() const; + QString clangTidyChecksAsJson() const; void setClangTidyChecks(const QString &checks); bool isClangTidyEnabled() const; + using TidyCheckOptions = QMap; + void setTidyCheckOptions(const QString &check, const TidyCheckOptions &options); + TidyCheckOptions tidyCheckOptions(const QString &check) const; + void setTidyChecksOptionsFromSettings(const QVariant &options); + QVariant tidyChecksOptionsForSettings() const; + // Clazy enum class ClazyMode { @@ -96,6 +104,7 @@ private: QStringList m_clangOptions; TidyMode m_clangTidyMode = TidyMode::UseDefaultChecks; QString m_clangTidyChecks; + QHash m_tidyChecksOptions; QString m_clazyChecks; ClazyMode m_clazyMode = ClazyMode::UseDefaultChecks; bool m_isReadOnly = false;