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