File indexing completed on 2024-05-05 04:40:54

0001 /*
0002     SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "quickopenplugin.h"
0008 
0009 #include "quickopenwidget.h"
0010 
0011 #include <cassert>
0012 #include <typeinfo>
0013 #include <QKeyEvent>
0014 #include <QApplication>
0015 #include <QMetaObject>
0016 #include <QWidgetAction>
0017 #include <QAction>
0018 #include <QDesktopWidget>
0019 
0020 #include <KLocalizedString>
0021 #include <KPluginFactory>
0022 #include <KTextEditor/Document>
0023 #include <KTextEditor/View>
0024 #include <KParts/MainWindow>
0025 #include <KSharedConfig>
0026 #include <KConfigGroup>
0027 #include <KActionCollection>
0028 
0029 #include <interfaces/icore.h>
0030 #include <interfaces/iuicontroller.h>
0031 #include <interfaces/idocumentcontroller.h>
0032 #include <interfaces/ilanguagecontroller.h>
0033 #include <language/interfaces/ilanguagesupport.h>
0034 #include <language/duchain/duchainutils.h>
0035 #include <language/duchain/duchainlock.h>
0036 #include <language/duchain/duchain.h>
0037 #include <language/duchain/types/identifiedtype.h>
0038 #include <serialization/indexedstring.h>
0039 #include <language/duchain/types/functiontype.h>
0040 
0041 #include "quickopenmodel.h"
0042 #include "projectfilequickopen.h"
0043 #include "projectitemquickopen.h"
0044 #include "declarationlistquickopen.h"
0045 #include "documentationquickopenprovider.h"
0046 #include "actionsquickopenprovider.h"
0047 #include "debug.h"
0048 #include <language/duchain/functiondefinition.h>
0049 #include <interfaces/contextmenuextension.h>
0050 #include <language/interfaces/codecontext.h>
0051 
0052 
0053 using namespace KDevelop;
0054 
0055 const bool noHtmlDestriptionInOutline = true;
0056 
0057 namespace {
0058 namespace Strings {
0059 QString scopeProjectName()       { return i18nc("@item quick open scope", "Project"); }
0060 QString scopeIncludesName()      { return i18nc("@item quick open scope", "Includes"); }
0061 QString scopeIncludersName()     { return i18nc("@item quick open scope", "Includers"); }
0062 QString scopeCurrentlyOpenName() { return i18nc("@item quick open scope", "Currently Open"); }
0063 
0064 QString typeFilesName()         { return i18nc("@item quick open item type", "Files"); }
0065 QString typeFunctionsName()     { return i18nc("@item quick open item type", "Functions"); }
0066 QString typeClassesName()       { return i18nc("@item quick open item type", "Classes"); }
0067 QString typeDocumentationName() { return i18nc("@item quick open item type", "Documentation"); }
0068 QString typeActionsName()       { return i18nc("@item quick open item type", "Actions"); }
0069 
0070 }
0071 }
0072 
0073 
0074 class QuickOpenWidgetCreator
0075 {
0076 public:
0077     virtual ~QuickOpenWidgetCreator()
0078     {
0079     }
0080     virtual QuickOpenWidget* createWidget() = 0;
0081     virtual QString objectNameForLine() = 0;
0082     virtual void widgetShown()
0083     {
0084     }
0085 };
0086 
0087 class StandardQuickOpenWidgetCreator
0088     : public QuickOpenWidgetCreator
0089 {
0090 public:
0091     StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes)
0092         : m_items(items)
0093         , m_scopes(scopes)
0094     {
0095     }
0096 
0097     QString objectNameForLine() override
0098     {
0099         return QStringLiteral("Quickopen");
0100     }
0101 
0102     void setItems(const QStringList& scopes, const QStringList& items)
0103     {
0104         m_scopes = scopes;
0105         m_items = items;
0106     }
0107 
0108     QuickOpenWidget* createWidget() override
0109     {
0110         QStringList useItems = m_items;
0111         if (useItems.isEmpty()) {
0112             useItems = QuickOpenPlugin::self()->lastUsedItems;
0113         }
0114 
0115         QStringList useScopes = m_scopes;
0116         if (useScopes.isEmpty()) {
0117             useScopes = QuickOpenPlugin::self()->lastUsedScopes;
0118         }
0119 
0120         return new QuickOpenWidget(QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true);
0121     }
0122 
0123     QStringList m_items;
0124     QStringList m_scopes;
0125 };
0126 
0127 class OutlineFilter
0128     : public DUChainUtils::DUChainItemFilter
0129 {
0130 public:
0131     enum OutlineMode { Functions, FunctionsAndClasses };
0132     explicit OutlineFilter(QVector<DUChainItem>& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items)
0133         , mode(_mode)
0134     {
0135     }
0136     bool accept(Declaration* decl) override
0137     {
0138         if (decl->range().isEmpty()) {
0139             return false;
0140         }
0141         bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class));
0142         if (collectable) {
0143             DUChainItem item;
0144             item.m_item = IndexedDeclaration(decl);
0145             item.m_text = decl->toString();
0146             items << item;
0147 
0148             return true;
0149         } else {
0150             return false;
0151         }
0152     }
0153     bool accept(DUContext* ctx) override
0154     {
0155         if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper) {
0156             return true;
0157         } else {
0158             return false;
0159         }
0160     }
0161     QVector<DUChainItem>& items;
0162     OutlineMode mode;
0163 };
0164 
0165 K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin<QuickOpenPlugin>(); )
0166 
0167 Declaration * cursorDeclaration() {
0168     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0169     if (!view) {
0170         return nullptr;
0171     }
0172 
0173     KDevelop::DUChainReadLocker lock(DUChain::lock());
0174 
0175     return DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration);
0176 }
0177 
0178 ///The first definition that belongs to a context that surrounds the current cursor
0179 Declaration* cursorContextDeclaration()
0180 {
0181     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0182     if (!view) {
0183         return nullptr;
0184     }
0185 
0186     KDevelop::DUChainReadLocker lock(DUChain::lock());
0187 
0188     TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url());
0189     if (!ctx) {
0190         return nullptr;
0191     }
0192 
0193     KTextEditor::Cursor cursor(view->cursorPosition());
0194 
0195     DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor));
0196 
0197     while (subCtx && !subCtx->owner())
0198         subCtx = subCtx->parentContext();
0199 
0200     Declaration* definition = nullptr;
0201 
0202     if (!subCtx || !subCtx->owner()) {
0203         definition = DUChainUtils::declarationInLine(cursor, ctx);
0204     } else {
0205         definition = subCtx->owner();
0206     }
0207 
0208     if (!definition) {
0209         return nullptr;
0210     }
0211 
0212     return definition;
0213 }
0214 
0215 //Returns only the name, no template-parameters or scope
0216 QString cursorItemText()
0217 {
0218     KDevelop::DUChainReadLocker lock(DUChain::lock());
0219 
0220     Declaration* decl = cursorDeclaration();
0221     if (!decl) {
0222         return QString();
0223     }
0224 
0225     IDocument* doc = ICore::self()->documentController()->activeDocument();
0226     if (!doc) {
0227         return QString();
0228     }
0229 
0230     TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url());
0231 
0232     if (!context) {
0233         qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context";
0234         return QString();
0235     }
0236 
0237     AbstractType::Ptr t = decl->abstractType();
0238     auto* idType = dynamic_cast<IdentifiedType*>(t.data());
0239     if (idType && idType->declaration(context)) {
0240         decl = idType->declaration(context);
0241     }
0242 
0243     if (!decl->qualifiedIdentifier().isEmpty()) {
0244         return decl->qualifiedIdentifier().last().identifier().str();
0245     }
0246 
0247     return QString();
0248 }
0249 
0250 QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget()
0251 {
0252     return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList()));
0253 }
0254 
0255 QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(const QString& name)
0256 {
0257     const QList<QuickOpenLineEdit*> lines = ICore::self()->uiController()->activeMainWindow()->findChildren<QuickOpenLineEdit*>(name);
0258     for (QuickOpenLineEdit* line : lines) {
0259         if (line->isVisible()) {
0260             return line;
0261         }
0262     }
0263 
0264     return nullptr;
0265 }
0266 
0267 static QuickOpenPlugin* staticQuickOpenPlugin = nullptr;
0268 
0269 QuickOpenPlugin* QuickOpenPlugin::self()
0270 {
0271     return staticQuickOpenPlugin;
0272 }
0273 
0274 void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions)
0275 {
0276     xmlFile = QStringLiteral("kdevquickopen.rc");
0277 
0278     QAction* quickOpen = actions.addAction(QStringLiteral("quick_open"));
0279     quickOpen->setText(i18nc("@action", "&Quick Open"));
0280     quickOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen")));
0281     actions.setDefaultShortcut(quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q);
0282     connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen);
0283 
0284     QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file"));
0285     quickOpenFile->setText(i18nc("@action", "Quick Open &File"));
0286     quickOpenFile->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file")));
0287     actions.setDefaultShortcut(quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O);
0288     connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile);
0289 
0290     QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class"));
0291     quickOpenClass->setText(i18nc("@action", "Quick Open &Class"));
0292     quickOpenClass->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-class")));
0293     actions.setDefaultShortcut(quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C);
0294     connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass);
0295 
0296     QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function"));
0297     quickOpenFunction->setText(i18nc("@action", "Quick Open &Function"));
0298     quickOpenFunction->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-function")));
0299     actions.setDefaultShortcut(quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M);
0300     connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction);
0301 
0302     QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open"));
0303     quickOpenAlreadyOpen->setText(i18nc("@action", "Quick Open &Already Open File"));
0304     quickOpenAlreadyOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file")));
0305     connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile);
0306 
0307     QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation"));
0308     quickOpenDocumentation->setText(i18nc("@action", "Quick Open &Documentation"));
0309     quickOpenDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-documentation")));
0310     actions.setDefaultShortcut(quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D);
0311     connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation);
0312 
0313     QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions"));
0314     quickOpenActions->setText(i18nc("@action", "Quick Open &Actions"));
0315     actions.setDefaultShortcut(quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A);
0316     connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions);
0317 
0318     m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration"));
0319     m_quickOpenDeclaration->setText(i18nc("@action", "Jump to Declaration"));
0320     m_quickOpenDeclaration->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-declaration")));
0321     actions.setDefaultShortcut(m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period);
0322     connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection);
0323 
0324     m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition"));
0325     m_quickOpenDefinition->setText(i18nc("@action", "Jump to Definition"));
0326     m_quickOpenDefinition->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-definition")));
0327     connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection);
0328 
0329     auto* quickOpenLine = new QWidgetAction(this);
0330     quickOpenLine->setText(i18nc("@action", "Embedded Quick Open"));
0331     //     actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E );
0332 //     connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool)));
0333     quickOpenLine->setDefaultWidget(createQuickOpenLineWidget());
0334     actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine);
0335 
0336     QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function"));
0337     quickOpenNextFunction->setText(i18nc("@action jump to", "Next Function"));
0338     actions.setDefaultShortcut(quickOpenNextFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageDown);
0339     connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction);
0340 
0341     QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function"));
0342     quickOpenPrevFunction->setText(i18nc("@action jump to", "Previous Function"));
0343     actions.setDefaultShortcut(quickOpenPrevFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageUp);
0344     connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction);
0345 
0346     QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline"));
0347     quickOpenNavigateFunctions->setText(i18nc("@action open outline quick open menu", "Outline"));
0348     actions.setDefaultShortcut(quickOpenNavigateFunctions, Qt::CTRL | Qt::ALT | Qt::Key_N);
0349     connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions);
0350 }
0351 
0352 QuickOpenPlugin::QuickOpenPlugin(QObject* parent,
0353                                  const QVariantList&)
0354     : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent)
0355 {
0356     staticQuickOpenPlugin = this;
0357     m_model = new QuickOpenModel(nullptr);
0358 
0359     KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen");
0360     lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList{
0361         Strings::scopeProjectName(),
0362         Strings::scopeIncludesName(),
0363         Strings::scopeIncludersName(),
0364         Strings::scopeCurrentlyOpenName(),
0365     });
0366     lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList());
0367 
0368     {
0369         m_openFilesData = new OpenFilesDataProvider();
0370         const QStringList scopes(Strings::scopeCurrentlyOpenName());
0371         const QStringList types(Strings::typeFilesName());
0372         m_model->registerProvider(scopes, types, m_openFilesData);
0373     }
0374     {
0375         m_projectFileData = new ProjectFileDataProvider();
0376         const QStringList scopes(Strings::scopeProjectName());
0377         const QStringList types(Strings::typeFilesName());
0378         m_model->registerProvider(scopes, types, m_projectFileData);
0379     }
0380     {
0381         m_projectItemData = new ProjectItemDataProvider(this);
0382         const QStringList scopes(Strings::scopeProjectName());
0383         const QStringList types = ProjectItemDataProvider::supportedItemTypes();
0384         m_model->registerProvider(scopes, types, m_projectItemData);
0385     }
0386     {
0387         m_documentationItemData = new DocumentationQuickOpenProvider;
0388         const QStringList scopes(Strings::scopeIncludesName());
0389         const QStringList types(Strings::typeDocumentationName());
0390         m_model->registerProvider(scopes, types, m_documentationItemData);
0391     }
0392     {
0393         m_actionsItemData = new ActionsQuickOpenProvider;
0394         const QStringList scopes(Strings::scopeIncludesName());
0395         const QStringList types(Strings::typeActionsName());
0396         m_model->registerProvider(scopes, types, m_actionsItemData);
0397     }
0398 }
0399 
0400 QuickOpenPlugin::~QuickOpenPlugin()
0401 {
0402     freeModel();
0403 
0404     delete m_model;
0405     delete m_projectFileData;
0406     delete m_projectItemData;
0407     delete m_openFilesData;
0408     delete m_documentationItemData;
0409     delete m_actionsItemData;
0410 }
0411 
0412 void QuickOpenPlugin::unload()
0413 {
0414 }
0415 
0416 ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context, QWidget* parent)
0417 {
0418     KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent);
0419 
0420     auto* codeContext = dynamic_cast<KDevelop::DeclarationContext*>(context);
0421 
0422     if (!codeContext) {
0423         return menuExt;
0424     }
0425 
0426     DUChainReadLocker readLock;
0427     Declaration* decl(codeContext->declaration().data());
0428 
0429     if (decl) {
0430         const bool isDef = FunctionDefinition::definition(decl);
0431         if (codeContext->use().isValid() || !isDef) {
0432             menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDeclaration);
0433         }
0434 
0435         if (isDef) {
0436             menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDefinition);
0437         }
0438     }
0439 
0440     return menuExt;
0441 }
0442 
0443 void QuickOpenPlugin::showQuickOpen(const QStringList& items)
0444 {
0445     if (!freeModel()) {
0446         return;
0447     }
0448 
0449     QStringList initialItems = items;
0450 
0451     QStringList useScopes = lastUsedScopes;
0452 
0453     const QString scopeCurrentlyOpenName = Strings::scopeCurrentlyOpenName();
0454     if (!useScopes.contains(scopeCurrentlyOpenName)) {
0455         useScopes << scopeCurrentlyOpenName;
0456     }
0457 
0458     showQuickOpenWidget(initialItems, useScopes, false);
0459 }
0460 
0461 void QuickOpenPlugin::showQuickOpen(ModelTypes modes)
0462 {
0463     if (!freeModel()) {
0464         return;
0465     }
0466 
0467     QStringList initialItems;
0468     if (modes & Files || modes & OpenFiles) {
0469         initialItems << Strings::typeFilesName();
0470     }
0471 
0472     if (modes & Functions) {
0473         initialItems << Strings::typeFunctionsName();
0474     }
0475 
0476     if (modes & Classes) {
0477         initialItems << Strings::typeClassesName();
0478     }
0479 
0480     QStringList useScopes;
0481     if (modes != OpenFiles) {
0482         useScopes = lastUsedScopes;
0483     }
0484 
0485     if ((modes & OpenFiles)) {
0486         const QString currentlyOpen = Strings::scopeCurrentlyOpenName();
0487         if (!useScopes.contains(currentlyOpen)) {
0488             useScopes << currentlyOpen;
0489         }
0490     }
0491 
0492     bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All);
0493     showQuickOpenWidget(initialItems, useScopes, preselectText);
0494 }
0495 
0496 void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText)
0497 {
0498     auto* dialog = new QuickOpenWidgetDialog(i18nc("@title:window", "Quick Open"), m_model, items, scopes);
0499     m_currentWidgetHandler = dialog;
0500     if (preselectText) {
0501         KDevelop::IDocument* currentDoc = core()->documentController()->activeDocument();
0502         if (currentDoc && currentDoc->textDocument()) {
0503             QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection());
0504             dialog->widget()->setPreselectedText(preselected);
0505         }
0506     }
0507 
0508     connect(dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes);
0509     //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes
0510     //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) );
0511     dialog->widget()->ui.itemsButton->setEnabled(false);
0512 
0513     if (quickOpenLine()) {
0514         quickOpenLine()->showWithWidget(dialog->widget());
0515         dialog->deleteLater();
0516     } else {
0517         dialog->run();
0518     }
0519 }
0520 
0521 void QuickOpenPlugin::storeScopes(const QStringList& scopes)
0522 {
0523     lastUsedScopes = scopes;
0524     KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen");
0525     grp.writeEntry("SelectedScopes", scopes);
0526 }
0527 
0528 void QuickOpenPlugin::storeItems(const QStringList& items)
0529 {
0530     lastUsedItems = items;
0531     KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen");
0532     grp.writeEntry("SelectedItems", items);
0533 }
0534 
0535 void QuickOpenPlugin::quickOpen()
0536 {
0537     if (quickOpenLine()) { //Same as clicking on Quick Open
0538         quickOpenLine()->setFocus();
0539     } else {
0540         showQuickOpen(All);
0541     }
0542 }
0543 
0544 void QuickOpenPlugin::quickOpenFile()
0545 {
0546     showQuickOpen(( ModelTypes )(Files | OpenFiles));
0547 }
0548 
0549 void QuickOpenPlugin::quickOpenFunction()
0550 {
0551     showQuickOpen(Functions);
0552 }
0553 
0554 void QuickOpenPlugin::quickOpenClass()
0555 {
0556     showQuickOpen(Classes);
0557 }
0558 
0559 void QuickOpenPlugin::quickOpenOpenFile()
0560 {
0561     showQuickOpen(OpenFiles);
0562 }
0563 
0564 void QuickOpenPlugin::quickOpenDocumentation()
0565 {
0566     const QStringList scopes(Strings::scopeIncludesName());
0567     const QStringList types(Strings::typeDocumentationName());
0568     showQuickOpenWidget(types, scopes, true);
0569 }
0570 
0571 void QuickOpenPlugin::quickOpenActions()
0572 {
0573     const QStringList scopes(Strings::scopeIncludesName());
0574     const QStringList types(Strings::typeActionsName());
0575     showQuickOpenWidget(types, scopes, true);
0576 }
0577 
0578 QSet<KDevelop::IndexedString> QuickOpenPlugin::fileSet() const
0579 {
0580     return m_model->fileSet();
0581 }
0582 
0583 void QuickOpenPlugin::registerProvider(const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider)
0584 {
0585     m_model->registerProvider(scope, type, provider);
0586 }
0587 
0588 bool QuickOpenPlugin::removeProvider(KDevelop::QuickOpenDataProviderBase* provider)
0589 {
0590     m_model->removeProvider(provider);
0591     return true;
0592 }
0593 
0594 void QuickOpenPlugin::quickOpenDeclaration()
0595 {
0596     if (jumpToSpecialObject()) {
0597         return;
0598     }
0599 
0600     KDevelop::DUChainReadLocker lock(DUChain::lock());
0601     Declaration* decl = cursorDeclaration();
0602 
0603     if (!decl) {
0604         qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump";
0605         return;
0606     }
0607     decl->activateSpecialization();
0608 
0609     IndexedString u = decl->url();
0610     KTextEditor::Cursor c = decl->rangeInCurrentRevision().start();
0611 
0612     if (u.isEmpty()) {
0613         qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString();
0614         return;
0615     }
0616 
0617     lock.unlock();
0618     core()->documentController()->openDocument(u.toUrl(), c);
0619 }
0620 
0621 QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const
0622 {
0623     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0624     if (!view) {
0625         return nullptr;
0626     }
0627 
0628     QUrl url = ICore::self()->documentController()->activeDocument()->url();
0629 
0630     const auto languages = ICore::self()->languageController()->languagesForUrl(url);
0631     for (const auto language : languages) {
0632         QWidget* w = language->specialLanguageObjectNavigationWidget(url, view->cursorPosition()).first;
0633         if (w) {
0634             return w;
0635         }
0636     }
0637 
0638     return nullptr;
0639 }
0640 
0641 QPair<QUrl, KTextEditor::Cursor> QuickOpenPlugin::specialObjectJumpPosition() const
0642 {
0643     KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView();
0644     if (!view) {
0645         return qMakePair(QUrl(), KTextEditor::Cursor());
0646     }
0647 
0648     QUrl url = ICore::self()->documentController()->activeDocument()->url();
0649     const auto languages = ICore::self()->languageController()->languagesForUrl(url);
0650     for (const auto language : languages) {
0651         QPair<QUrl, KTextEditor::Cursor> pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition()));
0652         if (pos.second.isValid()) {
0653             return pos;
0654         }
0655     }
0656 
0657     return qMakePair(QUrl(), KTextEditor::Cursor::invalid());
0658 }
0659 
0660 bool QuickOpenPlugin::jumpToSpecialObject()
0661 {
0662     QPair<QUrl, KTextEditor::Cursor> pos = specialObjectJumpPosition();
0663     if (pos.second.isValid()) {
0664         if (pos.first.isEmpty()) {
0665             qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object";
0666             return false;
0667         }
0668 
0669         ICore::self()->documentController()->openDocument(pos.first, pos.second);
0670         return true;
0671     }
0672     return false;
0673 }
0674 
0675 void QuickOpenPlugin::quickOpenDefinition()
0676 {
0677     if (jumpToSpecialObject()) {
0678         return;
0679     }
0680 
0681     KDevelop::DUChainReadLocker lock(DUChain::lock());
0682     Declaration* decl = cursorDeclaration();
0683 
0684     if (!decl) {
0685         qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump";
0686         return;
0687     }
0688 
0689     IndexedString u = decl->url();
0690     KTextEditor::Cursor c = decl->rangeInCurrentRevision().start();
0691     if (auto* def = FunctionDefinition::definition(decl)) {
0692         def->activateSpecialization();
0693         u = def->url();
0694         c = def->rangeInCurrentRevision().start();
0695     } else {
0696         qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration";
0697         decl->activateSpecialization();
0698     }
0699 
0700     if (u.isEmpty()) {
0701         qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString();
0702         return;
0703     }
0704 
0705     lock.unlock();
0706     core()->documentController()->openDocument(u.toUrl(), c);
0707 }
0708 
0709 bool QuickOpenPlugin::freeModel()
0710 {
0711     if (m_currentWidgetHandler) {
0712         delete m_currentWidgetHandler;
0713     }
0714     m_currentWidgetHandler = nullptr;
0715 
0716     return true;
0717 }
0718 
0719 void QuickOpenPlugin::nextFunction()
0720 {
0721     jumpToNearestFunction(NextFunction);
0722 }
0723 
0724 void QuickOpenPlugin::previousFunction()
0725 {
0726     jumpToNearestFunction(PreviousFunction);
0727 }
0728 
0729 void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction)
0730 {
0731     IDocument* doc = ICore::self()->documentController()->activeDocument();
0732     if (!doc) {
0733         qCDebug(PLUGIN_QUICKOPEN) << "No active document";
0734         return;
0735     }
0736 
0737     KDevelop::DUChainReadLocker lock(DUChain::lock());
0738 
0739     TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url());
0740 
0741     if (!context) {
0742         qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context";
0743         return;
0744     }
0745 
0746     QVector<DUChainItem> items;
0747     OutlineFilter filter(items, OutlineFilter::Functions);
0748     DUChainUtils::collectItems(context, filter);
0749 
0750     CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition()));
0751     if (!cursor.isValid()) {
0752         return;
0753     }
0754 
0755     Declaration* nearestDeclBefore = nullptr;
0756     int distanceBefore = INT_MIN;
0757     Declaration* nearestDeclAfter = nullptr;
0758     int distanceAfter = INT_MAX;
0759 
0760     for (auto& item : qAsConst(items)) {
0761         Declaration* decl = item.m_item.data();
0762 
0763         int distance = decl->range().start.line - cursor.line;
0764         if (distance < 0 && distance >= distanceBefore) {
0765             distanceBefore = distance;
0766             nearestDeclBefore = decl;
0767         } else if (distance > 0 && distance <= distanceAfter) {
0768             distanceAfter = distance;
0769             nearestDeclAfter = decl;
0770         }
0771     }
0772 
0773     CursorInRevision c = CursorInRevision::invalid();
0774     if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) {
0775         c = nearestDeclAfter->range().start;
0776     } else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) {
0777         c = nearestDeclBefore->range().start;
0778     }
0779 
0780     KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid();
0781     if (c.isValid()) {
0782         textCursor = context->transformFromLocalRevision(c);
0783     }
0784 
0785     lock.unlock();
0786     if (textCursor.isValid()) {
0787         core()->documentController()->openDocument(doc->url(), textCursor);
0788     } else {
0789         qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to";
0790     }
0791 }
0792 
0793 struct CreateOutlineDialog
0794 {
0795     void start()
0796     {
0797         if (!QuickOpenPlugin::self()->freeModel()) {
0798             return;
0799         }
0800 
0801         IDocument* doc = ICore::self()->documentController()->activeDocument();
0802         if (!doc) {
0803             qCDebug(PLUGIN_QUICKOPEN) << "No active document";
0804             return;
0805         }
0806 
0807         DUChainReadLocker lock;
0808 
0809         TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url());
0810 
0811         if (!context) {
0812             qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context";
0813             return;
0814         }
0815 
0816         model = new QuickOpenModel(nullptr);
0817 
0818         OutlineFilter filter(items);
0819 
0820         DUChainUtils::collectItems(context, filter);
0821 
0822         if (noHtmlDestriptionInOutline) {
0823             for (auto& item : items) {
0824                 item.m_noHtmlDestription = true;
0825             }
0826         }
0827 
0828         cursorDecl = cursorContextDeclaration();
0829 
0830         model->registerProvider(QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true));
0831 
0832         dialog = new QuickOpenWidgetDialog(i18nc("@title:window", "Outline"), model, QStringList(), QStringList(), true);
0833         dialog->widget()->setSortingEnabled(true);
0834 
0835         model->setParent(dialog->widget());
0836     }
0837     void finish()
0838     {
0839         //Select the declaration that contains the cursor
0840         if (cursorDecl.isValid() && dialog) {
0841             auto it = std::find_if(items.constBegin(), items.constEnd(),
0842                                    [this](const DUChainItem& item) { return item.m_item == cursorDecl; });
0843             if (it != items.constEnd()) {
0844                 // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect,
0845                 // apparently because the widget internals aren't initialized yet properly (although we've
0846                 // already called 'widget->show()'.
0847                 auto list = dialog->widget()->ui.list;
0848                 const auto num = std::distance(items.constBegin(), it);
0849                 QTimer::singleShot(0, list, [list, num]() {
0850                     const auto index = list->model()->index(num, 0, {});
0851                     list->setCurrentIndex(index);
0852                     list->scrollTo(index, QAbstractItemView::PositionAtCenter);
0853                 });
0854             }
0855         }
0856     }
0857     QPointer<QuickOpenWidgetDialog> dialog;
0858     IndexedDeclaration cursorDecl;
0859     QVector<DUChainItem> items;
0860     QuickOpenModel* model = nullptr;
0861 };
0862 
0863 class OutlineQuickopenWidgetCreator
0864     : public QuickOpenWidgetCreator
0865 {
0866 public:
0867     OutlineQuickopenWidgetCreator(const QStringList& /*scopes*/, const QStringList& /*items*/) : m_creator(nullptr)
0868     {
0869     }
0870 
0871     ~OutlineQuickopenWidgetCreator() override
0872     {
0873         delete m_creator;
0874     }
0875 
0876     QuickOpenWidget* createWidget() override
0877     {
0878         delete m_creator;
0879         m_creator = new CreateOutlineDialog;
0880         m_creator->start();
0881 
0882         if (!m_creator->dialog) {
0883             return nullptr;
0884         }
0885 
0886         m_creator->dialog->deleteLater();
0887         return m_creator->dialog->widget();
0888     }
0889 
0890     void widgetShown() override
0891     {
0892         if (m_creator) {
0893             m_creator->finish();
0894             delete m_creator;
0895             m_creator = nullptr;
0896         }
0897     }
0898 
0899     QString objectNameForLine() override
0900     {
0901         return QStringLiteral("Outline");
0902     }
0903 
0904     CreateOutlineDialog* m_creator;
0905 };
0906 
0907 void QuickOpenPlugin::quickOpenNavigateFunctions()
0908 {
0909     CreateOutlineDialog create;
0910     create.start();
0911 
0912     if (!create.dialog) {
0913         return;
0914     }
0915 
0916     m_currentWidgetHandler = create.dialog;
0917 
0918     QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline"));
0919     if (!line) {
0920         line  = quickOpenLine();
0921     }
0922 
0923     if (line) {
0924         line->showWithWidget(create.dialog->widget());
0925         create.dialog->deleteLater();
0926     } else {
0927         create.dialog->run();
0928     }
0929 
0930     create.finish();
0931 }
0932 
0933 QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr)
0934     , m_forceUpdate(false)
0935     , m_widgetCreator(creator)
0936 {
0937     setFont(qApp->font("QToolButton"));
0938     setMinimumWidth(200);
0939     setMaximumWidth(400);
0940 
0941     deactivate();
0942     setPlaceholderText(i18nc("@info:placeholder", "Quick Open..."));
0943     setToolTip(i18nc("@info:tooltip", "Search for files, classes, functions and more,"
0944                     " allowing you to quickly navigate in your source code."));
0945     setObjectName(m_widgetCreator->objectNameForLine());
0946     setFocusPolicy(Qt::ClickFocus);
0947 }
0948 
0949 QuickOpenLineEdit::~QuickOpenLineEdit()
0950 {
0951     delete m_widget;
0952     delete m_widgetCreator;
0953 }
0954 
0955 bool QuickOpenLineEdit::insideThis(QObject* object)
0956 {
0957     while (object) {
0958         qCDebug(PLUGIN_QUICKOPEN) << object;
0959         if (object == this || object == m_widget) {
0960             return true;
0961         }
0962         object = object->parent();
0963     }
0964     return false;
0965 }
0966 
0967 void QuickOpenLineEdit::widgetDestroyed(QObject* obj)
0968 {
0969     Q_UNUSED(obj);
0970     // need to use a queued connection here, because this function is called in ~QWidget!
0971     // => QuickOpenWidget instance is half-destructed => connections are not yet cleared
0972     // => clear() will trigger signals which will operate on the invalid QuickOpenWidget
0973     // So, just wait until properly destructed
0974     QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection);
0975 }
0976 
0977 void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget)
0978 {
0979     connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed);
0980     qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget;
0981     deactivate();
0982     if (m_widget) {
0983         qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget;
0984         delete m_widget;
0985     }
0986     m_widget = widget;
0987     m_forceUpdate = true;
0988     setFocus();
0989 }
0990 
0991 void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev)
0992 {
0993     QLineEdit::focusInEvent(ev);
0994 //       delete m_widget;
0995     qCDebug(PLUGIN_QUICKOPEN) << "got focus";
0996     qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate;
0997     if (m_widget && !m_forceUpdate) {
0998         return;
0999     }
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 
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 
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 }
1114 void QuickOpenLineEdit::activate()
1115 {
1116     qCDebug(PLUGIN_QUICKOPEN) << "activating";
1117     setText(QString());
1118     setStyleSheet(QString());
1119     qApp->installEventFilter(this);
1120 }
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 
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 
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 #include "moc_quickopenplugin.cpp"