File indexing completed on 2024-04-28 05:49:07

0001 /*  This file is part of the Kate project.
0002  *
0003  *  SPDX-FileCopyrightText: 2012 Christoph Cullmann <cullmann@kde.org>
0004  *
0005  *  SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #include "kateprojectinfoviewcodeanalysis.h"
0009 #include "hostprocess.h"
0010 #include "kateproject.h"
0011 #include "kateprojectcodeanalysistool.h"
0012 #include "kateprojectpluginview.h"
0013 #include "tools/codeanalysisselector.h"
0014 
0015 #include "diagnostics/diagnostic_types.h"
0016 #include "diagnostics/diagnosticview.h"
0017 #include "ktexteditor_utils.h"
0018 
0019 #include <QFileInfo>
0020 #include <QHBoxLayout>
0021 #include <QLabel>
0022 #include <QSortFilterProxyModel>
0023 #include <QStandardPaths>
0024 #include <QToolTip>
0025 #include <QVBoxLayout>
0026 
0027 #include <KLocalizedString>
0028 #include <QTimer>
0029 
0030 #include <KTextEditor/MainWindow>
0031 
0032 KateProjectInfoViewCodeAnalysis::KateProjectInfoViewCodeAnalysis(KateProjectPluginView *pluginView, KateProject *project)
0033     : m_pluginView(pluginView)
0034     , m_project(project)
0035     , m_startStopAnalysis(new QPushButton(i18n("Start Analysis...")))
0036     , m_analyzer(nullptr)
0037     , m_analysisTool(nullptr)
0038     , m_toolSelector(new QComboBox())
0039     , m_toolInfoLabel(new QLabel(this))
0040     , m_diagnosticProvider(new DiagnosticsProvider(pluginView->mainWindow(), this))
0041 {
0042     m_diagnosticProvider->setObjectName(QStringLiteral("CodeAnalysisDiagnosticProvider"));
0043     m_diagnosticProvider->name = i18nc("'%1' refers to project name, e.g,. Code Analysis - MyProject", "Code Analysis - %1", project->name());
0044 
0045     // We don't want the diagnostics to be cleared automatically if a file closes
0046     m_diagnosticProvider->setPersistentDiagnostics(true);
0047 
0048     /**
0049      * Connect selection change callback
0050      * and attach model to code analysis selector
0051      */
0052     connect(m_toolSelector,
0053             static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
0054             this,
0055             &KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged);
0056     m_toolSelector->setModel(KateProjectCodeAnalysisSelector::model(this));
0057     m_toolSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
0058 
0059     /**
0060      * layout widget
0061      */
0062     QVBoxLayout *layout = new QVBoxLayout;
0063     // top: selector and buttons...
0064     QHBoxLayout *hlayout = new QHBoxLayout;
0065     layout->addLayout(hlayout);
0066     hlayout->addWidget(m_toolSelector);
0067     hlayout->addWidget(m_startStopAnalysis);
0068     hlayout->addStretch();
0069 
0070     layout->addWidget(m_toolInfoLabel);
0071 
0072     // below: result list...
0073     layout->addStretch();
0074     setLayout(layout);
0075 
0076     /**
0077      * connect needed signals
0078      */
0079     connect(m_startStopAnalysis, &QPushButton::clicked, this, &KateProjectInfoViewCodeAnalysis::slotStartStopClicked);
0080 }
0081 
0082 KateProjectInfoViewCodeAnalysis::~KateProjectInfoViewCodeAnalysis()
0083 {
0084     if (m_analyzer && m_analyzer->state() != QProcess::NotRunning) {
0085         m_analyzer->kill();
0086         m_analyzer->blockSignals(true);
0087         m_analyzer->waitForFinished();
0088     }
0089     delete m_analyzer;
0090 }
0091 
0092 void KateProjectInfoViewCodeAnalysis::slotToolSelectionChanged(int)
0093 {
0094     m_analysisTool = m_toolSelector->currentData(Qt::UserRole + 1).value<KateProjectCodeAnalysisTool *>();
0095     if (m_analysisTool) {
0096         const QString fullExecutable = safeExecutableName(m_analysisTool->path());
0097         if (fullExecutable.isEmpty()) {
0098             m_startStopAnalysis->setEnabled(false);
0099             m_toolInfoLabel->setText(
0100                 i18n("'%1' is not installed on your system, %2.<br/><br/>%3. The tool will be run on all project files which match this list of file "
0101                      "extensions:<br/><b>%4</b>",
0102                      m_analysisTool->name(),
0103                      m_analysisTool->notInstalledMessage(),
0104                      m_analysisTool->description(),
0105                      m_analysisTool->fileExtensions()));
0106 
0107         } else {
0108             m_startStopAnalysis->setEnabled(true);
0109             m_toolInfoLabel->setText(
0110                 i18n("Using %1 installed at: %2.<br/><br/>%3. The tool will be run on all project files which match this list of file "
0111                      "extensions:<br/><b>%4</b>",
0112                      m_analysisTool->name(),
0113                      fullExecutable,
0114                      m_analysisTool->description(),
0115                      m_analysisTool->fileExtensions()));
0116         }
0117     }
0118 }
0119 
0120 void KateProjectInfoViewCodeAnalysis::slotStartStopClicked()
0121 {
0122     /**
0123      * get files for the external tool
0124      */
0125     m_analysisTool = m_toolSelector->currentData(Qt::UserRole + 1).value<KateProjectCodeAnalysisTool *>();
0126     m_analysisTool->setProject(m_project);
0127     m_analysisTool->setMainWindow(m_pluginView->mainWindow());
0128 
0129     /**
0130      * clear existing entries
0131      */
0132     Q_EMIT m_diagnosticProvider->requestClearDiagnostics(m_diagnosticProvider);
0133 
0134     /**
0135      * launch selected tool
0136      */
0137     delete m_analyzer;
0138     m_analyzer = new QProcess;
0139     m_analyzer->setProcessChannelMode(QProcess::MergedChannels);
0140 
0141     connect(m_analyzer, &QProcess::readyRead, this, &KateProjectInfoViewCodeAnalysis::slotReadyRead);
0142     connect(m_analyzer, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KateProjectInfoViewCodeAnalysis::finished);
0143 
0144     // ensure we only run the code analyzer from PATH
0145     const QString fullExecutable = safeExecutableName(m_analysisTool->path());
0146     if (!fullExecutable.isEmpty()) {
0147         m_analyzer->setWorkingDirectory(m_project->baseDir());
0148         startHostProcess(*m_analyzer, fullExecutable, m_analysisTool->arguments());
0149     }
0150 
0151     if (fullExecutable.isEmpty() || !m_analyzer->waitForStarted()) {
0152         Utils::showMessage(m_analysisTool->notInstalledMessage(), {}, i18n("CodeAnalysis"), MessageType::Warning);
0153         return;
0154     }
0155 
0156     m_startStopAnalysis->setEnabled(false);
0157 
0158     /**
0159      * write files list and close write channel
0160      */
0161     const QString stdinMessage = m_analysisTool->stdinMessages();
0162     if (!stdinMessage.isEmpty()) {
0163         m_analyzer->write(stdinMessage.toLocal8Bit());
0164     }
0165     m_analyzer->closeWriteChannel();
0166 }
0167 
0168 void KateProjectInfoViewCodeAnalysis::slotReadyRead()
0169 {
0170     /**
0171      * get results of analysis
0172      */
0173     m_errOutput = {};
0174     QHash<QUrl, QList<Diagnostic>> fileDiagnostics;
0175     while (m_analyzer->canReadLine()) {
0176         /**
0177          * get one line, split it, skip it, if too few elements
0178          */
0179         auto rawLine = m_analyzer->readLine();
0180         QString line = QString::fromLocal8Bit(rawLine);
0181         FileDiagnostics fd = m_analysisTool->parseLine(line);
0182         if (!fd.uri.isValid()) {
0183             m_errOutput += rawLine;
0184             continue;
0185         }
0186         fileDiagnostics[fd.uri] << fd.diagnostics;
0187     }
0188 
0189     for (auto it = fileDiagnostics.cbegin(); it != fileDiagnostics.cend(); ++it) {
0190         m_diagnosticProvider->diagnosticsAdded(FileDiagnostics{it.key(), it.value()});
0191     }
0192 
0193     if (!fileDiagnostics.isEmpty()) {
0194         m_diagnosticProvider->showDiagnosticsView();
0195     }
0196 }
0197 
0198 void KateProjectInfoViewCodeAnalysis::finished(int exitCode, QProcess::ExitStatus)
0199 {
0200     m_startStopAnalysis->setEnabled(true);
0201 
0202     if (m_analysisTool->isSuccessfulExitCode(exitCode)) {
0203         // normally 0 is successful but there are exceptions
0204         const QString msg = i18ncp(
0205             "Message to the user that analysis finished. %1 is the name of the program that did the analysis, %2 is a number. e.g., [clang-tidy]Analysis on 5 "
0206             "files finished",
0207             "[%1]Analysis on %2 file finished.",
0208             "[%1]Analysis on %2 files finished.",
0209             m_analysisTool->name(),
0210             m_analysisTool->getActualFilesCount());
0211         // We only log here because once the analysis starts, the user will be taken to diagnosticview to see the results
0212         Utils::showMessage(msg, {}, i18n("CodeAnalysis"), MessageType::Log, m_pluginView->mainWindow());
0213     } else {
0214         const QString err = QString::fromUtf8(m_errOutput);
0215         const QString message = i18n("Analysis failed with exit code %1, Error: %2", exitCode, err);
0216         Utils::showMessage(message, {}, i18n("CodeAnalysis"), MessageType::Error, m_pluginView->mainWindow());
0217     }
0218     m_errOutput = {};
0219 }
0220 
0221 #include "moc_kateprojectinfoviewcodeanalysis.cpp"