1 /*
2     SPDX-FileCopyrightText: 2009 Aleix Pol Gonzalez <aleixpol@kde.org>
3     SPDX-FileCopyrightText: 2010 Benjamin Port <port.benjamin@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "documentationcontroller.h"
9 #include "debug.h"
10 
11 #include <interfaces/iplugin.h>
12 #include <interfaces/idocumentationprovider.h>
13 #include <interfaces/idocumentationproviderprovider.h>
14 #include <interfaces/icore.h>
15 #include <interfaces/iplugincontroller.h>
16 #include <interfaces/iuicontroller.h>
17 #include <shell/core.h>
18 
19 #include <QAction>
20 
21 #include <KActionCollection>
22 #include <KParts/MainWindow>
23 #include <KTextEditor/Document>
24 #include <KTextEditor/View>
25 
26 #include <interfaces/contextmenuextension.h>
27 #include <interfaces/idocumentcontroller.h>
28 
29 #include <language/interfaces/codecontext.h>
30 #include <language/duchain/duchain.h>
31 #include <language/duchain/duchainlock.h>
32 #include <language/duchain/duchainutils.h>
33 #include <language/duchain/types/identifiedtype.h>
34 #include <language/duchain/types/typeutils.h>
35 #include <documentation/documentationview.h>
36 
37 using namespace KDevelop;
38 
39 namespace {
40 
41 /**
42  * Return a "more useful" declaration that documentation providers can look-up
43  *
44  * @code
45  *   QPoint point;
46  *            ^-- cursor here
47  * @endcode
48  *
49  * In this case, this method returns a Declaration pointer to the *type*
50  * instead of a pointer to the instance, which is more useful when looking for help
51  *
52  * @return A more appropriate Declaration pointer or the given parameter @p decl
53  */
usefulDeclaration(Declaration * decl)54 Declaration* usefulDeclaration(Declaration* decl)
55 {
56     if (!decl)
57         return nullptr;
58 
59     // First: Attempt to find the declaration of a definition
60     decl = DUChainUtils::declarationForDefinition(decl);
61 
62     // Convenience feature: Retrieve the type declaration of instances,
63     // it makes no sense to pass the declaration pointer of instances of types
64     if (decl->kind() == Declaration::Instance) {
65         AbstractType::Ptr type = TypeUtils::targetTypeKeepAliases(decl->abstractType(), decl->topContext());
66         auto* idType = dynamic_cast<IdentifiedType*>(type.data());
67         Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : nullptr;
68         if (idDecl) {
69             decl = idDecl;
70         }
71     }
72     return decl;
73 }
74 
75 }
76 
77 class DocumentationViewFactory: public KDevelop::IToolViewFactory
78 {
79 public:
DocumentationViewFactory()80     DocumentationViewFactory()
81     {}
82 
create(QWidget * parent=nullptr)83     QWidget* create(QWidget *parent = nullptr) override
84     {
85         if (!m_providersModel) {
86             m_providersModel.reset(new ProvidersModel);
87         }
88         return new DocumentationView(parent, m_providersModel.data());
89     }
90 
defaultPosition() const91     Qt::DockWidgetArea defaultPosition() const override
92     {
93         return Qt::RightDockWidgetArea;
94     }
id() const95     QString id() const override { return QStringLiteral("org.kdevelop.DocumentationView"); }
contextMenuActions(QWidget * viewWidget) const96     QList<QAction*> contextMenuActions(QWidget* viewWidget) const override
97     {
98         auto documentationViewWidget = qobject_cast<DocumentationView*>(viewWidget);
99         Q_ASSERT(documentationViewWidget);
100         return documentationViewWidget->contextMenuActions();
101     }
102 
103 private:
104     QScopedPointer<ProvidersModel> m_providersModel;
105 };
106 
DocumentationController(Core * core)107 DocumentationController::DocumentationController(Core* core)
108     : m_factory(new DocumentationViewFactory)
109 {
110     m_showDocumentation = core->uiController()->activeMainWindow()->actionCollection()->addAction(QStringLiteral("showDocumentation"));
111     m_showDocumentation->setText(i18nc("@action", "Show Documentation"));
112     m_showDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("documentation")));
113     connect(m_showDocumentation, &QAction::triggered, this, &DocumentationController::doShowDocumentation);
114 
115     // registering the tool view here so it registered before the areas are restored
116     // and thus also gets treated like the ones registered from plugins
117     // cmp. comment about tool views in CorePrivate::initialize
118     core->uiController()->addToolView(i18nc("@title:window", "Documentation"), m_factory);
119 }
120 
~DocumentationController()121 DocumentationController::~DocumentationController()
122 {
123 }
124 
initialize()125 void DocumentationController::initialize()
126 {
127 }
128 
129 
doShowDocumentation()130 void KDevelop::DocumentationController::doShowDocumentation()
131 {
132     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
133     if(!view)
134       return;
135 
136     KDevelop::DUChainReadLocker lock( DUChain::lock() );
137 
138     Declaration* decl = usefulDeclaration(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration);
139     auto documentation = documentationForDeclaration(decl);
140     if (documentation) {
141         showDocumentation(documentation);
142     }
143 }
144 
contextMenuExtension(Context * context,QWidget * parent)145 KDevelop::ContextMenuExtension KDevelop::DocumentationController::contextMenuExtension(Context* context, QWidget* parent)
146 {
147     Q_UNUSED(parent);
148 
149     ContextMenuExtension menuExt;
150 
151     auto* ctx = dynamic_cast<DeclarationContext*>(context);
152     if(ctx) {
153         DUChainReadLocker lock(DUChain::lock());
154         if(!ctx->declaration().data())
155             return menuExt;
156 
157         auto doc = documentationForDeclaration(ctx->declaration().data());
158         if (doc) {
159             menuExt.addAction(ContextMenuExtension::ExtensionGroup, m_showDocumentation);
160         }
161     }
162 
163     return menuExt;
164 }
165 
documentationForDeclaration(Declaration * decl)166 IDocumentation::Ptr DocumentationController::documentationForDeclaration(Declaration* decl)
167 {
168     if (!decl)
169         return {};
170 
171     const auto documentationProviders = this->documentationProviders();
172     for (IDocumentationProvider* doc : documentationProviders) {
173         qCDebug(SHELL) << "Documentation provider found:" << doc;
174         auto ret = doc->documentationForDeclaration(decl);
175 
176         qCDebug(SHELL) << "Documentation proposed: " << ret.data();
177         if (ret) {
178             return ret;
179         }
180     }
181 
182     return {};
183 }
184 
documentation(const QUrl & url) const185 IDocumentation::Ptr DocumentationController::documentation(const QUrl& url) const
186 {
187     const auto providers = this->documentationProviders();
188     for (const IDocumentationProvider* provider : providers) {
189         IDocumentation::Ptr doc = provider->documentation(url);
190         if (doc) {
191             return doc;
192         }
193     }
194     return {};
195 }
196 
documentationProviders() const197 QList< IDocumentationProvider* > DocumentationController::documentationProviders() const
198 {
199     const QList<IPlugin*> plugins = ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProvider"));
200     const QList<IPlugin*> pluginsProvider = ICore::self()->pluginController()->allPluginsForExtension(QStringLiteral("org.kdevelop.IDocumentationProviderProvider"));
201 
202     QList<IDocumentationProvider*> ret;
203     for (IPlugin* p : pluginsProvider) {
204         auto *docProvider=p->extension<IDocumentationProviderProvider>();
205         if (!docProvider) {
206             qCWarning(SHELL) << "plugin" << p << "does not implement ProviderProvider extension, rerun kbuildsycoca5";
207             continue;
208         }
209         ret.append(docProvider->providers());
210     }
211 
212     for (IPlugin* p : plugins) {
213         auto *doc=p->extension<IDocumentationProvider>();
214         if (!doc) {
215             qCWarning(SHELL) << "plugin" << p << "does not implement Provider extension, rerun kbuildsycoca5";
216             continue;
217         }
218         ret.append(doc);
219     }
220 
221     return ret;
222 }
223 
showDocumentation(const IDocumentation::Ptr & doc)224 void KDevelop::DocumentationController::showDocumentation(const IDocumentation::Ptr& doc)
225 {
226     Q_ASSERT_X(doc, Q_FUNC_INFO, "Null documentation pointer is unsupported.");
227     QWidget* w = ICore::self()->uiController()->findToolView(i18nc("@title:window", "Documentation"), m_factory, KDevelop::IUiController::CreateAndRaise);
228     if(!w) {
229         qCWarning(SHELL) << "Could not add documentation tool view";
230         return;
231     }
232 
233     auto* view = dynamic_cast<DocumentationView*>(w);
234     if( !view ) {
235         qCWarning(SHELL) << "Could not cast tool view" << w << "to DocumentationView class!";
236         return;
237     }
238     view->showDocumentation(doc);
239 }
240 
changedDocumentationProviders()241 void DocumentationController::changedDocumentationProviders()
242 {
243     emit providersChanged();
244 }
245 
246