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"