File indexing completed on 2024-04-28 04:37:16

0001 /*
0002     SPDX-FileCopyrightText: 2009 Aleix Pol Gonzalez <aleixpol@kde.org>
0003     SPDX-FileCopyrightText: 2010 Benjamin Port <port.benjamin@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "documentationcontroller.h"
0009 #include "debug.h"
0010 
0011 #include <interfaces/iplugin.h>
0012 #include <interfaces/idocumentationprovider.h>
0013 #include <interfaces/idocumentationproviderprovider.h>
0014 #include <interfaces/icore.h>
0015 #include <interfaces/iplugincontroller.h>
0016 #include <interfaces/iuicontroller.h>
0017 #include <shell/core.h>
0018 
0019 #include <QAction>
0020 
0021 #include <KActionCollection>
0022 #include <KParts/MainWindow>
0023 #include <KTextEditor/Document>
0024 #include <KTextEditor/View>
0025 
0026 #include <interfaces/contextmenuextension.h>
0027 #include <interfaces/idocumentcontroller.h>
0028 
0029 #include <language/interfaces/codecontext.h>
0030 #include <language/duchain/duchain.h>
0031 #include <language/duchain/duchainlock.h>
0032 #include <language/duchain/duchainutils.h>
0033 #include <language/duchain/types/identifiedtype.h>
0034 #include <language/duchain/types/typeutils.h>
0035 #include <documentation/documentationview.h>
0036 #include <documentation/standarddocumentationview.h>
0037 
0038 using namespace KDevelop;
0039 
0040 namespace {
0041 
0042 /**
0043  * Return a "more useful" declaration that documentation providers can look-up
0044  *
0045  * @code
0046  *   QPoint point;
0047  *            ^-- cursor here
0048  * @endcode
0049  *
0050  * In this case, this method returns a Declaration pointer to the *type*
0051  * instead of a pointer to the instance, which is more useful when looking for help
0052  *
0053  * @return A more appropriate Declaration pointer or the given parameter @p decl
0054  */
0055 Declaration* usefulDeclaration(Declaration* decl)
0056 {
0057     if (!decl)
0058         return nullptr;
0059 
0060     // First: Attempt to find the declaration of a definition
0061     decl = DUChainUtils::declarationForDefinition(decl);
0062 
0063     // Convenience feature: Retrieve the type declaration of instances,
0064     // it makes no sense to pass the declaration pointer of instances of types
0065     if (decl->kind() == Declaration::Instance) {
0066         AbstractType::Ptr type = TypeUtils::targetTypeKeepAliases(decl->abstractType(), decl->topContext());
0067         auto* idType = dynamic_cast<IdentifiedType*>(type.data());
0068         Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : nullptr;
0069         if (idDecl) {
0070             decl = idDecl;
0071         }
0072     }
0073     return decl;
0074 }
0075 
0076 }
0077 
0078 class DocumentationViewFactory: public KDevelop::IToolViewFactory
0079 {
0080 public:
0081     DocumentationViewFactory()
0082     {}
0083 
0084     QWidget* create(QWidget *parent = nullptr) override
0085     {
0086         if (!m_providersModel) {
0087             m_providersModel.reset(new ProvidersModel);
0088         }
0089         return new DocumentationView(parent, m_providersModel.data());
0090     }
0091 
0092     Qt::DockWidgetArea defaultPosition() const override
0093     {
0094         return Qt::RightDockWidgetArea;
0095     }
0096     QString id() const override { return QStringLiteral("org.kdevelop.DocumentationView"); }
0097     QList<QAction*> contextMenuActions(QWidget* viewWidget) const override
0098     {
0099         auto documentationViewWidget = qobject_cast<DocumentationView*>(viewWidget);
0100         Q_ASSERT(documentationViewWidget);
0101         return documentationViewWidget->contextMenuActions();
0102     }
0103 
0104 private:
0105     QScopedPointer<ProvidersModel> m_providersModel;
0106 };
0107 
0108 DocumentationController::DocumentationController(Core* core)
0109     : m_factory(new DocumentationViewFactory)
0110 {
0111     StandardDocumentationView::registerCustomUrlSchemes();
0112 
0113     m_showDocumentation = core->uiController()->activeMainWindow()->actionCollection()->addAction(QStringLiteral("showDocumentation"));
0114     m_showDocumentation->setText(i18nc("@action", "Show Documentation"));
0115     m_showDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("documentation")));
0116     connect(m_showDocumentation, &QAction::triggered, this, &DocumentationController::doShowDocumentation);
0117 
0118     // registering the tool view here so it registered before the areas are restored
0119     // and thus also gets treated like the ones registered from plugins
0120     // cmp. comment about tool views in CorePrivate::initialize
0121     core->uiController()->addToolView(i18nc("@title:window", "Documentation"), m_factory);
0122 }
0123 
0124 DocumentationController::~DocumentationController()
0125 {
0126 }
0127 
0128 void DocumentationController::initialize()
0129 {
0130 }
0131 
0132 
0133 void KDevelop::DocumentationController::doShowDocumentation()
0134 {
0135     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0136     if(!view)
0137       return;
0138 
0139     KDevelop::DUChainReadLocker lock( DUChain::lock() );
0140 
0141     Declaration* decl = usefulDeclaration(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration);
0142     auto documentation = documentationForDeclaration(decl);
0143     if (documentation) {
0144         showDocumentation(documentation);
0145     }
0146 }
0147 
0148 KDevelop::ContextMenuExtension KDevelop::DocumentationController::contextMenuExtension(Context* context, QWidget* parent)
0149 {
0150     Q_UNUSED(parent);
0151 
0152     ContextMenuExtension menuExt;
0153 
0154     auto* ctx = dynamic_cast<DeclarationContext*>(context);
0155     if(ctx) {
0156         DUChainReadLocker lock(DUChain::lock());
0157         if(!ctx->declaration().data())
0158             return menuExt;
0159 
0160         auto doc = documentationForDeclaration(ctx->declaration().data());
0161         if (doc) {
0162             menuExt.addAction(ContextMenuExtension::ExtensionGroup, m_showDocumentation);
0163         }
0164     }
0165 
0166     return menuExt;
0167 }
0168 
0169 IDocumentation::Ptr DocumentationController::documentationForDeclaration(Declaration* decl)
0170 {
0171     if (!decl)
0172         return {};
0173 
0174     const auto documentationProviders = this->documentationProviders();
0175     for (IDocumentationProvider* doc : documentationProviders) {
0176         qCDebug(SHELL) << "Documentation provider found:" << doc;
0177         auto ret = doc->documentationForDeclaration(decl);
0178 
0179         qCDebug(SHELL) << "Documentation proposed: " << ret.data();
0180         if (ret) {
0181             return ret;
0182         }
0183     }
0184 
0185     return {};
0186 }
0187 
0188 IDocumentation::Ptr DocumentationController::documentation(const QUrl& url) const
0189 {
0190     const auto providers = this->documentationProviders();
0191     for (const IDocumentationProvider* provider : providers) {
0192         IDocumentation::Ptr doc = provider->documentation(url);
0193         if (doc) {
0194             return doc;
0195         }
0196     }
0197     return {};
0198 }
0199 
0200 QList< IDocumentationProvider* > DocumentationController::documentationProviders() const
0201 {
0202     const QList<IPlugin*> plugins = ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProvider"));
0203     const QList<IPlugin*> pluginsProvider = ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProviderProvider"));
0204 
0205     QList<IDocumentationProvider*> ret;
0206     for (IPlugin* p : pluginsProvider) {
0207         auto *docProvider=p->extension<IDocumentationProviderProvider>();
0208         if (!docProvider) {
0209             qCWarning(SHELL) << "plugin" << p << "does not implement ProviderProvider extension, rerun kbuildsycoca5";
0210             continue;
0211         }
0212         ret.append(docProvider->providers());
0213     }
0214 
0215     for (IPlugin* p : plugins) {
0216         auto *doc=p->extension<IDocumentationProvider>();
0217         if (!doc) {
0218             qCWarning(SHELL) << "plugin" << p << "does not implement Provider extension, rerun kbuildsycoca5";
0219             continue;
0220         }
0221         ret.append(doc);
0222     }
0223 
0224     return ret;
0225 }
0226 
0227 void KDevelop::DocumentationController::showDocumentation(const IDocumentation::Ptr& doc)
0228 {
0229     Q_ASSERT_X(doc, Q_FUNC_INFO, "Null documentation pointer is unsupported.");
0230     QWidget* w = ICore::self()->uiController()->findToolView(i18nc("@title:window", "Documentation"), m_factory, KDevelop::IUiController::CreateAndRaise);
0231     if(!w) {
0232         qCWarning(SHELL) << "Could not add documentation tool view";
0233         return;
0234     }
0235 
0236     auto* view = dynamic_cast<DocumentationView*>(w);
0237     if( !view ) {
0238         qCWarning(SHELL) << "Could not cast tool view" << w << "to DocumentationView class!";
0239         return;
0240     }
0241     view->showDocumentation(doc);
0242 }
0243 
0244 void DocumentationController::changedDocumentationProviders()
0245 {
0246     emit providersChanged();
0247 }
0248 
0249 #include "moc_documentationcontroller.cpp"