1 /*
2     SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "quickopenplugin.h"
8 
9 #include "quickopenwidget.h"
10 
11 #include <cassert>
12 #include <typeinfo>
13 #include <QKeyEvent>
14 #include <QApplication>
15 #include <QMetaObject>
16 #include <QWidgetAction>
17 #include <QAction>
18 #include <QDesktopWidget>
19 
20 #include <KLocalizedString>
21 #include <KPluginFactory>
22 #include <KTextEditor/Document>
23 #include <KTextEditor/View>
24 #include <KParts/MainWindow>
25 #include <KSharedConfig>
26 #include <KConfigGroup>
27 #include <KActionCollection>
28 
29 #include <interfaces/icore.h>
30 #include <interfaces/iuicontroller.h>
31 #include <interfaces/idocumentcontroller.h>
32 #include <interfaces/ilanguagecontroller.h>
33 #include <language/interfaces/ilanguagesupport.h>
34 #include <language/duchain/duchainutils.h>
35 #include <language/duchain/duchainlock.h>
36 #include <language/duchain/duchain.h>
37 #include <language/duchain/types/identifiedtype.h>
38 #include <serialization/indexedstring.h>
39 #include <language/duchain/types/functiontype.h>
40 
41 #include "quickopenmodel.h"
42 #include "projectfilequickopen.h"
43 #include "projectitemquickopen.h"
44 #include "declarationlistquickopen.h"
45 #include "documentationquickopenprovider.h"
46 #include "actionsquickopenprovider.h"
47 #include "debug.h"
48 #include <language/duchain/functiondefinition.h>
49 #include <interfaces/contextmenuextension.h>
50 #include <language/interfaces/codecontext.h>
51 
52 
53 using namespace KDevelop;
54 
55 const bool noHtmlDestriptionInOutline = true;
56 
57 namespace {
58 namespace Strings {
scopeProjectName()59 QString scopeProjectName()       { return i18nc("@item quick open scope", "Project"); }
scopeIncludesName()60 QString scopeIncludesName()      { return i18nc("@item quick open scope", "Includes"); }
scopeIncludersName()61 QString scopeIncludersName()     { return i18nc("@item quick open scope", "Includers"); }
scopeCurrentlyOpenName()62 QString scopeCurrentlyOpenName() { return i18nc("@item quick open scope", "Currently Open"); }
63 
typeFilesName()64 QString typeFilesName()         { return i18nc("@item quick open item type", "Files"); }
typeFunctionsName()65 QString typeFunctionsName()     { return i18nc("@item quick open item type", "Functions"); }
typeClassesName()66 QString typeClassesName()       { return i18nc("@item quick open item type", "Classes"); }
typeDocumentationName()67 QString typeDocumentationName() { return i18nc("@item quick open item type", "Documentation"); }
typeActionsName()68 QString typeActionsName()       { return i18nc("@item quick open item type", "Actions"); }
69 
70 }
71 }
72 
73 
74 class QuickOpenWidgetCreator
75 {
76 public:
~QuickOpenWidgetCreator()77     virtual ~QuickOpenWidgetCreator()
78     {
79     }
80     virtual QuickOpenWidget* createWidget() = 0;
81     virtual QString objectNameForLine() = 0;
widgetShown()82     virtual void widgetShown()
83     {
84     }
85 };
86 
87 class StandardQuickOpenWidgetCreator
88     : public QuickOpenWidgetCreator
89 {
90 public:
StandardQuickOpenWidgetCreator(const QStringList & items,const QStringList & scopes)91     StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes)
92         : m_items(items)
93         , m_scopes(scopes)
94     {
95     }
96 
objectNameForLine()97     QString objectNameForLine() override
98     {
99         return QStringLiteral("Quickopen");
100     }
101 
setItems(const QStringList & scopes,const QStringList & items)102     void setItems(const QStringList& scopes, const QStringList& items)
103     {
104         m_scopes = scopes;
105         m_items = items;
106     }
107 
createWidget()108     QuickOpenWidget* createWidget() override
109     {
110         QStringList useItems = m_items;
111         if (useItems.isEmpty()) {
112             useItems = QuickOpenPlugin::self()->lastUsedItems;
113         }
114 
115         QStringList useScopes = m_scopes;
116         if (useScopes.isEmpty()) {
117             useScopes = QuickOpenPlugin::self()->lastUsedScopes;
118         }
119 
120         return new QuickOpenWidget(QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true);
121     }
122 
123     QStringList m_items;
124     QStringList m_scopes;
125 };
126 
127 class OutlineFilter
128     : public DUChainUtils::DUChainItemFilter
129 {
130 public:
131     enum OutlineMode { Functions, FunctionsAndClasses };
OutlineFilter(QVector<DUChainItem> & _items,OutlineMode _mode=FunctionsAndClasses)132     explicit OutlineFilter(QVector<DUChainItem>& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items)
133         , mode(_mode)
134     {
135     }
accept(Declaration * decl)136     bool accept(Declaration* decl) override
137     {
138         if (decl->range().isEmpty()) {
139             return false;
140         }
141         bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class));
142         if (collectable) {
143             DUChainItem item;
144             item.m_item = IndexedDeclaration(decl);
145             item.m_text = decl->toString();
146             items << item;
147 
148             return true;
149         } else {
150             return false;
151         }
152     }
accept(DUContext * ctx)153     bool accept(DUContext* ctx) override
154     {
155         if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper) {
156             return true;
157         } else {
158             return false;
159         }
160     }
161     QVector<DUChainItem>& items;
162     OutlineMode mode;
163 };
164 
165 K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin<QuickOpenPlugin>(); )
166 
cursorDeclaration()167 Declaration * cursorDeclaration() {
168     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
169     if (!view) {
170         return nullptr;
171     }
172 
173     KDevelop::DUChainReadLocker lock(DUChain::lock());
174 
175     return DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration);
176 }
177 
178 ///The first definition that belongs to a context that surrounds the current cursor
cursorContextDeclaration()179 Declaration* cursorContextDeclaration()
180 {
181     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
182     if (!view) {
183         return nullptr;
184     }
185 
186     KDevelop::DUChainReadLocker lock(DUChain::lock());
187 
188     TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url());
189     if (!ctx) {
190         return nullptr;
191     }
192 
193     KTextEditor::Cursor cursor(view->cursorPosition());
194 
195     DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor));
196 
197     while (subCtx && !subCtx->owner())
198         subCtx = subCtx->parentContext();
199 
200     Declaration* definition = nullptr;
201 
202     if (!subCtx || !subCtx->owner()) {
203         definition = DUChainUtils::declarationInLine(cursor, ctx);
204     } else {
205         definition = subCtx->owner();
206     }
207 
208     if (!definition) {
209         return nullptr;
210     }
211 
212     return definition;
213 }
214 
215 //Returns only the name, no template-parameters or scope
cursorItemText()216 QString cursorItemText()
217 {
218     KDevelop::DUChainReadLocker lock(DUChain::lock());
219 
220     Declaration* decl = cursorDeclaration();
221     if (!decl) {
222         return QString();
223     }
224 
225     IDocument* doc = ICore::self()->documentController()->activeDocument();
226     if (!doc) {
227         return QString();
228     }
229 
230     TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url());
231 
232     if (!context) {
233         qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context";
234         return QString();
235     }
236 
237     AbstractType::Ptr t = decl->abstractType();
238     auto* idType = dynamic_cast<IdentifiedType*>(t.data());
239     if (idType && idType->declaration(context)) {
240         decl = idType->declaration(context);
241     }
242 
243     if (!decl->qualifiedIdentifier().isEmpty()) {
244         return decl->qualifiedIdentifier().last().identifier().str();
245     }
246 
247     return QString();
248 }
249 
createQuickOpenLineWidget()250 QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget()
251 {
252     return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList()));
253 }
254 
quickOpenLine(const QString & name)255 QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(const QString& name)
256 {
257     const QList<QuickOpenLineEdit*> lines = ICore::self()->uiController()->activeMainWindow()->findChildren<QuickOpenLineEdit*>(name);
258     for (QuickOpenLineEdit* line : lines) {
259         if (line->isVisible()) {
260             return line;
261         }
262     }
263 
264     return nullptr;
265 }
266 
267 static QuickOpenPlugin* staticQuickOpenPlugin = nullptr;
268 
self()269 QuickOpenPlugin* QuickOpenPlugin::self()
270 {
271     return staticQuickOpenPlugin;
272 }
273 
createActionsForMainWindow(Sublime::MainWindow *,QString & xmlFile,KActionCollection & actions)274 void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions)
275 {
276     xmlFile = QStringLiteral("kdevquickopen.rc");
277 
278     QAction* quickOpen = actions.addAction(QStringLiteral("quick_open"));
279     quickOpen->setText(i18nc("@action", "&Quick Open"));
280     quickOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen")));
281     actions.setDefaultShortcut(quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q);
282     connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen);
283 
284     QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file"));
285     quickOpenFile->setText(i18nc("@action", "Quick Open &File"));
286     quickOpenFile->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file")));
287     actions.setDefaultShortcut(quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O);
288     connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile);
289 
290     QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class"));
291     quickOpenClass->setText(i18nc("@action", "Quick Open &Class"));
292     quickOpenClass->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-class")));
293     actions.setDefaultShortcut(quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C);
294     connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass);
295 
296     QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function"));
297     quickOpenFunction->setText(i18nc("@action", "Quick Open &Function"));
298     quickOpenFunction->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-function")));
299     actions.setDefaultShortcut(quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M);
300     connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction);
301 
302     QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open"));
303     quickOpenAlreadyOpen->setText(i18nc("@action", "Quick Open &Already Open File"));
304     quickOpenAlreadyOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file")));
305     connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile);
306 
307     QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation"));
308     quickOpenDocumentation->setText(i18nc("@action", "Quick Open &Documentation"));
309     quickOpenDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-documentation")));
310     actions.setDefaultShortcut(quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D);
311     connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation);
312 
313     QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions"));
314     quickOpenActions->setText(i18nc("@action", "Quick Open &Actions"));
315     actions.setDefaultShortcut(quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A);
316     connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions);
317 
318     m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration"));
319     m_quickOpenDeclaration->setText(i18nc("@action", "Jump to Declaration"));
320     m_quickOpenDeclaration->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-declaration")));
321     actions.setDefaultShortcut(m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period);
322     connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection);
323 
324     m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition"));
325     m_quickOpenDefinition->setText(i18nc("@action", "Jump to Definition"));
326     m_quickOpenDefinition->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-definition")));
327     connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection);
328 
329     auto* quickOpenLine = new QWidgetAction(this);
330     quickOpenLine->setText(i18nc("@action", "Embedded Quick Open"));
331     //     actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E );
332 //     connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool)));
333     quickOpenLine->setDefaultWidget(createQuickOpenLineWidget());
334     actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine);
335 
336     QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function"));
337     quickOpenNextFunction->setText(i18nc("@action jump to", "Next Function"));
338     actions.setDefaultShortcut(quickOpenNextFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageDown);
339     connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction);
340 
341     QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function"));
342     quickOpenPrevFunction->setText(i18nc("@action jump to", "Previous Function"));
343     actions.setDefaultShortcut(quickOpenPrevFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageUp);
344     connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction);
345 
346     QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline"));
347     quickOpenNavigateFunctions->setText(i18nc("@action open outline quick open menu", "Outline"));
348     actions.setDefaultShortcut(quickOpenNavigateFunctions, Qt::CTRL | Qt::ALT | Qt::Key_N);
349     connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions);
350 }
351 
QuickOpenPlugin(QObject * parent,const QVariantList &)352 QuickOpenPlugin::QuickOpenPlugin(QObject* parent,
353                                  const QVariantList&)
354     : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent)
355 {
356     staticQuickOpenPlugin = this;
357     m_model = new QuickOpenModel(nullptr);
358 
359     KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen");
360     lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList{
361         Strings::scopeProjectName(),
362         Strings::scopeIncludesName(),
363         Strings::scopeIncludersName(),
364         Strings::scopeCurrentlyOpenName(),
365     });
366     lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList());
367 
368     {
369         m_openFilesData = new OpenFilesDataProvider();
370         const QStringList scopes(Strings::scopeCurrentlyOpenName());
371         const QStringList types(Strings::typeFilesName());
372         m_model->registerProvider(scopes, types, m_openFilesData);
373     }
374     {
375         m_projectFileData = new ProjectFileDataProvider();
376         const QStringList scopes(Strings::scopeProjectName());
377         const QStringList types(Strings::typeFilesName());
378         m_model->registerProvider(scopes, types, m_projectFileData);
379     }
380     {
381         m_projectItemData = new ProjectItemDataProvider(this);
382         const QStringList scopes(Strings::scopeProjectName());
383         const QStringList types = ProjectItemDataProvider::supportedItemTypes();
384         m_model->registerProvider(scopes, types, m_projectItemData);
385     }
386     {
387         m_documentationItemData = new DocumentationQuickOpenProvider;
388         const QStringList scopes(Strings::scopeIncludesName());
389         const QStringList types(Strings::typeDocumentationName());
390         m_model->registerProvider(scopes, types, m_documentationItemData);
391     }
392     {
393         m_actionsItemData = new ActionsQuickOpenProvider;
394         const QStringList scopes(Strings::scopeIncludesName());
395         const QStringList types(Strings::typeActionsName());
396         m_model->registerProvider(scopes, types, m_actionsItemData);
397     }
398 }
399 
~QuickOpenPlugin()400 QuickOpenPlugin::~QuickOpenPlugin()
401 {
402     freeModel();
403 
404     delete m_model;
405     delete m_projectFileData;
406     delete m_projectItemData;
407     delete m_openFilesData;
408     delete m_documentationItemData;
409     delete m_actionsItemData;
410 }
411 
unload()412 void QuickOpenPlugin::unload()
413 {
414 }
415 
contextMenuExtension(Context * context,QWidget * parent)416 ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context, QWidget* parent)
417 {
418     KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent);
419 
420     auto* codeContext = dynamic_cast<KDevelop::DeclarationContext*>(context);
421 
422     if (!codeContext) {
423         return menuExt;
424     }
425 
426     DUChainReadLocker readLock;
427     Declaration* decl(codeContext->declaration().data());
428 
429     if (decl) {
430         const bool isDef = FunctionDefinition::definition(decl);
431         if (codeContext->use().isValid() || !isDef) {
432             menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDeclaration);
433         }
434 
435         if (isDef) {
436             menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDefinition);
437         }
438     }
439 
440     return menuExt;
441 }
442 
showQuickOpen(const QStringList & items)443 void QuickOpenPlugin::showQuickOpen(const QStringList& items)
444 {
445     if (!freeModel()) {
446         return;
447     }
448 
449     QStringList initialItems = items;
450 
451     QStringList useScopes = lastUsedScopes;
452 
453     const QString scopeCurrentlyOpenName = Strings::scopeCurrentlyOpenName();
454     if (!useScopes.contains(scopeCurrentlyOpenName)) {
455         useScopes << scopeCurrentlyOpenName;
456     }
457 
458     showQuickOpenWidget(initialItems, useScopes, false);
459 }
460 
showQuickOpen(ModelTypes modes)461 void QuickOpenPlugin::showQuickOpen(ModelTypes modes)
462 {
463     if (!freeModel()) {
464         return;
465     }
466 
467     QStringList initialItems;
468     if (modes & Files || modes & OpenFiles) {
469         initialItems << Strings::typeFilesName();
470     }
471 
472     if (modes & Functions) {
473         initialItems << Strings::typeFunctionsName();
474     }
475 
476     if (modes & Classes) {
477         initialItems << Strings::typeClassesName();
478     }
479 
480     QStringList useScopes;
481     if (modes != OpenFiles) {
482         useScopes = lastUsedScopes;
483     }
484 
485     if ((modes & OpenFiles)) {
486         const QString currentlyOpen = Strings::scopeCurrentlyOpenName();
487         if (!useScopes.contains(currentlyOpen)) {
488             useScopes << currentlyOpen;
489         }
490     }
491 
492     bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All);
493     showQuickOpenWidget(initialItems, useScopes, preselectText);
494 }
495 
showQuickOpenWidget(const QStringList & items,const QStringList & scopes,bool preselectText)496 void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText)
497 {
498     auto* dialog = new QuickOpenWidgetDialog(i18nc("@title:window", "Quick Open"), m_model, items, scopes);
499     m_currentWidgetHandler = dialog;
500     if (preselectText) {
501         KDevelop::IDocument* currentDoc = core()->documentController()->activeDocument();
502         if (currentDoc && currentDoc->isTextDocument()) {
503             QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection());
504             dialog->widget()->setPreselectedText(preselected);
505         }
506     }
507 
508     connect(dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes);
509     //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes
510     //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) );
511     dialog->widget()->ui.itemsButton->setEnabled(false);
512 
513     if (quickOpenLine()) {
514         quickOpenLine()->showWithWidget(dialog->widget());
515         dialog->deleteLater();
516     } else {
517         dialog->run();
518     }
519 }
520 
storeScopes(const QStringList & scopes)521 void QuickOpenPlugin::storeScopes(const QStringList& scopes)
522 {
523     lastUsedScopes = scopes;
524     KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen");
525     grp.writeEntry("SelectedScopes", scopes);
526 }
527 
storeItems(const QStringList & items)528 void QuickOpenPlugin::storeItems(const QStringList& items)
529 {
530     lastUsedItems = items;
531     KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen");
532     grp.writeEntry("SelectedItems", items);
533 }
534 
quickOpen()535 void QuickOpenPlugin::quickOpen()
536 {
537     if (quickOpenLine()) { //Same as clicking on Quick Open
538         quickOpenLine()->setFocus();
539     } else {
540         showQuickOpen(All);
541     }
542 }
543 
quickOpenFile()544 void QuickOpenPlugin::quickOpenFile()
545 {
546     showQuickOpen(( ModelTypes )(Files | OpenFiles));
547 }
548 
quickOpenFunction()549 void QuickOpenPlugin::quickOpenFunction()
550 {
551     showQuickOpen(Functions);
552 }
553 
quickOpenClass()554 void QuickOpenPlugin::quickOpenClass()
555 {
556     showQuickOpen(Classes);
557 }
558 
quickOpenOpenFile()559 void QuickOpenPlugin::quickOpenOpenFile()
560 {
561     showQuickOpen(OpenFiles);
562 }
563 
quickOpenDocumentation()564 void QuickOpenPlugin::quickOpenDocumentation()
565 {
566     const QStringList scopes(Strings::scopeIncludesName());
567     const QStringList types(Strings::typeDocumentationName());
568     showQuickOpenWidget(types, scopes, true);
569 }
570 
quickOpenActions()571 void QuickOpenPlugin::quickOpenActions()
572 {
573     const QStringList scopes(Strings::scopeIncludesName());
574     const QStringList types(Strings::typeActionsName());
575     showQuickOpenWidget(types, scopes, true);
576 }
577 
fileSet() const578 QSet<KDevelop::IndexedString> QuickOpenPlugin::fileSet() const
579 {
580     return m_model->fileSet();
581 }
582 
registerProvider(const QStringList & scope,const QStringList & type,KDevelop::QuickOpenDataProviderBase * provider)583 void QuickOpenPlugin::registerProvider(const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider)
584 {
585     m_model->registerProvider(scope, type, provider);
586 }
587 
removeProvider(KDevelop::QuickOpenDataProviderBase * provider)588 bool QuickOpenPlugin::removeProvider(KDevelop::QuickOpenDataProviderBase* provider)
589 {
590     m_model->removeProvider(provider);
591     return true;
592 }
593 
quickOpenDeclaration()594 void QuickOpenPlugin::quickOpenDeclaration()
595 {
596     if (jumpToSpecialObject()) {
597         return;
598     }
599 
600     KDevelop::DUChainReadLocker lock(DUChain::lock());
601     Declaration* decl = cursorDeclaration();
602 
603     if (!decl) {
604         qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump";
605         return;
606     }
607     decl->activateSpecialization();
608 
609     IndexedString u = decl->url();
610     KTextEditor::Cursor c = decl->rangeInCurrentRevision().start();
611 
612     if (u.isEmpty()) {
613         qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString();
614         return;
615     }
616 
617     lock.unlock();
618     core()->documentController()->openDocument(u.toUrl(), c);
619 }
620 
specialObjectNavigationWidget() const621 QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const
622 {
623     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
624     if (!view) {
625         return nullptr;
626     }
627 
628     QUrl url = ICore::self()->documentController()->activeDocument()->url();
629 
630     const auto languages = ICore::self()->languageController()->languagesForUrl(url);
631     for (const auto language : languages) {
632         QWidget* w = language->specialLanguageObjectNavigationWidget(url, view->cursorPosition()).first;
633         if (w) {
634             return w;
635         }
636     }
637 
638     return nullptr;
639 }
640 
specialObjectJumpPosition() const641 QPair<QUrl, KTextEditor::Cursor> QuickOpenPlugin::specialObjectJumpPosition() const
642 {
643     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
644     if (!view) {
645         return qMakePair(QUrl(), KTextEditor::Cursor());
646     }
647 
648     QUrl url = ICore::self()->documentController()->activeDocument()->url();
649     const auto languages = ICore::self()->languageController()->languagesForUrl(url);
650     for (const auto language : languages) {
651         QPair<QUrl, KTextEditor::Cursor> pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition()));
652         if (pos.second.isValid()) {
653             return pos;
654         }
655     }
656 
657     return qMakePair(QUrl(), KTextEditor::Cursor::invalid());
658 }
659 
jumpToSpecialObject()660 bool QuickOpenPlugin::jumpToSpecialObject()
661 {
662     QPair<QUrl, KTextEditor::Cursor> pos = specialObjectJumpPosition();
663     if (pos.second.isValid()) {
664         if (pos.first.isEmpty()) {
665             qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object";
666             return false;
667         }
668 
669         ICore::self()->documentController()->openDocument(pos.first, pos.second);
670         return true;
671     }
672     return false;
673 }
674 
quickOpenDefinition()675 void QuickOpenPlugin::quickOpenDefinition()
676 {
677     if (jumpToSpecialObject()) {
678         return;
679     }
680 
681     KDevelop::DUChainReadLocker lock(DUChain::lock());
682     Declaration* decl = cursorDeclaration();
683 
684     if (!decl) {
685         qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump";
686         return;
687     }
688 
689     IndexedString u = decl->url();
690     KTextEditor::Cursor c = decl->rangeInCurrentRevision().start();
691     if (auto* def = FunctionDefinition::definition(decl)) {
692         def->activateSpecialization();
693         u = def->url();
694         c = def->rangeInCurrentRevision().start();
695     } else {
696         qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration";
697         decl->activateSpecialization();
698     }
699 
700     if (u.isEmpty()) {
701         qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString();
702         return;
703     }
704 
705     lock.unlock();
706     core()->documentController()->openDocument(u.toUrl(), c);
707 }
708 
freeModel()709 bool QuickOpenPlugin::freeModel()
710 {
711     if (m_currentWidgetHandler) {
712         delete m_currentWidgetHandler;
713     }
714     m_currentWidgetHandler = nullptr;
715 
716     return true;
717 }
718 
nextFunction()719 void QuickOpenPlugin::nextFunction()
720 {
721     jumpToNearestFunction(NextFunction);
722 }
723 
previousFunction()724 void QuickOpenPlugin::previousFunction()
725 {
726     jumpToNearestFunction(PreviousFunction);
727 }
728 
jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction)729 void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction)
730 {
731     IDocument* doc = ICore::self()->documentController()->activeDocument();
732     if (!doc) {
733         qCDebug(PLUGIN_QUICKOPEN) << "No active document";
734         return;
735     }
736 
737     KDevelop::DUChainReadLocker lock(DUChain::lock());
738 
739     TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url());
740 
741     if (!context) {
742         qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context";
743         return;
744     }
745 
746     QVector<DUChainItem> items;
747     OutlineFilter filter(items, OutlineFilter::Functions);
748     DUChainUtils::collectItems(context, filter);
749 
750     CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition()));
751     if (!cursor.isValid()) {
752         return;
753     }
754 
755     Declaration* nearestDeclBefore = nullptr;
756     int distanceBefore = INT_MIN;
757     Declaration* nearestDeclAfter = nullptr;
758     int distanceAfter = INT_MAX;
759 
760     for (auto& item : qAsConst(items)) {
761         Declaration* decl = item.m_item.data();
762 
763         int distance = decl->range().start.line - cursor.line;
764         if (distance < 0 && distance >= distanceBefore) {
765             distanceBefore = distance;
766             nearestDeclBefore = decl;
767         } else if (distance > 0 && distance <= distanceAfter) {
768             distanceAfter = distance;
769             nearestDeclAfter = decl;
770         }
771     }
772 
773     CursorInRevision c = CursorInRevision::invalid();
774     if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) {
775         c = nearestDeclAfter->range().start;
776     } else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) {
777         c = nearestDeclBefore->range().start;
778     }
779 
780     KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid();
781     if (c.isValid()) {
782         textCursor = context->transformFromLocalRevision(c);
783     }
784 
785     lock.unlock();
786     if (textCursor.isValid()) {
787         core()->documentController()->openDocument(doc->url(), textCursor);
788     } else {
789         qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to";
790     }
791 }
792 
793 struct CreateOutlineDialog
794 {
startCreateOutlineDialog795     void start()
796     {
797         if (!QuickOpenPlugin::self()->freeModel()) {
798             return;
799         }
800 
801         IDocument* doc = ICore::self()->documentController()->activeDocument();
802         if (!doc) {
803             qCDebug(PLUGIN_QUICKOPEN) << "No active document";
804             return;
805         }
806 
807         DUChainReadLocker lock;
808 
809         TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url());
810 
811         if (!context) {
812             qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context";
813             return;
814         }
815 
816         model = new QuickOpenModel(nullptr);
817 
818         OutlineFilter filter(items);
819 
820         DUChainUtils::collectItems(context, filter);
821 
822         if (noHtmlDestriptionInOutline) {
823             for (auto& item : items) {
824                 item.m_noHtmlDestription = true;
825             }
826         }
827 
828         cursorDecl = cursorContextDeclaration();
829 
830         model->registerProvider(QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true));
831 
832         dialog = new QuickOpenWidgetDialog(i18nc("@title:window", "Outline"), model, QStringList(), QStringList(), true);
833         dialog->widget()->setSortingEnabled(true);
834 
835         model->setParent(dialog->widget());
836     }
finishCreateOutlineDialog837     void finish()
838     {
839         //Select the declaration that contains the cursor
840         if (cursorDecl.isValid() && dialog) {
841             auto it = std::find_if(items.constBegin(), items.constEnd(),
842                                    [this](const DUChainItem& item) { return item.m_item == cursorDecl; });
843             if (it != items.constEnd()) {
844                 // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect,
845                 // apparently because the widget internals aren't initialized yet properly (although we've
846                 // already called 'widget->show()'.
847                 auto list = dialog->widget()->ui.list;
848                 const auto num = std::distance(items.constBegin(), it);
849                 QTimer::singleShot(0, list, [list, num]() {
850                     const auto index = list->model()->index(num, 0, {});
851                     list->setCurrentIndex(index);
852                     list->scrollTo(index, QAbstractItemView::PositionAtCenter);
853                 });
854             }
855         }
856     }
857     QPointer<QuickOpenWidgetDialog> dialog;
858     IndexedDeclaration cursorDecl;
859     QVector<DUChainItem> items;
860     QuickOpenModel* model = nullptr;
861 };
862 
863 class OutlineQuickopenWidgetCreator
864     : public QuickOpenWidgetCreator
865 {
866 public:
OutlineQuickopenWidgetCreator(const QStringList &,const QStringList &)867     OutlineQuickopenWidgetCreator(const QStringList& /*scopes*/, const QStringList& /*items*/) : m_creator(nullptr)
868     {
869     }
870 
~OutlineQuickopenWidgetCreator()871     ~OutlineQuickopenWidgetCreator() override
872     {
873         delete m_creator;
874     }
875 
createWidget()876     QuickOpenWidget* createWidget() override
877     {
878         delete m_creator;
879         m_creator = new CreateOutlineDialog;
880         m_creator->start();
881 
882         if (!m_creator->dialog) {
883             return nullptr;
884         }
885 
886         m_creator->dialog->deleteLater();
887         return m_creator->dialog->widget();
888     }
889 
widgetShown()890     void widgetShown() override
891     {
892         if (m_creator) {
893             m_creator->finish();
894             delete m_creator;
895             m_creator = nullptr;
896         }
897     }
898 
objectNameForLine()899     QString objectNameForLine() override
900     {
901         return QStringLiteral("Outline");
902     }
903 
904     CreateOutlineDialog* m_creator;
905 };
906 
quickOpenNavigateFunctions()907 void QuickOpenPlugin::quickOpenNavigateFunctions()
908 {
909     CreateOutlineDialog create;
910     create.start();
911 
912     if (!create.dialog) {
913         return;
914     }
915 
916     m_currentWidgetHandler = create.dialog;
917 
918     QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline"));
919     if (!line) {
920         line  = quickOpenLine();
921     }
922 
923     if (line) {
924         line->showWithWidget(create.dialog->widget());
925         create.dialog->deleteLater();
926     } else {
927         create.dialog->run();
928     }
929 
930     create.finish();
931 }
932 
QuickOpenLineEdit(QuickOpenWidgetCreator * creator)933 QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr)
934     , m_forceUpdate(false)
935     , m_widgetCreator(creator)
936 {
937     setFont(qApp->font("QToolButton"));
938     setMinimumWidth(200);
939     setMaximumWidth(400);
940 
941     deactivate();
942     setPlaceholderText(i18nc("@info:placeholder", "Quick Open..."));
943     setToolTip(i18nc("@info:tooltip", "Search for files, classes, functions and more,"
944                     " allowing you to quickly navigate in your source code."));
945     setObjectName(m_widgetCreator->objectNameForLine());
946     setFocusPolicy(Qt::ClickFocus);
947 }
948 
~QuickOpenLineEdit()949 QuickOpenLineEdit::~QuickOpenLineEdit()
950 {
951     delete m_widget;
952     delete m_widgetCreator;
953 }
954 
insideThis(QObject * object)955 bool QuickOpenLineEdit::insideThis(QObject* object)
956 {
957     while (object) {
958         qCDebug(PLUGIN_QUICKOPEN) << object;
959         if (object == this || object == m_widget) {
960             return true;
961         }
962         object = object->parent();
963     }
964     return false;
965 }
966 
widgetDestroyed(QObject * obj)967 void QuickOpenLineEdit::widgetDestroyed(QObject* obj)
968 {
969     Q_UNUSED(obj);
970     // need to use a queued connection here, because this function is called in ~QWidget!
971     // => QuickOpenWidget instance is half-destructed => connections are not yet cleared
972     // => clear() will trigger signals which will operate on the invalid QuickOpenWidget
973     // So, just wait until properly destructed
974     QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection);
975 }
976 
showWithWidget(QuickOpenWidget * widget)977 void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget)
978 {
979     connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed);
980     qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget;
981     deactivate();
982     if (m_widget) {
983         qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget;
984         delete m_widget;
985     }
986     m_widget = widget;
987     m_forceUpdate = true;
988     setFocus();
989 }
990 
focusInEvent(QFocusEvent * ev)991 void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev)
992 {
993     QLineEdit::focusInEvent(ev);
994 //       delete m_widget;
995     qCDebug(PLUGIN_QUICKOPEN) << "got focus";
996     qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate;
997     if (m_widget && !m_forceUpdate) {
998         return;
999     }
1000 
1001     if (!m_forceUpdate && !QuickOpenPlugin::self()->freeModel()) {
1002         deactivate();
1003         return;
1004     }
1005 
1006     m_forceUpdate = false;
1007 
1008     if (!m_widget) {
1009         m_widget = m_widgetCreator->createWidget();
1010         if (!m_widget) {
1011             deactivate();
1012             return;
1013         }
1014     }
1015 
1016     activate();
1017 
1018     m_widget->showStandardButtons(false);
1019     m_widget->showSearchField(false);
1020 
1021     m_widget->setParent(nullptr, Qt::ToolTip);
1022     m_widget->setFocusPolicy(Qt::NoFocus);
1023     m_widget->setAlternativeSearchField(this);
1024 
1025     QuickOpenPlugin::self()->m_currentWidgetHandler = m_widget;
1026     connect(m_widget.data(), &QuickOpenWidget::ready, this, &QuickOpenLineEdit::deactivate);
1027 
1028     connect(m_widget.data(), &QuickOpenWidget::scopesChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeScopes);
1029     connect(m_widget.data(), &QuickOpenWidget::itemsChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeItems);
1030     Q_ASSERT(m_widget->ui.searchLine == this);
1031     m_widget->prepareShow();
1032     QRect widgetGeometry = QRect(mapToGlobal(QPoint(0, height())), mapToGlobal(QPoint(width(), height() + 400)));
1033     widgetGeometry.setWidth(700); ///@todo Waste less space
1034     QRect screenGeom = QApplication::desktop()->screenGeometry(this);
1035     if (widgetGeometry.right() > screenGeom.right()) {
1036         widgetGeometry.moveRight(screenGeom.right());
1037     }
1038     if (widgetGeometry.bottom() > screenGeom.bottom()) {
1039         widgetGeometry.moveBottom(mapToGlobal(QPoint(0, 0)).y());
1040     }
1041     m_widget->setGeometry(widgetGeometry);
1042     m_widget->show();
1043 
1044     m_widgetCreator->widgetShown();
1045 }
1046 
hideEvent(QHideEvent * ev)1047 void QuickOpenLineEdit::hideEvent(QHideEvent* ev)
1048 {
1049     QWidget::hideEvent(ev);
1050     if (m_widget) {
1051         QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection);
1052     }
1053 //       deactivate();
1054 }
1055 
eventFilter(QObject * obj,QEvent * e)1056 bool QuickOpenLineEdit::eventFilter(QObject* obj, QEvent* e)
1057 {
1058     if (!m_widget) {
1059         return QLineEdit::eventFilter(obj, e);
1060     }
1061 
1062     switch (e->type()) {
1063     case QEvent::KeyPress:
1064     case QEvent::ShortcutOverride:
1065         if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) {
1066             deactivate();
1067             e->accept();
1068             return true; // eat event
1069         }
1070         break;
1071     case QEvent::WindowActivate:
1072     case QEvent::WindowDeactivate:
1073         QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection);
1074         break;
1075     // handle bug 260657 - "Outline menu doesn't follow main window on its move"
1076     case QEvent::Move: {
1077         if (QWidget* widget = qobject_cast<QWidget*>(obj)) {
1078             // close the outline menu in case a parent widget moved
1079             if (widget->isAncestorOf(this)) {
1080                 qCDebug(PLUGIN_QUICKOPEN) << "closing because of parent widget move";
1081                 deactivate();
1082             }
1083         }
1084         break;
1085     }
1086     case QEvent::FocusIn:
1087         if (qobject_cast<QWidget*>(obj)) {
1088             auto* focusEvent = dynamic_cast<QFocusEvent*>(e);
1089             Q_ASSERT(focusEvent);
1090             //Eat the focus event, keep the focus
1091             qCDebug(PLUGIN_QUICKOPEN) << "focus change" << "inside this: " << insideThis(obj) << "this" << this << "obj" << obj;
1092             if (obj == this) {
1093                 break;
1094             }
1095 
1096             qCDebug(PLUGIN_QUICKOPEN) << "reason" << focusEvent->reason();
1097             if (focusEvent->reason() != Qt::MouseFocusReason && focusEvent->reason() != Qt::ActiveWindowFocusReason) {
1098                 QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection);
1099                 break;
1100             }
1101             if (!insideThis(obj)) {
1102                 deactivate();
1103             }
1104         } else if (obj != this) {
1105             QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection);
1106         }
1107         break;
1108     default:
1109         break;
1110     }
1111 
1112     return QLineEdit::eventFilter(obj, e);
1113 }
activate()1114 void QuickOpenLineEdit::activate()
1115 {
1116     qCDebug(PLUGIN_QUICKOPEN) << "activating";
1117     setText(QString());
1118     setStyleSheet(QString());
1119     qApp->installEventFilter(this);
1120 }
deactivate()1121 void QuickOpenLineEdit::deactivate()
1122 {
1123     qCDebug(PLUGIN_QUICKOPEN) << "deactivating";
1124 
1125     clear();
1126 
1127     if (m_widget || hasFocus()) {
1128         QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection);
1129     }
1130 
1131     if (m_widget) {
1132         m_widget->deleteLater();
1133     }
1134 
1135     m_widget = nullptr;
1136     qApp->removeEventFilter(this);
1137 }
1138 
checkFocus()1139 void QuickOpenLineEdit::checkFocus()
1140 {
1141     qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget;
1142     if (m_widget) {
1143         QWidget* focusWidget = QApplication::focusWidget();
1144         bool focusWidgetInsideThis = focusWidget ? insideThis(focusWidget) : false;
1145         if (QApplication::focusWindow() && isVisible() && !isHidden() && (!focusWidget || (focusWidget && focusWidgetInsideThis))) {
1146             qCDebug(PLUGIN_QUICKOPEN) << "setting focus to line edit";
1147             activateWindow();
1148             setFocus();
1149         } else {
1150             qCDebug(PLUGIN_QUICKOPEN) << "deactivating because check failed, focusWidget" << focusWidget << "insideThis" << focusWidgetInsideThis;
1151             deactivate();
1152         }
1153     } else {
1154         if (ICore::self()->documentController()->activeDocument()) {
1155             ICore::self()->documentController()->activateDocument(ICore::self()->documentController()->activeDocument());
1156         }
1157 
1158         //Make sure the focus is somewhere else, even if there is no active document
1159         setEnabled(false);
1160         setEnabled(true);
1161     }
1162 }
1163 
createQuickOpenLine(const QStringList & scopes,const QStringList & type,IQuickOpen::QuickOpenType kind)1164 QLineEdit* QuickOpenPlugin::createQuickOpenLine(const QStringList& scopes, const QStringList& type, IQuickOpen::QuickOpenType kind)
1165 {
1166     if (kind == Outline) {
1167         return new QuickOpenLineEdit(new OutlineQuickopenWidgetCreator(scopes, type));
1168     } else {
1169         return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(scopes, type));
1170     }
1171 }
1172 
1173 #include "quickopenplugin.moc"
1174