File indexing completed on 2024-05-05 04:40:14
0001 /* 0002 SPDX-FileCopyrightText: 2006 Adam Treat <treat@kde.org> 0003 SPDX-FileCopyrightText: 2006-2007 Hamish Rodda <rodda@kde.org> 0004 SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "problemreporterplugin.h" 0010 0011 #include <QMenu> 0012 0013 #include <KLocalizedString> 0014 #include <KPluginFactory> 0015 0016 #include <KTextEditor/Document> 0017 0018 #include <interfaces/icore.h> 0019 #include <interfaces/iuicontroller.h> 0020 #include <interfaces/idocumentcontroller.h> 0021 0022 #include <interfaces/ilanguagecontroller.h> 0023 #include <language/duchain/duchainlock.h> 0024 #include <language/duchain/duchain.h> 0025 #include <util/kdevstringhandler.h> 0026 0027 #include "problemhighlighter.h" 0028 #include "probleminlinenoteprovider.h" 0029 #include "problemreportermodel.h" 0030 #include "language/assistant/staticassistantsmanager.h" 0031 #include <interfaces/context.h> 0032 #include <language/interfaces/editorcontext.h> 0033 #include <language/duchain/duchainutils.h> 0034 #include <interfaces/contextmenuextension.h> 0035 #include <interfaces/iassistant.h> 0036 #include <QAction> 0037 0038 #include "shell/problemmodelset.h" 0039 #include "problemsview.h" 0040 #include <debug.h> 0041 0042 #include <shell/problem.h> 0043 0044 K_PLUGIN_FACTORY_WITH_JSON(KDevProblemReporterFactory, "kdevproblemreporter.json", 0045 registerPlugin<ProblemReporterPlugin>();) 0046 0047 using namespace KDevelop; 0048 0049 class ProblemReporterFactory : public KDevelop::IToolViewFactory 0050 { 0051 public: 0052 QWidget* create(QWidget* parent = nullptr) override 0053 { 0054 Q_UNUSED(parent); 0055 0056 auto* v = new ProblemsView(); 0057 v->load(); 0058 return v; 0059 } 0060 0061 Qt::DockWidgetArea defaultPosition() const override 0062 { 0063 return Qt::BottomDockWidgetArea; 0064 } 0065 0066 QString id() const override { return QStringLiteral("org.kdevelop.ProblemReporterView"); } 0067 }; 0068 0069 class ProblemReporterPlugin::ProblemVisualizer 0070 { 0071 public: 0072 explicit ProblemVisualizer(KTextEditor::Document* document) 0073 : m_highlighter(document) 0074 , m_inlineNoteProvider(document) 0075 {} 0076 0077 KTextEditor::Document* document() const 0078 { 0079 return m_highlighter.document(); 0080 } 0081 0082 void setProblems(const QVector<IProblem::Ptr>& problems) 0083 { 0084 m_highlighter.setProblems(problems); 0085 m_inlineNoteProvider.setProblems(problems); 0086 } 0087 0088 private: 0089 ProblemHighlighter m_highlighter; 0090 ProblemInlineNoteProvider m_inlineNoteProvider; 0091 }; 0092 0093 ProblemReporterPlugin::ProblemReporterPlugin(QObject* parent, const QVariantList&) 0094 : KDevelop::IPlugin(QStringLiteral("kdevproblemreporter"), parent) 0095 , m_factory(new ProblemReporterFactory) 0096 , m_model(new ProblemReporterModel(this)) 0097 { 0098 KDevelop::ProblemModelSet* pms = core()->languageController()->problemModelSet(); 0099 pms->addModel(QStringLiteral("Parser"), i18n("Parser"), m_model); 0100 core()->uiController()->addToolView(i18nc("@title:window", "Problems"), m_factory); 0101 setXMLFile(QStringLiteral("kdevproblemreporter.rc")); 0102 0103 connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, 0104 &ProblemReporterPlugin::documentClosed); 0105 connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, 0106 &ProblemReporterPlugin::textDocumentCreated); 0107 connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, 0108 &ProblemReporterPlugin::documentUrlChanged); 0109 connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, 0110 &ProblemReporterPlugin::documentActivated); 0111 connect(DUChain::self(), &DUChain::updateReady, 0112 this, &ProblemReporterPlugin::updateReady); 0113 connect(ICore::self()->languageController()->staticAssistantsManager(), &StaticAssistantsManager::problemsChanged, 0114 this, &ProblemReporterPlugin::updateHighlight); 0115 connect(pms, &ProblemModelSet::showRequested, this, &ProblemReporterPlugin::showModel); 0116 connect(pms, &ProblemModelSet::problemsChanged, this, &ProblemReporterPlugin::updateOpenedDocumentsHighlight); 0117 } 0118 0119 ProblemReporterPlugin::~ProblemReporterPlugin() 0120 { 0121 qDeleteAll(m_visualizers); 0122 } 0123 0124 ProblemReporterModel* ProblemReporterPlugin::model() const 0125 { 0126 return m_model; 0127 } 0128 0129 void ProblemReporterPlugin::unload() 0130 { 0131 KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); 0132 pms->removeModel(QStringLiteral("Parser")); 0133 0134 core()->uiController()->removeToolView(m_factory); 0135 } 0136 0137 void ProblemReporterPlugin::documentClosed(IDocument* doc) 0138 { 0139 if (!doc->textDocument()) 0140 return; 0141 0142 const IndexedString url(doc->url()); 0143 0144 const auto it = m_visualizers.constFind(url); 0145 if (it == m_visualizers.cend()) { 0146 qCDebug(PLUGIN_PROBLEMREPORTER) << "closed an unregistered text document:" << doc << doc->url().toString(); 0147 return; 0148 } 0149 0150 if (it.value()->document() != doc->textDocument()) { 0151 // doc is being renamed, DocumentControllerPrivate::changeDocumentUrl() is closing it 0152 // because of a conflict with another open modified document at doc's new URL. 0153 // documentUrlChanged(doc, ...) will be invoked soon and will remove doc's visualizer. Nothing to do here. 0154 qCDebug(PLUGIN_PROBLEMREPORTER) << "closed a text document that shares another text document's URL:" << doc 0155 << doc->url().toString(); 0156 return; 0157 } 0158 0159 delete it.value(); 0160 m_visualizers.erase(it); 0161 m_reHighlightNeeded.remove(url); 0162 } 0163 0164 void ProblemReporterPlugin::textDocumentCreated(KDevelop::IDocument* document) 0165 { 0166 Q_ASSERT(document->textDocument()); 0167 const IndexedString documentUrl(document->url()); 0168 0169 Q_ASSERT(!m_visualizers.contains(documentUrl)); 0170 m_visualizers.insert(documentUrl, new ProblemVisualizer{document->textDocument()}); 0171 0172 DUChain::self()->updateContextForUrl(documentUrl, 0173 KDevelop::TopDUContext::AllDeclarationsContextsAndUses, this); 0174 } 0175 0176 void ProblemReporterPlugin::documentUrlChanged(IDocument* document, const QUrl& previousUrl) 0177 { 0178 if (!document->textDocument()) 0179 return; 0180 0181 qCDebug(PLUGIN_PROBLEMREPORTER) << "document URL changed from" << previousUrl.toString() << "to" 0182 << document->url().toString(); 0183 0184 const IndexedString previousUrlIndexed(previousUrl); 0185 const auto it = m_visualizers.constFind(previousUrlIndexed); 0186 if (it == m_visualizers.cend()) { 0187 qCWarning(PLUGIN_PROBLEMREPORTER) 0188 << "a visualizer for renamed document is missing:" << document->textDocument(); 0189 return; 0190 } 0191 Q_ASSERT(it.value()->document() == document->textDocument()); 0192 0193 m_reHighlightNeeded.remove(previousUrlIndexed); 0194 0195 auto* const visualizer = it.value(); 0196 m_visualizers.erase(it); 0197 0198 const IndexedString currentUrl{document->url()}; 0199 if (m_visualizers.contains(currentUrl)) { 0200 // The renamed document must have been closed already in DocumentControllerPrivate::changeDocumentUrl() 0201 // because of a conflict with another open modified document at its new URL. See a similar comment in 0202 // ProblemReporterPlugin::documentClosed(). Just destroy document's obsolete visualizer here. 0203 delete visualizer; 0204 qCDebug(PLUGIN_PROBLEMREPORTER) << "the renamed document's URL equals another document's URL:" << document; 0205 return; 0206 } 0207 m_visualizers.insert(currentUrl, visualizer); 0208 } 0209 0210 void ProblemReporterPlugin::documentActivated(KDevelop::IDocument* document) 0211 { 0212 IndexedString documentUrl(document->url()); 0213 0214 const auto neededIt = m_reHighlightNeeded.find(documentUrl); 0215 if (neededIt != m_reHighlightNeeded.end()) { 0216 m_reHighlightNeeded.erase(neededIt); 0217 updateHighlight(documentUrl); 0218 } 0219 } 0220 0221 void ProblemReporterPlugin::updateReady(const IndexedString& url, const KDevelop::ReferencedTopDUContext&) 0222 { 0223 m_model->problemsUpdated(url); 0224 updateHighlight(url); 0225 } 0226 0227 void ProblemReporterPlugin::updateHighlight(const KDevelop::IndexedString& url) 0228 { 0229 auto* const visualizer = m_visualizers.value(url); 0230 if (!visualizer) 0231 return; 0232 0233 KDevelop::ProblemModelSet* pms(core()->languageController()->problemModelSet()); 0234 QVector<IProblem::Ptr> documentProblems; 0235 0236 const auto models = pms->models(); 0237 for (const ModelData& modelData : models) { 0238 documentProblems += modelData.model->problems({url}); 0239 } 0240 0241 visualizer->setProblems(documentProblems); 0242 } 0243 0244 void ProblemReporterPlugin::showModel(const QString& id) 0245 { 0246 auto w = qobject_cast<ProblemsView*>(core()->uiController()->findToolView(i18nc("@title:window", "Problems"), m_factory)); 0247 if (w) 0248 w->showModel(id); 0249 } 0250 0251 KDevelop::ContextMenuExtension ProblemReporterPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) 0252 { 0253 KDevelop::ContextMenuExtension extension; 0254 0255 auto* editorContext = dynamic_cast<KDevelop::EditorContext*>(context); 0256 if (editorContext) { 0257 DUChainReadLocker lock(DUChain::lock(), 1000); 0258 if (!lock.locked()) { 0259 qCDebug(PLUGIN_PROBLEMREPORTER) << "failed to lock duchain in time"; 0260 return extension; 0261 } 0262 0263 QString title; 0264 QList<QAction*> actions; 0265 0266 TopDUContext* top = DUChainUtils::standardContextForUrl(editorContext->url()); 0267 if (top) { 0268 const auto problems = top->problems(); 0269 for (auto& problem : problems) { 0270 if (problem->range().contains( 0271 top->transformToLocalRevision(KTextEditor::Cursor(editorContext->position())))) { 0272 KDevelop::IAssistant::Ptr solution = problem->solutionAssistant(); 0273 if (solution) { 0274 title = solution->title(); 0275 const auto solutionActions = solution->actions(); 0276 for (auto& action : solutionActions) { 0277 actions << action->toQAction(parent); 0278 } 0279 } 0280 } 0281 } 0282 } 0283 0284 if (!actions.isEmpty()) { 0285 QString text; 0286 if (title.isEmpty()) 0287 text = i18nc("@action:inmenu", "Solve Problem"); 0288 else { 0289 text = i18nc("@action:inmenu", "Solve: %1", KDevelop::htmlToPlainText(title)); 0290 } 0291 0292 auto* menu = new QMenu(text, parent); 0293 for (QAction* action : qAsConst(actions)) { 0294 menu->addAction(action); 0295 } 0296 0297 extension.addAction(ContextMenuExtension::ExtensionGroup, menu->menuAction()); 0298 } 0299 } 0300 return extension; 0301 } 0302 0303 void ProblemReporterPlugin::updateOpenedDocumentsHighlight() 0304 { 0305 const auto openDocuments = core()->documentController()->openDocuments(); 0306 for (auto* document : openDocuments) { 0307 // Skip non-text documents. 0308 // This also fixes crash caused by calling updateOpenedDocumentsHighlight() method without 0309 // any opened documents. In this case documentController()->openDocuments() returns single 0310 // (non-text) document with url like file:///tmp/kdevelop_QW2530.patch which has fatal bug: 0311 // if we call isActive() method from this document the crash will happens. 0312 if (!document->textDocument()) 0313 continue; 0314 0315 IndexedString documentUrl(document->url()); 0316 0317 if (document->isActive()) 0318 updateHighlight(documentUrl); 0319 else 0320 m_reHighlightNeeded.insert(documentUrl); 0321 } 0322 } 0323 0324 #include "problemreporterplugin.moc" 0325 #include "moc_problemreporterplugin.cpp"