ClangTools: Organize diagnostics by file path

* Introduce the file path as a top level node.
* Remove the location column.
  * Encode the line/column information in the DisplayRole, as for the
    Clang Code Model tooltips.
  * Double click on a diagnostic opens the editor.

Change-Id: I4c263537cc04c3c4feb6ccd5c395d60d8bee0bc3
Reviewed-by: Ivan Donchevskii <ivan.donchevskii@qt.io>
This commit is contained in:
Nikolai Kosjar
2019-01-25 10:17:56 +01:00
parent d386b3c241
commit 26a6cf3bb3
7 changed files with 246 additions and 86 deletions

View File

@@ -29,6 +29,7 @@
#include "clangtoolsprojectsettings.h"
#include "clangtoolsutils.h"
#include <coreplugin/fileiconprovider.h>
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
#include <utils/qtcassert.h>
@@ -37,11 +38,33 @@
#include <QFileInfo>
#include <QLoggingCategory>
#include <tuple>
static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.model", QtWarningMsg)
namespace ClangTools {
namespace Internal {
FilePathItem::FilePathItem(const QString &filePath)
: m_filePath(filePath)
{}
QVariant FilePathItem::data(int column, int role) const
{
if (column == DiagnosticView::DiagnosticColumn) {
switch (role) {
case Qt::DisplayRole:
return m_filePath;
case Qt::DecorationRole:
return Core::FileIconProvider::icon(m_filePath);
default:
return QVariant();
}
}
return QVariant();
}
class ExplainingStepItem : public Utils::TreeItem
{
public:
@@ -57,7 +80,7 @@ ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent)
: Utils::TreeModel<>(parent)
, m_filesWatcher(std::make_unique<QFileSystemWatcher>())
{
setHeader({tr("Issue"), tr("Location"), tr("Fixit Status")});
setHeader({tr("Issue"), tr("Fixit Status")});
connectFileWatcher();
}
@@ -85,6 +108,7 @@ void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnost
};
for (const Diagnostic &d : diagnostics) {
// Check for duplicates
const int previousItemCount = m_diagnostics.count();
m_diagnostics.insert(d);
if (m_diagnostics.count() == previousItemCount) {
@@ -92,9 +116,19 @@ void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnost
continue;
}
// Create file path item if necessary
const QString filePath = d.location.filePath;
FilePathItem *&filePathItem = m_filePathToItem[filePath];
if (!filePathItem) {
filePathItem = new FilePathItem(filePath);
rootItem()->appendChild(filePathItem);
addWatchedPath(d.location.filePath);
}
// Add to file path item
qCDebug(LOG) << "Adding diagnostic:" << d;
addWatchedPath(d.location.filePath);
rootItem()->appendChild(new DiagnosticItem(d, onFixitStatusChanged, this));
filePathItem->appendChild(new DiagnosticItem(d, onFixitStatusChanged, this));
}
}
@@ -105,6 +139,7 @@ QSet<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const
void ClangToolsDiagnosticModel::clear()
{
m_filePathToItem.clear();
m_diagnostics.clear();
clearAndSetupCache();
Utils::TreeModel<>::clear();
@@ -135,11 +170,11 @@ void ClangToolsDiagnosticModel::clearAndSetupCache()
void ClangToolsDiagnosticModel::onFileChanged(const QString &path)
{
for (Utils::TreeItem * const item : *rootItem()) {
rootItem()->forChildrenAtLevel(2, [&](Utils::TreeItem *item){
auto diagnosticItem = static_cast<DiagnosticItem *>(item);
if (diagnosticItem->diagnostic().location.filePath == path)
diagnosticItem->setFixItStatus(FixitStatus::Invalidated);
}
});
removeWatchedPath(path);
}
@@ -345,11 +380,15 @@ static QVariant iconData(const QString &type)
return QVariant();
}
static QString withLineColumnPrefixed(const QString &text,
const Debugger::DiagnosticLocation &location)
{
return QString("%1:%2: %3")
.arg(QString::number(location.line), QString::number(location.column), text);
}
QVariant DiagnosticItem::data(int column, int role) const
{
if (column == Debugger::DetailedErrorView::LocationColumn)
return Debugger::DetailedErrorView::locationData(role, m_diagnostic.location);
if (column == DiagnosticView::FixItColumn) {
if (role == Qt::CheckStateRole) {
switch (m_fixitStatus) {
@@ -378,24 +417,28 @@ QVariant DiagnosticItem::data(int column, int role) const
return ClangToolsDiagnosticModel::tr("Applied");
}
}
return QVariant();
} else if (column == DiagnosticView::DiagnosticColumn) {
switch (role) {
case Debugger::DetailedErrorView::LocationRole:
return QVariant::fromValue(m_diagnostic.location);
case Debugger::DetailedErrorView::FullTextRole:
return fullText(m_diagnostic);
case ClangToolsDiagnosticModel::DiagnosticRole:
return QVariant::fromValue(m_diagnostic);
case ClangToolsDiagnosticModel::TextRole:
return m_diagnostic.description;
case Qt::DisplayRole:
return withLineColumnPrefixed(m_diagnostic.description, m_diagnostic.location);
case Qt::ToolTipRole:
return createDiagnosticToolTipString(m_diagnostic);
case Qt::DecorationRole:
return iconData(m_diagnostic.type);
default:
return QVariant();
}
}
// DiagnosticColumn
switch (role) {
case Debugger::DetailedErrorView::FullTextRole:
return fullText(m_diagnostic);
case ClangToolsDiagnosticModel::DiagnosticRole:
return QVariant::fromValue(m_diagnostic);
case Qt::DisplayRole:
return m_diagnostic.description;
case Qt::ToolTipRole:
return createDiagnosticToolTipString(m_diagnostic);
case Qt::DecorationRole:
return iconData(m_diagnostic.type);
default:
return QVariant();
}
return QVariant();
}
bool DiagnosticItem::setData(int column, const QVariant &data, int role)
@@ -454,27 +497,32 @@ static QVariant iconForExplainingStepMessage(const QString &message)
QVariant ExplainingStepItem::data(int column, int role) const
{
if (column == Debugger::DetailedErrorView::LocationColumn)
return Debugger::DetailedErrorView::locationData(role, m_step.location);
if (column == DiagnosticView::FixItColumn)
return QVariant();
// DiagnosticColumn
switch (role) {
case Debugger::DetailedErrorView::FullTextRole:
return fullText(static_cast<DiagnosticItem *>(parent())->diagnostic());
case ClangToolsDiagnosticModel::DiagnosticRole:
return QVariant::fromValue(static_cast<DiagnosticItem *>(parent())->diagnostic());
case Qt::DisplayRole:
return m_step.message;
case Qt::ToolTipRole:
return createExplainingStepToolTipString(m_step);
case Qt::DecorationRole:
return iconForExplainingStepMessage(m_step.message);
default:
return QVariant();
if (column == DiagnosticView::DiagnosticColumn) {
// DiagnosticColumn
switch (role) {
case Debugger::DetailedErrorView::LocationRole:
return QVariant::fromValue(m_step.location);
case Debugger::DetailedErrorView::FullTextRole:
return fullText(static_cast<DiagnosticItem *>(parent())->diagnostic());
case ClangToolsDiagnosticModel::TextRole:
return m_step.message;
case ClangToolsDiagnosticModel::DiagnosticRole:
return QVariant::fromValue(static_cast<DiagnosticItem *>(parent())->diagnostic());
case Qt::DisplayRole:
return m_step.message;
case Qt::ToolTipRole:
return createExplainingStepToolTipString(m_step);
case Qt::DecorationRole:
return iconForExplainingStepMessage(m_step.message);
default:
return QVariant();
}
}
return QVariant();
}
@@ -518,30 +566,58 @@ void DiagnosticFilterModel::addSuppressedDiagnostic(
bool DiagnosticFilterModel::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
// Avoid filtering child diagnostics / explaining steps.
if (sourceParent.isValid())
return true;
// Is the diagnostic suppressed?
auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel());
auto item = static_cast<DiagnosticItem *>(model->rootItem()->childAt(sourceRow));
const Diagnostic &diag = item->diagnostic();
foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) {
if (d.description != diag.description)
continue;
QString filePath = d.filePath.toString();
QFileInfo fi(filePath);
if (fi.isRelative())
filePath = m_lastProjectDirectory.toString() + QLatin1Char('/') + filePath;
if (filePath == diag.location.filePath)
return false;
Utils::TreeItem *item = model->itemForIndex(sourceParent);
// DiagnosticItem
if (auto filePathItem = dynamic_cast<FilePathItem *>(item)) {
auto diagnosticItem = dynamic_cast<DiagnosticItem *>(filePathItem->childAt(sourceRow));
QTC_ASSERT(diagnosticItem, return false);
// Is the diagnostic explicitly suppressed?
const Diagnostic &diag = diagnosticItem->diagnostic();
foreach (const SuppressedDiagnostic &d, m_suppressedDiagnostics) {
if (d.description != diag.description)
continue;
QString filePath = d.filePath.toString();
QFileInfo fi(filePath);
if (fi.isRelative())
filePath = m_lastProjectDirectory.toString() + QLatin1Char('/') + filePath;
if (filePath == diag.location.filePath)
return false;
}
// Does the diagnostic match the filter?
return diag.description.contains(filterRegExp());
}
// Does the diagnostic match the filter?
if (diag.description.contains(filterRegExp()))
return true;
return true;
}
return false;
bool DiagnosticFilterModel::lessThan(const QModelIndex &l, const QModelIndex &r) const
{
auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel());
Utils::TreeItem *itemLeft = model->itemForIndex(l);
const bool isComparingDiagnostics = !dynamic_cast<FilePathItem *>(itemLeft);
if (sortColumn() == Debugger::DetailedErrorView::DiagnosticColumn && isComparingDiagnostics) {
using Debugger::DiagnosticLocation;
const int role = Debugger::DetailedErrorView::LocationRole;
const auto leftLoc = sourceModel()->data(l, role).value<DiagnosticLocation>();
const auto leftText = sourceModel()->data(l, ClangToolsDiagnosticModel::TextRole).toString();
const auto rightLoc = sourceModel()->data(r, role).value<DiagnosticLocation>();
const auto rightText = sourceModel()->data(r, ClangToolsDiagnosticModel::TextRole).toString();
const int result = std::tie(leftLoc.line, leftLoc.column, leftText)
< std::tie(rightLoc.line, rightLoc.column, rightText);
if (sortOrder() == Qt::DescendingOrder)
return !result; // Ensure that we always sort location from top to bottom.
return result;
}
return QSortFilterProxyModel::lessThan(l, r);
}
void DiagnosticFilterModel::handleSuppressedDiagnosticsChanged()