1 /*
2 SPDX-FileCopyrightText: 2006 Adam Treat <treat@kde.org>
3 SPDX-FileCopyrightText: 2006-2007 Hamish Rodda <rodda@kde.org>
4 SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
9 #include "problemreporterplugin.h"
10
11 #include <QMenu>
12
13 #include <KLocalizedString>
14 #include <KPluginFactory>
15
16 #include <KTextEditor/Document>
17
18 #include <interfaces/icore.h>
19 #include <interfaces/iuicontroller.h>
20 #include <interfaces/idocumentcontroller.h>
21
22 #include <interfaces/ilanguagecontroller.h>
23 #include <language/duchain/duchainlock.h>
24 #include <language/duchain/duchain.h>
25 #include <util/kdevstringhandler.h>
26
27 #include "problemhighlighter.h"
28 #include "probleminlinenoteprovider.h"
29 #include "problemreportermodel.h"
30 #include "language/assistant/staticassistantsmanager.h"
31 #include <interfaces/context.h>
32 #include <language/interfaces/editorcontext.h>
33 #include <language/duchain/duchainutils.h>
34 #include <interfaces/contextmenuextension.h>
35 #include <interfaces/iassistant.h>
36 #include <QAction>
37
38 #include "shell/problemmodelset.h"
39 #include "problemsview.h"
40 #include <debug.h>
41
42 #include <shell/problem.h>
43
44 K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json",
45 registerPlugin<ProblemReporterPlugin>();)
46
47 using namespace KDevelop;
48
49 class ProblemReporterFactory : public KDevelop::IToolViewFactory
50 {
51 public:
create(QWidget * parent=nullptr)52 QWidget* create(QWidget* parent = nullptr) override
53 {
54 Q_UNUSED(parent);
55
56 auto* v = new ProblemsView();
57 v->load();
58 return v;
59 }
60
defaultPosition() const61 Qt::DockWidgetArea defaultPosition() const override
62 {
63 return Qt::BottomDockWidgetArea;
64 }
65
id() const66 QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); }
67 };
68
ProblemReporterPlugin(QObject * parent,const QVariantList &)69 ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&)
70 : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent)
71 , m_factory(new ProblemReporterFactory)
72 , m_model(new ProblemReporterModel(this))
73 {
74 KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet();
75 pms->addModel(QStringLiteral("Parser"), i18n("Parser"), m_model);
76 core()->uiController()->addToolView(i18nc("@title:window", "Problems"), m_factory);
77 setXMLFile(QStringLiteral("kdevproblemreporter.rc"));
78
79 connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this,
80 &ProblemReporterPlugin::documentClosed);
81 connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this,
82 &ProblemReporterPlugin::textDocumentCreated);
83 connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this,
84 &ProblemReporterPlugin::documentActivated);
85 connect(DUChain::self(), &DUChain::updateReady,
86 this, &ProblemReporterPlugin::updateReady);
87 connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged,
88 this, &ProblemReporterPlugin::updateHighlight);
89 connect(pms, &ProblemModelSet::showRequested, this, &ProblemReporterPlugin::showModel);
90 connect(pms, &ProblemModelSet::problemsChanged, this, &ProblemReporterPlugin::updateOpenedDocumentsHighlight);
91 }
92
~ProblemReporterPlugin()93 ProblemReporterPlugin::~ProblemReporterPlugin()
94 {
95 qDeleteAll(m_highlighters);
96 qDeleteAll(m_inlineNoteProviders);
97 }
98
model() const99 ProblemReporterModel* ProblemReporterPlugin::model() const
100 {
101 return m_model;
102 }
103
unload()104 void ProblemReporterPlugin::unload()
105 {
106 KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet();
107 pms->removeModel(QStringLiteral("Parser"));
108
109 core()->uiController()->removeToolView(m_factory);
110 }
111
documentClosed(IDocument * doc)112 void ProblemReporterPlugin::documentClosed(IDocument* doc)
113 {
114 if (!doc->textDocument())
115 return;
116
117 IndexedString url(doc->url());
118 delete m_highlighters.take(url);
119 delete m_inlineNoteProviders.take(url);
120 m_reHighlightNeeded.remove(url);
121 }
122
textDocumentCreated(KDevelop::IDocument * document)123 void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document)
124 {
125 Q_ASSERT(document->textDocument());
126 IndexedString documentUrl(document->url());
127 m_highlighters.insert(documentUrl, new ProblemHighlighter(document->textDocument()));
128 m_inlineNoteProviders.insert(documentUrl, new ProblemInlineNoteProvider(document->textDocument()));
129 DUChain::self()->updateContextForUrl(documentUrl,
130 KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this);
131 }
132
documentActivated(KDevelop::IDocument * document)133 void ProblemReporterPlugin::documentActivated(KDevelop::IDocument* document)
134 {
135 IndexedString documentUrl(document->url());
136
137 const auto neededIt = m_reHighlightNeeded.find(documentUrl);
138 if (neededIt != m_reHighlightNeeded.end()) {
139 m_reHighlightNeeded.erase(neededIt);
140 updateHighlight(documentUrl);
141 }
142 }
143
updateReady(const IndexedString & url,const KDevelop::ReferencedTopDUContext &)144 void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&)
145 {
146 m_model->problemsUpdated(url);
147 updateHighlight(url);
148 }
149
updateHighlight(const KDevelop::IndexedString & url)150 void ProblemReporterPlugin::updateHighlight(const KDevelop::IndexedString& url)
151 {
152 ProblemHighlighter* ph = m_highlighters.value(url);
153 if (!ph)
154 return;
155
156 KDevelop::ProblemModelSet* pms(core()->languageController()->problemModelSet());
157 QVector<IProblem::Ptr> documentProblems;
158
159 const auto models = pms->models();
160 for (const ModelData& modelData : models) {
161 documentProblems += modelData.model->problems({url});
162 }
163
164 ph->setProblems(documentProblems);
165 m_inlineNoteProviders.value(url)->setProblems(documentProblems);
166 }
167
showModel(const QString & id)168 void ProblemReporterPlugin::showModel(const QString& id)
169 {
170 auto w = qobject_cast<ProblemsView*>(core()->uiController()->findToolView(i18nc("@title:window", "Problems"), m_factory));
171 if (w)
172 w->showModel(id);
173 }
174
contextMenuExtension(KDevelop::Context * context,QWidget * parent)175 KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
176 {
177 KDevelop::ContextMenuExtension extension;
178
179 auto* editorContext = dynamic_cast<KDevelop::EditorContext*>(context);
180 if (editorContext) {
181 DUChainReadLocker lock(DUChain::lock(), 1000);
182 if (!lock.locked()) {
183 qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time";
184 return extension;
185 }
186
187 QString title;
188 QList<QAction*> actions;
189
190 TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url());
191 if (top) {
192 const auto problems = top->problems();
193 for (auto& problem : problems) {
194 if (problem->range().contains(
195 top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) {
196 KDevelop::IAssistant::Ptr solution = problem->solutionAssistant();
197 if (solution) {
198 title = solution->title();
199 const auto solutionActions = solution->actions();
200 for (auto& action : solutionActions) {
201 actions << action->toQAction(parent);
202 }
203 }
204 }
205 }
206 }
207
208 if (!actions.isEmpty()) {
209 QString text;
210 if (title.isEmpty())
211 text = i18nc("@action:inmenu", "Solve Problem");
212 else {
213 text = i18nc("@action:inmenu", "Solve: %1", KDevelop::htmlToPlainText(title));
214 }
215
216 auto* menu = new QMenu(text, parent);
217 for (QAction* action : qAsConst(actions)) {
218 menu->addAction(action);
219 }
220
221 extension.addAction(ContextMenuExtension::ExtensionGroup, menu->menuAction());
222 }
223 }
224 return extension;
225 }
226
updateOpenedDocumentsHighlight()227 void ProblemReporterPlugin::updateOpenedDocumentsHighlight()
228 {
229 const auto openDocuments = core()->documentController()->openDocuments();
230 for (auto* document : openDocuments) {
231 // Skip non-text documents.
232 // This also fixes crash caused by calling updateOpenedDocumentsHighlight() method without
233 // any opened documents. In this case documentController()->openDocuments() returns single
234 // (non-text) document with url like file:///tmp/kdevelop_QW2530.patch which has fatal bug:
235 // if we call isActive() method from this document the crash will happens.
236 if (!document->isTextDocument())
237 continue;
238
239 IndexedString documentUrl(document->url());
240
241 if (document->isActive())
242 updateHighlight(documentUrl);
243 else
244 m_reHighlightNeeded.insert(documentUrl);
245 }
246 }
247
248 #include "problemreporterplugin.moc"
249