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 ¤tError);
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