1 /*
2 SPDX-FileCopyrightText: 2018 Anton Anikin <anton@anikin.xyz>
3 SPDX-FileCopyrightText: 2020 Friedrich W. H. Kossebau <kossebau@kde.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "compileanalyzer.h"
9
10 // lib
11 #include "compileanalyzeutils.h"
12 #include "compileanalyzejob.h"
13 #include "compileanalyzeproblemmodel.h"
14 // KDevPlatform
15 #include <interfaces/iplugin.h>
16 #include <interfaces/icore.h>
17 #include <interfaces/context.h>
18 #include <interfaces/contextmenuextension.h>
19 #include <interfaces/idocument.h>
20 #include <interfaces/idocumentcontroller.h>
21 #include <interfaces/ilanguagecontroller.h>
22 #include <interfaces/iplugincontroller.h>
23 #include <interfaces/iproject.h>
24 #include <interfaces/iprojectcontroller.h>
25 #include <interfaces/iruncontroller.h>
26 #include <interfaces/iuicontroller.h>
27 #include <project/interfaces/ibuildsystemmanager.h>
28 #include <project/projectconfigpage.h>
29 #include <project/projectmodel.h>
30 #include <shell/problemmodelset.h>
31 #include <util/jobstatus.h>
32 // KF
33 #include <KActionCollection>
34 #include <KLocalizedString>
35 #include <KSharedConfig>
36 #include <KPluginFactory>
37 // Qt
38 #include <QAction>
39 #include <QMessageBox>
40 #include <QMimeType>
41 #include <QMimeDatabase>
42 #include <QThread>
43
44 namespace KDevelop
45 {
46
isSupportedMimeType(const QMimeType & mimeType)47 bool isSupportedMimeType(const QMimeType& mimeType)
48 {
49 const QString mime = mimeType.name();
50 return (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc"));
51 }
52
CompileAnalyzer(IPlugin * plugin,const QString & toolName,const QString & toolIconName,const QString & fileActionId,const QString & allActionId,const QString & modelId,ProblemModel::Features modelFeatures,QObject * parent)53 CompileAnalyzer::CompileAnalyzer(IPlugin* plugin,
54 const QString& toolName, const QString& toolIconName,
55 const QString& fileActionId, const QString& allActionId,
56 const QString& modelId,
57 ProblemModel::Features modelFeatures,
58 QObject* parent)
59 : QObject(parent)
60 , m_core(plugin->core())
61 , m_toolName(toolName)
62 , m_toolIcon(QIcon::fromTheme(toolIconName))
63 , m_modelId(modelId)
64 , m_model(new CompileAnalyzeProblemModel(toolName, this))
65 {
66 m_model->setFeatures(modelFeatures);
67
68 ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet();
69 problemModelSet->addModel(m_modelId, m_toolName, m_model);
70
71 auto actionCollection = plugin->actionCollection();
72
73 m_checkFileAction = new QAction(m_toolIcon,
74 i18nc("@action", "Analyze Current File with %1", m_toolName), this);
75 connect(m_checkFileAction, &QAction::triggered, this, &CompileAnalyzer::runToolOnFile);
76 actionCollection->addAction(fileActionId, m_checkFileAction);
77
78 m_checkProjectAction = new QAction(m_toolIcon,
79 i18nc("@action", "Analyze Current Project with %1", m_toolName), this);
80 connect(m_checkProjectAction, &QAction::triggered, this, &CompileAnalyzer::runToolOnAll);
81 actionCollection->addAction(allActionId, m_checkProjectAction);
82
83 connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed,
84 this, &CompileAnalyzer::updateActions);
85 connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated,
86 this, &CompileAnalyzer::updateActions);
87
88 connect(core()->projectController(), &KDevelop::IProjectController::projectOpened,
89 this, &CompileAnalyzer::updateActions);
90 connect(core()->projectController(), &KDevelop::IProjectController::projectClosed,
91 this, &CompileAnalyzer::handleProjectClosed);
92
93 connect(m_model, &CompileAnalyzeProblemModel::rerunRequested,
94 this, &CompileAnalyzer::handleRerunRequest);
95
96 updateActions();
97 }
98
~CompileAnalyzer()99 CompileAnalyzer::~CompileAnalyzer()
100 {
101 killJob();
102
103 ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet();
104 problemModelSet->removeModel(m_modelId);
105 }
106
isOutputToolViewPreferred() const107 bool CompileAnalyzer::isOutputToolViewPreferred() const
108 {
109 return false;
110 }
111
core() const112 ICore* CompileAnalyzer::core() const
113 {
114 return m_core;
115 }
116
updateActions()117 void CompileAnalyzer::updateActions()
118 {
119 m_checkFileAction->setEnabled(false);
120 m_checkProjectAction->setEnabled(false);
121
122 if (isRunning()) {
123 return;
124 }
125
126 IDocument* activeDocument = core()->documentController()->activeDocument();
127 if (!activeDocument) {
128 return;
129 }
130
131 auto currentProject = core()->projectController()->findProjectForUrl(activeDocument->url());
132 if (!currentProject) {
133 return;
134 }
135
136 if (!currentProject->buildSystemManager()) {
137 return;
138 }
139
140 if (isSupportedMimeType(activeDocument->mimeType())) {
141 m_checkFileAction->setEnabled(true);
142 }
143 m_checkProjectAction->setEnabled(true);
144 }
145
handleProjectClosed(IProject * project)146 void CompileAnalyzer::handleProjectClosed(IProject* project)
147 {
148 if (project != m_model->project()) {
149 return;
150 }
151
152 killJob();
153 m_model->reset();
154 }
155
runTool(bool allFiles)156 void CompileAnalyzer::runTool(bool allFiles)
157 {
158 auto doc = core()->documentController()->activeDocument();
159 if (doc == nullptr) {
160 QMessageBox::critical(nullptr, m_toolName,
161 i18n("No suitable active file, unable to deduce project."));
162 return;
163 }
164
165 runTool(doc->url(), allFiles);
166 }
167
runTool(const QUrl & url,bool allFiles)168 void CompileAnalyzer::runTool(const QUrl& url, bool allFiles)
169 {
170 KDevelop::IProject* project = core()->projectController()->findProjectForUrl(url);
171 if (!project) {
172 QMessageBox::critical(nullptr, m_toolName,
173 i18n("Active file isn't in a project."));
174 return;
175 }
176
177 m_model->reset(project, url, allFiles);
178
179 const auto buildDir = project->buildSystemManager()->buildDirectory(project->projectItem());
180
181 QString error;
182 const auto filePaths = Utils::filesFromCompilationDatabase(buildDir, url, allFiles, error);
183
184 if (!error.isEmpty()) {
185 QMessageBox::critical(nullptr, m_toolName,
186 i18n("Unable to start check for '%1':\n\n%2", url.toLocalFile(), error));
187 return;
188 }
189
190 m_job = createJob(project, buildDir, url, filePaths);
191
192 connect(m_job, &CompileAnalyzeJob::problemsDetected, m_model, &CompileAnalyzeProblemModel::addProblems);
193 connect(m_job, &KJob::finished, this, &CompileAnalyzer::result);
194
195 core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, m_toolName));
196 core()->runController()->registerJob(m_job);
197
198 updateActions();
199
200 if (isOutputToolViewPreferred()) {
201 raiseOutputToolView();
202 } else {
203 raiseProblemsToolView();
204 }
205 }
206
raiseProblemsToolView()207 void CompileAnalyzer::raiseProblemsToolView()
208 {
209 ProblemModelSet* problemModelSet = core()->languageController()->problemModelSet();
210 problemModelSet->showModel(m_modelId);
211 }
212
raiseOutputToolView()213 void CompileAnalyzer::raiseOutputToolView()
214 {
215 core()->uiController()->findToolView(
216 i18ndc("kdevstandardoutputview", "@title:window", "Test"),
217 nullptr,
218 KDevelop::IUiController::FindFlags::Raise);
219 }
220
isRunning() const221 bool CompileAnalyzer::isRunning() const
222 {
223 return (m_job != nullptr);
224 }
225
killJob()226 void CompileAnalyzer::killJob()
227 {
228 if (m_job) {
229 m_job->kill(KJob::EmitResult);
230 }
231 }
232
runToolOnFile()233 void CompileAnalyzer::runToolOnFile()
234 {
235 bool allFiles = false;
236 runTool(allFiles);
237 }
238
runToolOnAll()239 void CompileAnalyzer::runToolOnAll()
240 {
241 bool allFiles = true;
242 runTool(allFiles);
243 }
244
handleRerunRequest(const QUrl & url,bool allFiles)245 void CompileAnalyzer::handleRerunRequest(const QUrl& url, bool allFiles)
246 {
247 if (!isRunning()) {
248 runTool(url, allFiles);
249 }
250 }
251
result(KJob * job)252 void CompileAnalyzer::result(KJob* job)
253 {
254 Q_UNUSED(job);
255
256 if (!core()->projectController()->projects().contains(m_model->project())) {
257 m_model->reset();
258 } else {
259 m_model->finishAddProblems();
260
261 if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded ||
262 m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) {
263 raiseProblemsToolView();
264 } else {
265 raiseOutputToolView();
266 }
267 }
268
269 m_job = nullptr; // job automatically deletes itself later
270
271 updateActions();
272 }
273
fillContextMenuExtension(ContextMenuExtension & extension,Context * context,QWidget * parent)274 void CompileAnalyzer::fillContextMenuExtension(ContextMenuExtension &extension,
275 Context* context, QWidget* parent)
276 {
277 if (context->hasType(KDevelop::Context::EditorContext) && !isRunning()) {
278 IDocument* doc = core()->documentController()->activeDocument();
279
280 auto project = core()->projectController()->findProjectForUrl(doc->url());
281 if (!project || !project->buildSystemManager()) {
282 return;
283 }
284 if (isSupportedMimeType(doc->mimeType())) {
285 auto action = new QAction(m_toolIcon, m_toolName, parent);
286 connect(action, &QAction::triggered, this, &CompileAnalyzer::runToolOnFile);
287 extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, action);
288 }
289 auto action = new QAction(m_toolIcon, m_toolName, parent);
290 connect(action, &QAction::triggered, this, &CompileAnalyzer::runToolOnAll);
291 extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action);
292 }
293
294 if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) {
295 auto projectItemContext = dynamic_cast<KDevelop::ProjectItemContext*>(context);
296 const auto items = projectItemContext->items();
297 if (items.size() != 1) {
298 return;
299 }
300
301 const auto item = items.first();
302 const auto itemType = item->type();
303 if ((itemType != KDevelop::ProjectBaseItem::File) &&
304 (itemType != KDevelop::ProjectBaseItem::Folder) &&
305 (itemType != KDevelop::ProjectBaseItem::BuildFolder)) {
306 return;
307 }
308 if (itemType == KDevelop::ProjectBaseItem::File) {
309 const QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(item->path().toUrl());
310 if (!isSupportedMimeType(mimetype)) {
311 return;
312 }
313 }
314 if (!item->project()->buildSystemManager()) {
315 return;
316 }
317
318 auto action = new QAction(m_toolIcon, m_toolName, parent);
319 connect(action, &QAction::triggered, this, [this, item]() {
320 runTool(item->path().toUrl());
321 });
322 extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, action);
323 }
324 }
325
326 }
327