File indexing completed on 2024-05-05 04:39:27

0001 /*
0002     SPDX-FileCopyrightText: 2018 Anton Anikin <anton@anikin.xyz>
0003     SPDX-FileCopyrightText: 2020 Friedrich W. H. Kossebau <kossebau@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "compileanalyzer.h"
0009 
0010 // lib
0011 #include "compileanalyzeutils.h"
0012 #include "compileanalyzejob.h"
0013 #include "compileanalyzeproblemmodel.h"
0014 // KDevPlatform
0015 #include <interfaces/iplugin.h>
0016 #include <interfaces/icore.h>
0017 #include <interfaces/context.h>
0018 #include <interfaces/contextmenuextension.h>
0019 #include <interfaces/idocument.h>
0020 #include <interfaces/idocumentcontroller.h>
0021 #include <interfaces/ilanguagecontroller.h>
0022 #include <interfaces/iplugincontroller.h>
0023 #include <interfaces/iproject.h>
0024 #include <interfaces/iprojectcontroller.h>
0025 #include <interfaces/iruncontroller.h>
0026 #include <interfaces/iuicontroller.h>
0027 #include <project/interfaces/ibuildsystemmanager.h>
0028 #include <project/projectconfigpage.h>
0029 #include <project/projectmodel.h>
0030 #include <shell/problemmodelset.h>
0031 #include <util/jobstatus.h>
0032 // KF
0033 #include <KActionCollection>
0034 #include <KLocalizedString>
0035 #include <KSharedConfig>
0036 #include <KPluginFactory>
0037 // Qt
0038 #include <QAction>
0039 #include <QMessageBox>
0040 #include <QMimeType>
0041 #include <QMimeDatabase>
0042 #include <QThread>
0043 
0044 namespace KDevelop
0045 {
0046 
0047 bool isSupportedMimeType(const QMimeType& mimeType)
0048 {
0049     const QString mime = mimeType.name();
0050     return (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc"));
0051 }
0052 
0053 CompileAnalyzer::CompileAnalyzer(IPlugin* plugin,
0054                                  const QString& toolName, const QString& toolIconName,
0055                                  const QString& fileActionId, const QString& allActionId,
0056                                  const QString& modelId,
0057                                  ProblemModel::Features modelFeatures,
0058                                  QObject* parent)
0059     : QObject(parent)
0060     , m_core(plugin->core())
0061     , m_toolName(toolName)
0062     , m_toolIcon(QIcon::fromTheme(toolIconName))
0063     , m_modelId(modelId)
0064     , m_model(new CompileAnalyzeProblemModel(toolName, this))
0065 {
0066     m_model->setFeatures(modelFeatures);
0067 
0068     ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet();
0069     problemModelSet->addModel(m_modelId, m_toolName, m_model);
0070 
0071     auto actionCollection = plugin->actionCollection();
0072 
0073     m_checkFileAction = new QAction(m_toolIcon,
0074                                     i18nc("@action", "Analyze Current File with %1", m_toolName), this);
0075     connect(m_checkFileAction, &QAction::triggered, this, &CompileAnalyzer::runToolOnFile);
0076     actionCollection->addAction(fileActionId, m_checkFileAction);
0077 
0078     m_checkProjectAction = new QAction(m_toolIcon,
0079                                        i18nc("@action", "Analyze Current Project with %1", m_toolName), this);
0080     connect(m_checkProjectAction, &QAction::triggered, this, &CompileAnalyzer::runToolOnAll);
0081     actionCollection->addAction(allActionId, m_checkProjectAction);
0082 
0083     connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed,
0084             this, &CompileAnalyzer::updateActions);
0085     connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated,
0086             this, &CompileAnalyzer::updateActions);
0087     // TODO: updateActions() should be connected to IDocumentController::documentUrlChanged as well. However, this
0088     // works incorrectly, because IProjectController::findProjectForUrl() returns nullptr for a just-renamed document.
0089     // The same applies to cppcheck::Plugin::updateActions(). The delayed inclusion of a renamed file into its project
0090     // negatively affects correctness of other slots connected to the documentUrlChanged signal, such as
0091     // CurrentProjectSet::setCurrentDocument() and (in case of custom project formatting configuration)
0092     // SourceFormatterController::updateFormatTextAction(). See also a similar TODO in
0093     // ProjectManagerView::ProjectManagerView().
0094 
0095     connect(core()->projectController(), &KDevelop::IProjectController::projectOpened,
0096             this, &CompileAnalyzer::updateActions);
0097     connect(core()->projectController(), &KDevelop::IProjectController::projectClosed,
0098             this, &CompileAnalyzer::handleProjectClosed);
0099 
0100     connect(m_model, &CompileAnalyzeProblemModel::rerunRequested,
0101             this, &CompileAnalyzer::handleRerunRequest);
0102 
0103     updateActions();
0104 }
0105 
0106 CompileAnalyzer::~CompileAnalyzer()
0107 {
0108     killJob();
0109 
0110     ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet();
0111     problemModelSet->removeModel(m_modelId);
0112 }
0113 
0114 bool CompileAnalyzer::isOutputToolViewPreferred() const
0115 {
0116     return false;
0117 }
0118 
0119 ICore* CompileAnalyzer::core() const
0120 {
0121     return m_core;
0122 }
0123 
0124 void CompileAnalyzer::updateActions()
0125 {
0126     m_checkFileAction->setEnabled(false);
0127     m_checkProjectAction->setEnabled(false);
0128 
0129     if (isRunning()) {
0130         return;
0131     }
0132 
0133     IDocument* activeDocument = core()->documentController()->activeDocument();
0134     if (!activeDocument) {
0135         return;
0136     }
0137 
0138     auto currentProject = core()->projectController()->findProjectForUrl(activeDocument->url());
0139     if (!currentProject) {
0140         return;
0141     }
0142 
0143     if (!currentProject->buildSystemManager()) {
0144         return;
0145     }
0146 
0147     if (isSupportedMimeType(activeDocument->mimeType())) {
0148         m_checkFileAction->setEnabled(true);
0149     }
0150     m_checkProjectAction->setEnabled(true);
0151 }
0152 
0153 void CompileAnalyzer::handleProjectClosed(IProject* project)
0154 {
0155     if (project != m_model->project()) {
0156         return;
0157     }
0158 
0159     killJob();
0160     m_model->reset();
0161 }
0162 
0163 void CompileAnalyzer::runTool(bool allFiles)
0164 {
0165     auto doc = core()->documentController()->activeDocument();
0166     if (doc == nullptr) {
0167         QMessageBox::critical(nullptr, m_toolName,
0168                               i18n("No suitable active file, unable to deduce project."));
0169         return;
0170     }
0171 
0172     runTool(doc->url(), allFiles);
0173 }
0174 
0175 void CompileAnalyzer::runTool(const QUrl& url, bool allFiles)
0176 {
0177     KDevelop::IProject* project = core()->projectController()->findProjectForUrl(url);
0178     if (!project) {
0179         QMessageBox::critical(nullptr, m_toolName,
0180                               i18n("Active file isn't in a project."));
0181         return;
0182     }
0183 
0184     m_model->reset(project, url, allFiles);
0185 
0186     const auto buildDir = project->buildSystemManager()->buildDirectory(project->projectItem());
0187 
0188     QString error;
0189     const auto filePaths = Utils::filesFromCompilationDatabase(buildDir, url, allFiles, error);
0190 
0191     if (!error.isEmpty()) {
0192         QMessageBox::critical(nullptr, m_toolName,
0193                               i18n("Unable to start check for '%1':\n\n%2", url.toLocalFile(), error));
0194         return;
0195     }
0196 
0197     m_job = createJob(project, buildDir, url, filePaths);
0198 
0199     connect(m_job, &CompileAnalyzeJob::problemsDetected, m_model, &CompileAnalyzeProblemModel::addProblems);
0200     connect(m_job, &KJob::finished, this, &CompileAnalyzer::result);
0201 
0202     core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, m_toolName));
0203     core()->runController()->registerJob(m_job);
0204 
0205     updateActions();
0206 
0207     if (isOutputToolViewPreferred()) {
0208         raiseOutputToolView();
0209     } else {
0210         raiseProblemsToolView();
0211     }
0212 }
0213 
0214 void CompileAnalyzer::raiseProblemsToolView()
0215 {
0216     ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet();
0217     problemModelSet->showModel(m_modelId);
0218 }
0219 
0220 void CompileAnalyzer::raiseOutputToolView()
0221 {
0222     core()->uiController()->findToolView(
0223         i18ndc("kdevstandardoutputview", "@title:window", "Test"),
0224         nullptr,
0225         KDevelop::IUiController::FindFlags::Raise);
0226 }
0227 
0228 bool CompileAnalyzer::isRunning() const
0229 {
0230     return (m_job != nullptr);
0231 }
0232 
0233 void CompileAnalyzer::killJob()
0234 {
0235     if (m_job) {
0236         m_job->kill();
0237     }
0238 }
0239 
0240 void CompileAnalyzer::runToolOnFile()
0241 {
0242     bool allFiles = false;
0243     runTool(allFiles);
0244 }
0245 
0246 void CompileAnalyzer::runToolOnAll()
0247 {
0248     bool allFiles = true;
0249     runTool(allFiles);
0250 }
0251 
0252 void CompileAnalyzer::handleRerunRequest(const QUrl& url, bool allFiles)
0253 {
0254     if (!isRunning()) {
0255         runTool(url, allFiles);
0256     }
0257 }
0258 
0259 void CompileAnalyzer::result(KJob* job)
0260 {
0261     Q_UNUSED(job);
0262 
0263     if (!core()->projectController()->projects().contains(m_model->project())) {
0264         m_model->reset();
0265     } else {
0266         m_model->finishAddProblems();
0267 
0268         if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded ||
0269             m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) {
0270             raiseProblemsToolView();
0271         } else {
0272             raiseOutputToolView();
0273         }
0274     }
0275 
0276     m_job = nullptr; // job automatically deletes itself later
0277 
0278     updateActions();
0279 }
0280 
0281 void CompileAnalyzer::fillContextMenuExtension(ContextMenuExtension &extension,
0282                                                Context* context, QWidget* parent)
0283 {
0284     if (context->hasType(KDevelop::Context::EditorContext) && !isRunning()) {
0285         IDocument* doc = core()->documentController()->activeDocument();
0286 
0287         auto project = core()->projectController()->findProjectForUrl(doc->url());
0288         if (!project || !project->buildSystemManager()) {
0289             return;
0290         }
0291         if (isSupportedMimeType(doc->mimeType())) {
0292             auto action = new QAction(m_toolIcon, m_toolName, parent);
0293             connect(action, &QAction::triggered, this, &CompileAnalyzer::runToolOnFile);
0294             extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, action);
0295         }
0296         auto action = new QAction(m_toolIcon, m_toolName, parent);
0297         connect(action, &QAction::triggered, this, &CompileAnalyzer::runToolOnAll);
0298         extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action);
0299     }
0300 
0301     if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) {
0302         auto projectItemContext = dynamic_cast<KDevelop::ProjectItemContext*>(context);
0303         const auto items = projectItemContext->items();
0304         if (items.size() != 1) {
0305             return;
0306         }
0307 
0308         const auto item = items.first();
0309         const auto itemType = item->type();
0310         if ((itemType != KDevelop::ProjectBaseItem::File) &&
0311             (itemType != KDevelop::ProjectBaseItem::Folder) &&
0312             (itemType != KDevelop::ProjectBaseItem::BuildFolder)) {
0313             return;
0314         }
0315         if (itemType == KDevelop::ProjectBaseItem::File) {
0316             const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl());
0317             if (!isSupportedMimeType(mimetype)) {
0318                 return;
0319             }
0320         }
0321         if (!item->project()->buildSystemManager()) {
0322             return;
0323         }
0324 
0325         auto action = new QAction(m_toolIcon, m_toolName, parent);
0326         connect(action, &QAction::triggered, this, [this, item]() {
0327             runTool(item->path().toUrl());
0328         });
0329         extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action);
0330     }
0331 }
0332 
0333 }
0334 
0335 #include "moc_compileanalyzer.cpp"