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