1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "clangtool.h"
27 
28 #include "clangfixitsrefactoringchanges.h"
29 #include "clangselectablefilesdialog.h"
30 #include "clangtoolruncontrol.h"
31 #include "clangtoolsconstants.h"
32 #include "clangtoolsdiagnostic.h"
33 #include "clangtoolsdiagnosticmodel.h"
34 #include "clangtoolsdiagnosticview.h"
35 #include "clangtoolslogfilereader.h"
36 #include "clangtoolsplugin.h"
37 #include "clangtoolsprojectsettings.h"
38 #include "clangtoolssettings.h"
39 #include "clangtoolsutils.h"
40 #include "filterdialog.h"
41 
42 #include <coreplugin/actionmanager/actioncontainer.h>
43 #include <coreplugin/actionmanager/actionmanager.h>
44 #include <coreplugin/coreconstants.h>
45 #include <coreplugin/editormanager/editormanager.h>
46 #include <coreplugin/icore.h>
47 #include <coreplugin/messagebox.h>
48 
49 #include <cpptools/clangdiagnosticconfigsmodel.h>
50 #include <cpptools/cppmodelmanager.h>
51 
52 #include <debugger/analyzer/analyzermanager.h>
53 
54 #include <projectexplorer/kitinformation.h>
55 #include <projectexplorer/projectexplorer.h>
56 #include <projectexplorer/projectexplorericons.h>
57 #include <projectexplorer/session.h>
58 #include <projectexplorer/target.h>
59 #include <projectexplorer/taskhub.h>
60 
61 #include <texteditor/textdocument.h>
62 
63 #include <utils/algorithm.h>
64 #include <utils/checkablemessagebox.h>
65 #include <utils/fancylineedit.h>
66 #include <utils/fancymainwindow.h>
67 #include <utils/infolabel.h>
68 #include <utils/progressindicator.h>
69 #include <utils/proxyaction.h>
70 #include <utils/utilsicons.h>
71 
72 #include <QAction>
73 #include <QCheckBox>
74 #include <QDesktopServices>
75 #include <QFileDialog>
76 #include <QHBoxLayout>
77 #include <QLabel>
78 #include <QSortFilterProxyModel>
79 #include <QToolButton>
80 
81 using namespace Core;
82 using namespace CppTools;
83 using namespace Debugger;
84 using namespace ProjectExplorer;
85 using namespace Utils;
86 
87 namespace ClangTools {
88 namespace Internal {
89 
90 static ClangTool *s_instance;
91 
makeLink(const QString & text)92 static QString makeLink(const QString &text)
93 {
94     return QString("<a href=t>%1</a>").arg(text);
95 }
96 
97 class InfoBarWidget : public QFrame
98 {
99     Q_OBJECT
100 
101 public:
InfoBarWidget()102     InfoBarWidget()
103         : m_progressIndicator(new Utils::ProgressIndicator(ProgressIndicatorSize::Small))
104         , m_info(new InfoLabel({}, InfoLabel::Information))
105         , m_error(new InfoLabel({}, InfoLabel::Warning))
106         , m_diagStats(new QLabel)
107     {
108         m_info->setElideMode(Qt::ElideNone);
109         m_error->setElideMode(Qt::ElideNone);
110 
111         m_diagStats->setTextInteractionFlags(Qt::TextBrowserInteraction);
112 
113         QHBoxLayout *layout = new QHBoxLayout;
114         layout->setContentsMargins(5, 5, 5, 5);
115         layout->addWidget(m_progressIndicator);
116         layout->addWidget(m_info);
117         layout->addWidget(m_error);
118         layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
119         layout->addWidget(m_diagStats);
120         setLayout(layout);
121 
122         QPalette pal;
123         pal.setColor(QPalette::Window, Utils::creatorTheme()->color(Utils::Theme::InfoBarBackground));
124         pal.setColor(QPalette::WindowText, Utils::creatorTheme()->color(Utils::Theme::InfoBarText));
125         setPalette(pal);
126 
127         setAutoFillBackground(true);
128     }
129 
130     // Info
131     enum InfoIconType { ProgressIcon, InfoIcon };
setInfoIcon(InfoIconType type)132     void setInfoIcon(InfoIconType type)
133     {
134         const bool showProgress = type == ProgressIcon;
135         m_progressIndicator->setVisible(showProgress);
136         m_info->setType(showProgress ? InfoLabel::None : InfoLabel::Information);
137     }
infoText() const138     QString infoText() const { return m_info->text(); }
setInfoText(const QString & text)139     void setInfoText(const QString &text)
140     {
141         m_info->setVisible(!text.isEmpty());
142         m_info->setText(text);
143         evaluateVisibility();
144     }
145 
146     // Error
147     using OnLinkActivated = std::function<void()>;
148     enum IssueType { Warning, Error };
149 
errorText() const150     QString errorText() const { return m_error->text(); }
setError(IssueType type,const QString & text,const OnLinkActivated & linkAction=OnLinkActivated ())151     void setError(IssueType type,
152                   const QString &text,
153                   const OnLinkActivated &linkAction = OnLinkActivated())
154     {
155         m_error->setVisible(!text.isEmpty());
156         m_error->setText(text);
157         m_error->setType(type == Warning ? InfoLabel::Warning : InfoLabel::Error);
158         m_error->disconnect();
159         if (linkAction)
160             connect(m_error, &QLabel::linkActivated, this, linkAction);
161         evaluateVisibility();
162     }
163 
164     // Diag stats
setDiagText(const QString & text)165     void setDiagText(const QString &text) { m_diagStats->setText(text); }
166 
reset()167     void reset()
168     {
169         setInfoIcon(InfoIcon);
170         setInfoText({});
171         setError(Warning, {}, {});
172         setDiagText({});
173     }
174 
evaluateVisibility()175     void evaluateVisibility()
176     {
177         setVisible(!infoText().isEmpty() || !errorText().isEmpty());
178     }
179 
180 private:
181     Utils::ProgressIndicator *m_progressIndicator;
182     InfoLabel *m_info;
183     InfoLabel *m_error;
184     QLabel *m_diagStats;
185 };
186 
187 class SelectFixitsCheckBox : public QCheckBox
188 {
189     Q_OBJECT
190 
191 private:
nextCheckState()192     void nextCheckState() final
193     {
194         setCheckState(checkState() == Qt::Checked ? Qt::Unchecked : Qt::Checked);
195     }
196 };
197 
198 class ApplyFixIts
199 {
200 public:
201     class RefactoringFileInfo
202     {
203     public:
204         FixitsRefactoringFile file;
205         QVector<DiagnosticItem *> diagnosticItems;
206         bool hasScheduledFixits = false;
207     };
208 
ApplyFixIts(const QVector<DiagnosticItem * > & diagnosticItems)209     ApplyFixIts(const QVector<DiagnosticItem *> &diagnosticItems)
210     {
211         for (DiagnosticItem *diagnosticItem : diagnosticItems) {
212             const Utils::FilePath &filePath = diagnosticItem->diagnostic().location.filePath;
213             QTC_ASSERT(!filePath.isEmpty(), continue);
214 
215             // Get or create refactoring file
216             RefactoringFileInfo &fileInfo = m_refactoringFileInfos[filePath];
217 
218             // Append item
219             fileInfo.diagnosticItems += diagnosticItem;
220             if (diagnosticItem->fixItStatus() == FixitStatus::Scheduled)
221                 fileInfo.hasScheduledFixits = true;
222         }
223     }
224 
addFixitOperations(DiagnosticItem * diagnosticItem,const FixitsRefactoringFile & file,bool apply)225     static void addFixitOperations(DiagnosticItem *diagnosticItem,
226                                    const FixitsRefactoringFile &file, bool apply)
227     {
228         if (!diagnosticItem->hasNewFixIts())
229             return;
230 
231         // Did we already created the fixit operations?
232         ReplacementOperations currentOps = diagnosticItem->fixitOperations();
233         if (!currentOps.isEmpty()) {
234             for (ReplacementOperation *op : currentOps)
235                 op->apply = apply;
236             return;
237         }
238 
239         // Collect/construct the fixit operations
240         ReplacementOperations replacements;
241 
242         for (const ExplainingStep &step : diagnosticItem->diagnostic().explainingSteps) {
243             if (!step.isFixIt)
244                 continue;
245 
246             const Debugger::DiagnosticLocation start = step.ranges.first();
247             const Debugger::DiagnosticLocation end = step.ranges.last();
248             const int startPos = file.position(start.filePath.toString(), start.line, start.column);
249             const int endPos = file.position(start.filePath.toString(), end.line, end.column);
250 
251             auto op = new ReplacementOperation;
252             op->pos = startPos;
253             op->length = endPos - startPos;
254             op->text = step.message;
255             op->fileName = start.filePath.toString();
256             op->apply = apply;
257 
258             replacements += op;
259         }
260 
261         diagnosticItem->setFixitOperations(replacements);
262     }
263 
apply(ClangToolsDiagnosticModel * model)264     void apply(ClangToolsDiagnosticModel *model)
265     {
266         for (auto it = m_refactoringFileInfos.begin(); it != m_refactoringFileInfos.end(); ++it) {
267             RefactoringFileInfo &fileInfo = it.value();
268 
269             QVector<DiagnosticItem *> itemsScheduledOrSchedulable;
270             QVector<DiagnosticItem *> itemsScheduled;
271             QVector<DiagnosticItem *> itemsSchedulable;
272 
273             // Construct refactoring operations
274             for (DiagnosticItem *diagnosticItem : qAsConst(fileInfo.diagnosticItems)) {
275                 const FixitStatus fixItStatus = diagnosticItem->fixItStatus();
276 
277                 const bool isScheduled = fixItStatus == FixitStatus::Scheduled;
278                 const bool isSchedulable = fileInfo.hasScheduledFixits
279                                            && fixItStatus == FixitStatus::NotScheduled;
280 
281                 if (isScheduled || isSchedulable) {
282                     addFixitOperations(diagnosticItem, fileInfo.file, isScheduled);
283                     itemsScheduledOrSchedulable += diagnosticItem;
284                     if (isScheduled)
285                         itemsScheduled += diagnosticItem;
286                     else
287                         itemsSchedulable += diagnosticItem;
288                 }
289             }
290 
291             // Collect replacements
292             ReplacementOperations ops;
293             for (DiagnosticItem *item : qAsConst(itemsScheduledOrSchedulable))
294                 ops += item->fixitOperations();
295 
296             if (ops.empty())
297                 continue;
298 
299             // Apply file
300             QVector<DiagnosticItem *> itemsApplied;
301             QVector<DiagnosticItem *> itemsFailedToApply;
302             QVector<DiagnosticItem *> itemsInvalidated;
303 
304             fileInfo.file.setReplacements(ops);
305             model->removeWatchedPath(ops.first()->fileName);
306             if (fileInfo.file.apply()) {
307                 itemsApplied = itemsScheduled;
308             } else {
309                 itemsFailedToApply = itemsScheduled;
310                 itemsInvalidated = itemsSchedulable;
311             }
312             model->addWatchedPath(ops.first()->fileName);
313 
314             // Update DiagnosticItem state
315             for (DiagnosticItem *diagnosticItem : qAsConst(itemsScheduled))
316                 diagnosticItem->setFixItStatus(FixitStatus::Applied);
317             for (DiagnosticItem *diagnosticItem : qAsConst(itemsFailedToApply))
318                 diagnosticItem->setFixItStatus(FixitStatus::FailedToApply);
319             for (DiagnosticItem *diagnosticItem : qAsConst(itemsInvalidated))
320                 diagnosticItem->setFixItStatus(FixitStatus::Invalidated);
321         }
322     }
323 
324 private:
325     QMap<Utils::FilePath, RefactoringFileInfo> m_refactoringFileInfos;
326 };
327 
sortedFileInfos(const QVector<CppTools::ProjectPart::Ptr> & projectParts)328 static FileInfos sortedFileInfos(const QVector<CppTools::ProjectPart::Ptr> &projectParts)
329 {
330     FileInfos fileInfos;
331 
332     for (const CppTools::ProjectPart::Ptr &projectPart : projectParts) {
333         QTC_ASSERT(projectPart, continue);
334         if (!projectPart->selectedForBuilding)
335             continue;
336 
337         for (const CppTools::ProjectFile &file : qAsConst(projectPart->files)) {
338             QTC_ASSERT(file.kind != CppTools::ProjectFile::Unclassified, continue);
339             QTC_ASSERT(file.kind != CppTools::ProjectFile::Unsupported, continue);
340             if (file.path == CppTools::CppModelManager::configurationFileName())
341                 continue;
342 
343             if (file.active && CppTools::ProjectFile::isSource(file.kind)) {
344                 fileInfos.emplace_back(Utils::FilePath::fromString(file.path),
345                                        file.kind,
346                                        projectPart);
347             }
348         }
349     }
350 
351     Utils::sort(fileInfos, [](const FileInfo &fi1, const FileInfo &fi2) {
352         if (fi1.file == fi2.file) {
353             // If the same file appears more than once, prefer contexts where the file is
354             // built as part of an application or library to those where it may not be,
355             // e.g. because it is just listed as some sort of resource.
356             return fi1.projectPart->buildTargetType != BuildTargetType::Unknown
357                     && fi2.projectPart->buildTargetType == BuildTargetType::Unknown;
358         }
359         return fi1.file < fi2.file;
360     });
361     fileInfos.erase(std::unique(fileInfos.begin(), fileInfos.end()), fileInfos.end());
362 
363     return fileInfos;
364 }
365 
runSettings()366 static RunSettings runSettings()
367 {
368     if (Project *project = SessionManager::startupProject()) {
369         const auto projectSettings = ClangToolsProjectSettings::getSettings(project);
370         if (!projectSettings->useGlobalSettings())
371             return projectSettings->runSettings();
372     }
373     return ClangToolsSettings::instance()->runSettings();
374 }
375 
instance()376 ClangTool *ClangTool::instance()
377 {
378     return s_instance;
379 }
380 
ClangTool()381 ClangTool::ClangTool()
382     : m_name("Clang-Tidy and Clazy")
383 {
384     setObjectName("ClangTidyClazyTool");
385     s_instance = this;
386     m_diagnosticModel = new ClangToolsDiagnosticModel(this);
387 
388     auto action = new QAction(tr("Analyze Project..."), this);
389     action->setIcon(Utils::Icons::RUN_SELECTED_TOOLBAR.icon());
390     m_startAction = action;
391 
392     action = new QAction(tr("Analyze Current File"), this);
393     action->setIcon(Utils::Icons::RUN_FILE.icon());
394     m_startOnCurrentFileAction = action;
395 
396     m_stopAction = Debugger::createStopAction();
397 
398     m_diagnosticFilterModel = new DiagnosticFilterModel(this);
399     m_diagnosticFilterModel->setSourceModel(m_diagnosticModel);
400     m_diagnosticFilterModel->setDynamicSortFilter(true);
401 
402     m_infoBarWidget = new InfoBarWidget;
403 
404     m_diagnosticView = new DiagnosticView;
405     initDiagnosticView();
406     m_diagnosticView->setModel(m_diagnosticFilterModel);
407     m_diagnosticView->setSortingEnabled(true);
408     m_diagnosticView->sortByColumn(Debugger::DetailedErrorView::DiagnosticColumn,
409                                    Qt::AscendingOrder);
410     connect(m_diagnosticView, &DiagnosticView::showHelp,
411             this, &ClangTool::help);
412     connect(m_diagnosticView, &DiagnosticView::showFilter,
413             this, &ClangTool::filter);
414     connect(m_diagnosticView, &DiagnosticView::clearFilter,
415             this, &ClangTool::clearFilter);
416     connect(m_diagnosticView, &DiagnosticView::filterForCurrentKind,
417             this, &ClangTool::filterForCurrentKind);
418     connect(m_diagnosticView, &DiagnosticView::filterOutCurrentKind,
419             this, &ClangTool::filterOutCurrentKind);
420 
421     foreach (auto * const model,
422              QList<QAbstractItemModel *>({m_diagnosticModel, m_diagnosticFilterModel})) {
423         connect(model, &QAbstractItemModel::rowsInserted,
424                 this, &ClangTool::updateForCurrentState);
425         connect(model, &QAbstractItemModel::rowsRemoved,
426                 this, &ClangTool::updateForCurrentState);
427         connect(model, &QAbstractItemModel::modelReset,
428                 this, &ClangTool::updateForCurrentState);
429         connect(model, &QAbstractItemModel::layoutChanged, // For QSortFilterProxyModel::invalidate()
430                 this, &ClangTool::updateForCurrentState);
431     }
432 
433     // Go to previous diagnostic
434     action = new QAction(this);
435     action->setDisabled(true);
436     action->setIcon(Utils::Icons::PREV_TOOLBAR.icon());
437     action->setToolTip(tr("Go to previous diagnostic."));
438     connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goBack);
439     m_goBack = action;
440 
441     // Go to next diagnostic
442     action = new QAction(this);
443     action->setDisabled(true);
444     action->setIcon(Utils::Icons::NEXT_TOOLBAR.icon());
445     action->setToolTip(tr("Go to next diagnostic."));
446     connect(action, &QAction::triggered, m_diagnosticView, &DetailedErrorView::goNext);
447     m_goNext = action;
448 
449     // Load diagnostics from file
450     action = new QAction(this);
451     action->setIcon(Utils::Icons::OPENFILE_TOOLBAR.icon());
452     action->setToolTip(tr("Load diagnostics from YAML files exported with \"-export-fixes\"."));
453     connect(action, &QAction::triggered, this, &ClangTool::loadDiagnosticsFromFiles);
454     m_loadExported = action;
455 
456     // Clear data
457     action = new QAction(this);
458     action->setDisabled(true);
459     action->setIcon(Utils::Icons::CLEAN_TOOLBAR.icon());
460     action->setToolTip(tr("Clear"));
461     connect(action, &QAction::triggered, this, [this]() {
462         reset();
463         update();
464     });
465     m_clear = action;
466 
467     // Expand/Collapse
468     action = new QAction(this);
469     action->setDisabled(true);
470     action->setCheckable(true);
471     action->setIcon(Utils::Icons::EXPAND_ALL_TOOLBAR.icon());
472     action->setToolTip(tr("Expand All"));
473     connect(action, &QAction::toggled, [this](bool checked){
474         if (checked) {
475             m_expandCollapse->setToolTip(tr("Collapse All"));
476             m_diagnosticView->expandAll();
477         } else {
478             m_expandCollapse->setToolTip(tr("Expand All"));
479             m_diagnosticView->collapseAll();
480         }
481     });
482     m_expandCollapse = action;
483 
484     // Filter button
485     action = m_showFilter = new QAction(this);
486     action->setIcon(
487         Utils::Icon({{":/utils/images/filtericon.png", Utils::Theme::IconsBaseColor}}).icon());
488     action->setToolTip(tr("Filter Diagnostics"));
489     action->setCheckable(true);
490     connect(action, &QAction::triggered, this, &ClangTool::filter);
491 
492     // Schedule/Unschedule all fixits
493     m_selectFixitsCheckBox = new SelectFixitsCheckBox;
494     m_selectFixitsCheckBox->setText("Select Fixits");
495     m_selectFixitsCheckBox->setEnabled(false);
496     m_selectFixitsCheckBox->setTristate(true);
497     connect(m_selectFixitsCheckBox, &QCheckBox::clicked, this, [this]() {
498         m_diagnosticView->scheduleAllFixits(m_selectFixitsCheckBox->isChecked());
499     });
500 
501     // Apply fixits button
502     m_applyFixitsButton = new QToolButton;
503     m_applyFixitsButton->setText(tr("Apply Fixits"));
504     m_applyFixitsButton->setEnabled(false);
505 
506     connect(m_diagnosticModel, &ClangToolsDiagnosticModel::fixitStatusChanged,
507             m_diagnosticFilterModel, &DiagnosticFilterModel::onFixitStatusChanged);
508     connect(m_diagnosticFilterModel, &DiagnosticFilterModel::fixitCountersChanged,
509             this,
510             [this](int scheduled, int scheduable){
511                 m_selectFixitsCheckBox->setEnabled(scheduable > 0);
512                 m_applyFixitsButton->setEnabled(scheduled > 0);
513 
514                 if (scheduled == 0)
515                     m_selectFixitsCheckBox->setCheckState(Qt::Unchecked);
516                 else if (scheduled == scheduable)
517                     m_selectFixitsCheckBox->setCheckState(Qt::Checked);
518                 else
519                     m_selectFixitsCheckBox->setCheckState(Qt::PartiallyChecked);
520 
521                 updateForCurrentState();
522             });
523     connect(m_applyFixitsButton, &QToolButton::clicked, [this]() {
524         QVector<DiagnosticItem *> diagnosticItems;
525         m_diagnosticModel->forItemsAtLevel<2>([&](DiagnosticItem *item){
526             diagnosticItems += item;
527         });
528 
529         ApplyFixIts(diagnosticItems).apply(m_diagnosticModel);
530     });
531 
532     // Open Project Settings
533     action = new QAction(this);
534     action->setIcon(Utils::Icons::SETTINGS_TOOLBAR.icon());
535     //action->setToolTip(tr("Open Project Settings")); // TODO: Uncomment in master.
536     connect(action, &QAction::triggered, []() {
537         ProjectExplorerPlugin::activateProjectPanel(Constants::PROJECT_PANEL_ID);
538     });
539     m_openProjectSettings = action;
540 
541     ActionContainer *menu = ActionManager::actionContainer(Debugger::Constants::M_DEBUG_ANALYZER);
542     const QString toolTip = tr("Clang-Tidy and Clazy use a customized Clang executable from the "
543                                "Clang project to search for diagnostics.");
544 
545     QVBoxLayout *mainLayout = new QVBoxLayout;
546     mainLayout->setContentsMargins(0, 0, 0, 0);
547     mainLayout->setSpacing(1);
548     mainLayout->addWidget(m_infoBarWidget);
549     mainLayout->addWidget(m_diagnosticView);
550     auto mainWidget = new QWidget;
551     mainWidget->setObjectName("ClangTidyClazyIssuesView");
552     mainWidget->setWindowTitle(tr("Clang-Tidy and Clazy"));
553     mainWidget->setLayout(mainLayout);
554 
555     m_perspective.addWindow(mainWidget, Perspective::SplitVertical, nullptr);
556 
557     action = new QAction(tr("Clang-Tidy and Clazy..."), this);
558     action->setToolTip(toolTip);
559     menu->addAction(ActionManager::registerAction(action, "ClangTidyClazy.Action"),
560                     Debugger::Constants::G_ANALYZER_TOOLS);
561     QObject::connect(action, &QAction::triggered, this, [this]() {
562         startTool(FileSelectionType::AskUser);
563     });
564     QObject::connect(m_startAction, &QAction::triggered, action, &QAction::triggered);
565     QObject::connect(m_startAction, &QAction::changed, action, [action, this] {
566         action->setEnabled(m_startAction->isEnabled());
567     });
568 
569     QObject::connect(m_startOnCurrentFileAction, &QAction::triggered, this, [this] {
570         startTool(FileSelectionType::CurrentFile);
571     });
572 
573     m_perspective.addToolBarAction(m_startAction);
574     m_perspective.addToolBarAction(ProxyAction::proxyActionWithIcon(
575                                        m_startOnCurrentFileAction,
576                                        Utils::Icons::RUN_FILE_TOOLBAR.icon()));
577     m_perspective.addToolBarAction(m_stopAction);
578     m_perspective.addToolBarAction(m_openProjectSettings);
579     m_perspective.addToolbarSeparator();
580     m_perspective.addToolBarAction(m_loadExported);
581     m_perspective.addToolBarAction(m_clear);
582     m_perspective.addToolbarSeparator();
583     m_perspective.addToolBarAction(m_expandCollapse);
584     m_perspective.addToolBarAction(m_goBack);
585     m_perspective.addToolBarAction(m_goNext);
586     m_perspective.addToolbarSeparator();
587     m_perspective.addToolBarAction(m_showFilter);
588     m_perspective.addToolBarWidget(m_selectFixitsCheckBox);
589     m_perspective.addToolBarWidget(m_applyFixitsButton);
590     m_perspective.registerNextPrevShortcuts(m_goNext, m_goBack);
591 
592     update();
593 
594     connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::runActionsUpdated,
595             this, &ClangTool::update);
596     connect(CppModelManager::instance(), &CppModelManager::projectPartsUpdated,
597             this, &ClangTool::update);
598     connect(ClangToolsSettings::instance(), &ClangToolsSettings::changed,
599             this, &ClangTool::update);
600 }
601 
selectPerspective()602 void ClangTool::selectPerspective()
603 {
604     m_perspective.select();
605 }
606 
startTool(ClangTool::FileSelection fileSelection)607 void ClangTool::startTool(ClangTool::FileSelection fileSelection)
608 {
609     const RunSettings theRunSettings = runSettings();
610     startTool(fileSelection, theRunSettings, diagnosticConfig(theRunSettings.diagnosticConfigId()));
611 }
612 
continueDespiteReleaseBuild(const QString & toolName)613 static bool continueDespiteReleaseBuild(const QString &toolName)
614 {
615     const QString wrongMode = ClangTool::tr("Release");
616     const QString title = ClangTool::tr("Run %1 in %2 Mode?").arg(toolName, wrongMode);
617     const QString problem
618         = ClangTool::tr(
619               "You are trying to run the tool \"%1\" on an application in %2 mode. The tool is "
620               "designed to be used in Debug mode since enabled assertions can reduce the number of "
621               "false positives.")
622               .arg(toolName, wrongMode);
623     const QString question = ClangTool::tr(
624                                  "Do you want to continue and run the tool in %1 mode?")
625                                  .arg(wrongMode);
626     const QString message = QString("<html><head/><body>"
627                                     "<p>%1</p>"
628                                     "<p>%2</p>"
629                                     "</body></html>")
630                                 .arg(problem, question);
631     return CheckableMessageBox::doNotAskAgainQuestion(ICore::dialogParent(),
632                                                       title,
633                                                       message,
634                                                       ICore::settings(),
635                                                       "ClangToolsCorrectModeWarning")
636            == QDialogButtonBox::Yes;
637 }
638 
startTool(ClangTool::FileSelection fileSelection,const RunSettings & runSettings,const CppTools::ClangDiagnosticConfig & diagnosticConfig)639 void ClangTool::startTool(ClangTool::FileSelection fileSelection,
640                           const RunSettings &runSettings,
641                           const CppTools::ClangDiagnosticConfig &diagnosticConfig)
642 {
643     Project *project = SessionManager::startupProject();
644     QTC_ASSERT(project, return);
645     QTC_ASSERT(project->activeTarget(), return);
646 
647     // Continue despite release mode?
648     if (BuildConfiguration *bc = project->activeTarget()->activeBuildConfiguration()) {
649         if (bc->buildType() == BuildConfiguration::Release)
650             if (!continueDespiteReleaseBuild(m_name))
651                 return;
652     }
653 
654     TaskHub::clearTasks(taskCategory());
655 
656     // Collect files to analyze
657     const FileInfos fileInfos = collectFileInfos(project, fileSelection);
658     if (fileInfos.empty())
659         return;
660 
661     // Reset
662     reset();
663 
664     // Run control
665     m_runControl = new RunControl(Constants::CLANGTIDYCLAZY_RUN_MODE);
666     m_runControl->setDisplayName(tr("Clang-Tidy and Clazy"));
667     m_runControl->setIcon(ProjectExplorer::Icons::ANALYZER_START_SMALL_TOOLBAR);
668     m_runControl->setTarget(project->activeTarget());
669     m_stopAction->disconnect();
670     connect(m_stopAction, &QAction::triggered, m_runControl, [this] {
671         m_runControl->appendMessage(tr("Clang-Tidy and Clazy tool stopped by user."),
672                                     NormalMessageFormat);
673         m_runControl->initiateStop();
674         setState(State::StoppedByUser);
675     });
676     connect(m_runControl, &RunControl::stopped, this, &ClangTool::onRunControlStopped);
677 
678     // Run worker
679     const bool preventBuild = holds_alternative<FilePath>(fileSelection)
680                               || get<FileSelectionType>(fileSelection)
681                                      == FileSelectionType::CurrentFile;
682     const bool buildBeforeAnalysis = !preventBuild && runSettings.buildBeforeAnalysis();
683     m_runWorker = new ClangToolRunWorker(m_runControl,
684                                          runSettings,
685                                          diagnosticConfig,
686                                          fileInfos,
687                                          buildBeforeAnalysis);
688     connect(m_runWorker, &ClangToolRunWorker::buildFailed,this, &ClangTool::onBuildFailed);
689     connect(m_runWorker, &ClangToolRunWorker::startFailed, this, &ClangTool::onStartFailed);
690     connect(m_runWorker, &ClangToolRunWorker::started, this, &ClangTool::onStarted);
691     connect(m_runWorker, &ClangToolRunWorker::runnerFinished, this, [this]() {
692         m_filesCount = m_runWorker->totalFilesToAnalyze();
693         m_filesSucceeded = m_runWorker->filesAnalyzed();
694         m_filesFailed = m_runWorker->filesNotAnalyzed();
695         updateForCurrentState();
696     });
697 
698     // More init and UI update
699     m_diagnosticFilterModel->setProject(project);
700     m_perspective.select();
701     if (buildBeforeAnalysis)
702         m_infoBarWidget->setInfoText("Waiting for build to finish...");
703     setState(State::PreparationStarted);
704 
705     // Start
706     ProjectExplorerPlugin::startRunControl(m_runControl);
707 }
708 
read(OutputFileFormat outputFileFormat,const QString & logFilePath,const QSet<FilePath> & projectFiles,QString * errorMessage) const709 Diagnostics ClangTool::read(OutputFileFormat outputFileFormat,
710                             const QString &logFilePath,
711                             const QSet<FilePath> &projectFiles,
712                             QString *errorMessage) const
713 {
714     const auto acceptFromFilePath = [projectFiles](const Utils::FilePath &filePath) {
715         return projectFiles.contains(filePath);
716     };
717 
718     if (outputFileFormat == OutputFileFormat::Yaml) {
719         return readExportedDiagnostics(Utils::FilePath::fromString(logFilePath),
720                                        acceptFromFilePath,
721                                        errorMessage);
722     }
723 
724     return {};
725 }
726 
collectFileInfos(Project * project,FileSelection fileSelection)727 FileInfos ClangTool::collectFileInfos(Project *project, FileSelection fileSelection)
728 {
729     FileSelectionType *selectionType = get_if<FileSelectionType>(&fileSelection);
730     // early bailout
731     if (selectionType && *selectionType == FileSelectionType::CurrentFile
732         && !EditorManager::currentDocument()) {
733         TaskHub::addTask(Task::Error, tr("Cannot analyze current file: No files open."),
734                                          taskCategory());
735         TaskHub::requestPopup();
736         return {};
737     }
738 
739     auto projectInfo = CppTools::CppModelManager::instance()->projectInfo(project);
740     QTC_ASSERT(projectInfo.isValid(), return FileInfos());
741 
742     const FileInfos allFileInfos = sortedFileInfos(projectInfo.projectParts());
743 
744     if (selectionType && *selectionType == FileSelectionType::AllFiles)
745         return allFileInfos;
746 
747     if (selectionType && *selectionType == FileSelectionType::AskUser) {
748         static int initialProviderIndex = 0;
749         SelectableFilesDialog dialog(projectInfo,
750                                      fileInfoProviders(project, allFileInfos),
751                                      initialProviderIndex);
752         if (dialog.exec() == QDialog::Rejected)
753             return FileInfos();
754         initialProviderIndex = dialog.currentProviderIndex();
755         return dialog.fileInfos();
756     }
757 
758     const FilePath filePath = holds_alternative<FilePath>(fileSelection)
759                                   ? get<FilePath>(fileSelection)
760                                   : EditorManager::currentDocument()->filePath(); // see early bailout
761     if (!filePath.isEmpty()) {
762         const FileInfo fileInfo = Utils::findOrDefault(allFileInfos, [&](const FileInfo &fi) {
763             return fi.file == filePath;
764         });
765         if (!fileInfo.file.isEmpty())
766             return {fileInfo};
767         TaskHub::addTask(Task::Error,
768                          tr("Cannot analyze current file: \"%1\" is not a known source file.")
769                          .arg(filePath.toUserOutput()),
770                          taskCategory());
771         TaskHub::requestPopup();
772     }
773 
774     return {};
775 }
776 
name() const777 const QString &ClangTool::name() const
778 {
779     return m_name;
780 }
781 
initDiagnosticView()782 void ClangTool::initDiagnosticView()
783 {
784     m_diagnosticView->setFrameStyle(QFrame::NoFrame);
785     m_diagnosticView->setAttribute(Qt::WA_MacShowFocusRect, false);
786     m_diagnosticView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
787     m_diagnosticView->setAutoScroll(false);
788 }
789 
loadDiagnosticsFromFiles()790 void ClangTool::loadDiagnosticsFromFiles()
791 {
792     // Ask user for files
793     const QStringList filePaths
794         = QFileDialog::getOpenFileNames(Core::ICore::dialogParent(),
795                                         tr("Select YAML Files with Diagnostics"),
796                                         QDir::homePath(),
797                                         tr("YAML Files (*.yml *.yaml);;All Files (*)"));
798     if (filePaths.isEmpty())
799         return;
800 
801     // Load files
802     Diagnostics diagnostics;
803     QString errors;
804     for (const QString &filePath : filePaths) {
805         QString currentError;
806         diagnostics << readExportedDiagnostics(Utils::FilePath::fromString(filePath),
807                                                {},
808                                                &currentError);
809 
810         if (!currentError.isEmpty()) {
811             if (!errors.isEmpty())
812                 errors.append("\n");
813             errors.append(currentError);
814         }
815     }
816 
817     // Show errors
818     if (!errors.isEmpty()) {
819         AsynchronousMessageBox::critical(tr("Error Loading Diagnostics"), errors);
820         return;
821     }
822 
823     // Show imported
824     reset();
825     onNewDiagnosticsAvailable(diagnostics, /*generateMarks =*/ true);
826     setState(State::ImportFinished);
827 }
828 
diagnosticItem(const QModelIndex & index) const829 DiagnosticItem *ClangTool::diagnosticItem(const QModelIndex &index) const
830 {
831     if (!index.isValid())
832         return {};
833 
834     TreeItem *item = m_diagnosticModel->itemForIndex(m_diagnosticFilterModel->mapToSource(index));
835     if (item->level() == 3)
836         item = item->parent();
837     if (item->level() == 2)
838         return static_cast<DiagnosticItem *>(item);
839 
840     return {};
841 }
842 
showOutputPane()843 void ClangTool::showOutputPane()
844 {
845     ProjectExplorerPlugin::showOutputPaneForRunControl(m_runControl);
846 }
847 
reset()848 void ClangTool::reset()
849 {
850     m_clear->setEnabled(false);
851     m_showFilter->setEnabled(false);
852     m_showFilter->setChecked(false);
853     m_selectFixitsCheckBox->setEnabled(false);
854     m_applyFixitsButton->setEnabled(false);
855 
856     m_diagnosticModel->clear();
857     m_diagnosticFilterModel->reset();
858 
859     m_infoBarWidget->reset();
860 
861     m_state = State::Initial;
862     m_runControl = nullptr;
863     m_runWorker = nullptr;
864 
865     m_filesCount = 0;
866     m_filesSucceeded = 0;
867     m_filesFailed = 0;
868 }
869 
canAnalyzeProject(Project * project)870 static bool canAnalyzeProject(Project *project)
871 {
872     if (const Target *target = project->activeTarget()) {
873         const Utils::Id c = ProjectExplorer::Constants::C_LANGUAGE_ID;
874         const Utils::Id cxx = ProjectExplorer::Constants::CXX_LANGUAGE_ID;
875         const bool projectSupportsLanguage = project->projectLanguages().contains(c)
876                                              || project->projectLanguages().contains(cxx);
877         return projectSupportsLanguage
878                && CppModelManager::instance()->projectInfo(project).isValid()
879                && ToolChainKitAspect::cxxToolChain(target->kit());
880     }
881     return false;
882 }
883 
884 struct CheckResult {
885     enum {
886         InvalidTidyExecutable,
887         InvalidClazyExecutable,
888         ProjectNotOpen,
889         ProjectNotSuitable,
890         ReadyToAnalyze,
891     } kind;
892     QString errorText;
893 };
894 
canAnalyze()895 static CheckResult canAnalyze()
896 {
897     const ClangDiagnosticConfig config = diagnosticConfig(runSettings().diagnosticConfigId());
898 
899     if (config.isClangTidyEnabled() && !isFileExecutable(clangTidyExecutable())) {
900         return {CheckResult::InvalidTidyExecutable,
901                 ClangTool::tr("Set a valid Clang-Tidy executable.")};
902     }
903 
904     if (config.isClazyEnabled() && !isFileExecutable(clazyStandaloneExecutable())) {
905         return {CheckResult::InvalidClazyExecutable,
906                 ClangTool::tr("Set a valid Clazy-Standalone executable.")};
907     }
908 
909     if (Project *project = SessionManager::startupProject()) {
910         if (!canAnalyzeProject(project)) {
911             return {CheckResult::ProjectNotSuitable,
912                     ClangTool::tr("Project \"%1\" is not a C/C++ project.")
913                         .arg(project->displayName())};
914         }
915     } else {
916         return {CheckResult::ProjectNotOpen,
917                 ClangTool::tr("Open a C/C++ project to start analyzing.")};
918     }
919 
920     return {CheckResult::ReadyToAnalyze, {}};
921 }
922 
updateForInitialState()923 void ClangTool::updateForInitialState()
924 {
925     if (m_state != State::Initial)
926         return;
927 
928     m_infoBarWidget->reset();
929 
930     const CheckResult result = canAnalyze();
931     switch (result.kind)
932     case CheckResult::InvalidTidyExecutable: {
933     case CheckResult::InvalidClazyExecutable:
934         m_infoBarWidget->setError(InfoBarWidget::Warning,
935                                   makeLink(result.errorText),
936                                   [](){ ICore::showOptionsDialog(Constants::SETTINGS_PAGE_ID); });
937         break;
938     case CheckResult::ProjectNotSuitable:
939     case CheckResult::ProjectNotOpen:
940     case CheckResult::ReadyToAnalyze:
941         break;
942         }
943 }
944 
help()945 void ClangTool::help()
946 {
947     if (DiagnosticItem *item = diagnosticItem(m_diagnosticView->currentIndex())) {
948         const QString url = documentationUrl(item->diagnostic().name);
949         if (!url.isEmpty())
950             QDesktopServices::openUrl(url);
951     }
952 }
953 
setFilterOptions(const OptionalFilterOptions & filterOptions)954 void ClangTool::setFilterOptions(const OptionalFilterOptions &filterOptions)
955 {
956     m_diagnosticFilterModel->setFilterOptions(filterOptions);
957     const bool isFilterActive = filterOptions
958                                     ? (filterOptions->checks != m_diagnosticModel->allChecks())
959                                     : false;
960     m_showFilter->setChecked(isFilterActive);
961 }
962 
filter()963 void ClangTool::filter()
964 {
965     const OptionalFilterOptions filterOptions = m_diagnosticFilterModel->filterOptions();
966 
967     // Collect available and currently shown checks
968     QHash<QString, Check> checks;
969     m_diagnosticModel->forItemsAtLevel<2>([&](DiagnosticItem *item) {
970         const QString checkName = item->diagnostic().name;
971         Check &check = checks[checkName];
972         if (check.name.isEmpty()) {
973             check.name = checkName;
974             check.displayName = checkName;
975             check.count = 1;
976             check.isShown = filterOptions ? filterOptions->checks.contains(checkName) : true;
977             check.hasFixit = check.hasFixit || item->diagnostic().hasFixits;
978             checks.insert(check.name, check);
979         } else {
980             ++check.count;
981         }
982     });
983 
984     // Show dialog
985     FilterDialog dialog(checks.values());
986     if (dialog.exec() == QDialog::Rejected)
987         return;
988 
989     // Apply filter
990     setFilterOptions(FilterOptions{dialog.selectedChecks()});
991 }
992 
clearFilter()993 void ClangTool::clearFilter()
994 {
995     m_diagnosticFilterModel->setFilterOptions({});
996     m_showFilter->setChecked(false);
997 }
998 
filterForCurrentKind()999 void ClangTool::filterForCurrentKind()
1000 {
1001     if (DiagnosticItem *item = diagnosticItem(m_diagnosticView->currentIndex()))
1002         setFilterOptions(FilterOptions{{item->diagnostic().name}});
1003 }
1004 
filterOutCurrentKind()1005 void ClangTool::filterOutCurrentKind()
1006 {
1007     if (DiagnosticItem *item = diagnosticItem(m_diagnosticView->currentIndex())) {
1008         const OptionalFilterOptions filterOpts = m_diagnosticFilterModel->filterOptions();
1009         QSet<QString> checks = filterOpts ? filterOpts->checks : m_diagnosticModel->allChecks();
1010         checks.remove(item->diagnostic().name);
1011 
1012         setFilterOptions(FilterOptions{checks});
1013     }
1014 }
1015 
onBuildFailed()1016 void ClangTool::onBuildFailed()
1017 {
1018     m_infoBarWidget->setError(InfoBarWidget::Error,
1019                               tr("Failed to build the project."),
1020                               [this]() { showOutputPane(); });
1021     setState(State::PreparationFailed);
1022 }
1023 
onStartFailed()1024 void ClangTool::onStartFailed()
1025 {
1026     m_infoBarWidget->setError(InfoBarWidget::Error,
1027                               makeLink(tr("Failed to start the analyzer.")),
1028                               [this]() { showOutputPane(); });
1029     setState(State::PreparationFailed);
1030 }
1031 
onStarted()1032 void ClangTool::onStarted()
1033 {
1034     setState(State::AnalyzerRunning);
1035 }
1036 
onRunControlStopped()1037 void ClangTool::onRunControlStopped()
1038 {
1039     if (m_state != State::StoppedByUser && m_state != State::PreparationFailed)
1040         setState(State::AnalyzerFinished);
1041     emit finished(m_infoBarWidget->errorText());
1042 }
1043 
update()1044 void ClangTool::update()
1045 {
1046     updateForInitialState();
1047     updateForCurrentState();
1048 }
1049 
1050 using DocumentPredicate = std::function<bool(Core::IDocument *)>;
1051 
fileInfosMatchingDocuments(const FileInfos & fileInfos,const DocumentPredicate & predicate)1052 static FileInfos fileInfosMatchingDocuments(const FileInfos &fileInfos,
1053                                             const DocumentPredicate &predicate)
1054 {
1055     QSet<Utils::FilePath> documentPaths;
1056     for (const Core::DocumentModel::Entry *e : Core::DocumentModel::entries()) {
1057         if (predicate(e->document))
1058             documentPaths.insert(e->fileName());
1059     }
1060 
1061     return Utils::filtered(fileInfos, [documentPaths](const FileInfo &fileInfo) {
1062         return documentPaths.contains(fileInfo.file);
1063     });
1064 }
1065 
fileInfosMatchingOpenedDocuments(const FileInfos & fileInfos)1066 static FileInfos fileInfosMatchingOpenedDocuments(const FileInfos &fileInfos)
1067 {
1068     // Note that (initially) suspended text documents are still IDocuments, not yet TextDocuments.
1069     return fileInfosMatchingDocuments(fileInfos, [](Core::IDocument *) { return true; });
1070 }
1071 
fileInfosMatchingEditedDocuments(const FileInfos & fileInfos)1072 static FileInfos fileInfosMatchingEditedDocuments(const FileInfos &fileInfos)
1073 {
1074     return fileInfosMatchingDocuments(fileInfos, [](Core::IDocument *document) {
1075         if (auto textDocument = qobject_cast<TextEditor::TextDocument*>(document))
1076             return textDocument->document()->revision() > 1;
1077         return false;
1078     });
1079 }
1080 
fileInfoProviders(ProjectExplorer::Project * project,const FileInfos & allFileInfos)1081 FileInfoProviders ClangTool::fileInfoProviders(ProjectExplorer::Project *project,
1082                                                const FileInfos &allFileInfos)
1083 {
1084     const QSharedPointer<ClangToolsProjectSettings> s = ClangToolsProjectSettings::getSettings(project);
1085     static FileInfoSelection openedFilesSelection;
1086     static FileInfoSelection editeddFilesSelection;
1087 
1088     return {
1089         {ClangTool::tr("All Files"),
1090          allFileInfos,
1091          FileInfoSelection{s->selectedDirs(), s->selectedFiles()},
1092          FileInfoProvider::Limited,
1093          [s](const FileInfoSelection &selection) {
1094              s->setSelectedDirs(selection.dirs);
1095              s->setSelectedFiles(selection.files);
1096          }},
1097 
1098         {ClangTool::tr("Opened Files"),
1099          fileInfosMatchingOpenedDocuments(allFileInfos),
1100          openedFilesSelection,
1101          FileInfoProvider::All,
1102          [](const FileInfoSelection &selection) { openedFilesSelection = selection; }},
1103 
1104         {ClangTool::tr("Edited Files"),
1105          fileInfosMatchingEditedDocuments(allFileInfos),
1106          editeddFilesSelection,
1107          FileInfoProvider::All,
1108          [](const FileInfoSelection &selection) { editeddFilesSelection = selection; }},
1109     };
1110 }
1111 
setState(ClangTool::State state)1112 void ClangTool::setState(ClangTool::State state)
1113 {
1114     m_state = state;
1115     updateForCurrentState();
1116 }
1117 
diagnostics() const1118 QSet<Diagnostic> ClangTool::diagnostics() const
1119 {
1120     return Utils::filtered(m_diagnosticModel->diagnostics(), [](const Diagnostic &diagnostic) {
1121         using CppTools::ProjectFile;
1122         return ProjectFile::isSource(ProjectFile::classify(diagnostic.location.filePath.toString()));
1123     });
1124 }
1125 
onNewDiagnosticsAvailable(const Diagnostics & diagnostics,bool generateMarks)1126 void ClangTool::onNewDiagnosticsAvailable(const Diagnostics &diagnostics, bool generateMarks)
1127 {
1128     QTC_ASSERT(m_diagnosticModel, return);
1129     m_diagnosticModel->addDiagnostics(diagnostics, generateMarks);
1130 }
1131 
updateForCurrentState()1132 void ClangTool::updateForCurrentState()
1133 {
1134     // Actions
1135     bool canStart = false;
1136     const bool isPreparing = m_state == State::PreparationStarted;
1137     const bool isRunning = m_state == State::AnalyzerRunning;
1138     QString startActionToolTip = m_startAction->text();
1139     QString startOnCurrentToolTip = m_startOnCurrentFileAction->text();
1140     if (!isRunning) {
1141         const CheckResult result = canAnalyze();
1142         canStart = result.kind == CheckResult::ReadyToAnalyze;
1143         if (!canStart) {
1144             startActionToolTip = result.errorText;
1145             startOnCurrentToolTip = result.errorText;
1146         }
1147     }
1148     m_startAction->setEnabled(canStart);
1149     m_startAction->setToolTip(startActionToolTip);
1150     m_startOnCurrentFileAction->setEnabled(canStart);
1151     m_startOnCurrentFileAction->setToolTip(startOnCurrentToolTip);
1152     m_stopAction->setEnabled(isPreparing || isRunning);
1153 
1154     const int issuesFound = m_diagnosticModel->diagnostics().count();
1155     const int issuesVisible = m_diagnosticFilterModel->diagnostics();
1156     m_goBack->setEnabled(issuesVisible > 0);
1157     m_goNext->setEnabled(issuesVisible > 0);
1158     m_clear->setEnabled(!isRunning);
1159     m_expandCollapse->setEnabled(issuesVisible);
1160     m_loadExported->setEnabled(!isRunning);
1161     m_showFilter->setEnabled(issuesFound > 1);
1162 
1163     // Diagnostic view
1164     m_diagnosticView->setCursor(isRunning ? Qt::BusyCursor : Qt::ArrowCursor);
1165 
1166     // Info bar: errors
1167     const bool hasErrorText = !m_infoBarWidget->errorText().isEmpty();
1168     const bool hasErrors = m_filesFailed > 0;
1169     if (hasErrors && !hasErrorText) {
1170         const QString text = makeLink(tr("Failed to analyze %n file(s).", nullptr, m_filesFailed));
1171         m_infoBarWidget->setError(InfoBarWidget::Warning, text, [this]() { showOutputPane(); });
1172     }
1173 
1174     // Info bar: info
1175     bool showProgressIcon = false;
1176     QString infoText;
1177     switch (m_state) {
1178     case State::Initial:
1179         infoText = m_infoBarWidget->infoText();
1180         break;
1181     case State::AnalyzerRunning:
1182         showProgressIcon = true;
1183         if (m_filesCount == 0) {
1184             infoText = tr("Analyzing..."); // Not yet fully started/initialized
1185         } else {
1186             infoText = tr("Analyzing... %1 of %n file(s) processed.", nullptr, m_filesCount)
1187                            .arg(m_filesSucceeded + m_filesFailed);
1188         }
1189         break;
1190     case State::PreparationStarted:
1191         showProgressIcon = true;
1192         infoText = m_infoBarWidget->infoText();
1193         break;
1194     case State::PreparationFailed:
1195         break; // OK, we just show an error.
1196     case State::StoppedByUser:
1197         infoText = tr("Analysis stopped by user.");
1198         break;
1199     case State::AnalyzerFinished:
1200         infoText = tr("Finished processing %n file(s).", nullptr, m_filesCount);
1201         break;
1202     case State::ImportFinished:
1203         infoText = tr("Diagnostics imported.");
1204         break;
1205     }
1206     m_infoBarWidget->setInfoText(infoText);
1207     m_infoBarWidget->setInfoIcon(showProgressIcon ? InfoBarWidget::ProgressIcon
1208                                                   : InfoBarWidget::InfoIcon);
1209 
1210     // Info bar: diagnostic stats
1211     QString diagText;
1212     if (issuesFound) {
1213         diagText = tr("%1 diagnostics. %2 fixits, %3 selected.")
1214                    .arg(issuesVisible)
1215                    .arg(m_diagnosticFilterModel->fixitsScheduable())
1216                    .arg(m_diagnosticFilterModel->fixitsScheduled());
1217     } else if (m_state != State::AnalyzerRunning
1218                && m_state != State::Initial
1219                && m_state != State::PreparationStarted
1220                && m_state != State::PreparationFailed) {
1221         diagText = tr("No diagnostics.");
1222     }
1223     m_infoBarWidget->setDiagText(diagText);
1224 }
1225 
1226 } // namespace Internal
1227 } // namespace ClangTools
1228 
1229 #include "clangtool.moc"
1230