forked from qt-creator/qt-creator
Replace the filter line edit in the toolbar by a tool button that pop ups a dialog. In the dialog, the available checkers can be selectd/unselected to filter the diagnostic view. Also, the diagnostic view can be limited to diagnostics with fixits so that these can be selected and applied as the next step. For convience, add also some context menu entries to modify the filter with regard to the current diagnostic. Change-Id: Ifba3028805840658d72a39516c2b02da9864d4a6 Reviewed-by: Cristian Adam <cristian.adam@qt.io>
1256 lines
45 KiB
C++
1256 lines
45 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2018 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** 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.
|
|
**
|
|
** 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.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "clangtool.h"
|
|
|
|
#include "clangfixitsrefactoringchanges.h"
|
|
#include "clangselectablefilesdialog.h"
|
|
#include "clangtoolruncontrol.h"
|
|
#include "clangtoolsconstants.h"
|
|
#include "clangtoolsdiagnostic.h"
|
|
#include "clangtoolsdiagnosticmodel.h"
|
|
#include "clangtoolsdiagnosticview.h"
|
|
#include "clangtoolslogfilereader.h"
|
|
#include "clangtoolsplugin.h"
|
|
#include "clangtoolsprojectsettings.h"
|
|
#include "clangtoolssettings.h"
|
|
#include "clangtoolsutils.h"
|
|
#include "filterdialog.h"
|
|
|
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
#include <coreplugin/coreconstants.h>
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/messagebox.h>
|
|
|
|
#include <cpptools/clangdiagnosticconfigsmodel.h>
|
|
#include <cpptools/cppmodelmanager.h>
|
|
|
|
#include <debugger/analyzer/analyzermanager.h>
|
|
|
|
#include <projectexplorer/kitinformation.h>
|
|
#include <projectexplorer/projectexplorer.h>
|
|
#include <projectexplorer/projectexplorericons.h>
|
|
#include <projectexplorer/session.h>
|
|
#include <projectexplorer/target.h>
|
|
|
|
#include <texteditor/textdocument.h>
|
|
|
|
#include <utils/algorithm.h>
|
|
#include <utils/checkablemessagebox.h>
|
|
#include <utils/fancylineedit.h>
|
|
#include <utils/fancymainwindow.h>
|
|
#include <utils/progressindicator.h>
|
|
#include <utils/theme/theme.h>
|
|
#include <utils/utilsicons.h>
|
|
|
|
#include <QAction>
|
|
#include <QCheckBox>
|
|
#include <QFileDialog>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QSortFilterProxyModel>
|
|
#include <QToolButton>
|
|
|
|
using namespace Core;
|
|
using namespace CppTools;
|
|
using namespace Debugger;
|
|
using namespace ProjectExplorer;
|
|
using namespace Utils;
|
|
|
|
namespace ClangTools {
|
|
namespace Internal {
|
|
|
|
static ClangTool *s_instance;
|
|
|
|
static QString makeLink(const QString &text)
|
|
{
|
|
return QString("<a href=t>%1</a>").arg(text);
|
|
}
|
|
|
|
class IconAndLabel : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
IconAndLabel(const QPixmap &pixmap, const QString &text = {})
|
|
: m_icon(new QLabel)
|
|
, m_label(new QLabel)
|
|
{
|
|
QSizePolicy minSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
minSizePolicy.setHorizontalStretch(0);
|
|
|
|
m_icon->setPixmap(pixmap);
|
|
m_icon->setSizePolicy(minSizePolicy);
|
|
|
|
m_label->setSizePolicy(minSizePolicy);
|
|
m_label->setText(text);
|
|
m_label->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout;
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->addWidget(m_icon);
|
|
layout->addWidget(m_label);
|
|
setLayout(layout);
|
|
|
|
connect(m_label, &QLabel::linkActivated, this, &IconAndLabel::linkActivated);
|
|
}
|
|
|
|
void setIcon(const QPixmap &pixmap) {
|
|
m_icon->setPixmap(pixmap);
|
|
m_icon->setVisible(!pixmap.isNull());
|
|
}
|
|
|
|
QString text() const { return m_label->text(); }
|
|
void setText(const QString &text)
|
|
{
|
|
m_label->setText(text);
|
|
setVisible(!text.isEmpty());
|
|
}
|
|
|
|
signals:
|
|
void linkActivated(const QString &link);
|
|
|
|
private:
|
|
QLabel *m_icon;
|
|
QLabel *m_label;
|
|
};
|
|
|
|
class InfoBarWidget : public QFrame
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
InfoBarWidget()
|
|
: m_progressIndicator(new Utils::ProgressIndicator(ProgressIndicatorSize::Small))
|
|
, m_info(new IconAndLabel(Utils::Icons::INFO.pixmap()))
|
|
, m_error(new IconAndLabel(Utils::Icons::WARNING.pixmap()))
|
|
, m_diagStats(new QLabel)
|
|
{
|
|
m_diagStats->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
|
|
|
QHBoxLayout *layout = new QHBoxLayout;
|
|
layout->setContentsMargins(5, 5, 5, 5);
|
|
layout->addWidget(m_progressIndicator);
|
|
layout->addWidget(m_info);
|
|
layout->addWidget(m_error);
|
|
layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
|
|
layout->addWidget(m_diagStats);
|
|
setLayout(layout);
|
|
|
|
QPalette pal;
|
|
pal.setColor(QPalette::Window, Utils::creatorTheme()->color(Utils::Theme::InfoBarBackground));
|
|
pal.setColor(QPalette::WindowText, Utils::creatorTheme()->color(Utils::Theme::InfoBarText));
|
|
setPalette(pal);
|
|
|
|
setAutoFillBackground(true);
|
|
}
|
|
|
|
// Info
|
|
enum InfoIconType { ProgressIcon, InfoIcon };
|
|
void setInfoIcon(InfoIconType type)
|
|
{
|
|
const bool showProgress = type == ProgressIcon;
|
|
m_progressIndicator->setVisible(showProgress);
|
|
m_info->setIcon(showProgress ? QPixmap() : Utils::Icons::INFO.pixmap());
|
|
}
|
|
QString infoText() const { return m_info->text(); }
|
|
void setInfoText(const QString &text)
|
|
{
|
|
m_info->setText(text);
|
|
evaluateVisibility();
|
|
}
|
|
|
|
// Error
|
|
using OnLinkActivated = std::function<void()>;
|
|
enum IssueType { Warning, Error };
|
|
|
|
QString errorText() const { return m_error->text(); }
|
|
void setError(IssueType type,
|
|
const QString &text,
|
|
const OnLinkActivated &linkAction = OnLinkActivated())
|
|
{
|
|
m_error->setText(text);
|
|
m_error->setIcon(type == Warning ? Utils::Icons::WARNING.pixmap()
|
|
: Utils::Icons::CRITICAL.pixmap());
|
|
m_error->disconnect();
|
|
if (linkAction)
|
|
connect(m_error, &IconAndLabel::linkActivated, this, linkAction);
|
|
evaluateVisibility();
|
|
}
|
|
|
|
// Diag stats
|
|
void setDiagText(const QString &text) { m_diagStats->setText(text); }
|
|
|
|
void reset()
|
|
{
|
|
setInfoIcon(InfoIcon);
|
|
setInfoText({});
|
|
setError(Warning, {}, {});
|
|
setDiagText({});
|
|
}
|
|
|
|
void evaluateVisibility()
|
|
{
|
|
setVisible(!infoText().isEmpty() || !errorText().isEmpty());
|
|
}
|
|
|
|
private:
|
|
Utils::ProgressIndicator *m_progressIndicator;
|
|
IconAndLabel *m_info;
|
|
IconAndLabel *m_error;
|
|
QLabel *m_diagStats;
|
|
};
|
|
|
|
class SelectFixitsCheckBox : public QCheckBox
|
|
{
|
|
Q_OBJECT
|
|
|
|
private:
|
|
void nextCheckState() final override
|
|
{
|
|
setCheckState(checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
|
|
}
|
|
};
|
|
|
|
class ApplyFixIts
|
|
{
|
|
public:
|
|
class RefactoringFileInfo
|
|
{
|
|
public:
|
|
bool isValid() const { return file.isValid(); }
|
|
|
|
FixitsRefactoringFile file;
|
|
QVector<DiagnosticItem *> diagnosticItems;
|
|
bool hasScheduledFixits = false;
|
|
};
|
|
|
|
ApplyFixIts(const QVector<DiagnosticItem *> &diagnosticItems)
|
|
{
|
|
for (DiagnosticItem *diagnosticItem : diagnosticItems) {
|
|
const QString &filePath = diagnosticItem->diagnostic().location.filePath;
|
|
QTC_ASSERT(!filePath.isEmpty(), continue);
|
|
|
|
// Get or create refactoring file
|
|
RefactoringFileInfo &fileInfo = m_refactoringFileInfos[filePath];
|
|
if (!fileInfo.isValid())
|
|
fileInfo.file = FixitsRefactoringFile(filePath);
|
|
|
|
// Append item
|
|
fileInfo.diagnosticItems += diagnosticItem;
|
|
if (diagnosticItem->fixItStatus() == FixitStatus::Scheduled)
|
|
fileInfo.hasScheduledFixits = true;
|
|
}
|
|
}
|
|
|
|
static void addFixitOperations(DiagnosticItem *diagnosticItem,
|
|
const FixitsRefactoringFile &file, bool apply)
|
|
{
|
|
if (!diagnosticItem->hasNewFixIts())
|
|
return;
|
|
|
|
// Did we already created the fixit operations?
|
|
ReplacementOperations currentOps = diagnosticItem->fixitOperations();
|
|
if (!currentOps.isEmpty()) {
|
|
for (ReplacementOperation *op : currentOps)
|
|
op->apply = apply;
|
|
return;
|
|
}
|
|
|
|
// Collect/construct the fixit operations
|
|
ReplacementOperations replacements;
|
|
|
|
for (const ExplainingStep &step : diagnosticItem->diagnostic().explainingSteps) {
|
|
if (!step.isFixIt)
|
|
continue;
|
|
|
|
const Debugger::DiagnosticLocation start = step.ranges.first();
|
|
const Debugger::DiagnosticLocation end = step.ranges.last();
|
|
const int startPos = file.position(start.filePath, start.line, start.column);
|
|
const int endPos = file.position(start.filePath, end.line, end.column);
|
|
|
|
auto op = new ReplacementOperation;
|
|
op->pos = startPos;
|
|
op->length = endPos - startPos;
|
|
op->text = step.message;
|
|
op->fileName = start.filePath;
|
|
op->apply = apply;
|
|
|
|
replacements += op;
|
|
}
|
|
|
|
diagnosticItem->setFixitOperations(replacements);
|
|
}
|
|
|
|
void apply(ClangToolsDiagnosticModel *model)
|
|
{
|
|
for (auto it = m_refactoringFileInfos.begin(); it != m_refactoringFileInfos.end(); ++it) {
|
|
RefactoringFileInfo &fileInfo = it.value();
|
|
|
|
QVector<DiagnosticItem *> itemsScheduledOrSchedulable;
|
|
QVector<DiagnosticItem *> itemsScheduled;
|
|
QVector<DiagnosticItem *> itemsSchedulable;
|
|
|
|
// Construct refactoring operations
|
|
for (DiagnosticItem *diagnosticItem : fileInfo.diagnosticItems) {
|
|
const FixitStatus fixItStatus = diagnosticItem->fixItStatus();
|
|
|
|
const bool isScheduled = fixItStatus == FixitStatus::Scheduled;
|
|
const bool isSchedulable = fileInfo.hasScheduledFixits
|
|
&& fixItStatus == FixitStatus::NotScheduled;
|
|
|
|
if (isScheduled || isSchedulable) {
|
|
addFixitOperations(diagnosticItem, fileInfo.file, isScheduled);
|
|
itemsScheduledOrSchedulable += diagnosticItem;
|
|
if (isScheduled)
|
|
itemsScheduled += diagnosticItem;
|
|
else
|
|
itemsSchedulable += diagnosticItem;
|
|
}
|
|
}
|
|
|
|
// Collect replacements
|
|
ReplacementOperations ops;
|
|
for (DiagnosticItem *item : itemsScheduledOrSchedulable)
|
|
ops += item->fixitOperations();
|
|
|
|
if (ops.empty())
|
|
continue;
|
|
|
|
// Apply file
|
|
QVector<DiagnosticItem *> itemsApplied;
|
|
QVector<DiagnosticItem *> itemsFailedToApply;
|
|
QVector<DiagnosticItem *> itemsInvalidated;
|
|
|
|
fileInfo.file.setReplacements(ops);
|
|
model->removeWatchedPath(ops.first()->fileName);
|
|
if (fileInfo.file.apply()) {
|
|
itemsApplied = itemsScheduled;
|
|
} else {
|
|
itemsFailedToApply = itemsScheduled;
|
|
itemsInvalidated = itemsSchedulable;
|
|
}
|
|
model->addWatchedPath(ops.first()->fileName);
|
|
|
|
// Update DiagnosticItem state
|
|
for (DiagnosticItem *diagnosticItem : itemsScheduled)
|
|
diagnosticItem->setFixItStatus(FixitStatus::Applied);
|
|
for (DiagnosticItem *diagnosticItem : itemsFailedToApply)
|
|
diagnosticItem->setFixItStatus(FixitStatus::FailedToApply);
|
|
for (DiagnosticItem *diagnosticItem : itemsInvalidated)
|
|
diagnosticItem->setFixItStatus(FixitStatus::Invalidated);
|
|
}
|
|
}
|
|
|
|
private:
|
|
QMap<QString, RefactoringFileInfo> m_refactoringFileInfos;
|
|
};
|
|
|
|
static FileInfos sortedFileInfos(const QVector<CppTools::ProjectPart::Ptr> &projectParts)
|
|
{
|
|
FileInfos fileInfos;
|
|
|
|
for (CppTools::ProjectPart::Ptr projectPart : projectParts) {
|
|
QTC_ASSERT(projectPart, continue);
|
|
if (!projectPart->selectedForBuilding)
|
|
continue;
|
|
|
|
for (const CppTools::ProjectFile &file : projectPart->files) {
|
|
QTC_ASSERT(file.kind != CppTools::ProjectFile::Unclassified, continue);
|
|
QTC_ASSERT(file.kind != CppTools::ProjectFile::Unsupported, continue);
|
|
if (file.path == CppTools::CppModelManager::configurationFileName())
|
|
continue;
|
|
|
|
if (file.active && CppTools::ProjectFile::isSource(file.kind)) {
|
|
fileInfos.emplace_back(Utils::FilePath::fromString(file.path),
|
|
file.kind,
|
|
projectPart);
|
|
}
|
|
}
|
|
}
|
|
|
|
Utils::sort(fileInfos, &FileInfo::file);
|
|
fileInfos.erase(std::unique(fileInfos.begin(), fileInfos.end()), fileInfos.end());
|
|
|
|
return fileInfos;
|
|
}
|
|
|
|
static RunSettings runSettings()
|
|
{
|
|
if (Project *project = SessionManager::startupProject()) {
|
|
auto *projectSettings = ClangToolsProjectSettingsManager::getSettings(project);
|
|
if (!projectSettings->useGlobalSettings())
|
|
return projectSettings->runSettings();
|
|
}
|
|
return ClangToolsSettings::instance()->runSettings();
|
|
}
|
|
|
|
static ClangDiagnosticConfig diagnosticConfig(const Core::Id &diagConfigId)
|
|
{
|
|
const ClangDiagnosticConfigsModel configs = diagnosticConfigsModel();
|
|
QTC_ASSERT(configs.hasConfigWithId(diagConfigId), return ClangDiagnosticConfig());
|
|
return configs.configWithId(diagConfigId);
|
|
}
|
|
|
|
ClangTool *ClangTool::instance()
|
|
{
|
|
return s_instance;
|
|
}
|
|
|
|
ClangTool::ClangTool()
|
|
: m_name("Clang-Tidy and Clazy")
|
|
{
|
|
setObjectName("ClangTidyClazyTool");
|
|
s_instance = this;
|
|
m_diagnosticModel = new ClangToolsDiagnosticModel(this);
|
|
|
|
const Utils::Icon RUN_FILE_OVERLAY(
|
|
{{":/utils/images/run_file.png", Utils::Theme::IconsBaseColor}});
|
|
|
|
const Utils::Icon RUN_SELECTED_OVERLAY(
|
|
{{":/utils/images/runselected_boxes.png", Utils::Theme::BackgroundColorDark},
|
|
{":/utils/images/runselected_tickmarks.png", Utils::Theme::IconsBaseColor}});
|
|
|
|
auto action = new QAction(tr("Analyze Project..."), this);
|
|
Utils::Icon runSelectedIcon = Utils::Icons::RUN_SMALL_TOOLBAR;
|
|
for (const Utils::IconMaskAndColor &maskAndColor : RUN_SELECTED_OVERLAY)
|
|
runSelectedIcon.append(maskAndColor);
|
|
action->setIcon(runSelectedIcon.icon());
|
|
m_startAction = action;
|
|
|
|
action = new QAction(tr("Analyze Current File"), this);
|
|
Utils::Icon runFileIcon = Utils::Icons::RUN_SMALL_TOOLBAR;
|
|
for (const Utils::IconMaskAndColor &maskAndColor : RUN_FILE_OVERLAY)
|
|
runFileIcon.append(maskAndColor);
|
|
action->setIcon(runFileIcon.icon());
|
|
m_startOnCurrentFileAction = action;
|
|
|
|
m_stopAction = Debugger::createStopAction();
|
|
|
|
m_diagnosticFilterModel = new DiagnosticFilterModel(this);
|
|
m_diagnosticFilterModel->setSourceModel(m_diagnosticModel);
|
|
m_diagnosticFilterModel->setDynamicSortFilter(true);
|
|
|
|
m_infoBarWidget = new InfoBarWidget;
|
|
|
|
m_diagnosticView = new DiagnosticView;
|
|
initDiagnosticView();
|
|
m_diagnosticView->setModel(m_diagnosticFilterModel);
|
|
m_diagnosticView->setSortingEnabled(true);
|
|
m_diagnosticView->sortByColumn(Debugger::DetailedErrorView::DiagnosticColumn,
|
|
Qt::AscendingOrder);
|
|
connect(m_diagnosticView, &DiagnosticView::showFilter,
|
|
this, &ClangTool::filter);
|
|
connect(m_diagnosticView, &DiagnosticView::clearFilter,
|
|
this, &ClangTool::clearFilter);
|
|
connect(m_diagnosticView, &DiagnosticView::filterForCurrentKind,
|
|
this, &ClangTool::filterForCurrentKind);
|
|
connect(m_diagnosticView, &DiagnosticView::filterOutCurrentKind,
|
|
this, &ClangTool::filterOutCurrentKind);
|
|
|
|
foreach (auto * const model,
|
|
QList<QAbstractItemModel *>({m_diagnosticModel, m_diagnosticFilterModel})) {
|
|
connect(model, &QAbstractItemModel::rowsInserted,
|
|
this, &ClangTool::updateForCurrentState);
|
|
connect(model, &QAbstractItemModel::rowsRemoved,
|
|
this, &ClangTool::updateForCurrentState);
|
|
connect(model, &QAbstractItemModel::modelReset,
|
|
this, &ClangTool::updateForCurrentState);
|
|
connect(model, &QAbstractItemModel::layoutChanged, // For QSortFilterProxyModel::invalidate()
|
|
this, &ClangTool::updateForCurrentState);
|
|
}
|
|
|
|
// Go to previous diagnostic
|
|
action = new QAction(this);
|
|
action->setDisabled(true);
|
|
action->setIcon(Utils::Icons::PREV_TOOLBAR.icon());
|
|
action->setToolTip(tr("Go to previous diagnostic."));
|
|
connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goBack);
|
|
m_goBack = action;
|
|
|
|
// Go to next diagnostic
|
|
action = new QAction(this);
|
|
action->setDisabled(true);
|
|
action->setIcon(Utils::Icons::NEXT_TOOLBAR.icon());
|
|
action->setToolTip(tr("Go to next diagnostic."));
|
|
connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goNext);
|
|
m_goNext = action;
|
|
|
|
// Load diagnostics from file
|
|
action = new QAction(this);
|
|
action->setIcon(Utils::Icons::OPENFILE_TOOLBAR.icon());
|
|
action->setToolTip(tr("Load Diagnostics from YAML Files exported with \"-export-fixes\"."));
|
|
connect(action, &QAction::triggered, this, &ClangTool::loadDiagnosticsFromFiles);
|
|
m_loadExported = action;
|
|
|
|
// Clear data
|
|
action = new QAction(this);
|
|
action->setDisabled(true);
|
|
action->setIcon(Utils::Icons::CLEAN_TOOLBAR.icon());
|
|
action->setToolTip(tr("Clear"));
|
|
connect(action, &QAction::triggered, this, [this]() {
|
|
reset();
|
|
update();
|
|
});
|
|
m_clear = action;
|
|
|
|
// Expand/Collapse
|
|
action = new QAction(this);
|
|
action->setDisabled(true);
|
|
action->setCheckable(true);
|
|
action->setIcon(Utils::Icons::EXPAND_ALL_TOOLBAR.icon());
|
|
action->setToolTip(tr("Expand All"));
|
|
connect(action, &QAction::toggled, [this](bool checked){
|
|
if (checked) {
|
|
m_expandCollapse->setToolTip(tr("Collapse All"));
|
|
m_diagnosticView->expandAll();
|
|
} else {
|
|
m_expandCollapse->setToolTip(tr("Expand All"));
|
|
m_diagnosticView->collapseAll();
|
|
}
|
|
});
|
|
m_expandCollapse = action;
|
|
|
|
// Filter button
|
|
action = m_showFilter = new QAction(this);
|
|
action->setIcon(
|
|
Utils::Icon({{":/utils/images/filtericon.png", Utils::Theme::IconsBaseColor}}).icon());
|
|
action->setToolTip(tr("Filter Diagnostics"));
|
|
action->setCheckable(true);
|
|
connect(action, &QAction::triggered, this, &ClangTool::filter);
|
|
|
|
// Schedule/Unschedule all fixits
|
|
m_selectFixitsCheckBox = new SelectFixitsCheckBox;
|
|
m_selectFixitsCheckBox->setText("Select Fixits");
|
|
m_selectFixitsCheckBox->setEnabled(false);
|
|
m_selectFixitsCheckBox->setTristate(true);
|
|
connect(m_selectFixitsCheckBox, &QCheckBox::clicked, this, [this]() {
|
|
m_diagnosticView->scheduleAllFixits(m_selectFixitsCheckBox->isChecked());
|
|
});
|
|
|
|
// Apply fixits button
|
|
m_applyFixitsButton = new QToolButton;
|
|
m_applyFixitsButton->setText(tr("Apply Fixits"));
|
|
m_applyFixitsButton->setEnabled(false);
|
|
|
|
connect(m_diagnosticModel, &ClangToolsDiagnosticModel::fixitStatusChanged,
|
|
m_diagnosticFilterModel, &DiagnosticFilterModel::onFixitStatusChanged);
|
|
connect(m_diagnosticFilterModel, &DiagnosticFilterModel::fixitCountersChanged,
|
|
this,
|
|
[this](int scheduled, int scheduable){
|
|
m_selectFixitsCheckBox->setEnabled(scheduable > 0);
|
|
m_applyFixitsButton->setEnabled(scheduled > 0);
|
|
|
|
if (scheduled == 0)
|
|
m_selectFixitsCheckBox->setCheckState(Qt::Unchecked);
|
|
else if (scheduled == scheduable)
|
|
m_selectFixitsCheckBox->setCheckState(Qt::Checked);
|
|
else
|
|
m_selectFixitsCheckBox->setCheckState(Qt::PartiallyChecked);
|
|
|
|
updateForCurrentState();
|
|
});
|
|
connect(m_applyFixitsButton, &QToolButton::clicked, [this]() {
|
|
QVector<DiagnosticItem *> diagnosticItems;
|
|
m_diagnosticModel->forItemsAtLevel<2>([&](DiagnosticItem *item){
|
|
diagnosticItems += item;
|
|
});
|
|
|
|
ApplyFixIts(diagnosticItems).apply(m_diagnosticModel);
|
|
});
|
|
|
|
// Open Project Settings
|
|
action = new QAction(this);
|
|
action->setIcon(Utils::Icons::SETTINGS_TOOLBAR.icon());
|
|
//action->setToolTip(tr("Open Project Settings")); // TODO: Uncomment in master.
|
|
connect(action, &QAction::triggered, []() {
|
|
ProjectExplorerPlugin::activateProjectPanel(Constants::PROJECT_PANEL_ID);
|
|
});
|
|
m_openProjectSettings = action;
|
|
|
|
ActionContainer *menu = ActionManager::actionContainer(Debugger::Constants::M_DEBUG_ANALYZER);
|
|
const QString toolTip = tr("Clang-Tidy and Clazy use a customized Clang executable from the "
|
|
"Clang project to search for diagnostics.");
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
mainLayout->setSpacing(1);
|
|
mainLayout->addWidget(m_infoBarWidget);
|
|
mainLayout->addWidget(m_diagnosticView);
|
|
auto mainWidget = new QWidget;
|
|
mainWidget->setObjectName("ClangTidyClazyIssuesView");
|
|
mainWidget->setWindowTitle(tr("Clang-Tidy and Clazy"));
|
|
mainWidget->setLayout(mainLayout);
|
|
|
|
m_perspective.addWindow(mainWidget, Perspective::SplitVertical, nullptr);
|
|
|
|
action = new QAction(tr("Clang-Tidy and Clazy..."), this);
|
|
action->setToolTip(toolTip);
|
|
menu->addAction(ActionManager::registerAction(action, "ClangTidyClazy.Action"),
|
|
Debugger::Constants::G_ANALYZER_TOOLS);
|
|
QObject::connect(action, &QAction::triggered, this, [this]() {
|
|
startTool(FileSelection::AskUser);
|
|
});
|
|
QObject::connect(m_startAction, &QAction::triggered, action, &QAction::triggered);
|
|
QObject::connect(m_startAction, &QAction::changed, action, [action, this] {
|
|
action->setEnabled(m_startAction->isEnabled());
|
|
});
|
|
|
|
QObject::connect(m_startOnCurrentFileAction, &QAction::triggered, this, [this] {
|
|
startTool(FileSelection::CurrentFile);
|
|
});
|
|
|
|
m_perspective.addToolBarAction(m_startAction);
|
|
m_perspective.addToolBarAction(m_startOnCurrentFileAction);
|
|
m_perspective.addToolBarAction(m_stopAction);
|
|
m_perspective.addToolBarAction(m_openProjectSettings);
|
|
m_perspective.addToolbarSeparator();
|
|
m_perspective.addToolBarAction(m_loadExported);
|
|
m_perspective.addToolBarAction(m_clear);
|
|
m_perspective.addToolbarSeparator();
|
|
m_perspective.addToolBarAction(m_expandCollapse);
|
|
m_perspective.addToolBarAction(m_goBack);
|
|
m_perspective.addToolBarAction(m_goNext);
|
|
m_perspective.addToolbarSeparator();
|
|
m_perspective.addToolBarAction(m_showFilter);
|
|
m_perspective.addToolBarWidget(m_selectFixitsCheckBox);
|
|
m_perspective.addToolBarWidget(m_applyFixitsButton);
|
|
|
|
update();
|
|
|
|
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::updateRunActions,
|
|
this, &ClangTool::update);
|
|
connect(CppModelManager::instance(), &CppModelManager::projectPartsUpdated,
|
|
this, &ClangTool::update);
|
|
connect(ClangToolsSettings::instance(), &ClangToolsSettings::changed,
|
|
this, &ClangTool::update);
|
|
}
|
|
|
|
void ClangTool::selectPerspective()
|
|
{
|
|
m_perspective.select();
|
|
}
|
|
|
|
void ClangTool::startTool(ClangTool::FileSelection fileSelection)
|
|
{
|
|
const RunSettings theRunSettings = runSettings();
|
|
startTool(fileSelection, theRunSettings, diagnosticConfig(theRunSettings.diagnosticConfigId()));
|
|
}
|
|
|
|
static bool continueDespiteReleaseBuild(const QString &toolName)
|
|
{
|
|
const QString wrongMode = ClangTool::tr("Release");
|
|
const QString title = ClangTool::tr("Run %1 in %2 Mode?").arg(toolName, wrongMode);
|
|
const QString problem
|
|
= ClangTool::tr(
|
|
"You are trying to run the tool \"%1\" on an application in %2 mode. The tool is "
|
|
"designed to be used in Debug mode since enabled assertions can reduce the number of "
|
|
"false positives.")
|
|
.arg(toolName, wrongMode);
|
|
const QString question = ClangTool::tr(
|
|
"Do you want to continue and run the tool in %1 mode?")
|
|
.arg(wrongMode);
|
|
const QString message = QString("<html><head/><body>"
|
|
"<p>%1</p>"
|
|
"<p>%2</p>"
|
|
"</body></html>")
|
|
.arg(problem, question);
|
|
return CheckableMessageBox::doNotAskAgainQuestion(ICore::mainWindow(),
|
|
title,
|
|
message,
|
|
ICore::settings(),
|
|
"ClangToolsCorrectModeWarning")
|
|
== QDialogButtonBox::Yes;
|
|
}
|
|
|
|
|
|
void ClangTool::startTool(ClangTool::FileSelection fileSelection,
|
|
const RunSettings &runSettings,
|
|
const CppTools::ClangDiagnosticConfig &diagnosticConfig)
|
|
{
|
|
Project *project = SessionManager::startupProject();
|
|
QTC_ASSERT(project, return);
|
|
QTC_ASSERT(project->activeTarget(), return);
|
|
|
|
// Continue despite release mode?
|
|
if (BuildConfiguration *bc = project->activeTarget()->activeBuildConfiguration()) {
|
|
if (bc->buildType() == BuildConfiguration::Release)
|
|
if (!continueDespiteReleaseBuild(m_name))
|
|
return;
|
|
}
|
|
|
|
// Collect files to analyze
|
|
const FileInfos fileInfos = collectFileInfos(project, fileSelection);
|
|
if (fileInfos.empty())
|
|
return;
|
|
|
|
// Reset
|
|
reset();
|
|
|
|
// Run control
|
|
m_runControl = new RunControl(Constants::CLANGTIDYCLAZY_RUN_MODE);
|
|
m_runControl->setDisplayName(tr("Clang-Tidy and Clazy"));
|
|
m_runControl->setIcon(ProjectExplorer::Icons::ANALYZER_START_SMALL_TOOLBAR);
|
|
m_runControl->setTarget(project->activeTarget());
|
|
m_stopAction->disconnect();
|
|
connect(m_stopAction, &QAction::triggered, m_runControl, [this] {
|
|
m_runControl->appendMessage(tr("Clang-Tidy and Clazy tool stopped by user."),
|
|
NormalMessageFormat);
|
|
m_runControl->initiateStop();
|
|
setState(State::StoppedByUser);
|
|
});
|
|
connect(m_runControl, &RunControl::stopped, this, &ClangTool::onRunControlStopped);
|
|
|
|
// Run worker
|
|
const bool preventBuild = fileSelection == FileSelection::CurrentFile;
|
|
const bool buildBeforeAnalysis = !preventBuild && runSettings.buildBeforeAnalysis();
|
|
m_runWorker = new ClangToolRunWorker(m_runControl,
|
|
runSettings,
|
|
diagnosticConfig,
|
|
fileInfos,
|
|
buildBeforeAnalysis);
|
|
connect(m_runWorker, &ClangToolRunWorker::buildFailed,this, &ClangTool::onBuildFailed);
|
|
connect(m_runWorker, &ClangToolRunWorker::startFailed, this, &ClangTool::onStartFailed);
|
|
connect(m_runWorker, &ClangToolRunWorker::started, this, &ClangTool::onStarted);
|
|
connect(m_runWorker, &ClangToolRunWorker::runnerFinished,
|
|
this, &ClangTool::updateForCurrentState);
|
|
|
|
// More init and UI update
|
|
m_diagnosticFilterModel->setProject(project);
|
|
m_perspective.select();
|
|
if (buildBeforeAnalysis)
|
|
m_infoBarWidget->setInfoText("Waiting for build to finish...");
|
|
setState(State::PreparationStarted);
|
|
|
|
// Start
|
|
ProjectExplorerPlugin::startRunControl(m_runControl);
|
|
}
|
|
|
|
Diagnostics ClangTool::read(OutputFileFormat outputFileFormat,
|
|
const QString &logFilePath,
|
|
const QString &mainFilePath,
|
|
const QSet<FilePath> &projectFiles,
|
|
QString *errorMessage) const
|
|
{
|
|
const auto acceptFromFilePath = [projectFiles](const Utils::FilePath &filePath) {
|
|
return projectFiles.contains(filePath);
|
|
};
|
|
|
|
if (outputFileFormat == OutputFileFormat::Yaml) {
|
|
return readExportedDiagnostics(Utils::FilePath::fromString(logFilePath),
|
|
acceptFromFilePath,
|
|
errorMessage);
|
|
}
|
|
return readSerializedDiagnostics(Utils::FilePath::fromString(logFilePath),
|
|
Utils::FilePath::fromString(mainFilePath),
|
|
acceptFromFilePath,
|
|
errorMessage);
|
|
}
|
|
|
|
FileInfos ClangTool::collectFileInfos(Project *project, FileSelection fileSelection)
|
|
{
|
|
auto projectInfo = CppTools::CppModelManager::instance()->projectInfo(project);
|
|
QTC_ASSERT(projectInfo.isValid(), return FileInfos());
|
|
|
|
const FileInfos allFileInfos = sortedFileInfos(projectInfo.projectParts());
|
|
|
|
if (fileSelection == FileSelection::AllFiles)
|
|
return allFileInfos;
|
|
|
|
if (fileSelection == FileSelection::AskUser) {
|
|
static int initialProviderIndex = 0;
|
|
SelectableFilesDialog dialog(projectInfo,
|
|
fileInfoProviders(project, allFileInfos),
|
|
initialProviderIndex);
|
|
if (dialog.exec() == QDialog::Rejected)
|
|
return FileInfos();
|
|
initialProviderIndex = dialog.currentProviderIndex();
|
|
return dialog.fileInfos();
|
|
}
|
|
|
|
if (fileSelection == FileSelection::CurrentFile) {
|
|
if (const IDocument *document = EditorManager::currentDocument()) {
|
|
const Utils::FilePath filePath = document->filePath();
|
|
if (!filePath.isEmpty()) {
|
|
const FileInfo fileInfo = Utils::findOrDefault(allFileInfos,
|
|
[&](const FileInfo &fi) {
|
|
return fi.file == filePath;
|
|
});
|
|
if (!fileInfo.file.isEmpty())
|
|
return {fileInfo};
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
const QString &ClangTool::name() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
void ClangTool::initDiagnosticView()
|
|
{
|
|
m_diagnosticView->setFrameStyle(QFrame::NoFrame);
|
|
m_diagnosticView->setAttribute(Qt::WA_MacShowFocusRect, false);
|
|
m_diagnosticView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
|
m_diagnosticView->setAutoScroll(false);
|
|
}
|
|
|
|
void ClangTool::loadDiagnosticsFromFiles()
|
|
{
|
|
// Ask user for files
|
|
const QStringList filePaths
|
|
= QFileDialog::getOpenFileNames(Core::ICore::mainWindow(),
|
|
tr("Select YAML Files with Diagnostics"),
|
|
QDir::homePath(),
|
|
tr("YAML Files (*.yml *.yaml);;All Files (*)"));
|
|
if (filePaths.isEmpty())
|
|
return;
|
|
|
|
// Load files
|
|
Diagnostics diagnostics;
|
|
QString errors;
|
|
for (const QString &filePath : filePaths) {
|
|
QString currentError;
|
|
diagnostics << readExportedDiagnostics(Utils::FilePath::fromString(filePath),
|
|
{},
|
|
¤tError);
|
|
|
|
if (!currentError.isEmpty()) {
|
|
if (!errors.isEmpty())
|
|
errors.append("\n");
|
|
errors.append(currentError);
|
|
}
|
|
}
|
|
|
|
// Show errors
|
|
if (!errors.isEmpty()) {
|
|
AsynchronousMessageBox::critical(tr("Error Loading Diagnostics"), errors);
|
|
return;
|
|
}
|
|
|
|
// Show imported
|
|
reset();
|
|
onNewDiagnosticsAvailable(diagnostics);
|
|
setState(State::ImportFinished);
|
|
}
|
|
|
|
DiagnosticItem *ClangTool::diagnosticItem(const QModelIndex &index) const
|
|
{
|
|
if (!index.isValid())
|
|
return {};
|
|
|
|
TreeItem *item = m_diagnosticModel->itemForIndex(m_diagnosticFilterModel->mapToSource(index));
|
|
if (item->level() == 3)
|
|
item = item->parent();
|
|
if (item->level() == 2)
|
|
return static_cast<DiagnosticItem *>(item);
|
|
|
|
return {};
|
|
}
|
|
|
|
void ClangTool::showOutputPane()
|
|
{
|
|
ProjectExplorerPlugin::showOutputPaneForRunControl(m_runControl);
|
|
}
|
|
|
|
void ClangTool::reset()
|
|
{
|
|
m_clear->setEnabled(false);
|
|
m_showFilter->setEnabled(false);
|
|
m_showFilter->setChecked(false);
|
|
m_selectFixitsCheckBox->setEnabled(false);
|
|
m_applyFixitsButton->setEnabled(false);
|
|
|
|
m_diagnosticModel->clear();
|
|
m_diagnosticFilterModel->reset();
|
|
|
|
m_infoBarWidget->reset();
|
|
|
|
m_state = State::Initial;
|
|
m_runControl = nullptr;
|
|
m_runWorker = nullptr;
|
|
}
|
|
|
|
static bool canAnalyzeProject(Project *project)
|
|
{
|
|
if (const Target *target = project->activeTarget()) {
|
|
const Core::Id c = ProjectExplorer::Constants::C_LANGUAGE_ID;
|
|
const Core::Id cxx = ProjectExplorer::Constants::CXX_LANGUAGE_ID;
|
|
const bool projectSupportsLanguage = project->projectLanguages().contains(c)
|
|
|| project->projectLanguages().contains(cxx);
|
|
return projectSupportsLanguage
|
|
&& CppModelManager::instance()->projectInfo(project).isValid()
|
|
&& ToolChainKitAspect::toolChain(target->kit(), cxx);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct CheckResult {
|
|
enum {
|
|
InvalidTidyExecutable,
|
|
InvalidClazyExecutable,
|
|
ProjectNotOpen,
|
|
ProjectNotSuitable,
|
|
ReadyToAnalyze,
|
|
} kind;
|
|
QString errorText;
|
|
};
|
|
|
|
static CheckResult canAnalyze()
|
|
{
|
|
const ClangDiagnosticConfig config = diagnosticConfig(runSettings().diagnosticConfigId());
|
|
|
|
if (config.isClangTidyEnabled() && !isFileExecutable(clangTidyExecutable())) {
|
|
return {CheckResult::InvalidTidyExecutable,
|
|
ClangTool::tr("Set a valid Clang-Tidy executable.")};
|
|
}
|
|
|
|
if (config.isClazyEnabled() && !isFileExecutable(clazyStandaloneExecutable())) {
|
|
return {CheckResult::InvalidClazyExecutable,
|
|
ClangTool::tr("Set a valid Clazy-Standalone executable.")};
|
|
}
|
|
|
|
if (Project *project = SessionManager::startupProject()) {
|
|
if (!canAnalyzeProject(project)) {
|
|
return {CheckResult::ProjectNotSuitable,
|
|
ClangTool::tr("Project \"%1\" is not a C/C++ project.")
|
|
.arg(project->displayName())};
|
|
}
|
|
} else {
|
|
return {CheckResult::ProjectNotOpen,
|
|
ClangTool::tr("Open a C/C++ project to start analyzing.")};
|
|
}
|
|
|
|
return {CheckResult::ReadyToAnalyze, {}};
|
|
}
|
|
|
|
void ClangTool::updateForInitialState()
|
|
{
|
|
if (m_state != State::Initial)
|
|
return;
|
|
|
|
m_infoBarWidget->reset();
|
|
|
|
const CheckResult result = canAnalyze();
|
|
switch (result.kind)
|
|
case CheckResult::InvalidTidyExecutable: {
|
|
case CheckResult::InvalidClazyExecutable:
|
|
m_infoBarWidget->setError(InfoBarWidget::Warning,
|
|
makeLink(result.errorText),
|
|
[](){ ICore::showOptionsDialog(Constants::SETTINGS_PAGE_ID); });
|
|
break;
|
|
case CheckResult::ProjectNotSuitable:
|
|
case CheckResult::ProjectNotOpen:
|
|
case CheckResult::ReadyToAnalyze:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClangTool::setFilterOptions(const OptionalFilterOptions &filterOptions)
|
|
{
|
|
m_diagnosticFilterModel->setFilterOptions(filterOptions);
|
|
const bool isFilterActive = filterOptions
|
|
? (filterOptions->checks != m_diagnosticModel->allChecks())
|
|
: false;
|
|
m_showFilter->setChecked(isFilterActive);
|
|
}
|
|
|
|
void ClangTool::filter()
|
|
{
|
|
const OptionalFilterOptions filterOptions = m_diagnosticFilterModel->filterOptions();
|
|
|
|
// Collect available and currently shown checks
|
|
QHash<QString, Check> checks;
|
|
m_diagnosticModel->forItemsAtLevel<2>([&](DiagnosticItem *item) {
|
|
const QString checkName = item->diagnostic().name;
|
|
Check &check = checks[checkName];
|
|
if (check.name.isEmpty()) {
|
|
check.name = checkName;
|
|
check.displayName = checkName;
|
|
const QString clangDiagPrefix = "clang-diagnostic-";
|
|
if (check.displayName.startsWith(clangDiagPrefix))
|
|
check.displayName = QString("-W%1").arg(check.name.mid(clangDiagPrefix.size()));
|
|
check.count = 1;
|
|
check.isShown = filterOptions ? filterOptions->checks.contains(checkName) : true;
|
|
check.hasFixit = check.hasFixit || item->diagnostic().hasFixits;
|
|
checks.insert(check.name, check);
|
|
} else {
|
|
++check.count;
|
|
}
|
|
});
|
|
|
|
// Show dialog
|
|
FilterDialog dialog(checks.values());
|
|
if (dialog.exec() == QDialog::Rejected)
|
|
return;
|
|
|
|
// Apply filter
|
|
setFilterOptions(FilterOptions{dialog.selectedChecks()});
|
|
}
|
|
|
|
void ClangTool::clearFilter()
|
|
{
|
|
m_diagnosticFilterModel->setFilterOptions({});
|
|
m_showFilter->setChecked(false);
|
|
}
|
|
|
|
void ClangTool::filterForCurrentKind()
|
|
{
|
|
if (DiagnosticItem *item = diagnosticItem(m_diagnosticView->currentIndex()))
|
|
setFilterOptions(FilterOptions{{item->diagnostic().name}});
|
|
}
|
|
|
|
void ClangTool::filterOutCurrentKind()
|
|
{
|
|
if (DiagnosticItem *item = diagnosticItem(m_diagnosticView->currentIndex())) {
|
|
const OptionalFilterOptions filterOpts = m_diagnosticFilterModel->filterOptions();
|
|
QSet<QString> checks = filterOpts ? filterOpts->checks : m_diagnosticModel->allChecks();
|
|
checks.remove(item->diagnostic().name);
|
|
|
|
setFilterOptions(FilterOptions{checks});
|
|
}
|
|
}
|
|
|
|
void ClangTool::onBuildFailed()
|
|
{
|
|
m_infoBarWidget->setError(InfoBarWidget::Error,
|
|
tr("Failed to build the project."),
|
|
[this]() { showOutputPane(); });
|
|
setState(State::PreparationFailed);
|
|
}
|
|
|
|
void ClangTool::onStartFailed()
|
|
{
|
|
m_infoBarWidget->setError(InfoBarWidget::Error,
|
|
makeLink(tr("Failed to start the analyzer.")),
|
|
[this]() { showOutputPane(); });
|
|
setState(State::PreparationFailed);
|
|
}
|
|
|
|
void ClangTool::onStarted()
|
|
{
|
|
setState(State::AnalyzerRunning);
|
|
}
|
|
|
|
void ClangTool::onRunControlStopped()
|
|
{
|
|
if (m_state != State::StoppedByUser && m_state != State::PreparationFailed)
|
|
setState(State::AnalyzerFinished);
|
|
emit finished(m_runWorker->success());
|
|
}
|
|
|
|
void ClangTool::update()
|
|
{
|
|
updateForInitialState();
|
|
updateForCurrentState();
|
|
}
|
|
|
|
using DocumentPredicate = std::function<bool(Core::IDocument *)>;
|
|
|
|
static FileInfos fileInfosMatchingDocuments(const FileInfos &fileInfos,
|
|
const DocumentPredicate &predicate)
|
|
{
|
|
QSet<Utils::FilePath> documentPaths;
|
|
for (const Core::DocumentModel::Entry *e : Core::DocumentModel::entries()) {
|
|
if (predicate(e->document))
|
|
documentPaths.insert(e->fileName());
|
|
}
|
|
|
|
return Utils::filtered(fileInfos, [documentPaths](const FileInfo &fileInfo) {
|
|
return documentPaths.contains(fileInfo.file);
|
|
});
|
|
}
|
|
|
|
static FileInfos fileInfosMatchingOpenedDocuments(const FileInfos &fileInfos)
|
|
{
|
|
// Note that (initially) suspended text documents are still IDocuments, not yet TextDocuments.
|
|
return fileInfosMatchingDocuments(fileInfos, [](Core::IDocument *) { return true; });
|
|
}
|
|
|
|
static FileInfos fileInfosMatchingEditedDocuments(const FileInfos &fileInfos)
|
|
{
|
|
return fileInfosMatchingDocuments(fileInfos, [](Core::IDocument *document) {
|
|
if (auto textDocument = qobject_cast<TextEditor::TextDocument*>(document))
|
|
return textDocument->document()->revision() > 1;
|
|
return false;
|
|
});
|
|
}
|
|
|
|
FileInfoProviders ClangTool::fileInfoProviders(ProjectExplorer::Project *project,
|
|
const FileInfos &allFileInfos)
|
|
{
|
|
ClangToolsProjectSettings *s = ClangToolsProjectSettingsManager::getSettings(project);
|
|
static FileInfoSelection openedFilesSelection;
|
|
static FileInfoSelection editeddFilesSelection;
|
|
|
|
return {
|
|
{ClangTool::tr("All Files"),
|
|
allFileInfos,
|
|
FileInfoSelection{s->selectedDirs(), s->selectedFiles()},
|
|
FileInfoProvider::Limited,
|
|
[s](const FileInfoSelection &selection) {
|
|
s->setSelectedDirs(selection.dirs);
|
|
s->setSelectedFiles(selection.files);
|
|
}},
|
|
|
|
{ClangTool::tr("Opened Files"),
|
|
fileInfosMatchingOpenedDocuments(allFileInfos),
|
|
openedFilesSelection,
|
|
FileInfoProvider::All,
|
|
[](const FileInfoSelection &selection) { openedFilesSelection = selection; }},
|
|
|
|
{ClangTool::tr("Edited Files"),
|
|
fileInfosMatchingEditedDocuments(allFileInfos),
|
|
editeddFilesSelection,
|
|
FileInfoProvider::All,
|
|
[](const FileInfoSelection &selection) { editeddFilesSelection = selection; }},
|
|
};
|
|
}
|
|
|
|
void ClangTool::setState(ClangTool::State state)
|
|
{
|
|
m_state = state;
|
|
updateForCurrentState();
|
|
}
|
|
|
|
QSet<Diagnostic> ClangTool::diagnostics() const
|
|
{
|
|
return Utils::filtered(m_diagnosticModel->diagnostics(), [](const Diagnostic &diagnostic) {
|
|
using CppTools::ProjectFile;
|
|
return ProjectFile::isSource(ProjectFile::classify(diagnostic.location.filePath));
|
|
});
|
|
}
|
|
|
|
void ClangTool::onNewDiagnosticsAvailable(const Diagnostics &diagnostics)
|
|
{
|
|
QTC_ASSERT(m_diagnosticModel, return);
|
|
m_diagnosticModel->addDiagnostics(diagnostics);
|
|
}
|
|
|
|
void ClangTool::updateForCurrentState()
|
|
{
|
|
// Actions
|
|
bool canStart = false;
|
|
const bool isPreparing = m_state == State::PreparationStarted;
|
|
const bool isRunning = m_state == State::AnalyzerRunning;
|
|
QString startActionToolTip = m_startAction->text();
|
|
QString startOnCurrentToolTip = m_startOnCurrentFileAction->text();
|
|
if (!isRunning) {
|
|
const CheckResult result = canAnalyze();
|
|
canStart = result.kind == CheckResult::ReadyToAnalyze;
|
|
if (!canStart) {
|
|
startActionToolTip = result.errorText;
|
|
startOnCurrentToolTip = result.errorText;
|
|
}
|
|
}
|
|
m_startAction->setEnabled(canStart);
|
|
m_startAction->setToolTip(startActionToolTip);
|
|
m_startOnCurrentFileAction->setEnabled(canStart);
|
|
m_startOnCurrentFileAction->setToolTip(startOnCurrentToolTip);
|
|
m_stopAction->setEnabled(isPreparing || isRunning);
|
|
|
|
const int issuesFound = m_diagnosticModel->diagnostics().count();
|
|
const int issuesVisible = m_diagnosticFilterModel->diagnostics();
|
|
m_goBack->setEnabled(issuesVisible > 1);
|
|
m_goNext->setEnabled(issuesVisible > 1);
|
|
m_clear->setEnabled(!isRunning);
|
|
m_expandCollapse->setEnabled(issuesVisible);
|
|
m_loadExported->setEnabled(!isRunning);
|
|
m_showFilter->setEnabled(issuesFound > 1);
|
|
|
|
// Diagnostic view
|
|
m_diagnosticView->setCursor(isRunning ? Qt::BusyCursor : Qt::ArrowCursor);
|
|
|
|
// Info bar: errors
|
|
const bool hasErrorText = !m_infoBarWidget->errorText().isEmpty();
|
|
const bool hasErrors = m_runWorker && m_runWorker->filesNotAnalyzed() > 0;
|
|
if (hasErrors && !hasErrorText) {
|
|
const QString text = makeLink( tr("Failed to analyze %1 files.").arg(m_runWorker->filesNotAnalyzed()));
|
|
m_infoBarWidget->setError(InfoBarWidget::Warning, text, [this]() { showOutputPane(); });
|
|
}
|
|
|
|
// Info bar: info
|
|
bool showProgressIcon = false;
|
|
QString infoText;
|
|
switch (m_state) {
|
|
case State::Initial:
|
|
infoText = m_infoBarWidget->infoText();
|
|
break;
|
|
case State::AnalyzerRunning:
|
|
showProgressIcon = true;
|
|
if (m_runWorker->totalFilesToAnalyze() == 0) {
|
|
infoText = tr("Analyzing..."); // Not yet fully started/initialized
|
|
} else {
|
|
infoText = tr("Analyzing... %1 of %2 files processed.")
|
|
.arg(m_runWorker->filesAnalyzed() + m_runWorker->filesNotAnalyzed())
|
|
.arg(m_runWorker->totalFilesToAnalyze());
|
|
}
|
|
break;
|
|
case State::PreparationStarted:
|
|
showProgressIcon = true;
|
|
infoText = m_infoBarWidget->infoText();
|
|
break;
|
|
case State::PreparationFailed:
|
|
break; // OK, we just show an error.
|
|
case State::StoppedByUser:
|
|
infoText = tr("Analysis stopped by user.");
|
|
break;
|
|
case State::AnalyzerFinished:
|
|
infoText = tr("Finished processing %1 files.").arg(m_runWorker->totalFilesToAnalyze());
|
|
break;
|
|
case State::ImportFinished:
|
|
infoText = tr("Diagnostics imported.");
|
|
break;
|
|
}
|
|
m_infoBarWidget->setInfoText(infoText);
|
|
m_infoBarWidget->setInfoIcon(showProgressIcon ? InfoBarWidget::ProgressIcon
|
|
: InfoBarWidget::InfoIcon);
|
|
|
|
// Info bar: diagnostic stats
|
|
QString diagText;
|
|
if (issuesFound) {
|
|
diagText = tr("%1 diagnostics. %2 fixits, %4 selected.")
|
|
.arg(issuesVisible)
|
|
.arg(m_diagnosticFilterModel->fixitsScheduable())
|
|
.arg(m_diagnosticFilterModel->fixitsScheduled());
|
|
} else if (m_state != State::AnalyzerRunning
|
|
&& m_state != State::Initial
|
|
&& m_state != State::PreparationStarted
|
|
&& m_state != State::PreparationFailed) {
|
|
diagText = tr("No diagnostics.");
|
|
}
|
|
m_infoBarWidget->setDiagText(diagText);
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace ClangTools
|
|
|
|
#include "clangtool.moc"
|