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"