2014-09-25 11:11:58 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** This file is part of Qt Creator.
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
2014-09-25 11:11:58 +02:00
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
2016-01-14 10:59:10 +01:00
|
|
|
** 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.
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
2016-01-14 10:59:10 +01:00
|
|
|
** 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.
|
2014-09-25 11:11:58 +02:00
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
2018-01-17 15:08:30 +01:00
|
|
|
#include "clangtoolsdiagnosticmodel.h"
|
2014-09-25 11:11:58 +02:00
|
|
|
|
2018-05-08 17:33:29 +02:00
|
|
|
#include "clangtoolsdiagnosticview.h"
|
2018-05-08 16:47:27 +02:00
|
|
|
#include "clangtoolsprojectsettings.h"
|
2018-01-17 15:08:30 +01:00
|
|
|
#include "clangtoolsutils.h"
|
2014-09-25 11:11:58 +02:00
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
#include <coreplugin/fileiconprovider.h>
|
2015-02-19 18:08:38 +01:00
|
|
|
#include <projectexplorer/project.h>
|
|
|
|
|
#include <projectexplorer/session.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
2018-01-17 15:08:30 +01:00
|
|
|
#include <utils/utilsicons.h>
|
2015-02-19 18:08:38 +01:00
|
|
|
|
|
|
|
|
#include <QFileInfo>
|
2019-01-22 12:04:51 +01:00
|
|
|
#include <QLoggingCategory>
|
2014-09-25 11:11:58 +02:00
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
#include <tuple>
|
|
|
|
|
|
2019-01-22 12:04:51 +01:00
|
|
|
static Q_LOGGING_CATEGORY(LOG, "qtc.clangtools.model", QtWarningMsg)
|
|
|
|
|
|
2018-03-14 12:58:12 +01:00
|
|
|
namespace ClangTools {
|
2014-09-25 11:11:58 +02:00
|
|
|
namespace Internal {
|
|
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-19 15:37:16 +02:00
|
|
|
class ExplainingStepItem : public Utils::TreeItem
|
2014-09-25 11:11:58 +02:00
|
|
|
{
|
2015-06-19 15:37:16 +02:00
|
|
|
public:
|
|
|
|
|
ExplainingStepItem(const ExplainingStep &step);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QVariant data(int column, int role) const override;
|
|
|
|
|
|
|
|
|
|
const ExplainingStep m_step;
|
|
|
|
|
};
|
|
|
|
|
|
2018-01-17 15:08:30 +01:00
|
|
|
ClangToolsDiagnosticModel::ClangToolsDiagnosticModel(QObject *parent)
|
2016-06-24 09:36:42 +02:00
|
|
|
: Utils::TreeModel<>(parent)
|
2018-12-07 16:36:27 +01:00
|
|
|
, m_filesWatcher(std::make_unique<QFileSystemWatcher>())
|
2015-06-19 15:37:16 +02:00
|
|
|
{
|
2019-01-25 10:17:56 +01:00
|
|
|
setHeader({tr("Issue"), tr("Fixit Status")});
|
2018-12-07 16:36:27 +01:00
|
|
|
connectFileWatcher();
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-22 12:04:51 +01:00
|
|
|
QDebug operator<<(QDebug debug, const Diagnostic &d)
|
|
|
|
|
{
|
|
|
|
|
return debug << "category:" << d.category
|
|
|
|
|
<< "type:" << d.type
|
|
|
|
|
<< "context:" << d.issueContext
|
|
|
|
|
<< "contextKind:" << d.issueContextKind
|
|
|
|
|
<< "hasFixits:" << d.hasFixits
|
|
|
|
|
<< "explainingSteps:" << d.explainingSteps.size()
|
|
|
|
|
<< "location:" << d.location
|
|
|
|
|
<< "description:" << d.description
|
|
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-17 15:08:30 +01:00
|
|
|
void ClangToolsDiagnosticModel::addDiagnostics(const QList<Diagnostic> &diagnostics)
|
2014-09-25 11:11:58 +02:00
|
|
|
{
|
2018-05-16 14:49:12 +02:00
|
|
|
const auto onFixitStatusChanged = [this](FixitStatus newStatus) {
|
|
|
|
|
if (newStatus == FixitStatus::Scheduled)
|
|
|
|
|
++m_fixItsToApplyCount;
|
|
|
|
|
else
|
|
|
|
|
--m_fixItsToApplyCount;
|
2018-05-16 13:07:10 +02:00
|
|
|
emit fixItsToApplyCountChanged(m_fixItsToApplyCount);
|
|
|
|
|
};
|
|
|
|
|
|
2019-01-22 12:04:51 +01:00
|
|
|
for (const Diagnostic &d : diagnostics) {
|
2019-01-25 10:17:56 +01:00
|
|
|
// Check for duplicates
|
2019-01-22 12:04:51 +01:00
|
|
|
const int previousItemCount = m_diagnostics.count();
|
|
|
|
|
m_diagnostics.insert(d);
|
|
|
|
|
if (m_diagnostics.count() == previousItemCount) {
|
|
|
|
|
qCDebug(LOG) << "Not adding duplicate diagnostic:" << d;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2018-12-07 16:36:27 +01:00
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
// 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
|
2019-01-22 12:04:51 +01:00
|
|
|
qCDebug(LOG) << "Adding diagnostic:" << d;
|
2019-01-25 10:17:56 +01:00
|
|
|
filePathItem->appendChild(new DiagnosticItem(d, onFixitStatusChanged, this));
|
2019-01-22 12:04:51 +01:00
|
|
|
}
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-22 12:04:51 +01:00
|
|
|
QSet<Diagnostic> ClangToolsDiagnosticModel::diagnostics() const
|
2018-05-29 12:30:59 +02:00
|
|
|
{
|
2019-01-22 12:04:51 +01:00
|
|
|
return m_diagnostics;
|
2018-05-29 12:30:59 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-22 15:19:17 +01:00
|
|
|
void ClangToolsDiagnosticModel::clear()
|
|
|
|
|
{
|
2019-01-25 10:17:56 +01:00
|
|
|
m_filePathToItem.clear();
|
2019-01-22 12:04:51 +01:00
|
|
|
m_diagnostics.clear();
|
2019-01-22 15:19:17 +01:00
|
|
|
clearAndSetupCache();
|
|
|
|
|
Utils::TreeModel<>::clear();
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-07 16:36:27 +01:00
|
|
|
void ClangToolsDiagnosticModel::updateItems(const DiagnosticItem *changedItem)
|
|
|
|
|
{
|
|
|
|
|
for (auto item : stepsToItemsCache[changedItem->diagnostic().explainingSteps]) {
|
|
|
|
|
if (item != changedItem)
|
|
|
|
|
item->setFixItStatus(changedItem->fixItStatus());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangToolsDiagnosticModel::connectFileWatcher()
|
|
|
|
|
{
|
|
|
|
|
connect(m_filesWatcher.get(),
|
|
|
|
|
&QFileSystemWatcher::fileChanged,
|
|
|
|
|
this,
|
|
|
|
|
&ClangToolsDiagnosticModel::onFileChanged);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangToolsDiagnosticModel::clearAndSetupCache()
|
|
|
|
|
{
|
|
|
|
|
m_filesWatcher = std::make_unique<QFileSystemWatcher>();
|
|
|
|
|
connectFileWatcher();
|
|
|
|
|
stepsToItemsCache.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangToolsDiagnosticModel::onFileChanged(const QString &path)
|
|
|
|
|
{
|
2019-01-25 10:17:56 +01:00
|
|
|
rootItem()->forChildrenAtLevel(2, [&](Utils::TreeItem *item){
|
2018-12-07 16:36:27 +01:00
|
|
|
auto diagnosticItem = static_cast<DiagnosticItem *>(item);
|
|
|
|
|
if (diagnosticItem->diagnostic().location.filePath == path)
|
|
|
|
|
diagnosticItem->setFixItStatus(FixitStatus::Invalidated);
|
2019-01-25 10:17:56 +01:00
|
|
|
});
|
2018-12-07 16:36:27 +01:00
|
|
|
removeWatchedPath(path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangToolsDiagnosticModel::removeWatchedPath(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
m_filesWatcher->removePath(path);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ClangToolsDiagnosticModel::addWatchedPath(const QString &path)
|
|
|
|
|
{
|
|
|
|
|
m_filesWatcher->addPath(path);
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-25 11:11:58 +02:00
|
|
|
static QString createDiagnosticToolTipString(const Diagnostic &diagnostic)
|
|
|
|
|
{
|
2018-11-04 22:30:54 +01:00
|
|
|
using StringPair = QPair<QString, QString>;
|
2014-09-25 11:11:58 +02:00
|
|
|
QList<StringPair> lines;
|
|
|
|
|
|
|
|
|
|
if (!diagnostic.category.isEmpty()) {
|
|
|
|
|
lines << qMakePair(
|
2018-03-14 12:58:12 +01:00
|
|
|
QCoreApplication::translate("ClangTools::Diagnostic", "Category:"),
|
2014-11-14 16:32:36 +01:00
|
|
|
diagnostic.category.toHtmlEscaped());
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!diagnostic.type.isEmpty()) {
|
|
|
|
|
lines << qMakePair(
|
2018-03-14 12:58:12 +01:00
|
|
|
QCoreApplication::translate("ClangTools::Diagnostic", "Type:"),
|
2014-11-14 16:32:36 +01:00
|
|
|
diagnostic.type.toHtmlEscaped());
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
|
2018-05-22 15:35:12 +02:00
|
|
|
if (!diagnostic.description.isEmpty()) {
|
|
|
|
|
lines << qMakePair(
|
|
|
|
|
QCoreApplication::translate("ClangTools::Diagnostic", "Description:"),
|
|
|
|
|
diagnostic.description.toHtmlEscaped());
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-25 11:11:58 +02:00
|
|
|
if (!diagnostic.issueContext.isEmpty() && !diagnostic.issueContextKind.isEmpty()) {
|
|
|
|
|
lines << qMakePair(
|
2018-03-14 12:58:12 +01:00
|
|
|
QCoreApplication::translate("ClangTools::Diagnostic", "Context:"),
|
2014-11-14 16:32:36 +01:00
|
|
|
diagnostic.issueContextKind.toHtmlEscaped() + QLatin1Char(' ')
|
|
|
|
|
+ diagnostic.issueContext.toHtmlEscaped());
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines << qMakePair(
|
2018-03-14 12:58:12 +01:00
|
|
|
QCoreApplication::translate("ClangTools::Diagnostic", "Location:"),
|
2014-09-25 11:11:58 +02:00
|
|
|
createFullLocationString(diagnostic.location));
|
|
|
|
|
|
|
|
|
|
QString html = QLatin1String("<html>"
|
|
|
|
|
"<head>"
|
|
|
|
|
"<style>dt { font-weight:bold; } dd { font-family: monospace; }</style>\n"
|
|
|
|
|
"<body><dl>");
|
|
|
|
|
|
|
|
|
|
foreach (const StringPair &pair, lines) {
|
|
|
|
|
html += QLatin1String("<dt>");
|
|
|
|
|
html += pair.first;
|
|
|
|
|
html += QLatin1String("</dt><dd>");
|
|
|
|
|
html += pair.second;
|
|
|
|
|
html += QLatin1String("</dd>\n");
|
|
|
|
|
}
|
|
|
|
|
html += QLatin1String("</dl></body></html>");
|
|
|
|
|
return html;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-19 15:37:16 +02:00
|
|
|
static QString createExplainingStepToolTipString(const ExplainingStep &step)
|
2014-09-25 11:11:58 +02:00
|
|
|
{
|
2015-06-19 15:37:16 +02:00
|
|
|
if (step.message == step.extendedMessage)
|
|
|
|
|
return createFullLocationString(step.location);
|
|
|
|
|
|
2018-11-04 22:30:54 +01:00
|
|
|
using StringPair = QPair<QString, QString>;
|
2015-06-19 15:37:16 +02:00
|
|
|
QList<StringPair> lines;
|
|
|
|
|
|
|
|
|
|
if (!step.message.isEmpty()) {
|
|
|
|
|
lines << qMakePair(
|
2018-03-14 12:58:12 +01:00
|
|
|
QCoreApplication::translate("ClangTools::ExplainingStep", "Message:"),
|
2015-06-19 15:37:16 +02:00
|
|
|
step.message.toHtmlEscaped());
|
|
|
|
|
}
|
|
|
|
|
if (!step.extendedMessage.isEmpty()) {
|
|
|
|
|
lines << qMakePair(
|
2018-03-14 12:58:12 +01:00
|
|
|
QCoreApplication::translate("ClangTools::ExplainingStep", "Extended message:"),
|
2015-06-19 15:37:16 +02:00
|
|
|
step.extendedMessage.toHtmlEscaped());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines << qMakePair(
|
2018-03-14 12:58:12 +01:00
|
|
|
QCoreApplication::translate("ClangTools::ExplainingStep", "Location:"),
|
2015-06-19 15:37:16 +02:00
|
|
|
createFullLocationString(step.location));
|
|
|
|
|
|
|
|
|
|
QString html = QLatin1String("<html>"
|
|
|
|
|
"<head>"
|
|
|
|
|
"<style>dt { font-weight:bold; } dd { font-family: monospace; }</style>\n"
|
|
|
|
|
"<body><dl>");
|
|
|
|
|
|
|
|
|
|
foreach (const StringPair &pair, lines) {
|
|
|
|
|
html += QLatin1String("<dt>");
|
|
|
|
|
html += pair.first;
|
|
|
|
|
html += QLatin1String("</dt><dd>");
|
|
|
|
|
html += pair.second;
|
|
|
|
|
html += QLatin1String("</dd>\n");
|
|
|
|
|
}
|
|
|
|
|
html += QLatin1String("</dl></body></html>");
|
|
|
|
|
return html;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-02 13:57:37 +01:00
|
|
|
static QString createLocationString(const Debugger::DiagnosticLocation &location)
|
2015-06-19 15:37:16 +02:00
|
|
|
{
|
|
|
|
|
const QString filePath = location.filePath;
|
|
|
|
|
const QString lineNumber = QString::number(location.line);
|
|
|
|
|
const QString fileAndLine = filePath + QLatin1Char(':') + lineNumber;
|
|
|
|
|
return QLatin1String("in ") + fileAndLine;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString createExplainingStepNumberString(int number)
|
|
|
|
|
{
|
|
|
|
|
const int fieldWidth = 2;
|
|
|
|
|
return QString::fromLatin1("%1:").arg(number, fieldWidth);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString createExplainingStepString(const ExplainingStep &explainingStep, int number)
|
|
|
|
|
{
|
|
|
|
|
return createExplainingStepNumberString(number)
|
|
|
|
|
+ QLatin1Char(' ')
|
|
|
|
|
+ explainingStep.extendedMessage
|
|
|
|
|
+ QLatin1Char(' ')
|
|
|
|
|
+ createLocationString(explainingStep.location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QString fullText(const Diagnostic &diagnostic)
|
|
|
|
|
{
|
|
|
|
|
// Summary.
|
|
|
|
|
QString text = diagnostic.category + QLatin1String(": ") + diagnostic.type;
|
|
|
|
|
if (diagnostic.type != diagnostic.description)
|
|
|
|
|
text += QLatin1String(": ") + diagnostic.description;
|
|
|
|
|
text += QLatin1Char('\n');
|
|
|
|
|
|
|
|
|
|
// Explaining steps.
|
|
|
|
|
int explainingStepNumber = 1;
|
|
|
|
|
foreach (const ExplainingStep &explainingStep, diagnostic.explainingSteps) {
|
|
|
|
|
text += createExplainingStepString(explainingStep, explainingStepNumber++)
|
|
|
|
|
+ QLatin1Char('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
text.chop(1); // Trailing newline.
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-07 16:36:27 +01:00
|
|
|
DiagnosticItem::DiagnosticItem(const Diagnostic &diag,
|
|
|
|
|
const OnFixitStatusChanged &onFixitStatusChanged,
|
|
|
|
|
ClangToolsDiagnosticModel *parent)
|
2018-05-16 13:07:10 +02:00
|
|
|
: m_diagnostic(diag)
|
2018-05-16 14:49:12 +02:00
|
|
|
, m_onFixitStatusChanged(onFixitStatusChanged)
|
2018-12-07 16:36:27 +01:00
|
|
|
, m_parentModel(parent)
|
2015-06-19 15:37:16 +02:00
|
|
|
{
|
2018-05-16 14:49:12 +02:00
|
|
|
if (diag.hasFixits)
|
|
|
|
|
m_fixitStatus = FixitStatus::NotScheduled;
|
|
|
|
|
|
2015-06-19 15:37:16 +02:00
|
|
|
// Don't show explaining steps if they add no information.
|
|
|
|
|
if (diag.explainingSteps.count() == 1) {
|
|
|
|
|
const ExplainingStep &step = diag.explainingSteps.first();
|
|
|
|
|
if (step.message == diag.description && step.location == diag.location)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2018-12-07 16:36:27 +01:00
|
|
|
if (!diag.explainingSteps.isEmpty())
|
|
|
|
|
m_parentModel->stepsToItemsCache[diag.explainingSteps].push_back(this);
|
|
|
|
|
|
2015-06-19 15:37:16 +02:00
|
|
|
foreach (const ExplainingStep &s, diag.explainingSteps)
|
|
|
|
|
appendChild(new ExplainingStepItem(s));
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-24 09:03:44 +02:00
|
|
|
DiagnosticItem::~DiagnosticItem()
|
|
|
|
|
{
|
|
|
|
|
setFixitOperations(ReplacementOperations());
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-15 14:31:48 +02:00
|
|
|
Qt::ItemFlags DiagnosticItem::flags(int column) const
|
|
|
|
|
{
|
2018-05-16 13:24:40 +02:00
|
|
|
const Qt::ItemFlags itemFlags = TreeItem::flags(column);
|
|
|
|
|
if (column == DiagnosticView::FixItColumn) {
|
2018-05-16 14:49:12 +02:00
|
|
|
switch (m_fixitStatus) {
|
|
|
|
|
case FixitStatus::NotAvailable:
|
|
|
|
|
case FixitStatus::Applied:
|
|
|
|
|
case FixitStatus::FailedToApply:
|
|
|
|
|
case FixitStatus::Invalidated:
|
2018-05-16 13:24:40 +02:00
|
|
|
return itemFlags & ~Qt::ItemIsEnabled;
|
2018-05-16 14:49:12 +02:00
|
|
|
case FixitStatus::Scheduled:
|
|
|
|
|
case FixitStatus::NotScheduled:
|
|
|
|
|
return itemFlags | Qt::ItemIsUserCheckable;
|
|
|
|
|
}
|
2018-05-16 13:24:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return itemFlags;
|
2018-05-15 14:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2018-01-17 15:08:30 +01:00
|
|
|
static QVariant iconData(const QString &type)
|
|
|
|
|
{
|
|
|
|
|
if (type == "warning")
|
|
|
|
|
return Utils::Icons::CODEMODEL_WARNING.icon();
|
2018-08-17 15:35:13 +02:00
|
|
|
if (type == "error" || type == "fatal")
|
2018-01-17 15:08:30 +01:00
|
|
|
return Utils::Icons::CODEMODEL_ERROR.icon();
|
|
|
|
|
if (type == "note")
|
2019-01-24 15:39:15 +01:00
|
|
|
return Utils::Icons::INFO.icon();
|
2018-01-17 15:08:30 +01:00
|
|
|
if (type == "fix-it")
|
|
|
|
|
return Utils::Icons::CODEMODEL_FIXIT.icon();
|
|
|
|
|
return QVariant();
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
static QString withLineColumnPrefixed(const QString &text,
|
|
|
|
|
const Debugger::DiagnosticLocation &location)
|
2015-06-19 15:37:16 +02:00
|
|
|
{
|
2019-01-25 10:17:56 +01:00
|
|
|
return QString("%1:%2: %3")
|
|
|
|
|
.arg(QString::number(location.line), QString::number(location.column), text);
|
|
|
|
|
}
|
2014-09-25 11:11:58 +02:00
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
QVariant DiagnosticItem::data(int column, int role) const
|
|
|
|
|
{
|
2018-05-15 14:31:48 +02:00
|
|
|
if (column == DiagnosticView::FixItColumn) {
|
2018-05-16 14:49:12 +02:00
|
|
|
if (role == Qt::CheckStateRole) {
|
|
|
|
|
switch (m_fixitStatus) {
|
|
|
|
|
case FixitStatus::NotAvailable:
|
|
|
|
|
case FixitStatus::NotScheduled:
|
|
|
|
|
case FixitStatus::Invalidated:
|
|
|
|
|
case FixitStatus::Applied:
|
|
|
|
|
case FixitStatus::FailedToApply:
|
|
|
|
|
return Qt::Unchecked;
|
|
|
|
|
case FixitStatus::Scheduled:
|
|
|
|
|
return Qt::Checked;
|
|
|
|
|
}
|
|
|
|
|
} else if (role == Qt::DisplayRole) {
|
|
|
|
|
switch (m_fixitStatus) {
|
|
|
|
|
case FixitStatus::NotAvailable:
|
|
|
|
|
return ClangToolsDiagnosticModel::tr("No Fixits");
|
|
|
|
|
case FixitStatus::NotScheduled:
|
|
|
|
|
return ClangToolsDiagnosticModel::tr("Not Scheduled");
|
|
|
|
|
case FixitStatus::Invalidated:
|
|
|
|
|
return ClangToolsDiagnosticModel::tr("Invalidated");
|
|
|
|
|
case FixitStatus::Scheduled:
|
|
|
|
|
return ClangToolsDiagnosticModel::tr("Scheduled");
|
|
|
|
|
case FixitStatus::FailedToApply:
|
|
|
|
|
return ClangToolsDiagnosticModel::tr("Failed to Apply");
|
|
|
|
|
case FixitStatus::Applied:
|
|
|
|
|
return ClangToolsDiagnosticModel::tr("Applied");
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-01-25 10:17:56 +01:00
|
|
|
} 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();
|
|
|
|
|
}
|
2018-05-15 14:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
return QVariant();
|
2015-06-19 15:37:16 +02:00
|
|
|
}
|
2014-09-25 11:11:58 +02:00
|
|
|
|
2018-05-15 14:31:48 +02:00
|
|
|
bool DiagnosticItem::setData(int column, const QVariant &data, int role)
|
|
|
|
|
{
|
|
|
|
|
if (column == DiagnosticView::FixItColumn && role == Qt::CheckStateRole) {
|
2018-06-21 13:53:41 +02:00
|
|
|
if (m_fixitStatus != FixitStatus::Scheduled && m_fixitStatus != FixitStatus::NotScheduled)
|
|
|
|
|
return false;
|
|
|
|
|
|
2018-05-16 14:49:12 +02:00
|
|
|
const FixitStatus newStatus = data.value<Qt::CheckState>() == Qt::Checked
|
|
|
|
|
? FixitStatus::Scheduled
|
|
|
|
|
: FixitStatus::NotScheduled;
|
|
|
|
|
|
|
|
|
|
setFixItStatus(newStatus);
|
2018-12-07 16:36:27 +01:00
|
|
|
m_parentModel->updateItems(this);
|
2018-05-15 14:31:48 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Utils::TreeItem::setData(column, data, role);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-16 14:49:12 +02:00
|
|
|
void DiagnosticItem::setFixItStatus(const FixitStatus &status)
|
|
|
|
|
{
|
|
|
|
|
const FixitStatus oldStatus = m_fixitStatus;
|
|
|
|
|
m_fixitStatus = status;
|
|
|
|
|
update();
|
|
|
|
|
if (m_onFixitStatusChanged && status != oldStatus)
|
|
|
|
|
m_onFixitStatusChanged(status);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-24 09:03:44 +02:00
|
|
|
void DiagnosticItem::setFixitOperations(const ReplacementOperations &replacements)
|
2018-05-16 14:49:12 +02:00
|
|
|
{
|
2018-05-24 09:03:44 +02:00
|
|
|
qDeleteAll(m_fixitOperations);
|
|
|
|
|
m_fixitOperations = replacements;
|
2018-05-16 14:49:12 +02:00
|
|
|
}
|
|
|
|
|
|
2018-12-07 16:36:27 +01:00
|
|
|
bool DiagnosticItem::hasNewFixIts() const
|
|
|
|
|
{
|
|
|
|
|
if (m_diagnostic.explainingSteps.empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return m_parentModel->stepsToItemsCache[m_diagnostic.explainingSteps].front() == this;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-19 15:37:16 +02:00
|
|
|
ExplainingStepItem::ExplainingStepItem(const ExplainingStep &step) : m_step(step)
|
|
|
|
|
{
|
|
|
|
|
}
|
2014-09-25 11:11:58 +02:00
|
|
|
|
2019-01-24 15:39:15 +01:00
|
|
|
// We expect something like "note: ..."
|
|
|
|
|
static QVariant iconForExplainingStepMessage(const QString &message)
|
|
|
|
|
{
|
|
|
|
|
const int index = message.indexOf(':');
|
|
|
|
|
if (index == -1)
|
|
|
|
|
return QVariant();
|
|
|
|
|
return iconData(message.mid(0, index));
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-19 15:37:16 +02:00
|
|
|
QVariant ExplainingStepItem::data(int column, int role) const
|
|
|
|
|
{
|
2018-05-15 14:31:48 +02:00
|
|
|
if (column == DiagnosticView::FixItColumn)
|
|
|
|
|
return QVariant();
|
|
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
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();
|
|
|
|
|
}
|
2015-06-19 15:37:16 +02:00
|
|
|
}
|
2019-01-25 10:17:56 +01:00
|
|
|
|
|
|
|
|
return QVariant();
|
2014-09-25 11:11:58 +02:00
|
|
|
}
|
|
|
|
|
|
2015-02-19 18:08:38 +01:00
|
|
|
|
2018-05-08 17:33:29 +02:00
|
|
|
DiagnosticFilterModel::DiagnosticFilterModel(QObject *parent)
|
2015-02-19 18:08:38 +01:00
|
|
|
: QSortFilterProxyModel(parent)
|
|
|
|
|
{
|
|
|
|
|
// So that when a user closes and re-opens a project and *then* clicks "Suppress",
|
|
|
|
|
// we enter that information into the project settings.
|
|
|
|
|
connect(ProjectExplorer::SessionManager::instance(),
|
|
|
|
|
&ProjectExplorer::SessionManager::projectAdded, this,
|
|
|
|
|
[this](ProjectExplorer::Project *project) {
|
|
|
|
|
if (!m_project && project->projectDirectory() == m_lastProjectDirectory)
|
|
|
|
|
setProject(project);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 17:33:29 +02:00
|
|
|
void DiagnosticFilterModel::setProject(ProjectExplorer::Project *project)
|
2015-02-19 18:08:38 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(project, return);
|
|
|
|
|
if (m_project) {
|
2018-05-08 16:47:27 +02:00
|
|
|
disconnect(ClangToolsProjectSettingsManager::getSettings(m_project),
|
|
|
|
|
&ClangToolsProjectSettings::suppressedDiagnosticsChanged, this,
|
2018-05-08 17:33:29 +02:00
|
|
|
&DiagnosticFilterModel::handleSuppressedDiagnosticsChanged);
|
2015-02-19 18:08:38 +01:00
|
|
|
}
|
|
|
|
|
m_project = project;
|
|
|
|
|
m_lastProjectDirectory = m_project->projectDirectory();
|
2018-05-08 16:47:27 +02:00
|
|
|
connect(ClangToolsProjectSettingsManager::getSettings(m_project),
|
|
|
|
|
&ClangToolsProjectSettings::suppressedDiagnosticsChanged,
|
2018-05-08 17:33:29 +02:00
|
|
|
this, &DiagnosticFilterModel::handleSuppressedDiagnosticsChanged);
|
2015-02-19 18:08:38 +01:00
|
|
|
handleSuppressedDiagnosticsChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 17:33:29 +02:00
|
|
|
void DiagnosticFilterModel::addSuppressedDiagnostic(
|
2015-02-19 18:08:38 +01:00
|
|
|
const SuppressedDiagnostic &diag)
|
|
|
|
|
{
|
|
|
|
|
QTC_ASSERT(!m_project, return);
|
|
|
|
|
m_suppressedDiagnostics << diag;
|
|
|
|
|
invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-08 17:33:29 +02:00
|
|
|
bool DiagnosticFilterModel::filterAcceptsRow(int sourceRow,
|
2015-02-19 18:08:38 +01:00
|
|
|
const QModelIndex &sourceParent) const
|
|
|
|
|
{
|
2018-05-29 12:30:59 +02:00
|
|
|
auto model = static_cast<ClangToolsDiagnosticModel *>(sourceModel());
|
2019-01-25 10:17:56 +01:00
|
|
|
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());
|
2015-02-19 18:08:38 +01:00
|
|
|
}
|
2018-05-15 11:27:14 +02:00
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2018-05-15 11:27:14 +02:00
|
|
|
|
2019-01-25 10:17:56 +01:00
|
|
|
return QSortFilterProxyModel::lessThan(l, r);
|
2015-02-19 18:08:38 +01:00
|
|
|
}
|
|
|
|
|
|
2018-05-08 17:33:29 +02:00
|
|
|
void DiagnosticFilterModel::handleSuppressedDiagnosticsChanged()
|
2015-02-19 18:08:38 +01:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(m_project, return);
|
|
|
|
|
m_suppressedDiagnostics
|
2018-05-08 16:47:27 +02:00
|
|
|
= ClangToolsProjectSettingsManager::getSettings(m_project)->suppressedDiagnostics();
|
2015-02-19 18:08:38 +01:00
|
|
|
invalidate();
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-25 11:11:58 +02:00
|
|
|
} // namespace Internal
|
2018-03-14 12:58:12 +01:00
|
|
|
} // namespace ClangTools
|