Clang: Use the tree instead of the list for Clang-Tidy settings

List of checks does not give enough flexibility to
select/unselect specific checks. The tree fixes that.

Also remove Clang-Tidy checks line edit because it is
now integrated into the tree mode as an alternative way
of providing checks by pressing "Plain text edit" button.

'cpptools_clangtidychecks.h' is generated using python
script 'generateClangTidyChecks.py' and clang-tidy
from our LLVM/Clang 6.0 build.

Change-Id: I2ed1738cb2cbbf8dac6aba563469f06f69b11593
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@qt.io>
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
This commit is contained in:
Ivan Donchevskii
2018-05-16 07:58:53 +02:00
parent b1fcbfecce
commit bc458c7b5f
13 changed files with 1085 additions and 209 deletions

View File

@@ -26,28 +26,169 @@
#include "clangdiagnosticconfigswidget.h"
#include "cppcodemodelsettings.h"
#include "cpptools_clangtidychecks.h"
#include "cpptoolsreuse.h"
#include "ui_clangdiagnosticconfigswidget.h"
#include "ui_clangbasechecks.h"
#include "ui_clazychecks.h"
#include "ui_tidychecks.h"
#include <projectexplorer/selectablefilesmodel.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
#include <QDebug>
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QPushButton>
#include <QUuid>
namespace CppTools {
static void buildTree(ProjectExplorer::Tree *parent,
ProjectExplorer::Tree *current,
const Constants::TidyNode &node)
{
current->name = QString::fromUtf8(node.name);
current->isDir = node.children.size();
if (parent) {
current->fullPath = parent->fullPath + current->name;
parent->childDirectories.push_back(current);
} else {
current->fullPath = Utils::FileName::fromString(current->name);
}
current->parent = parent;
for (const Constants::TidyNode &nodeChild : node.children)
buildTree(current, new ProjectExplorer::Tree, nodeChild);
}
class TidyChecksTreeModel final : public ProjectExplorer::SelectableFilesModel
{
Q_OBJECT
public:
TidyChecksTreeModel()
: ProjectExplorer::SelectableFilesModel(nullptr)
{
buildTree(nullptr, m_root, Constants::CLANG_TIDY_CHECKS_ROOT);
}
QString selectedChecks() const
{
QString checks;
collectChecks(m_root, checks);
return "-*" + checks;
}
void selectChecks(const QString &checks)
{
m_root->checked = Qt::Unchecked;
propagateDown(index(0, 0, QModelIndex()));
QStringList checksList = checks.simplified().remove(" ")
.split(",", QString::SkipEmptyParts);
for (QString &check : checksList) {
Qt::CheckState state;
if (check.startsWith("-")) {
check = check.right(check.length() - 1);
state = Qt::Unchecked;
} else {
state = Qt::Checked;
}
const QModelIndex index = indexForCheck(check);
if (!index.isValid())
continue;
auto node = static_cast<ProjectExplorer::Tree *>(index.internalPointer());
node->checked = state;
propagateUp(index);
propagateDown(index);
}
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const final
{
if (!index.isValid() || role == Qt::DecorationRole)
return QVariant();
if (role == Qt::DisplayRole) {
auto *node = static_cast<ProjectExplorer::Tree *>(index.internalPointer());
return node->isDir ? (node->name + "*") : node->name;
}
return ProjectExplorer::SelectableFilesModel::data(index, role);
}
private:
// TODO: Remove/replace this method after base class refactoring is done.
void traverse(const QModelIndex &index,
const std::function<bool(const QModelIndex &)> &visit) const
{
if (!index.isValid())
return;
if (!visit(index))
return;
if (!hasChildren(index))
return;
const int rows = rowCount(index);
const int cols = columnCount(index);
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j)
traverse(this->index(i, j, index), visit);
}
}
QModelIndex indexForCheck(const QString &check) const {
if (check == "*")
return index(0, 0, QModelIndex());
QModelIndex result;
traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
using ProjectExplorer::Tree;
if (result.isValid())
return false;
auto *node = static_cast<Tree *>(index.internalPointer());
const QString nodeName = node->fullPath.toString();
if ((check.endsWith("*") && nodeName.startsWith(check.left(check.length() - 1)))
|| (!node->isDir && nodeName == check)) {
result = index;
return false;
}
if (!check.startsWith(nodeName))
return false;
return true;
});
return result;
}
static void collectChecks(const ProjectExplorer::Tree *root, QString &checks)
{
if (root->checked == Qt::Unchecked)
return;
if (root->checked == Qt::Checked) {
checks += "," + root->fullPath.toString();
if (root->isDir)
checks += "*";
return;
}
for (const ProjectExplorer::Tree *t : root->childDirectories)
collectChecks(t, checks);
}
};
ClangDiagnosticConfigsWidget::ClangDiagnosticConfigsWidget(const Core::Id &configToSelect,
QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::ClangDiagnosticConfigsWidget)
, m_diagnosticConfigsModel(codeModelSettings()->clangCustomDiagnosticConfigs())
, m_tidyTreeModel(new TidyChecksTreeModel())
{
m_ui->setupUi(this);
setupTabs();
@@ -134,22 +275,10 @@ void ClangDiagnosticConfigsWidget::onClangTidyModeChanged(int index)
syncClangTidyWidgets(config);
}
void ClangDiagnosticConfigsWidget::onClangTidyItemChanged(QListWidgetItem *item)
{
const QString prefix = item->text();
ClangDiagnosticConfig config = selectedConfig();
QString checks = config.clangTidyChecksPrefixes();
item->checkState() == Qt::Checked
? checks.append(',' + prefix)
: checks.remove(',' + prefix);
config.setClangTidyChecksPrefixes(checks);
updateConfig(config);
}
void ClangDiagnosticConfigsWidget::onClangTidyLineEdited(const QString &text)
void ClangDiagnosticConfigsWidget::onClangTidyTreeChanged()
{
ClangDiagnosticConfig config = selectedConfig();
config.setClangTidyChecksString(text);
config.setClangTidyChecks(m_tidyTreeModel->selectedChecks());
updateConfig(config);
}
@@ -302,18 +431,13 @@ void ClangDiagnosticConfigsWidget::syncClangTidyWidgets(const ClangDiagnosticCon
switch (tidyMode) {
case ClangDiagnosticConfig::TidyMode::Disabled:
case ClangDiagnosticConfig::TidyMode::File:
m_tidyChecks->checksString->setVisible(false);
m_tidyChecks->plainTextEditButton->setVisible(false);
m_tidyChecks->checksListWrapper->setCurrentIndex(1);
break;
case ClangDiagnosticConfig::TidyMode::ChecksString:
m_tidyChecks->checksString->setVisible(true);
m_tidyChecks->checksListWrapper->setCurrentIndex(1);
m_tidyChecks->checksString->setText(config.clangTidyChecksString());
break;
case ClangDiagnosticConfig::TidyMode::ChecksPrefixList:
m_tidyChecks->checksString->setVisible(false);
m_tidyChecks->plainTextEditButton->setVisible(true);
m_tidyChecks->checksListWrapper->setCurrentIndex(0);
syncTidyChecksList(config);
syncTidyChecksToTree(config);
break;
}
@@ -321,25 +445,9 @@ void ClangDiagnosticConfigsWidget::syncClangTidyWidgets(const ClangDiagnosticCon
connectClangTidyItemChanged();
}
void ClangDiagnosticConfigsWidget::syncTidyChecksList(const ClangDiagnosticConfig &config)
void ClangDiagnosticConfigsWidget::syncTidyChecksToTree(const ClangDiagnosticConfig &config)
{
const QString tidyChecks = config.clangTidyChecksPrefixes();
for (int row = 0; row < m_tidyChecks->checksPrefixesList->count(); ++row) {
QListWidgetItem *item = m_tidyChecks->checksPrefixesList->item(row);
Qt::ItemFlags flags = item->flags();
flags |= Qt::ItemIsUserCheckable;
if (config.isReadOnly())
flags &= ~Qt::ItemIsEnabled;
else
flags |= Qt::ItemIsEnabled;
item->setFlags(flags);
if (tidyChecks.indexOf(item->text()) != -1)
item->setCheckState(Qt::Checked);
else
item->setCheckState(Qt::Unchecked);
}
m_tidyTreeModel->selectChecks(config.clangTidyChecks());
}
void ClangDiagnosticConfigsWidget::syncClazyWidgets(const ClangDiagnosticConfig &config)
@@ -411,10 +519,8 @@ void ClangDiagnosticConfigsWidget::connectClangTidyItemChanged()
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&ClangDiagnosticConfigsWidget::onClangTidyModeChanged);
connect(m_tidyChecks->checksPrefixesList, &QListWidget::itemChanged,
this, &ClangDiagnosticConfigsWidget::onClangTidyItemChanged);
connect(m_tidyChecks->checksString, &QLineEdit::textEdited,
this, &ClangDiagnosticConfigsWidget::onClangTidyLineEdited);
connect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged,
this, &ClangDiagnosticConfigsWidget::onClangTidyTreeChanged);
}
void ClangDiagnosticConfigsWidget::disconnectClangTidyItemChanged()
@@ -423,10 +529,8 @@ void ClangDiagnosticConfigsWidget::disconnectClangTidyItemChanged()
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
&ClangDiagnosticConfigsWidget::onClangTidyModeChanged);
disconnect(m_tidyChecks->checksPrefixesList, &QListWidget::itemChanged,
this, &ClangDiagnosticConfigsWidget::onClangTidyItemChanged);
disconnect(m_tidyChecks->checksString, &QLineEdit::textEdited,
this, &ClangDiagnosticConfigsWidget::onClangTidyLineEdited);
disconnect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged,
this, &ClangDiagnosticConfigsWidget::onClangTidyTreeChanged);
}
void ClangDiagnosticConfigsWidget::connectClazyRadioButtonClicked(QRadioButton *button)
@@ -493,6 +597,37 @@ void ClangDiagnosticConfigsWidget::setupTabs()
m_tidyChecks.reset(new CppTools::Ui::TidyChecks);
m_tidyChecksWidget = new QWidget();
m_tidyChecks->setupUi(m_tidyChecksWidget);
m_tidyChecks->checksPrefixesTree->setModel(m_tidyTreeModel.get());
m_tidyChecks->checksPrefixesTree->expandToDepth(0);
connect(m_tidyChecks->plainTextEditButton, &QPushButton::clicked, this, [this]() {
QDialog dialog;
dialog.setWindowTitle(tr("Checks"));
dialog.setLayout(new QVBoxLayout);
auto *textEdit = new QTextEdit(&dialog);
dialog.layout()->addWidget(textEdit);
auto *buttonsBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
dialog.layout()->addWidget(buttonsBox);
QObject::connect(buttonsBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
QObject::connect(buttonsBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
const QString initialChecks = m_tidyTreeModel->selectedChecks();
textEdit->setPlainText(initialChecks);
QObject::connect(&dialog, &QDialog::accepted, [=, &initialChecks]() {
const QString updatedChecks = textEdit->toPlainText();
if (updatedChecks == initialChecks)
return;
disconnectClangTidyItemChanged();
// Also throws away invalid options.
m_tidyTreeModel->selectChecks(updatedChecks);
onClangTidyTreeChanged();
connectClangTidyItemChanged();
});
dialog.exec();
});
connectClangTidyItemChanged();
m_ui->tabWidget->addTab(m_clangBaseChecksWidget, tr("Clang"));
@@ -502,3 +637,5 @@ void ClangDiagnosticConfigsWidget::setupTabs()
}
} // CppTools namespace
#include "clangdiagnosticconfigswidget.moc"