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