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"