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

0001 /*
0002     SPDX-FileCopyrightText: 2013 Christoph Thielecke <crissi99@gmx.de>
0003     SPDX-FileCopyrightText: 2016-2017 Anton Anikin <anton.anikin@htower.ru>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "plugin.h"
0009 
0010 #include "config/globalconfigpage.h"
0011 #include "config/projectconfigpage.h"
0012 #include "globalsettings.h"
0013 
0014 #include "debug.h"
0015 #include "job.h"
0016 #include "problemmodel.h"
0017 
0018 #include <interfaces/contextmenuextension.h>
0019 #include <interfaces/icore.h>
0020 #include <interfaces/idocumentcontroller.h>
0021 #include <interfaces/iprojectcontroller.h>
0022 #include <interfaces/iruncontroller.h>
0023 #include <interfaces/iuicontroller.h>
0024 #include <language/interfaces/editorcontext.h>
0025 #include <project/projectconfigpage.h>
0026 #include <project/projectmodel.h>
0027 #include <util/jobstatus.h>
0028 
0029 #include <KActionCollection>
0030 #include <KPluginFactory>
0031 
0032 #include <QAction>
0033 #include <QMimeDatabase>
0034 
0035 K_PLUGIN_FACTORY_WITH_JSON(CppcheckFactory, "kdevcppcheck.json", registerPlugin<cppcheck::Plugin>();)
0036 
0037 namespace cppcheck
0038 {
0039 
0040 Plugin::Plugin(QObject* parent, const QVariantList&)
0041     : IPlugin(QStringLiteral("kdevcppcheck"), parent)
0042     , m_job(nullptr)
0043     , m_currentProject(nullptr)
0044     , m_model(new ProblemModel(this))
0045 {
0046     qCDebug(KDEV_CPPCHECK) << "setting cppcheck rc file";
0047     setXMLFile(QStringLiteral("kdevcppcheck.rc"));
0048 
0049     QIcon cppcheckIcon = QIcon::fromTheme(QStringLiteral("cppcheck"));
0050 
0051     m_menuActionFile = new QAction(cppcheckIcon, i18nc("@action", "Analyze Current File with Cppcheck"), this);
0052     connect(m_menuActionFile, &QAction::triggered, this, [this](){
0053         runCppcheck(false);
0054     });
0055     actionCollection()->addAction(QStringLiteral("cppcheck_file"), m_menuActionFile);
0056 
0057     m_contextActionFile = new QAction(cppcheckIcon, i18nc("@item:inmenu", "Cppcheck"), this);
0058     connect(m_contextActionFile, &QAction::triggered, this, [this]() {
0059         runCppcheck(false);
0060     });
0061 
0062     m_menuActionProject = new QAction(cppcheckIcon, i18nc("@action", "Analyze Current Project with Cppcheck"), this);
0063     connect(m_menuActionProject, &QAction::triggered, this, [this](){
0064         runCppcheck(true);
0065     });
0066     actionCollection()->addAction(QStringLiteral("cppcheck_project"), m_menuActionProject);
0067 
0068     m_contextActionProject = new QAction(cppcheckIcon, i18nc("@item:inmenu", "Cppcheck"), this);
0069     connect(m_contextActionProject, &QAction::triggered, this, [this]() {
0070         runCppcheck(true);
0071     });
0072 
0073     m_contextActionProjectItem = new QAction(cppcheckIcon, i18nc("@item:inmenu", "Cppcheck"), this);
0074 
0075     connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed,
0076             this, &Plugin::updateActions);
0077     connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated,
0078             this, &Plugin::updateActions);
0079 
0080     connect(core()->projectController(), &KDevelop::IProjectController::projectOpened,
0081             this, &Plugin::updateActions);
0082     connect(core()->projectController(), &KDevelop::IProjectController::projectClosed,
0083             this, &Plugin::projectClosed);
0084 
0085     updateActions();
0086 }
0087 
0088 Plugin::~Plugin()
0089 {
0090     killCppcheck();
0091 }
0092 
0093 bool Plugin::isRunning()
0094 {
0095     return m_job;
0096 }
0097 
0098 void Plugin::killCppcheck()
0099 {
0100     if (m_job) {
0101         m_job->kill();
0102     }
0103 }
0104 
0105 void Plugin::raiseProblemsView()
0106 {
0107     m_model->show();
0108 }
0109 
0110 void Plugin::raiseOutputView()
0111 {
0112     core()->uiController()->findToolView(
0113         i18nc("@title:window", "Test"),
0114         nullptr,
0115         KDevelop::IUiController::FindFlags::Raise);
0116 }
0117 
0118 void Plugin::updateActions()
0119 {
0120     m_currentProject = nullptr;
0121 
0122     m_menuActionFile->setEnabled(false);
0123     m_menuActionProject->setEnabled(false);
0124 
0125     if (isRunning()) {
0126         return;
0127     }
0128 
0129     KDevelop::IDocument* activeDocument = core()->documentController()->activeDocument();
0130     if (!activeDocument) {
0131         return;
0132     }
0133 
0134     QUrl url = activeDocument->url();
0135 
0136     m_currentProject = core()->projectController()->findProjectForUrl(url);
0137     if (!m_currentProject) {
0138         return;
0139     }
0140 
0141     m_menuActionFile->setEnabled(true);
0142     m_menuActionProject->setEnabled(true);
0143 }
0144 
0145 void Plugin::projectClosed(KDevelop::IProject* project)
0146 {
0147     if (project != m_model->project()) {
0148         return;
0149     }
0150 
0151     killCppcheck();
0152     m_model->reset();
0153 }
0154 
0155 void Plugin::runCppcheck(bool checkProject)
0156 {
0157     KDevelop::IDocument* doc = core()->documentController()->activeDocument();
0158     Q_ASSERT(doc);
0159 
0160     if (checkProject) {
0161         runCppcheck(m_currentProject, m_currentProject->path().toUrl().toLocalFile());
0162     } else {
0163         runCppcheck(m_currentProject, doc->url().toLocalFile());
0164     }
0165 }
0166 
0167 void Plugin::runCppcheck(KDevelop::IProject* project, const QString& path)
0168 {
0169     m_model->reset(project, path);
0170 
0171     Parameters params(project);
0172     params.checkPath = path;
0173 
0174     m_job = new Job(params);
0175 
0176     connect(m_job, &Job::problemsDetected, m_model.data(), &ProblemModel::addProblems);
0177     connect(m_job, &Job::finished, this, &Plugin::result);
0178 
0179     core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, QStringLiteral("Cppcheck")));
0180     core()->runController()->registerJob(m_job);
0181 
0182     if (params.hideOutputView) {
0183         raiseProblemsView();
0184     } else {
0185         raiseOutputView();
0186     }
0187 
0188     updateActions();
0189 }
0190 
0191 void Plugin::result(KJob*)
0192 {
0193     if (!core()->projectController()->projects().contains(m_model->project())) {
0194         m_model->reset();
0195     } else {
0196         m_model->setProblems();
0197 
0198         if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded ||
0199             m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) {
0200             raiseProblemsView();
0201         } else {
0202             raiseOutputView();
0203         }
0204     }
0205 
0206     m_job = nullptr; // job is automatically deleted later
0207 
0208     updateActions();
0209 }
0210 
0211 static
0212 bool isSupportedMimeType(const QMimeType& mimeType)
0213 {
0214     const QString mimeName = mimeType.name();
0215     return (mimeName == QLatin1String("text/x-c++src") ||
0216             mimeName == QLatin1String("text/x-c++hdr") ||
0217             mimeName == QLatin1String("text/x-chdr")   ||
0218             mimeName == QLatin1String("text/x-csrc"));
0219 }
0220 
0221 KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
0222 {
0223     KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context, parent);
0224 
0225     if (context->hasType(KDevelop::Context::EditorContext) && m_currentProject && !isRunning()) {
0226         auto eContext = static_cast<KDevelop::EditorContext*>(context);
0227         QMimeDatabase db;
0228         const auto mime = db.mimeTypeForUrl(eContext->url());
0229 
0230         if (isSupportedMimeType(mime)) {
0231             extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, m_contextActionFile);
0232             extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProject);
0233         }
0234     }
0235 
0236     if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) {
0237         auto pContext = static_cast<KDevelop::ProjectItemContext*>(context);
0238         if (pContext->items().size() != 1) {
0239             return extension;
0240         }
0241 
0242         auto item = pContext->items().first();
0243 
0244         switch (item->type()) {
0245             case KDevelop::ProjectBaseItem::File: {
0246                 const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl());
0247                 if (!isSupportedMimeType(mimetype)) {
0248                     return extension;
0249                 }
0250                 break;
0251             }
0252             case KDevelop::ProjectBaseItem::Folder:
0253             case KDevelop::ProjectBaseItem::BuildFolder:
0254                 break;
0255 
0256             default:
0257                 return extension;
0258         }
0259 
0260         m_contextActionProjectItem->disconnect();
0261         connect(m_contextActionProjectItem, &QAction::triggered, this, [this, item](){
0262             runCppcheck(item->project(), item->path().toLocalFile());
0263         });
0264 
0265         extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProjectItem);
0266     }
0267 
0268     return extension;
0269 }
0270 
0271 KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent)
0272 {
0273     return number ? nullptr : new ProjectConfigPage(this, options.project, parent);
0274 }
0275 
0276 KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent)
0277 {
0278     return number ? nullptr : new GlobalConfigPage(this, parent);
0279 }
0280 
0281 }
0282 
0283 #include "plugin.moc"
0284 #include "moc_plugin.cpp"