diff --git a/kdevplatform/language/interfaces/ilanguagesupport.cpp b/kdevplatform/language/interfaces/ilanguagesupport.cpp index 0cf6352f2c..d61b293a3e 100644 --- a/kdevplatform/language/interfaces/ilanguagesupport.cpp +++ b/kdevplatform/language/interfaces/ilanguagesupport.cpp @@ -1,105 +1,106 @@ /*************************************************************************** * Copyright 2008 David Nolden * * Copyright 2014 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ilanguagesupport.h" #include "../duchain/duchain.h" #include namespace KDevelop { class ILanguageSupportPrivate { public: mutable QReadWriteLock lock; }; ILanguageSupport::ILanguageSupport() : d(new ILanguageSupportPrivate) { } ILanguageSupport::~ILanguageSupport() { } TopDUContext* ILanguageSupport::standardContext(const QUrl& url, bool proxyContext) { Q_UNUSED(proxyContext) return DUChain::self()->chainForDocument(url); } KTextEditor::Range ILanguageSupport::specialLanguageObjectRange(const QUrl& url, const KTextEditor::Cursor& position) { Q_UNUSED(url) Q_UNUSED(position) return KTextEditor::Range::invalid(); } QPair ILanguageSupport::specialLanguageObjectJumpCursor(const QUrl& url, const KTextEditor::Cursor& position) { Q_UNUSED(url) Q_UNUSED(position) return QPair(QUrl(), KTextEditor::Cursor::invalid()); } -QWidget* ILanguageSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { +QPair ILanguageSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) +{ Q_UNUSED(url) Q_UNUSED(position) - return nullptr; + return {nullptr, KTextEditor::Range::invalid()}; } ICodeHighlighting* ILanguageSupport::codeHighlighting() const { return nullptr; } BasicRefactoring* ILanguageSupport::refactoring() const { return nullptr; } ICreateClassHelper* ILanguageSupport::createClassHelper() const { return nullptr; } SourceFormatterItemList ILanguageSupport::sourceFormatterItems() const { return SourceFormatterItemList(); } QString ILanguageSupport::indentationSample() const { return QString(); } QReadWriteLock* ILanguageSupport::parseLock() const { return &d->lock; } int ILanguageSupport::suggestedReparseDelayForChange(KTextEditor::Document* doc, const KTextEditor::Range& changedRange, const QString& /*removedText*/, bool /*removal*/) const { auto text = doc->text(changedRange); bool joinedWord = doc->wordRangeAt(changedRange.start()).isEmpty() || doc->wordRangeAt(changedRange.end()).isEmpty(); auto isWhitespace = std::all_of(text.begin(), text.end(), [](const QChar& c) { return c.isSpace(); }); return (isWhitespace && !joinedWord) ? NoUpdateRequired : DefaultDelay; } } diff --git a/kdevplatform/language/interfaces/ilanguagesupport.h b/kdevplatform/language/interfaces/ilanguagesupport.h index d914f0c9bb..53cac5a669 100644 --- a/kdevplatform/language/interfaces/ilanguagesupport.h +++ b/kdevplatform/language/interfaces/ilanguagesupport.h @@ -1,171 +1,172 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * Copyright 2014 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_ILANGUAGESUPPORT_H #define KDEVPLATFORM_ILANGUAGESUPPORT_H #include #include #include #include "interfaces/isourceformatter.h" class QReadWriteLock; namespace KTextEditor { class Cursor; class Range; class Document; } namespace KDevelop { class BasicRefactoring; class IndexedString; class ParseJob; class TopDUContext; class ICodeHighlighting; class ICreateClassHelper; class KDEVPLATFORMLANGUAGE_EXPORT ILanguageSupport { public: ILanguageSupport(); virtual ~ILanguageSupport(); /** @return the name of the language.*/ virtual QString name() const = 0; /** @return the parse job that is used by background parser to parse given @p url.*/ virtual ParseJob *createParseJob(const IndexedString &url) = 0; /** * Only important for languages that can parse multiple different versions of a file, like C++ due to the preprocessor. * The default-implementation for other languages is "return DUChain::chainForDocument(url);" * * @param proxyContext Whether the returned context should be a proxy-contexts. In C++, a proxy-contexts has no direct content. * It mainly just imports an actual content-context, and it holds all the imports. It can also represent * multiple different versions of the same content in the eyes of the preprocessor. Also, a proxy-context may contain the problem- * descriptions of preprocessor problems. * The proxy-context should be preferred whenever the problem-list is required, or for deciding whether a document needs to be updated * (only the proxy-context knows about all the dependencies, since it contains most of the imports) * * @warning The DUChain must be locked before calling this, @see KDevelop::DUChainReadLocker * * @return the standard context used by this language for the given @p url **/ virtual TopDUContext *standardContext(const QUrl& url, bool proxyContext = false); /** * Should return a code-highlighting instance for this language, or zero. */ virtual ICodeHighlighting* codeHighlighting() const; /** * Should return a BasicRefactoring instance that controls the language-agnostic refactoring rules, or zero */ virtual BasicRefactoring* refactoring() const; /** * Should return a class creating helper for this language, or zero. * * If zero is returned, a default class helper will be created. * Reimplementing this method is therefore not necessary to have classes created in this language. * */ virtual ICreateClassHelper* createClassHelper() const; /** * Every thread that does background-parsing should read-lock its language's parse-mutex while parsing. * Any other thread may write-lock the parse-mutex in order to wait for all parsing-threads to finish the parsing. * The parse-mutex only needs to be locked while working on the du-chain, not while preprocessing or reading. * Tip: use QReadLocker for read-locking. * The duchain must always be unlocked when you try to lock a parseLock! */ virtual QReadWriteLock* parseLock() const; /** * The following functions are used to allow navigation-features, tooltips, etc. for non-duchain language objects. * In C++, they are used to allow highlighting and navigation of macro-uses. * */ /**Should return the local range within the given url that belongs to the *special language-object that contains @p position , or (QUrl(), KTextEditor::Range:invalid()) */ virtual KTextEditor::Range specialLanguageObjectRange(const QUrl& url, const KTextEditor::Cursor& position); /**Should return the source-range and source-document that the *special language-object that contains @p position refers to, or KTextEditor::Range:invalid(). */ virtual QPair specialLanguageObjectJumpCursor(const QUrl& url, const KTextEditor::Cursor& position); /**Should return a navigation-widget for the - *special language-object that contains @p position refers, or 0. + *special language-object that contains @p position refers to as well as the range the object takes there, + *or nullptr and an invalid range. *If you setProperty("DoNotCloseOnCursorMove", true) on the widget returned, *then the widget will not close when the cursor moves in the document, which *enables you to change the document contents from the widget without immediately closing the widget.*/ - virtual QWidget* specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position); + virtual QPair specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position); /**Should return a tiny piece of code which makes it possible for KDevelop to derive the indentation *settings from an automatic source formatter. Example for C++: "class C{\n class D {\n void c() {\n int m;\n }\n }\n};\n" *The sample must be completely unindented (no line must start with leading whitespace), *and it must contain at least 4 indentation levels! *The default implementation returns an empty string.*/ virtual QString indentationSample() const; /** * Can return a list of source formatting items for this language. * For example, if your language wants to use the CustomScript engine with * a specific executable, return an item with "customscript" as the engine * and a style describing your options as the style (in this case, especially * the command to execute in the "content" member). * Multiple items can be returned. Make sure to set the mime type(s) of your language * on the returned items. */ virtual SourceFormatterItemList sourceFormatterItems() const; enum ReparseDelaySpecialValues { DefaultDelay = -1, NoUpdateRequired = -2 }; /** * @brief Enables the language to control how long the background parser waits until a changed document is reparsed. * * You can return DefaultDelay to use the default delay, or NoUpdateRequired to indicate that * this change does not require a re-parse at all. * * The default implementation returns DefaultDelay if the change was not whitespace-only, * and NoUpdateRequired otherwise. * * @param doc the document which was modified * @param changedRange the range which was modified * @param changedText the text which was inserted or removed * @param removal whether text was removed or inserted * @return int duration in ms to wait until re-parsing or a value of the ReparseDelaySpecialValues enum. */ virtual int suggestedReparseDelayForChange(KTextEditor::Document* doc, const KTextEditor::Range& changedRange, const QString& changedText, bool removal) const; private: const QScopedPointer d; }; } Q_DECLARE_INTERFACE( KDevelop::ILanguageSupport, "org.kdevelop.ILanguageSupport") #endif diff --git a/plugins/clang/clangsupport.cpp b/plugins/clang/clangsupport.cpp index f632678064..99e6a0720f 100644 --- a/plugins/clang/clangsupport.cpp +++ b/plugins/clang/clangsupport.cpp @@ -1,442 +1,445 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangsupport.h" #include "clangparsejob.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include "codecompletion/model.h" #include "clanghighlighting.h" #include #include #include #include #include "codegen/clangrefactoring.h" #include "codegen/clangclasshelper.h" #include "codegen/adaptsignatureassistant.h" #include "duchain/documentfinderhelpers.h" #include "duchain/navigationwidget.h" #include "duchain/clangindex.h" #include "duchain/clanghelpers.h" #include "duchain/macrodefinition.h" #include "duchain/clangparsingenvironmentfile.h" #include "duchain/duchainutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "clangsettings/sessionsettings/sessionsettings.h" #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevClangSupportFactory, "kdevclangsupport.json", registerPlugin(); ) using namespace KDevelop; namespace { QPair lineInDocument(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc || !doc->textDocument() || !ICore::self()->documentController()->activeTextDocumentView()) { return {}; } const int lineNumber = position.line(); const int lineLength = doc->textDocument()->lineLength(lineNumber); KTextEditor::Range range(lineNumber, 0, lineNumber, lineLength); QString line = doc->textDocument()->text(range); return {line, range}; } QPair importedContextForPosition(const QUrl &url, const KTextEditor::Cursor& position) { auto pair = lineInDocument(url, position); const QString line = pair.first; if (line.isEmpty()) return {{}, KTextEditor::Range::invalid()}; KTextEditor::Range wordRange = ClangUtils::rangeForIncludePathSpec(line, pair.second); if (!wordRange.isValid() || !wordRange.contains(position)) { return {{}, KTextEditor::Range::invalid()}; } // Since this is called by the editor while editing, use a fast timeout so the editor stays responsive DUChainReadLocker lock(nullptr, 100); if (!lock.locked()) { clangDebug() << "Failed to lock the du-chain in time"; return {TopDUContextPointer(), KTextEditor::Range::invalid()}; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (line.isEmpty() || !topContext || !topContext->parsingEnvironmentFile()) { return {TopDUContextPointer(), KTextEditor::Range::invalid()}; } // It's an #include, find out which file was included at the given line foreach(const DUContext::Import &imported, topContext->importedParentContexts()) { auto context = imported.context(nullptr); if (context) { if(topContext->transformFromLocalRevision(topContext->importPosition(context)).line() == wordRange.start().line()) { if (auto importedTop = dynamic_cast(context)) return {TopDUContextPointer(importedTop), wordRange}; } } } // The last resort. Check if the file is already included (maybe recursively from another files). // This is needed as clang doesn't visit (clang_getInclusions) those inclusions. // TODO: Maybe create an assistant that'll report whether the file is already included? auto includeName = line.mid(wordRange.start().column(), wordRange.end().column() - wordRange.start().column()); if (!includeName.isEmpty()) { if (includeName.startsWith(QLatin1Char('.'))) { const Path dir = Path(url).parent(); includeName = Path(dir, includeName).toLocalFile(); } const auto recursiveImports = topContext->recursiveImportIndices(); auto iterator = recursiveImports.iterator(); while (iterator) { const auto str = (*iterator).url().str(); if (str == includeName || (str.endsWith(includeName) && str[str.size()-includeName.size()-1] == QLatin1Char('/'))) { return {TopDUContextPointer((*iterator).data()), wordRange}; } ++iterator; } } return {{}, KTextEditor::Range::invalid()}; } QPair macroExpansionForPosition(const QUrl &url, const KTextEditor::Cursor& position) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (topContext) { int useAt = topContext->findUseAt(topContext->transformToLocalRevision(position)); if (useAt >= 0) { Use use = topContext->uses()[useAt]; if (dynamic_cast(use.usedDeclaration(topContext))) { return {TopDUContextPointer(topContext), use}; } } } return {{}, Use()}; } } ClangSupport::ClangSupport(QObject* parent, const QVariantList& ) : IPlugin( QStringLiteral("kdevclangsupport"), parent ) , ILanguageSupport() , m_highlighting(nullptr) , m_refactoring(nullptr) , m_index(nullptr) { clangDebug() << "Detected Clang version:" << ClangHelpers::clangVersion(); { const auto builtinDir = ClangHelpers::clangBuiltinIncludePath(); const auto headerToCheck = QLatin1String("cpuid.h"); if (!QFile::exists(builtinDir + QLatin1Char('/') + headerToCheck)) { setErrorDescription(i18n("The clang builtin include path \"%1\" is invalid (missing %2 header).\n" "Try setting the KDEV_CLANG_BUILTIN_DIR environment variable manually to fix this.\n" "See also: https://bugs.kde.org/show_bug.cgi?id=393779", builtinDir, headerToCheck)); return; } } setXMLFile( QStringLiteral("kdevclangsupport.rc") ); ClangIntegration::DUChainUtils::registerDUChainItems(); m_highlighting = new ClangHighlighting(this); m_refactoring = new ClangRefactoring(this); m_index.reset(new ClangIndex); auto model = new KDevelop::CodeCompletion( this, new ClangCodeCompletionModel(m_index.data(), this), name() ); connect(model, &CodeCompletion::registeredToView, this, &ClangSupport::disableKeywordCompletion); connect(model, &CodeCompletion::unregisteredFromView, this, &ClangSupport::enableKeywordCompletion); const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList(); for (const auto& type : mimeTypes) { KDevelop::IBuddyDocumentFinder::addFinder(type, this); } auto assistantsManager = core()->languageController()->staticAssistantsManager(); assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this))); assistantsManager->registerAssistant(StaticAssistant::Ptr(new AdaptSignatureAssistant(this))); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ClangSupport::documentActivated); } ClangSupport::~ClangSupport() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); const auto& mimeTypes = DocumentFinderHelpers::mimeTypesList(); for (const auto& type : mimeTypes) { KDevelop::IBuddyDocumentFinder::removeFinder(type); } ClangIntegration::DUChainUtils::unregisterDUChainItems(); } KDevelop::ConfigPage* ClangSupport::configPage(int number, QWidget* parent) { return number == 0 ? new SessionSettings(parent) : nullptr; } int ClangSupport::configPages() const { return 1; } ParseJob* ClangSupport::createParseJob(const IndexedString& url) { return new ClangParseJob(url, this); } QString ClangSupport::name() const { return QStringLiteral("clang"); } ICodeHighlighting* ClangSupport::codeHighlighting() const { return m_highlighting; } BasicRefactoring* ClangSupport::refactoring() const { return m_refactoring; } ICreateClassHelper* ClangSupport::createClassHelper() const { return new ClangClassHelper; } ClangIndex* ClangSupport::index() { return m_index.data(); } bool ClangSupport::areBuddies(const QUrl &url1, const QUrl& url2) { return DocumentFinderHelpers::areBuddies(url1, url2); } bool ClangSupport::buddyOrder(const QUrl &url1, const QUrl& url2) { return DocumentFinderHelpers::buddyOrder(url1, url2); } QVector ClangSupport::potentialBuddies(const QUrl& url) const { return DocumentFinderHelpers::potentialBuddies(url); } void ClangSupport::createActionsForMainWindow (Sublime::MainWindow* /*window*/, QString& _xmlFile, KActionCollection& actions) { _xmlFile = xmlFile(); QAction* renameDeclarationAction = actions.addAction(QStringLiteral("code_rename_declaration")); renameDeclarationAction->setText( i18n("Rename Declaration") ); renameDeclarationAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R); connect(renameDeclarationAction, &QAction::triggered, m_refactoring, &ClangRefactoring::executeRenameAction); QAction* moveIntoSourceAction = actions.addAction(QStringLiteral("code_move_definition")); moveIntoSourceAction->setText(i18n("Move into Source")); actions.setDefaultShortcut(moveIntoSourceAction, Qt::CTRL | Qt::ALT | Qt::Key_S); connect(moveIntoSourceAction, &QAction::triggered, m_refactoring, &ClangRefactoring::executeMoveIntoSourceAction); } KDevelop::ContextMenuExtension ClangSupport::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { ContextMenuExtension cm; EditorContext *ec = dynamic_cast(context); if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) { // It's a C++ file, let's add our context menu. m_refactoring->fillContextMenu(cm, context, parent); } return cm; } KTextEditor::Range ClangSupport::specialLanguageObjectRange(const QUrl &url, const KTextEditor::Cursor& position) { DUChainReadLocker lock; const QPair macroExpansion = macroExpansionForPosition(url, position); if (macroExpansion.first) { return macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range); } const QPair import = importedContextForPosition(url, position); if(import.first) { return import.second; } return KTextEditor::Range::invalid(); } QPair ClangSupport::specialLanguageObjectJumpCursor(const QUrl &url, const KTextEditor::Cursor& position) { const QPair import = importedContextForPosition(url, position); DUChainReadLocker lock; if (import.first) { return qMakePair(import.first->url().toUrl(), KTextEditor::Cursor(0,0)); } return {{}, KTextEditor::Cursor::invalid()}; } -QWidget* ClangSupport::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) +QPair ClangSupport::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { DUChainReadLocker lock; const QPair macroExpansion = macroExpansionForPosition(url, position); if (macroExpansion.first) { Declaration* declaration = macroExpansion.second.usedDeclaration(macroExpansion.first.data()); const MacroDefinition::Ptr macroDefinition(dynamic_cast(declaration)); Q_ASSERT(macroDefinition); auto rangeInRevision = macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range.start); - return new ClangNavigationWidget(macroDefinition, DocumentCursor(IndexedString(url), rangeInRevision)); + return { + new ClangNavigationWidget(macroDefinition, DocumentCursor(IndexedString(url), rangeInRevision)), + macroExpansion.second.m_range.castToSimpleRange() + }; } const QPair import = importedContextForPosition(url, position); if (import.first) { - return import.first->createNavigationWidget(); + return {import.first->createNavigationWidget(), import.second}; } - return nullptr; + return {nullptr, KTextEditor::Range::invalid()}; } TopDUContext* ClangSupport::standardContext(const QUrl &url, bool /*proxyContext*/) { ClangParsingEnvironment env; return DUChain::self()->chainForDocument(url, &env); } void ClangSupport::documentActivated(IDocument* doc) { TopDUContext::Features features; { DUChainReadLocker lock; auto ctx = DUChainUtils::standardContextForUrl(doc->url()); if (!ctx) { return; } auto file = ctx->parsingEnvironmentFile(); if (!file) { return; } if (file->type() != CppParsingEnvironment) { return; } if (file->needsUpdate()) { return; } features = ctx->features(); } const auto indexedUrl = IndexedString(doc->url()); auto sessionData = ClangIntegration::DUChainUtils::findParseSessionData(indexedUrl, index()->translationUnitForUrl(IndexedString(doc->url()))); if (sessionData) { return; } if ((features & TopDUContext::AllDeclarationsContextsAndUses) != TopDUContext::AllDeclarationsContextsAndUses) { // the file was parsed in simplified mode, we need to reparse it to get all data // now that its opened in the editor features = TopDUContext::AllDeclarationsContextsAndUses; } else { features = static_cast(ClangParseJob::AttachASTWithoutUpdating | features); if (ICore::self()->languageController()->backgroundParser()->isQueued(indexedUrl)) { // The document is already scheduled for parsing (happens when opening a project with an active document) // The background parser will optimize the previous request out, so we need to update highlighting features = static_cast(ClangParseJob::UpdateHighlighting | features); } } ICore::self()->languageController()->backgroundParser()->addDocument(indexedUrl, features); } static void setKeywordCompletion(KTextEditor::View* view, bool enabled) { if (auto config = qobject_cast(view)) { config->setConfigValue(QStringLiteral("keyword-completion"), enabled); } } int ClangSupport::suggestedReparseDelayForChange(KTextEditor::Document* /*doc*/, const KTextEditor::Range& /*changedRange*/, const QString& /*changedText*/, bool /*removal*/) const { return ILanguageSupport::DefaultDelay; } void ClangSupport::disableKeywordCompletion(KTextEditor::View* view) { setKeywordCompletion(view, false); } void ClangSupport::enableKeywordCompletion(KTextEditor::View* view) { setKeywordCompletion(view, true); } #include "clangsupport.moc" diff --git a/plugins/clang/clangsupport.h b/plugins/clang/clangsupport.h index daf0b71890..fe00aa488e 100644 --- a/plugins/clang/clangsupport.h +++ b/plugins/clang/clangsupport.h @@ -1,104 +1,105 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLANGSUPPORT_H #define KDEVCLANGSUPPORT_H #include #include #include #include class ClangIndex; class ClangRefactoring; namespace KDevelop { class IDocument; } namespace KTextEditor { class View; class Document; } class ClangSupport : public KDevelop::IPlugin, public KDevelop::ILanguageSupport, public KDevelop::IBuddyDocumentFinder { Q_OBJECT Q_INTERFACES(KDevelop::ILanguageSupport) public: explicit ClangSupport(QObject *parent, const QVariantList& args = QVariantList()); ~ClangSupport() override; /** Name Of the Language */ QString name() const override; /** Parsejob used by background parser to parse given url */ KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &url) override; /** the code highlighter */ KDevelop::ICodeHighlighting* codeHighlighting() const override; KDevelop::BasicRefactoring* refactoring() const override; KDevelop::ICreateClassHelper* createClassHelper() const override; void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; KTextEditor::Range specialLanguageObjectRange(const QUrl &url, const KTextEditor::Cursor& position) override; QPair specialLanguageObjectJumpCursor(const QUrl &url, const KTextEditor::Cursor& position) override; - QWidget* specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) override; + QPair specialLanguageObjectNavigationWidget(const QUrl& url, + const KTextEditor::Cursor& position) override; ClangIndex* index(); KDevelop::TopDUContext* standardContext(const QUrl &url, bool proxyContext = false) override; KDevelop::ConfigPage* configPage(int number, QWidget *parent) override; int configPages() const override; int suggestedReparseDelayForChange(KTextEditor::Document* doc, const KTextEditor::Range& changedRange, const QString& changedText, bool removal) const override; //BEGIN IBuddyDocumentFinder bool areBuddies(const QUrl &url1, const QUrl& url2) override; bool buddyOrder(const QUrl &url1, const QUrl& url2) override; QVector potentialBuddies(const QUrl& url) const override; //END IBuddyDocumentFinder private Q_SLOTS: void documentActivated(KDevelop::IDocument* doc); void disableKeywordCompletion(KTextEditor::View* view); void enableKeywordCompletion(KTextEditor::View* view); private: KDevelop::ICodeHighlighting *m_highlighting; ClangRefactoring *m_refactoring; QScopedPointer m_index; }; #endif diff --git a/plugins/cmake/cmakemanager.cpp b/plugins/cmake/cmakemanager.cpp index b30258bf1a..65b17bfade 100644 --- a/plugins/cmake/cmakemanager.cpp +++ b/plugins/cmake/cmakemanager.cpp @@ -1,1026 +1,1026 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2013 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "cmakemanager.h" #include "cmakeedit.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "duchain/cmakeparsejob.h" #include "cmakeimportjsonjob.h" #include "debug.h" #include "settings/cmakepreferences.h" #include "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.h" #include "cmakeserverimportjob.h" #include "cmakeserver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProject*) using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin(); ) const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support"); CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcmakemanager"), parent ) , m_filter( new ProjectFilterManager( this ) ) { if (CMake::findExecutable().isEmpty()) { setErrorDescription(i18n("Unable to find a CMake executable. Is one installed on the system?")); m_highlight = nullptr; return; } m_highlight = new KDevelop::CodeHighlighting(this); new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &CMakeManager::reloadProjects); connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); // m_fileSystemChangeTimer = new QTimer(this); // m_fileSystemChangeTimer->setSingleShot(true); // m_fileSystemChangeTimer->setInterval(100); // connect(m_fileSystemChangeTimer,SIGNAL(timeout()),SLOT(filesystemBuffererTimeout())); } CMakeManager::~CMakeManager() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); } bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const { return m_projects[item->project()].compilationData.files.contains(item->path()); } Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const { // CMakeFolderItem *fi=dynamic_cast(item); // Path ret; // ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent(); // if (parent) // ret=buildDirectory(parent); // else // ret=Path(CMake::currentBuildDir(item->project())); // // if(fi) // ret.addPath(fi->buildDir()); // return ret; return Path(CMake::currentBuildDir(item->project())); } KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) { CMake::checkForNeedingConfigure(project); return AbstractFileManagerPlugin::import(project); } class ChooseCMakeInterfaceJob : public ExecuteCompositeJob { Q_OBJECT public: ChooseCMakeInterfaceJob(IProject* project, CMakeManager* manager) : ExecuteCompositeJob(manager, {}) , project(project) , manager(manager) { } void start() override { server = new CMakeServer(project); connect(server, &CMakeServer::connected, this, &ChooseCMakeInterfaceJob::successfulConnection); connect(server, &CMakeServer::finished, this, &ChooseCMakeInterfaceJob::failedConnection); } private: void successfulConnection() { auto job = new CMakeServerImportJob(project, server, this); connect(job, &CMakeServerImportJob::result, this, [this, job](){ if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } void failedConnection(int code) { Q_ASSERT(code > 0); Q_ASSERT(!server->isServerAvailable()); server->deleteLater(); server = nullptr; // parse the JSON file CMakeImportJsonJob* job = new CMakeImportJsonJob(project, this); // create the JSON file if it doesn't exist auto commandsFile = CMake::commandsFile(project); if (!QFileInfo::exists(commandsFile.toLocalFile())) { qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; addSubjob(manager->builder()->configure(project)); } connect(job, &CMakeImportJsonJob::result, this, [this, job]() { if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } CMakeServer* server = nullptr; IProject* const project; CMakeManager* const manager; }; KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); auto job = new ChooseCMakeInterfaceJob(project, this); connect(job, &KJob::result, this, [this, job, project](){ if (job->error() != 0) { qCWarning(CMAKE) << "couldn't load project successfully" << project->name(); m_projects.remove(project); } }); const QList jobs = { job, KDevelop::AbstractFileManagerPlugin::createImportJob(item) // generate the file system listing }; Q_ASSERT(!jobs.contains(nullptr)); ExecuteCompositeJob* composite = new ExecuteCompositeJob(this, jobs); // even if the cmake call failed, we want to load the project so that the project can be worked on composite->setAbortOnError(false); return composite; } // QList CMakeManager::parse(ProjectFolderItem*) // { return QList< ProjectFolderItem* >(); } // // QList CMakeManager::targets() const { QList ret; foreach(IProject* p, m_projects.keys()) { ret+=p->projectItem()->targetList(); } return ret; } CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const { const auto & data = m_projects[item->project()].compilationData; QHash::const_iterator it = data.files.constFind(item->path()); if (it == data.files.constEnd()) { // if the item path contains a symlink, then we will not find it in the lookup table // as that only only stores canonicalized paths. Thus, we fallback to // to the canonicalized path and see if that brings up any matches const auto canonicalized = Path(QFileInfo(item->path().toLocalFile()).canonicalFilePath()); it = data.files.constFind(canonicalized); } if (it != data.files.constEnd()) { return *it; } else { // otherwise look for siblings and use the include paths of any we find const Path folder = item->folder() ? item->path() : item->path().parent(); for( it = data.files.constBegin(); it != data.files.constEnd(); ++it) { if (folder.isDirectParentOf(it.key())) { return *it; } } } // last-resort fallback: bubble up the parent chain, and keep looking for include paths if (auto parent = item->parent()) { return fileInformation(parent); } return {}; } Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).includes; } Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).frameworkDirectories; } QHash CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const { return fileInformation(item).defines; } QString CMakeManager::extraArguments(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).compileFlags; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevCMakeBuilder")); Q_ASSERT(i); KDevelop::IProjectBuilder* _builder = i->extension(); Q_ASSERT(_builder ); return _builder ; } bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) { qCDebug(CMAKE) << "reloading" << folder->path(); IProject* project = folder->project(); if (!project->isReady()) return false; KJob *job = createImportJob(folder); project->setReloadJob(job); ICore::self()->runController()->registerJob( job ); if (folder == project->projectItem()) { connect(job, &KJob::finished, this, [project](KJob* job) { if (job->error()) return; KDevelop::ICore::self()->projectController()->reparseProject(project, true); }); } return true; } static void populateTargets(ProjectFolderItem* folder, const QHash>& targets) { static QSet standardTargets = { QStringLiteral("edit_cache"), QStringLiteral("rebuild_cache"), QStringLiteral("list_install_components"), QStringLiteral("test"), //not really standard, but applicable for make and ninja QStringLiteral("install") }; QList dirTargets = kFilter>(targets[folder->path()], [](const CMakeTarget& target) -> bool { return target.type != CMakeTarget::Custom || (!target.name.endsWith(QLatin1String("_automoc")) && !target.name.endsWith(QLatin1String("_autogen")) && !standardTargets.contains(target.name) && !target.name.startsWith(QLatin1String("install/")) ); }); const auto tl = folder->targetList(); for (ProjectTargetItem* item : tl) { const auto idx = kIndexOf(dirTargets, [item](const CMakeTarget& target) { return target.name == item->text(); }); if (idx < 0) { delete item; } else { dirTargets.removeAt(idx); } } foreach (const auto& target, dirTargets) { switch(target.type) { case CMakeTarget::Executable: new CMakeTargetItem(folder, target.name, target.artifacts.value(0)); break; case CMakeTarget::Library: new ProjectLibraryTargetItem(folder->project(), target.name, folder); break; case CMakeTarget::Custom: new ProjectTargetItem(folder->project(), target.name, folder); break; } } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::integrateData(const CMakeProjectData &data, KDevelop::IProject* project) { if (data.m_server) { connect(data.m_server.data(), &CMakeServer::response, project, [this, project](const QJsonObject& response) { serverResponse(project, response); }); } else { connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); } m_projects[project] = data; populateTargets(project->projectItem(), data.targets); CTestUtils::createTestSuites(data.m_testSuites, data.targets, project); } void CMakeManager::serverResponse(KDevelop::IProject* project, const QJsonObject& response) { if (response[QStringLiteral("type")] == QLatin1String("signal")) { if (response[QStringLiteral("name")] == QLatin1String("dirty")) { m_projects[project].m_server->configure({}); } else qCDebug(CMAKE) << "unhandled signal response..." << project << response; } else if (response[QStringLiteral("type")] == QLatin1String("reply")) { const auto inReplyTo = response[QStringLiteral("inReplyTo")]; if (inReplyTo == QLatin1String("configure")) { m_projects[project].m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_projects[project].m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { auto &data = m_projects[project]; CMakeServerImportJob::processCodeModel(response, data); populateTargets(project->projectItem(), data.targets); } else { qCDebug(CMAKE) << "unhandled reply response..." << project << response; } } else { qCDebug(CMAKE) << "unhandled response..." << project << response; } } // void CMakeManager::deletedWatchedDirectory(IProject* p, const QUrl &dir) // { // if(p->folder().equals(dir, QUrl::CompareWithoutTrailingSlash)) { // ICore::self()->projectController()->closeProject(p); // } else { // if(dir.fileName()=="CMakeLists.txt") { // QList folders = p->foldersForUrl(dir.upUrl()); // foreach(ProjectFolderItem* folder, folders) // reload(folder); // } else { // qDeleteAll(p->itemsForUrl(dir)); // } // } // } // void CMakeManager::directoryChanged(const QString& dir) // { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // void CMakeManager::filesystemBuffererTimeout() // { // Q_FOREACH(const QString& file, m_fileSystemChangedBuffer) { // realDirectoryChanged(file); // } // m_fileSystemChangedBuffer.clear(); // } // void CMakeManager::realDirectoryChanged(const QString& dir) // { // QUrl path(dir); // IProject* p=ICore::self()->projectController()->findProjectForUrl(dir); // if(!p || !p->isReady()) { // if(p) { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // return; // } // // if(!QFile::exists(dir)) { // path.adjustPath(QUrl::AddTrailingSlash); // deletedWatchedDirectory(p, path); // } else // dirtyFile(dir); // } QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const { return folder->targetList(); } QString CMakeManager::name() const { return languageName().str(); } IndexedString CMakeManager::languageName() { static IndexedString name("CMake"); return name; } KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) { return new CMakeParseJob(url, this); } KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const { return m_highlight; } // ContextMenuExtension CMakeManager::contextMenuExtension( KDevelop::Context* context ) // { // if( context->type() != KDevelop::Context::ProjectItemContext ) // return IPlugin::contextMenuExtension( context ); // // KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); // QList items = ctx->items(); // // if( items.isEmpty() ) // return IPlugin::contextMenuExtension( context ); // // m_clickedItems = items; // ContextMenuExtension menuExt; // if(items.count()==1 && dynamic_cast(items.first())) // { // QAction * action = new QAction( i18n( "Jump to Target Definition" ), this ); // connect( action, SIGNAL(triggered()), this, SLOT(jumpToDeclaration()) ); // menuExt.addAction( ContextMenuExtension::ProjectGroup, action ); // } // // return menuExt; // } // // void CMakeManager::jumpToDeclaration() // { // DUChainAttatched* du=dynamic_cast(m_clickedItems.first()); // if(du) // { // KTextEditor::Cursor c; // QUrl url; // { // KDevelop::DUChainReadLocker lock; // Declaration* decl = du->declaration().data(); // if(!decl) // return; // c = decl->rangeInCurrentRevision().start(); // url = decl->url().toUrl(); // } // // ICore::self()->documentController()->openDocument(url, c); // } // } // // // TODO: Port to Path API // bool CMakeManager::moveFilesAndFolders(const QList< ProjectBaseItem* > &items, ProjectFolderItem* toFolder) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Move files and folders within CMakeLists as follows:")); // // bool cmakeSuccessful = true; // CMakeFolderItem *nearestCMakeFolderItem = nearestCMakeFolder(toFolder); // IProject* project=toFolder->project(); // // QList movedUrls; // QList oldUrls; // foreach(ProjectBaseItem *movedItem, items) // { // QList dirtyItems = cmakeListedItemsAffectedByUrlChange(project, movedItem->url()); // QUrl movedItemNewUrl = toFolder->url(); // movedItemNewUrl.addPath(movedItem->baseName()); // if (movedItem->folder()) // movedItemNewUrl.adjustPath(QUrl::AddTrailingSlash); // foreach(ProjectBaseItem* dirtyItem, dirtyItems) // { // QUrl dirtyItemNewUrl = afterMoveUrl(dirtyItem->url(), movedItem->url(), movedItemNewUrl); // if (CMakeFolderItem* folder = dynamic_cast(dirtyItem)) // { // cmakeSuccessful &= changesWidgetRemoveCMakeFolder(folder, &changesWidget); // cmakeSuccessful &= changesWidgetAddFolder(dirtyItemNewUrl, nearestCMakeFolderItem, &changesWidget); // } // else if (dirtyItem->parent()->target()) // { // cmakeSuccessful &= changesWidgetMoveTargetFile(dirtyItem, dirtyItemNewUrl, &changesWidget); // } // } // // oldUrls += movedItem->url(); // movedUrls += movedItemNewUrl; // } // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort move?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // QList::const_iterator it1=oldUrls.constBegin(), it1End=oldUrls.constEnd(); // QList::const_iterator it2=movedUrls.constBegin(); // Q_ASSERT(oldUrls.size()==movedUrls.size()); // for(; it1!=it1End; ++it1, ++it2) // { // if (!KDevelop::renameUrl(project, *it1, *it2)) // return false; // // QList renamedItems = project->itemsForUrl(*it2); // bool dir = QFileInfo(it2->toLocalFile()).isDir(); // foreach(ProjectBaseItem* item, renamedItems) { // if(dir) // emit folderRenamed(Path(*it1), item->folder()); // else // emit fileRenamed(Path(*it1), item->file()); // } // } // // return true; // } // // bool CMakeManager::copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* toFolder) // { // IProject* project = toFolder->project(); // foreach(const Path& path, items) { // if (!KDevelop::copyUrl(project, path.toUrl(), toFolder->url())) // return false; // } // // return true; // } // // bool CMakeManager::removeFilesAndFolders(const QList &items) // { // using namespace CMakeEdit; // // IProject* p = 0; // QList urls; // foreach(ProjectBaseItem* item, items) // { // Q_ASSERT(item->folder() || item->file()); // // urls += item->url(); // if(!p) // p = item->project(); // } // // //First do CMakeLists changes // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Remove files and folders from CMakeLists as follows:")); // // bool cmakeSuccessful = changesWidgetRemoveItems(cmakeListedItemsAffectedByItemsChanged(items).toSet(), &changesWidget); // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort deletion?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = true; // //Then delete the files/folders // foreach(const QUrl& file, urls) // { // ret &= KDevelop::removeUrl(p, file, QDir(file.toLocalFile()).exists()); // } // // return ret; // } bool CMakeManager::removeFilesFromTargets(const QList &/*files*/) { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify project targets as follows:")); // // if (!files.isEmpty() && // changesWidgetRemoveFilesFromTargets(files, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges()) { // return true; // } return false; } // ProjectFolderItem* CMakeManager::addFolder(const Path& folder, ProjectFolderItem* parent) // { // using namespace CMakeEdit; // // CMakeFolderItem *cmakeParent = nearestCMakeFolder(parent); // if(!cmakeParent) // return 0; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Create folder '%1':", folder.lastPathSegment())); // // ///FIXME: use path in changes widget // changesWidgetAddFolder(folder.toUrl(), cmakeParent, &changesWidget); // // if(changesWidget.exec() && changesWidget.applyAllChanges()) // { // if(KDevelop::createFolder(folder.toUrl())) { //If saved we create the folder then the CMakeLists.txt file // Path newCMakeLists(folder, "CMakeLists.txt"); // KDevelop::createFile( newCMakeLists.toUrl() ); // } else // KMessageBox::error(0, i18n("Could not save the change."), // DIALOG_CAPTION); // } // // return 0; // } // // KDevelop::ProjectFileItem* CMakeManager::addFile( const Path& file, KDevelop::ProjectFolderItem* parent) // { // KDevelop::ProjectFileItem* created = 0; // if ( KDevelop::createFile(file.toUrl()) ) { // QList< ProjectFileItem* > files = parent->project()->filesForPath(IndexedString(file.pathOrUrl())); // if(!files.isEmpty()) // created = files.first(); // else // created = new KDevelop::ProjectFileItem( parent->project(), file, parent ); // } // return created; // } bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) { return false; // using namespace CMakeEdit; // // const QSet headerExt = QSet() << ".h" << ".hpp" << ".hxx"; // QList< ProjectFileItem* > files = _files; // for (int i = files.count() - 1; i >= 0; --i) // { // QString fileName = files[i]->fileName(); // QString fileExt = fileName.mid(fileName.lastIndexOf('.')); // QList sameUrlItems = files[i]->project()->itemsForUrl(files[i]->url()); // if (headerExt.contains(fileExt)) // files.removeAt(i); // else foreach(ProjectBaseItem* item, sameUrlItems) // { // if (item->parent() == target) // { // files.removeAt(i); // break; // } // } // } // // if(files.isEmpty()) // return true; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify target '%1' as follows:", target->baseName())); // // bool success = changesWidgetAddFilesToTarget(files, target, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges(); // // if(!success) // KMessageBox::error(0, i18n("CMakeLists changes failed."), DIALOG_CAPTION); // // return success; } // bool CMakeManager::renameFileOrFolder(ProjectBaseItem *item, const Path &newPath) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Rename '%1' to '%2':", item->text(), // newPath.lastPathSegment())); // // bool cmakeSuccessful = true, changedCMakeLists=false; // IProject* project=item->project(); // const Path oldPath=item->path(); // QUrl oldUrl=oldPath.toUrl(); // if (item->file()) // { // QList targetFiles = cmakeListedItemsAffectedByUrlChange(project, oldUrl); // foreach(ProjectBaseItem* targetFile, targetFiles) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetMoveTargetFile(targetFile, newPath.toUrl(), &changesWidget); // } // else if (CMakeFolderItem *folder = dynamic_cast(item)) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetRenameFolder(folder, newPath.toUrl(), &changesWidget); // // item->setPath(newPath); // if (changesWidget.hasDocuments() && cmakeSuccessful) { // changedCMakeLists = changesWidget.exec() && changesWidget.applyAllChanges(); // cmakeSuccessful &= changedCMakeLists; // } // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort rename?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = KDevelop::renameUrl(project, oldUrl, newPath.toUrl()); // if(!ret) { // item->setPath(oldPath); // } // return ret; // } // // bool CMakeManager::renameFile(ProjectFileItem *item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } // // bool CMakeManager::renameFolder(ProjectFolderItem* item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } -QString CMakeManager::termAtPosition(const KTextEditor::Document* textDocument, - const KTextEditor::Cursor& position) const +KTextEditor::Range CMakeManager::termRangeAtPosition(const KTextEditor::Document* textDocument, + const KTextEditor::Cursor& position) const { const KTextEditor::Cursor step(0, 1); enum ParseState { NoChar, NonLeadingChar, AnyChar, }; ParseState parseState = NoChar; KTextEditor::Cursor start = position; while (true) { const QChar c = textDocument->characterAt(start); if (c.isDigit()) { parseState = NonLeadingChar; } else if (c.isLetter() || c == QLatin1Char('_')) { parseState = AnyChar; } else { // also catches going out of document range, where c is invalid break; } start -= step; } if (parseState != AnyChar) { - return QString(); + return KTextEditor::Range::invalid(); } // undo step before last valid char start += step; KTextEditor::Cursor end = position + step; while (true) { const QChar c = textDocument->characterAt(end); if (!(c.isDigit() || c.isLetter() || c == QLatin1Char('_'))) { // also catches going out of document range, where c is invalid break; } end += step; } - return textDocument->text(KTextEditor::Range(start, end)); + return KTextEditor::Range(start, end); } -QWidget* CMakeManager::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) +QPair CMakeManager::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { + KTextEditor::Range itemRange; + CMakeNavigationWidget* doc = nullptr; + KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); - Declaration *decl=nullptr; if(top) { int useAt=top->findUseAt(top->transformToLocalRevision(position)); if(useAt>=0) { Use u=top->uses()[useAt]; - decl=u.usedDeclaration(top->topContext()); + doc = new CMakeNavigationWidget(top, u.usedDeclaration(top->topContext())); + itemRange = u.m_range.castToSimpleRange(); } } - CMakeNavigationWidget* doc=nullptr; - if(decl) - { - doc=new CMakeNavigationWidget(top, decl); - } - else - { + if (!doc) { ICMakeDocumentation* docu=CMake::cmakeDocumentation(); if( docu ) { const auto* document = ICore::self()->documentController()->documentForUrl(url); const auto* textDocument = document->textDocument(); - const auto id = termAtPosition(textDocument, position); - if (!id.isEmpty()) { - IDocumentation::Ptr desc=docu->description(id, url); - if(desc) - { - doc=new CMakeNavigationWidget(top, desc); + itemRange = termRangeAtPosition(textDocument, position); + if (itemRange.isValid()) { + const auto id = textDocument->text(itemRange); + + if (!id.isEmpty()) { + IDocumentation::Ptr desc=docu->description(id, url); + if (desc) { + doc=new CMakeNavigationWidget(top, desc); + } } } } } - return doc; + return {doc, itemRange}; } QPair CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const { return QPair(); } // { // QPair ret; // if(project==0 && !m_projectsData.isEmpty()) // { // project=m_projectsData.keys().first(); // } // // // qCDebug(CMAKE) << "cache value " << id << project << (m_projectsData.contains(project) && m_projectsData[project].cache.contains(id)); // CMakeProjectData* data = m_projectsData[project]; // if(data && data->cache.contains(id)) // { // const CacheEntry& e=data->cache.value(id); // ret.first=e.value; // ret.second=e.doc; // } // return ret; // }Add // void CMakeManager::projectClosing(IProject* p) { m_projects.remove(p); // delete m_projectsData.take(p); // delete m_watchers.take(p); // // m_filter->remove(p); // // qCDebug(CMAKE) << "Project closed" << p; } // // QStringList CMakeManager::processGeneratorExpression(const QStringList& expr, IProject* project, ProjectTargetItem* target) const // { // QStringList ret; // const CMakeProjectData* data = m_projectsData[project]; // GenerationExpressionSolver exec(data->properties, data->targetAlias); // if(target) // exec.setTargetName(target->text()); // // exec.defineVariable("INSTALL_PREFIX", data->vm.value("CMAKE_INSTALL_PREFIX").join(QString())); // for(QStringList::const_iterator it = expr.constBegin(), itEnd = expr.constEnd(); it!=itEnd; ++it) { // QStringList val = exec.run(*it).split(';'); // ret += val; // } // return ret; // } /* void CMakeManager::addPending(const Path& path, CMakeFolderItem* folder) { m_pending.insert(path, folder); } CMakeFolderItem* CMakeManager::takePending(const Path& path) { return m_pending.take(path); } void CMakeManager::addWatcher(IProject* p, const QString& path) { if (QFileSystemWatcher* watcher = m_watchers.value(p)) { watcher->addPath(path); } else { qCWarning(CMAKE) << "Could not find a watcher for project" << p << p->name() << ", path " << path; Q_ASSERT(false); } }*/ // CMakeProjectData CMakeManager::projectData(IProject* project) // { // Q_ASSERT(QThread::currentThread() == project->thread()); // CMakeProjectData* data = m_projectsData[project]; // if(!data) { // data = new CMakeProjectData; // m_projectsData[project] = data; // } // return *data; // } ProjectFilterManager* CMakeManager::filterManager() const { return m_filter; } void CMakeManager::dirtyFile(const QString& path) { qCDebug(CMAKE) << "dirty!" << path; //we initialize again hte project that sent the signal for(QHash::const_iterator it = m_projects.constBegin(), itEnd = m_projects.constEnd(); it!=itEnd; ++it) { if(it->watcher == sender()) { reload(it.key()->projectItem()); break; } } } void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) { populateTargets(folder, m_projects[folder->project()].targets); } ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO: when we have data about targets, use folders with targets or similar if (QFile::exists(path.toLocalFile()+QLatin1String("/CMakeLists.txt"))) return new KDevelop::ProjectBuildFolderItem( project, path, parent ); else return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); } int CMakeManager::perProjectConfigPages() const { return 1; } ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CMakePreferences(this, options, parent); } return nullptr; } void CMakeManager::reloadProjects() { const auto& projects = m_projects.keys(); for (IProject* project : projects) { CMake::checkForNeedingConfigure(project); reload(project->projectItem()); } } #include "cmakemanager.moc" diff --git a/plugins/cmake/cmakemanager.h b/plugins/cmake/cmakemanager.h index 7aea67cfe4..d690e22f61 100644 --- a/plugins/cmake/cmakemanager.h +++ b/plugins/cmake/cmakemanager.h @@ -1,170 +1,170 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2009 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef CMAKEMANAGER_H #define CMAKEMANAGER_H #include #include #include #include #include #include #include #include #include "cmakeprojectdata.h" #include "icmakemanager.h" class WaitAllJobs; class CMakeCommitChangesJob; struct CMakeProjectData; class QObject; class CMakeHighlighting; class CMakeDocumentation; namespace KDevelop { class IProject; class IProjectBuilder; class ICodeHighlighting; class ProjectFolderItem; class ProjectBaseItem; class ProjectFileItem; class ProjectTargetItem; class ProjectFilterManager; class IProjectFilter; class ParseJob; class ContextMenuExtension; class Context; class IRuntime; } class CMakeFolderItem; class CMakeManager : public KDevelop::AbstractFileManagerPlugin , public KDevelop::IBuildSystemManager , public KDevelop::ILanguageSupport , public ICMakeManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) Q_INTERFACES( KDevelop::IProjectFileManager ) Q_INTERFACES( KDevelop::ILanguageSupport ) Q_INTERFACES( ICMakeManager ) public: explicit CMakeManager( QObject* parent = nullptr, const QVariantList& args = QVariantList() ); ~CMakeManager() override; Features features() const override { return Features(Folders | Targets | Files ); } KDevelop::IProjectBuilder* builder() const override; bool hasBuildInfo(KDevelop::ProjectBaseItem*) const override; KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem *) const override; KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem *item) const override; QHash defines(KDevelop::ProjectBaseItem *) const override; QString extraArguments(KDevelop::ProjectBaseItem *item) const override; KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return nullptr; } virtual QList targets() const; QList targets(KDevelop::ProjectFolderItem* folder) const override; // virtual KDevelop::ProjectFolderItem* addFolder( const KDevelop::Path& folder, KDevelop::ProjectFolderItem* parent ); // virtual KDevelop::ProjectFileItem* addFile( const KDevelop::Path&, KDevelop::ProjectFolderItem* ); bool addFilesToTarget( const QList &files, KDevelop::ProjectTargetItem* target) override; bool removeTarget( KDevelop::ProjectTargetItem* ) override { return false; } bool removeFilesFromTargets( const QList &files ) override; // virtual bool removeFilesAndFolders( const QList &items); // // virtual bool renameFile(KDevelop::ProjectFileItem*, const KDevelop::Path&); // virtual bool renameFolder(KDevelop::ProjectFolderItem*, const KDevelop::Path&); // virtual bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &items, KDevelop::ProjectFolderItem *newParent); // virtual bool copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* newParent); // // virtual QList parse( KDevelop::ProjectFolderItem* dom ); KDevelop::ProjectFolderItem* import( KDevelop::IProject *project ) override; KJob* createImportJob(KDevelop::ProjectFolderItem* item) override; // bool reload(KDevelop::ProjectFolderItem*) override; // // virtual KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* context ); KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr) override; QPair cacheValue(KDevelop::IProject* project, const QString& id) const override; //LanguageSupport QString name() const override; KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &url) override; KDevelop::ICodeHighlighting* codeHighlighting() const override; - QWidget* specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) override; - + QPair specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) override; + // void addPending(const KDevelop::Path& path, CMakeFolderItem* folder); // CMakeFolderItem* takePending(const KDevelop::Path& path); // void addWatcher(KDevelop::IProject* p, const QString& path); // CMakeProjectData projectData(KDevelop::IProject* project); KDevelop::ProjectFilterManager* filterManager() const; static KDevelop::IndexedString languageName(); int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; void integrateData(const CMakeProjectData &data, KDevelop::IProject* project); Q_SIGNALS: void folderRenamed(const KDevelop::Path& oldFolder, KDevelop::ProjectFolderItem* newFolder); void fileRenamed(const KDevelop::Path& oldFile, KDevelop::ProjectFileItem* newFile); private Q_SLOTS: void serverResponse(KDevelop::IProject* project, const QJsonObject &value); // void jumpToDeclaration(); void projectClosing(KDevelop::IProject*); void dirtyFile(const QString& file); // // void directoryChanged(const QString& dir); // void filesystemBuffererTimeout(); private: void reloadProjects(); CMakeFile fileInformation(KDevelop::ProjectBaseItem* item) const; void folderAdded(KDevelop::ProjectFolderItem* folder); - QString termAtPosition(const KTextEditor::Document* textDocument, - const KTextEditor::Cursor& position) const; + KTextEditor::Range termRangeAtPosition(const KTextEditor::Document* textDocument, + const KTextEditor::Cursor& position) const; private: QHash m_projects; KDevelop::ProjectFilterManager* m_filter; KDevelop::ICodeHighlighting* m_highlight; }; #endif diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index eaef28dd3f..cdafb50a23 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1500 +1,1529 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using KTextEditor::Attribute; using KTextEditor::View; using namespace KDevelop; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* contextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return nullptr; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: explicit ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} QWidget* create(QWidget *parent = nullptr) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } QString id() const override { return QStringLiteral("org.kdevelop.ContextBrowser"); } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { m_browseManager = new BrowseManager(this); KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow(window); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setAutoRaise(true); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(m_previousButton); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setAutoRaise(true); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(m_nextButton); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin(QStringLiteral("org.kdevelop.IQuickOpen")); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); connect(m_browseManager, &BrowseManager::invokeAction, this, &ContextBrowserPlugin::invokeAction); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevcontextbrowser.rc"); QAction* sourceBrowseMode = actions.addAction(QStringLiteral("source_browse_mode")); sourceBrowseMode->setText( i18n("Source &Browse Mode") ); sourceBrowseMode->setIcon( QIcon::fromTheme(QStringLiteral("arrow-up")) ); sourceBrowseMode->setCheckable(true); connect(sourceBrowseMode, &QAction::triggered, m_browseManager, &BrowseManager::setBrowsing); QAction* previousContext = actions.addAction(QStringLiteral("previous_context")); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-context") ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction(QStringLiteral("next_context")); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme(QStringLiteral("go-next-context") ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction(QStringLiteral("previous_use")); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme(QStringLiteral("go-previous-use")) ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction(QStringLiteral("next_use")); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme(QStringLiteral("go-next-use")) ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction(QStringLiteral("outline_line"), outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction(QStringLiteral("find_uses"), m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevcontextbrowser"), parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) , m_textHintProvider(this) { qRegisterMetaType("KDevelop::IndexedDeclaration"); core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( DUChain::self(), &DUChain::updateReady, this, &ContextBrowserPlugin::updateReady); connect( ColorCache::self(), &ColorCache::colorsGotChanged, this, &ContextBrowserPlugin::colorSetupChanged ); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { auto nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::invokeAction(int index) { if (!m_currentNavigationWidget) return; auto navigationWidget = qobject_cast(m_currentNavigationWidget); if (!navigationWidget) return; // TODO: Add API in AbstractNavigation{Widget,Context}? QMetaObject::invokeMethod(navigationWidget->context().data(), "executeAction", Q_ARG(int, index)); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; m_currentToolTipProblems.clear(); m_currentToolTipDeclaration = {}; } } -static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position) +static QVector findProblemsUnderCursor(TopDUContext* topContext, KTextEditor::Cursor position, + KTextEditor::Range& handleRange) { QVector problems; + handleRange = KTextEditor::Range::invalid(); + const auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); for (const auto& modelData : modelsData) { foreach (const auto& problem, modelData.model->problems(topContext->url())) { DocumentRange problemRange = problem->finalLocation(); - if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) + if (problemRange.contains(position) || (problemRange.isEmpty() && problemRange.boundaryAtCursor(position))) { problems += problem; + // first? + if (!handleRange.isValid()) { + handleRange = problemRange; + } else { + handleRange.confineToRange(problemRange); + } + } } } return problems; } -static QVector findProblemsCloseToCursor(TopDUContext* topContext, KTextEditor::Cursor position, KTextEditor::View* view) +static QVector findProblemsCloseToCursor(const TopDUContext* topContext, KTextEditor::Cursor position, const KTextEditor::View* view, + KTextEditor::Range& handleRange) { + handleRange = KTextEditor::Range::invalid(); + QVector allProblems; const auto modelsData = ICore::self()->languageController()->problemModelSet()->models(); for (const auto& modelData : modelsData) { const auto problems = modelData.model->problems(topContext->url()); allProblems.reserve(allProblems.size() + problems.size()); for (const auto& problem : problems) { allProblems += problem; } } if (allProblems.isEmpty()) return allProblems; std::sort(allProblems.begin(), allProblems.end(), [position](const KDevelop::IProblem::Ptr& a, const KDevelop::IProblem::Ptr& b) { const auto aRange = a->finalLocation(); const auto bRange = b->finalLocation(); const auto aLineDistance = qMin(qAbs(aRange.start().line() - position.line()), qAbs(aRange.end().line() - position.line())); const auto bLineDistance = qMin(qAbs(bRange.start().line() - position.line()), qAbs(bRange.end().line() - position.line())); if (aLineDistance != bLineDistance) { return aLineDistance < bLineDistance; } if (aRange.start().line() == bRange.start().line()) { return qAbs(aRange.start().column() - position.column()) < qAbs(bRange.start().column() - position.column()); } return qAbs(aRange.end().column() - position.column()) < qAbs(bRange.end().column() - position.column()); }); QVector closestProblems; // Show problems, located on the same line foreach (auto problem, allProblems) { auto r = problem->finalLocation(); if (r.onSingleLine() && r.start().line() == position.line()) closestProblems += problem; else break; } // If not, only show it in case there's only whitespace // between the current cursor position and the problem line if (closestProblems.isEmpty()) { foreach (auto problem, allProblems) { auto r = problem->finalLocation(); KTextEditor::Range dist; KTextEditor::Cursor bound(r.start().line(), 0); if (position < r.start()) dist = KTextEditor::Range(position, bound); else { bound.setLine(r.end().line() + 1); dist = KTextEditor::Range(bound, position); } if (view->document()->text(dist).trimmed().isEmpty()) closestProblems += problem; else break; } } + if (!closestProblems.isEmpty()) { + auto it = closestProblems.constBegin(); + handleRange = (*it)->finalLocation(); + ++it; + for (auto end = closestProblems.constEnd(); it != end; ++it) { + handleRange.confineToRange((*it)->finalLocation()); + } + } + return closestProblems; } -QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position) +QWidget* ContextBrowserPlugin::navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position, + KTextEditor::Range& itemRange) { QUrl viewUrl = view->document()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); DUChainReadLocker lock(DUChain::lock()); + for (const auto language : languages) { - auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); - auto navigationWidget = qobject_cast(widget); - if(navigationWidget) + auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, position); + auto navigationWidget = qobject_cast(widget.first); + if (navigationWidget) { + itemRange = widget.second; return navigationWidget; + } } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (topContext) { // first pass: find problems under the cursor - const auto problems = findProblemsUnderCursor(topContext, position); + const auto problems = findProblemsUnderCursor(topContext, position, itemRange); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; + auto widget = new AbstractNavigationWidget; auto context = new ProblemNavigationContext(problems); context->setTopContext(TopDUContextPointer(topContext)); widget->setContext(NavigationContextPointer(context)); return widget; } } - auto declUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position).declaration; + const auto itemUnderCursor = DUChainUtils::itemUnderCursor(viewUrl, position); + auto declUnderCursor = itemUnderCursor.declaration; Declaration* decl = DUChainUtils::declarationForDefinition(declUnderCursor); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); - DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return nullptr; m_currentToolTipDeclaration = IndexedDeclaration(decl); + itemRange = itemUnderCursor.range; return decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } if (topContext) { // second pass: find closest problem to the cursor - const auto problems = findProblemsCloseToCursor(topContext, position, view); + const auto problems = findProblemsCloseToCursor(topContext, position, view, itemRange); if (!problems.isEmpty()) { if (problems == m_currentToolTipProblems && m_currentToolTip) { return nullptr; } m_currentToolTipProblems = problems; + auto widget = new AbstractNavigationWidget; // since the problem is not under cursor: show location widget->setContext(NavigationContextPointer(new ProblemNavigationContext(problems, ProblemNavigationContext::ShowLocation))); return widget; } } return nullptr; } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself - auto navigationWidget = navigationWidgetForPosition(view, position); + KTextEditor::Range itemRange = KTextEditor::Range::invalid(); + auto navigationWidget = navigationWidgetForPosition(view, position, itemRange); if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = nullptr; m_currentNavigationWidget = nullptr; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); - KTextEditor::Range itemRange; - { - DUChainReadLocker lock; - auto viewUrl = view->document()->url(); - itemRange = DUChainUtils::itemUnderCursor(viewUrl, position).range; + if (!itemRange.isValid()) { + qCWarning(PLUGIN_CONTEXTBROWSER) << "Got navigationwidget with invalid itemrange"; + itemRange = KTextEditor::Range(position, 0); } + tooltip->setHandleRect(KTextEditorHelpers::itemBoundingRect(view, itemRange)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); QObject::connect( view, &KTextEditor::View::verticalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); QObject::connect( view, &KTextEditor::View::horizontalScrollPositionChanged, this, &ContextBrowserPlugin::hideToolTip ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr ContextBrowserPlugin::highlightedUseAttribute(KTextEditor::View* view) const { if( !m_highlightAttribute ) { m_highlightAttribute = Attribute::Ptr( new Attribute() ); m_highlightAttribute->setDefaultStyle(KTextEditor::dsNormal); m_highlightAttribute->setForeground(m_highlightAttribute->selectedForeground()); m_highlightAttribute->setBackgroundFillWhitespace(true); auto iface = qobject_cast(view); auto background = iface->configValue(QStringLiteral("search-highlight-color")).value(); m_highlightAttribute->setBackground(background); } return m_highlightAttribute; } void ContextBrowserPlugin::colorSetupChanged() { m_highlightAttribute = Attribute::Ptr(); } Attribute::Ptr ContextBrowserPlugin::highlightedSpecialObjectAttribute(KTextEditor::View* view) const { return highlightedUseAttribute(view); } void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) { if( !view || !decl ) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { const auto currentRevisionUses = decl->usesCurrentRevision(); for (auto fileIt = currentRevisionUses.constBegin(); fileIt != currentRevisionUses.constEnd(); ++fileIt) { for (auto useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = nullptr; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position).declaration ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } return nullptr; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if(view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if(m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; }else{ language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : nullptr; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute(view)); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) - updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); + updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition).first); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::updateReady(const IndexedString& file, const ReferencedTopDUContext& /*topContext*/) { const auto url = file.toUrl(); for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == url) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing m_lastInsertionDocument = nullptr; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(&m_textHintProvider); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = nullptr; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent).declaration; if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = nullptr; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QVector useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QVector localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QVector nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = contextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = contextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = QStringLiteral(""); actionText += QLatin1String(" @ "); QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; indices.reserve(m_history.size()-m_nextHistoryIndex); for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; indices.reserve(m_nextHistoryIndex-1); for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); for (int index : historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history.remove(0, m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qCWarning(PLUGIN_CONTEXTBROWSER) << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(const KDevelop::DocumentCursor& pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/contextbrowser.h b/plugins/contextbrowser/contextbrowser.h index b67399c97e..06bfeff199 100644 --- a/plugins/contextbrowser/contextbrowser.h +++ b/plugins/contextbrowser/contextbrowser.h @@ -1,282 +1,283 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #define KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QHBoxLayout; class QMenu; class QToolButton; namespace Sublime { class MainWindow; } namespace KDevelop { class IDocument; class DUContext; class TopDUContext; class ReferencedTopDUContext; class DUChainBase; class AbstractNavigationWidget; } namespace KTextEditor { class Document; class View; } class ContextBrowserViewFactory; class ContextBrowserView; class ContextBrowserPlugin; class BrowseManager; class ContextBrowserHintProvider : public KTextEditor::TextHintProvider { public: explicit ContextBrowserHintProvider(ContextBrowserPlugin* plugin); QString textHint(KTextEditor::View* view, const KTextEditor::Cursor& position) override; private: ContextBrowserPlugin* m_plugin; }; QWidget* masterWidget(QWidget* w); struct ViewHighlights { ViewHighlights() : keep(false) { } // Whether the same highlighting should be kept highlighted (usually during typing) bool keep; // The declaration that is highlighted for this view KDevelop::IndexedDeclaration declaration; // Highlighted ranges. Those may also be contained by different views. QList highlights; }; class ContextBrowserPlugin : public KDevelop::IPlugin, public KDevelop::IContextBrowser { Q_OBJECT Q_INTERFACES( KDevelop::IContextBrowser ) public: explicit ContextBrowserPlugin(QObject *parent, const QVariantList & = QVariantList() ); ~ContextBrowserPlugin() override; void unload() override; void registerToolView(ContextBrowserView* view); void unRegisterToolView(ContextBrowserView* view); KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; KXMLGUIClient* createGUIForMainWindow( Sublime::MainWindow* window ) override; ///duchain must be locked ///@param force When this is true, the history-entry is added, no matter whether the context is "interesting" or not void updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& cursorPosition, bool force = false); void updateDeclarationListBox(KDevelop::DUContext* context); void showUses(const KDevelop::DeclarationPointer& declaration) override; KTextEditor::Attribute::Ptr highlightedUseAttribute(KTextEditor::View* view) const; KTextEditor::Attribute::Ptr highlightedSpecialObjectAttribute(KTextEditor::View* view) const; public Q_SLOTS: void showUsesDelayed(const KDevelop::DeclarationPointer& declaration); void previousContextShortcut(); void nextContextShortcut(); void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); void invokeAction(int index); void previousUseShortcut(); void nextUseShortcut(); void declarationSelectedInUI(const KDevelop::DeclarationPointer& decl); void updateReady(const KDevelop::IndexedString& url, const KDevelop::ReferencedTopDUContext& topContext); void textDocumentCreated( KDevelop::IDocument* document ); void documentActivated( KDevelop::IDocument* ); void viewDestroyed( QObject* obj ); void cursorPositionChanged( KTextEditor::View* view, const KTextEditor::Cursor& newPosition ); void viewCreated( KTextEditor::Document* , KTextEditor::View* ); void updateViews(); void hideToolTip(); void findUses(); void textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text); void selectionChanged(KTextEditor::View*); void historyNext(); void historyPrevious(); void colorSetupChanged(); private Q_SLOTS: // history browsing void documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor); void nextMenuAboutToShow(); void previousMenuAboutToShow(); void actionTriggered(); void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: QWidget* toolbarWidgetForMainWindow(Sublime::MainWindow* window); void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; - QWidget* navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position); + QWidget* navigationWidgetForPosition(KTextEditor::View* view, KTextEditor::Cursor position, + KTextEditor::Range& itemRange); void switchUse(bool forward); void clearMouseHover(); void addHighlight( KTextEditor::View* view, KDevelop::Declaration* decl ); /** helper for updateBrowserView(). * Tries to find a 'specialLanguageObject' (eg macro) in @p view under cursor @c. * If found returns true and sets @p pickedLanguage to the language this object belongs to */ KDevelop::Declaration* findDeclaration(KTextEditor::View* view, const KTextEditor::Cursor&, bool mouseHighlight); void updateForView(KTextEditor::View* view); // history browsing bool isPreviousEntry(KDevelop::DUContext*, const KTextEditor::Cursor& cursor) const; QString actionTextFor(int historyIndex) const; void updateButtonState(); void openDocument(int historyIndex); void fillHistoryPopup(QMenu* menu, const QList& historyIndices); enum NavigationActionType { Accept, Back, Down, Up, Left, Right }; void doNavigate(NavigationActionType action); private: // Returns the currently active and visible context browser view that belongs // to the same context (mainwindow and area) as the given widget ContextBrowserView* browserViewForWidget(QWidget* widget); void showToolTip(KTextEditor::View* view, KTextEditor::Cursor position); QTimer* m_updateTimer; //Contains the range, the old attribute, and the attribute it was replaced with QSet m_updateViews; QMap m_highlightedRanges; //Holds a list of all active context browser tool views QList m_views; //Used to override the next declaration that will be highlighted KDevelop::IndexedDeclaration m_useDeclaration; KDevelop::IndexedDeclaration m_lastHighlightedDeclaration; QUrl m_mouseHoverDocument; KTextEditor::Cursor m_mouseHoverCursor; ContextBrowserViewFactory* m_viewFactory; QPointer m_currentToolTip; QPointer m_currentNavigationWidget; KDevelop::IndexedDeclaration m_currentToolTipDeclaration; QVector m_currentToolTipProblems; QAction* m_findUses; QPointer m_lastInsertionDocument; KTextEditor::Cursor m_lastInsertionPos; // outline toolbar QPointer m_outlineLine; QPointer m_toolbarWidgetLayout; QPointer m_toolbarWidget; // history browsing struct HistoryEntry { //Duchain must be locked explicit HistoryEntry(KDevelop::IndexedDUContext ctx = KDevelop::IndexedDUContext(), const KTextEditor::Cursor& cursorPosition = KTextEditor::Cursor()); explicit HistoryEntry(const KDevelop::DocumentCursor& pos); //Duchain must be locked void setCursorPosition(const KTextEditor::Cursor& cursorPosition); //Duchain does not need to be locked KDevelop::DocumentCursor computePosition() const; KDevelop::IndexedDUContext context; KDevelop::DocumentCursor absoluteCursorPosition; KTextEditor::Cursor relativeCursorPosition; //Cursor position relative to the start line of the context QString alternativeString; }; QVector m_history; QPointer m_previousButton; QPointer m_nextButton; QPointer m_previousMenu, m_nextMenu; QList m_listDeclarations; KDevelop::IndexedString m_listUrl; BrowseManager* m_browseManager; //Used to not record jumps triggered by the context-browser as history entries QPointer m_focusBackWidget; int m_nextHistoryIndex; mutable KTextEditor::Attribute::Ptr m_highlightAttribute; friend class ContextBrowserHintProvider; ContextBrowserHintProvider m_textHintProvider; }; #endif // KDEVPLATFORM_PLUGIN_CONTEXTBROWSERPLUGIN_H // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/qmljs/kdevqmljsplugin.cpp b/plugins/qmljs/kdevqmljsplugin.cpp index 65cfd96c08..95f9523d81 100644 --- a/plugins/qmljs/kdevqmljsplugin.cpp +++ b/plugins/qmljs/kdevqmljsplugin.cpp @@ -1,204 +1,204 @@ /************************************************************************************* * Copyright (C) 2012 by Aleix Pol * * Copyright (C) 2012 by Milian Wolff * * Copyright (C) 2013 by Sven Brauch * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "kdevqmljsplugin.h" #include "qmljsparsejob.h" #include "qmljshighlighting.h" #include "codecompletion/model.h" #include "navigation/propertypreviewwidget.h" #include "duchain/helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevQmlJsSupportFactory, "kdevqmljs.json", registerPlugin(); ) using namespace KDevelop; /// TODO: Extend? See qmljsmodelmanager.h in qt-creator.git class ModelManager: public QmlJS::ModelManagerInterface { Q_OBJECT public: explicit ModelManager(QObject *parent = nullptr); ~ModelManager() override; }; ModelManager::ModelManager(QObject* parent) : QmlJS::ModelManagerInterface(parent) {} ModelManager::~ModelManager() {} KDevQmlJsPlugin::KDevQmlJsPlugin(QObject* parent, const QVariantList& ) : IPlugin(QStringLiteral("kdevqmljssupport"), parent ) , ILanguageSupport() , m_highlighting(new QmlJsHighlighting(this)) , m_refactoring(new BasicRefactoring(this)) , m_modelManager(new ModelManager(this)) { QmlJS::registerDUChainItems(); CodeCompletionModel* codeCompletion = new QmlJS::CodeCompletionModel(this); new KDevelop::CodeCompletion(this, codeCompletion, name()); auto assistantsManager = core()->languageController()->staticAssistantsManager(); assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this))); } KDevQmlJsPlugin::~KDevQmlJsPlugin() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); QmlJS::unregisterDUChainItems(); } ParseJob* KDevQmlJsPlugin::createParseJob(const IndexedString& url) { return new QmlJsParseJob(url, this); } QString KDevQmlJsPlugin::name() const { return QStringLiteral("qml/js"); } ICodeHighlighting* KDevQmlJsPlugin::codeHighlighting() const { return m_highlighting; } BasicRefactoring* KDevQmlJsPlugin::refactoring() const { return m_refactoring; } ContextMenuExtension KDevQmlJsPlugin::contextMenuExtension(Context* context, QWidget* parent) { ContextMenuExtension cm; EditorContext *ec = dynamic_cast(context); if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) { // It's a QML/JS file, let's add our context menu. m_refactoring->fillContextMenu(cm, context, parent); } return cm; } const QString textFromDoc(const IDocument* doc, const KTextEditor::Range& range) { return doc->textDocument()->line(range.start().line()).mid(range.start().column(), range.end().column()-range.start().column()); } // Finds how many spaces the given string has at one end. // direction=+1 -> left end of the string, -1 for right end. int spacesAtCorner(const QString& string, int direction = +1) { int spaces = 0; QString::const_iterator it; for ( it = direction == 1 ? string.begin() : string.end()-1 ; it != string.end(); it += direction ) { if ( ! it->isSpace() ) break; spaces += 1; } return spaces; } // Take the given QML line and check if it's a line of the form foo.bar: value. // Return ranges for the key and the value. const QPair parseProperty(const QString& line, const KTextEditor::Cursor& position) { const QStringList items = line.split(QLatin1Char(';')); QString matchingItem; int col_offset = -1; // This is to also support FooAnimation { foo: bar; baz: bang; duration: 200 } // or similar for (const QString& item : items) { col_offset += item.size() + 1; if ( position.column() < col_offset ) { matchingItem = item; break; } } QStringList split = matchingItem.split(QLatin1Char(':')); if ( split.size() != 2 ) { // The expression is not of the form foo:bar, thus invalid. return qMakePair(KTextEditor::Range::invalid(), KTextEditor::Range::invalid()); } QString key = split.at(0); QString value = split.at(1); // For animations or similar, remove the trailing '}' if there's no semicolon after the last entry if (value.trimmed().endsWith(QLatin1Char('}'))) { col_offset -= value.size() - value.lastIndexOf(QLatin1Char('}')) + 1; value = value.left(value.lastIndexOf(QLatin1Char('}'))-1); } return qMakePair( KTextEditor::Range( KTextEditor::Cursor(position.line(), col_offset - value.size() - key.size() + spacesAtCorner(key, +1) - 1), KTextEditor::Cursor(position.line(), col_offset - value.size() - 1 + spacesAtCorner(key, -1)) ), KTextEditor::Range( KTextEditor::Cursor(position.line(), col_offset - value.size() + spacesAtCorner(value, +1)), KTextEditor::Cursor(position.line(), col_offset + spacesAtCorner(value, -1)) )); } -QWidget* KDevQmlJsPlugin::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) +QPair KDevQmlJsPlugin::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if ( doc && doc->textDocument() ) { // Check for a QML property, and construct a property preview widget // if the property key is listed in the supported properties. QPair property = parseProperty(doc->textDocument()->line(position.line()), position); if ( property.first.isValid() && property.second.isValid() ) { - Declaration* decl = DUChainUtils::itemUnderCursor(url, property.first.start()).declaration; + const auto itemUnderCursor = DUChainUtils::itemUnderCursor(url, property.first.start()); - return PropertyPreviewWidget::constructIfPossible( + return {PropertyPreviewWidget::constructIfPossible( doc->textDocument(), property.first, property.second, - decl, + itemUnderCursor.declaration, textFromDoc(doc, property.first), textFromDoc(doc, property.second) - ); + ), itemUnderCursor.range}; } } // Otherwise, display no special "navigation" widget. return KDevelop::ILanguageSupport::specialLanguageObjectNavigationWidget(url, position); } #include "kdevqmljsplugin.moc" diff --git a/plugins/qmljs/kdevqmljsplugin.h b/plugins/qmljs/kdevqmljsplugin.h index 144fe78f56..ad7b1c957f 100644 --- a/plugins/qmljs/kdevqmljsplugin.h +++ b/plugins/qmljs/kdevqmljsplugin.h @@ -1,52 +1,52 @@ /************************************************************************************* * Copyright (C) 2012 by Aleix Pol * * Copyright (C) 2012 by Milian Wolff * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef KDEVQMLJSPLUGIN_H #define KDEVQMLJSPLUGIN_H #include #include class ModelManager; class KDevQmlJsPlugin : public KDevelop::IPlugin, public KDevelop::ILanguageSupport { Q_OBJECT Q_INTERFACES( KDevelop::ILanguageSupport ) public: explicit KDevQmlJsPlugin( QObject* parent, const QVariantList& args = QVariantList() ); ~KDevQmlJsPlugin() override; KDevelop::ParseJob* createParseJob(const KDevelop::IndexedString& url) override; QString name() const override; KDevelop::ICodeHighlighting* codeHighlighting() const override; KDevelop::BasicRefactoring* refactoring() const override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; - QWidget* specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) override; + QPair specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) override; private: KDevelop::ICodeHighlighting* m_highlighting; KDevelop::BasicRefactoring* m_refactoring; ModelManager* m_modelManager; }; #endif // KDEVQMLJSPLUGIN_H diff --git a/plugins/quickopen/quickopenplugin.cpp b/plugins/quickopen/quickopenplugin.cpp index 2e5321202f..6d446b76f7 100644 --- a/plugins/quickopen/quickopenplugin.cpp +++ b/plugins/quickopen/quickopenplugin.cpp @@ -1,1173 +1,1173 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "quickopenplugin.h" #include "quickopenwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "quickopenmodel.h" #include "projectfilequickopen.h" #include "projectitemquickopen.h" #include "declarationlistquickopen.h" #include "documentationquickopenprovider.h" #include "actionsquickopenprovider.h" #include "debug.h" #include #include #include using namespace KDevelop; const bool noHtmlDestriptionInOutline = true; class QuickOpenWidgetCreator { public: virtual ~QuickOpenWidgetCreator() { } virtual QuickOpenWidget* createWidget() = 0; virtual QString objectNameForLine() = 0; virtual void widgetShown() { } }; class StandardQuickOpenWidgetCreator : public QuickOpenWidgetCreator { public: StandardQuickOpenWidgetCreator(const QStringList& items, const QStringList& scopes) : m_items(items) , m_scopes(scopes) { } QString objectNameForLine() override { return QStringLiteral("Quickopen"); } void setItems(const QStringList& scopes, const QStringList& items) { m_scopes = scopes; m_items = items; } QuickOpenWidget* createWidget() override { QStringList useItems = m_items; if (useItems.isEmpty()) { useItems = QuickOpenPlugin::self()->lastUsedItems; } QStringList useScopes = m_scopes; if (useScopes.isEmpty()) { useScopes = QuickOpenPlugin::self()->lastUsedScopes; } return new QuickOpenWidget(i18n("Quick Open"), QuickOpenPlugin::self()->m_model, QuickOpenPlugin::self()->lastUsedItems, useScopes, false, true); } QStringList m_items; QStringList m_scopes; }; class OutlineFilter : public DUChainUtils::DUChainItemFilter { public: enum OutlineMode { Functions, FunctionsAndClasses }; explicit OutlineFilter(QVector& _items, OutlineMode _mode = FunctionsAndClasses) : items(_items) , mode(_mode) { } bool accept(Declaration* decl) override { if (decl->range().isEmpty()) { return false; } bool collectable = mode == Functions ? decl->isFunctionDeclaration() : (decl->isFunctionDeclaration() || (decl->internalContext() && decl->internalContext()->type() == DUContext::Class)); if (collectable) { DUChainItem item; item.m_item = IndexedDeclaration(decl); item.m_text = decl->toString(); items << item; return true; } else { return false; } } bool accept(DUContext* ctx) override { if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper) { return true; } else { return false; } } QVector& items; OutlineMode mode; }; K_PLUGIN_FACTORY_WITH_JSON(KDevQuickOpenFactory, "kdevquickopen.json", registerPlugin(); ) Declaration * cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); return DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition())).declaration); } ///The first definition that belongs to a context that surrounds the current cursor Declaration* cursorContextDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* ctx = DUChainUtils::standardContextForUrl(view->document()->url()); if (!ctx) { return nullptr; } KTextEditor::Cursor cursor(view->cursorPosition()); DUContext* subCtx = ctx->findContext(ctx->transformToLocalRevision(cursor)); while (subCtx && !subCtx->owner()) subCtx = subCtx->parentContext(); Declaration* definition = nullptr; if (!subCtx || !subCtx->owner()) { definition = DUChainUtils::declarationInLine(cursor, ctx); } else { definition = subCtx->owner(); } if (!definition) { return nullptr; } return definition; } //Returns only the name, no template-parameters or scope QString cursorItemText() { KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { return QString(); } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { return QString(); } TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return QString(); } AbstractType::Ptr t = decl->abstractType(); IdentifiedType* idType = dynamic_cast(t.data()); if (idType && idType->declaration(context)) { decl = idType->declaration(context); } if (!decl->qualifiedIdentifier().isEmpty()) { return decl->qualifiedIdentifier().last().identifier().str(); } return QString(); } QuickOpenLineEdit* QuickOpenPlugin::createQuickOpenLineWidget() { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(QStringList(), QStringList())); } QuickOpenLineEdit* QuickOpenPlugin::quickOpenLine(const QString& name) { const QList lines = ICore::self()->uiController()->activeMainWindow()->findChildren(name); for (QuickOpenLineEdit* line : lines) { if (line->isVisible()) { return line; } } return nullptr; } static QuickOpenPlugin* staticQuickOpenPlugin = nullptr; QuickOpenPlugin* QuickOpenPlugin::self() { return staticQuickOpenPlugin; } void QuickOpenPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) { xmlFile = QStringLiteral("kdevquickopen.rc"); QAction* quickOpen = actions.addAction(QStringLiteral("quick_open")); quickOpen->setText(i18n("&Quick Open")); quickOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen"))); actions.setDefaultShortcut(quickOpen, Qt::CTRL | Qt::ALT | Qt::Key_Q); connect(quickOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpen); QAction* quickOpenFile = actions.addAction(QStringLiteral("quick_open_file")); quickOpenFile->setText(i18n("Quick Open &File")); quickOpenFile->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); actions.setDefaultShortcut(quickOpenFile, Qt::CTRL | Qt::ALT | Qt::Key_O); connect(quickOpenFile, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFile); QAction* quickOpenClass = actions.addAction(QStringLiteral("quick_open_class")); quickOpenClass->setText(i18n("Quick Open &Class")); quickOpenClass->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-class"))); actions.setDefaultShortcut(quickOpenClass, Qt::CTRL | Qt::ALT | Qt::Key_C); connect(quickOpenClass, &QAction::triggered, this, &QuickOpenPlugin::quickOpenClass); QAction* quickOpenFunction = actions.addAction(QStringLiteral("quick_open_function")); quickOpenFunction->setText(i18n("Quick Open &Function")); quickOpenFunction->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-function"))); actions.setDefaultShortcut(quickOpenFunction, Qt::CTRL | Qt::ALT | Qt::Key_M); connect(quickOpenFunction, &QAction::triggered, this, &QuickOpenPlugin::quickOpenFunction); QAction* quickOpenAlreadyOpen = actions.addAction(QStringLiteral("quick_open_already_open")); quickOpenAlreadyOpen->setText(i18n("Quick Open &Already Open File")); quickOpenAlreadyOpen->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-file"))); connect(quickOpenAlreadyOpen, &QAction::triggered, this, &QuickOpenPlugin::quickOpenOpenFile); QAction* quickOpenDocumentation = actions.addAction(QStringLiteral("quick_open_documentation")); quickOpenDocumentation->setText(i18n("Quick Open &Documentation")); quickOpenDocumentation->setIcon(QIcon::fromTheme(QStringLiteral("quickopen-documentation"))); actions.setDefaultShortcut(quickOpenDocumentation, Qt::CTRL | Qt::ALT | Qt::Key_D); connect(quickOpenDocumentation, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDocumentation); QAction* quickOpenActions = actions.addAction(QStringLiteral("quick_open_actions")); quickOpenActions->setText(i18n("Quick Open &Actions")); actions.setDefaultShortcut(quickOpenActions, Qt::CTRL | Qt::ALT | Qt::Key_A); connect(quickOpenActions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenActions); m_quickOpenDeclaration = actions.addAction(QStringLiteral("quick_open_jump_declaration")); m_quickOpenDeclaration->setText(i18n("Jump to Declaration")); m_quickOpenDeclaration->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-declaration"))); actions.setDefaultShortcut(m_quickOpenDeclaration, Qt::CTRL | Qt::Key_Period); connect(m_quickOpenDeclaration, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDeclaration, Qt::QueuedConnection); m_quickOpenDefinition = actions.addAction(QStringLiteral("quick_open_jump_definition")); m_quickOpenDefinition->setText(i18n("Jump to Definition")); m_quickOpenDefinition->setIcon(QIcon::fromTheme(QStringLiteral("go-jump-definition"))); actions.setDefaultShortcut(m_quickOpenDefinition, Qt::CTRL | Qt::Key_Comma); connect(m_quickOpenDefinition, &QAction::triggered, this, &QuickOpenPlugin::quickOpenDefinition, Qt::QueuedConnection); QWidgetAction* quickOpenLine = new QWidgetAction(this); quickOpenLine->setText(i18n("Embedded Quick Open")); // actions.setDefaultShortcut( quickOpenLine, Qt::CTRL | Qt::ALT | Qt::Key_E ); // connect(quickOpenLine, SIGNAL(triggered(bool)), this, SLOT(quickOpenLine(bool))); quickOpenLine->setDefaultWidget(createQuickOpenLineWidget()); actions.addAction(QStringLiteral("quick_open_line"), quickOpenLine); QAction* quickOpenNextFunction = actions.addAction(QStringLiteral("quick_open_next_function")); quickOpenNextFunction->setText(i18n("Next Function")); actions.setDefaultShortcut(quickOpenNextFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageDown); connect(quickOpenNextFunction, &QAction::triggered, this, &QuickOpenPlugin::nextFunction); QAction* quickOpenPrevFunction = actions.addAction(QStringLiteral("quick_open_prev_function")); quickOpenPrevFunction->setText(i18n("Previous Function")); actions.setDefaultShortcut(quickOpenPrevFunction, Qt::CTRL | Qt::ALT | Qt::Key_PageUp); connect(quickOpenPrevFunction, &QAction::triggered, this, &QuickOpenPlugin::previousFunction); QAction* quickOpenNavigateFunctions = actions.addAction(QStringLiteral("quick_open_outline")); quickOpenNavigateFunctions->setText(i18n("Outline")); actions.setDefaultShortcut(quickOpenNavigateFunctions, Qt::CTRL | Qt::ALT | Qt::Key_N); connect(quickOpenNavigateFunctions, &QAction::triggered, this, &QuickOpenPlugin::quickOpenNavigateFunctions); } QuickOpenPlugin::QuickOpenPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin(QStringLiteral("kdevquickopen"), parent) { staticQuickOpenPlugin = this; m_model = new QuickOpenModel(nullptr); KConfigGroup quickopengrp = KSharedConfig::openConfig()->group("QuickOpen"); lastUsedScopes = quickopengrp.readEntry("SelectedScopes", QStringList{ i18n("Project"), i18n("Includes"), i18n("Includers"), i18n("Currently Open")}); lastUsedItems = quickopengrp.readEntry("SelectedItems", QStringList()); { m_openFilesData = new OpenFilesDataProvider(); QStringList scopes, items; scopes << i18n("Currently Open"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_openFilesData); } { m_projectFileData = new ProjectFileDataProvider(); QStringList scopes, items; scopes << i18n("Project"); items << i18n("Files"); m_model->registerProvider(scopes, items, m_projectFileData); } { m_projectItemData = new ProjectItemDataProvider(this); QStringList scopes, items; scopes << i18n("Project"); items << ProjectItemDataProvider::supportedItemTypes(); m_model->registerProvider(scopes, items, m_projectItemData); } { m_documentationItemData = new DocumentationQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Documentation"); m_model->registerProvider(scopes, items, m_documentationItemData); } { m_actionsItemData = new ActionsQuickOpenProvider; QStringList scopes, items; scopes << i18n("Includes"); items << i18n("Actions"); m_model->registerProvider(scopes, items, m_actionsItemData); } } QuickOpenPlugin::~QuickOpenPlugin() { freeModel(); delete m_model; delete m_projectFileData; delete m_projectItemData; delete m_openFilesData; delete m_documentationItemData; delete m_actionsItemData; } void QuickOpenPlugin::unload() { } ContextMenuExtension QuickOpenPlugin::contextMenuExtension(Context* context, QWidget* parent) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension(context, parent); KDevelop::DeclarationContext* codeContext = dynamic_cast(context); if (!codeContext) { return menuExt; } DUChainReadLocker readLock; Declaration* decl(codeContext->declaration().data()); if (decl) { const bool isDef = FunctionDefinition::definition(decl); if (codeContext->use().isValid() || !isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDeclaration); } if (isDef) { menuExt.addAction(KDevelop::ContextMenuExtension::NavigationGroup, m_quickOpenDefinition); } } return menuExt; } void QuickOpenPlugin::showQuickOpen(const QStringList& items) { if (!freeModel()) { return; } QStringList initialItems = items; QStringList useScopes = lastUsedScopes; if (!useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } showQuickOpenWidget(initialItems, useScopes, false); } void QuickOpenPlugin::showQuickOpen(ModelTypes modes) { if (!freeModel()) { return; } QStringList initialItems; if (modes & Files || modes & OpenFiles) { initialItems << i18n("Files"); } if (modes & Functions) { initialItems << i18n("Functions"); } if (modes & Classes) { initialItems << i18n("Classes"); } QStringList useScopes; if (modes != OpenFiles) { useScopes = lastUsedScopes; } if ((modes & OpenFiles) && !useScopes.contains(i18n("Currently Open"))) { useScopes << i18n("Currently Open"); } bool preselectText = (!(modes & Files) || modes == QuickOpenPlugin::All); showQuickOpenWidget(initialItems, useScopes, preselectText); } void QuickOpenPlugin::showQuickOpenWidget(const QStringList& items, const QStringList& scopes, bool preselectText) { QuickOpenWidgetDialog* dialog = new QuickOpenWidgetDialog(i18n("Quick Open"), m_model, items, scopes); m_currentWidgetHandler = dialog; if (preselectText) { KDevelop::IDocument* currentDoc = core()->documentController()->activeDocument(); if (currentDoc && currentDoc->isTextDocument()) { QString preselected = currentDoc->textSelection().isEmpty() ? currentDoc->textWord() : currentDoc->textDocument()->text(currentDoc->textSelection()); dialog->widget()->setPreselectedText(preselected); } } connect(dialog->widget(), &QuickOpenWidget::scopesChanged, this, &QuickOpenPlugin::storeScopes); //Not connecting itemsChanged to storeItems, as showQuickOpen doesn't use lastUsedItems and so shouldn't store item changes //connect( dialog->widget(), SIGNAL(itemsChanged(QStringList)), this, SLOT(storeItems(QStringList)) ); dialog->widget()->ui.itemsButton->setEnabled(false); if (quickOpenLine()) { quickOpenLine()->showWithWidget(dialog->widget()); dialog->deleteLater(); } else { dialog->run(); } } void QuickOpenPlugin::storeScopes(const QStringList& scopes) { lastUsedScopes = scopes; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedScopes", scopes); } void QuickOpenPlugin::storeItems(const QStringList& items) { lastUsedItems = items; KConfigGroup grp = KSharedConfig::openConfig()->group("QuickOpen"); grp.writeEntry("SelectedItems", items); } void QuickOpenPlugin::quickOpen() { if (quickOpenLine()) { //Same as clicking on Quick Open quickOpenLine()->setFocus(); } else { showQuickOpen(All); } } void QuickOpenPlugin::quickOpenFile() { showQuickOpen(( ModelTypes )(Files | OpenFiles)); } void QuickOpenPlugin::quickOpenFunction() { showQuickOpen(Functions); } void QuickOpenPlugin::quickOpenClass() { showQuickOpen(Classes); } void QuickOpenPlugin::quickOpenOpenFile() { showQuickOpen(OpenFiles); } void QuickOpenPlugin::quickOpenDocumentation() { showQuickOpenWidget(QStringList(i18n("Documentation")), QStringList(i18n("Includes")), true); } void QuickOpenPlugin::quickOpenActions() { showQuickOpenWidget(QStringList(i18n("Actions")), QStringList(i18n("Includes")), true); } QSet QuickOpenPlugin::fileSet() const { return m_model->fileSet(); } void QuickOpenPlugin::registerProvider(const QStringList& scope, const QStringList& type, KDevelop::QuickOpenDataProviderBase* provider) { m_model->registerProvider(scope, type, provider); } bool QuickOpenPlugin::removeProvider(KDevelop::QuickOpenDataProviderBase* provider) { m_model->removeProvider(provider); return true; } void QuickOpenPlugin::quickOpenDeclaration() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } decl->activateSpecialization(); IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } QWidget* QuickOpenPlugin::specialObjectNavigationWidget() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return nullptr; } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); for (const auto language : languages) { - QWidget* w = language->specialLanguageObjectNavigationWidget(url, KTextEditor::Cursor(view->cursorPosition())); + QWidget* w = language->specialLanguageObjectNavigationWidget(url, view->cursorPosition()).first; if (w) { return w; } } return nullptr; } QPair QuickOpenPlugin::specialObjectJumpPosition() const { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return qMakePair(QUrl(), KTextEditor::Cursor()); } QUrl url = ICore::self()->documentController()->activeDocument()->url(); const auto languages = ICore::self()->languageController()->languagesForUrl(url); for (const auto language : languages) { QPair pos = language->specialLanguageObjectJumpCursor(url, KTextEditor::Cursor(view->cursorPosition())); if (pos.second.isValid()) { return pos; } } return qMakePair(QUrl(), KTextEditor::Cursor::invalid()); } bool QuickOpenPlugin::jumpToSpecialObject() { QPair pos = specialObjectJumpPosition(); if (pos.second.isValid()) { if (pos.first.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for special language object"; return false; } ICore::self()->documentController()->openDocument(pos.first, pos.second); return true; } return false; } void QuickOpenPlugin::quickOpenDefinition() { if (jumpToSpecialObject()) { return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); Declaration* decl = cursorDeclaration(); if (!decl) { qCDebug(PLUGIN_QUICKOPEN) << "Found no declaration for cursor, cannot jump"; return; } IndexedString u = decl->url(); KTextEditor::Cursor c = decl->rangeInCurrentRevision().start(); if (FunctionDefinition* def = FunctionDefinition::definition(decl)) { def->activateSpecialization(); u = def->url(); c = def->rangeInCurrentRevision().start(); } else { qCDebug(PLUGIN_QUICKOPEN) << "Found no definition for declaration"; decl->activateSpecialization(); } if (u.isEmpty()) { qCDebug(PLUGIN_QUICKOPEN) << "Got empty url for declaration" << decl->toString(); return; } lock.unlock(); core()->documentController()->openDocument(u.toUrl(), c); } bool QuickOpenPlugin::freeModel() { if (m_currentWidgetHandler) { delete m_currentWidgetHandler; } m_currentWidgetHandler = nullptr; return true; } void QuickOpenPlugin::nextFunction() { jumpToNearestFunction(NextFunction); } void QuickOpenPlugin::previousFunction() { jumpToNearestFunction(PreviousFunction); } void QuickOpenPlugin::jumpToNearestFunction(QuickOpenPlugin::FunctionJumpDirection direction) { IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } QVector items; OutlineFilter filter(items, OutlineFilter::Functions); DUChainUtils::collectItems(context, filter); CursorInRevision cursor = context->transformToLocalRevision(KTextEditor::Cursor(doc->cursorPosition())); if (!cursor.isValid()) { return; } Declaration* nearestDeclBefore = nullptr; int distanceBefore = INT_MIN; Declaration* nearestDeclAfter = nullptr; int distanceAfter = INT_MAX; for (int i = 0; i < items.count(); ++i) { Declaration* decl = items[i].m_item.data(); int distance = decl->range().start.line - cursor.line; if (distance < 0 && distance >= distanceBefore) { distanceBefore = distance; nearestDeclBefore = decl; } else if (distance > 0 && distance <= distanceAfter) { distanceAfter = distance; nearestDeclAfter = decl; } } CursorInRevision c = CursorInRevision::invalid(); if (direction == QuickOpenPlugin::NextFunction && nearestDeclAfter) { c = nearestDeclAfter->range().start; } else if (direction == QuickOpenPlugin::PreviousFunction && nearestDeclBefore) { c = nearestDeclBefore->range().start; } KTextEditor::Cursor textCursor = KTextEditor::Cursor::invalid(); if (c.isValid()) { textCursor = context->transformFromLocalRevision(c); } lock.unlock(); if (textCursor.isValid()) { core()->documentController()->openDocument(doc->url(), textCursor); } else { qCDebug(PLUGIN_QUICKOPEN) << "No declaration to jump to"; } } struct CreateOutlineDialog { CreateOutlineDialog() : dialog(nullptr) , cursorDecl(nullptr) , model(nullptr) { } void start() { if (!QuickOpenPlugin::self()->freeModel()) { return; } IDocument* doc = ICore::self()->documentController()->activeDocument(); if (!doc) { qCDebug(PLUGIN_QUICKOPEN) << "No active document"; return; } KDevelop::DUChainReadLocker lock(DUChain::lock()); TopDUContext* context = DUChainUtils::standardContextForUrl(doc->url()); if (!context) { qCDebug(PLUGIN_QUICKOPEN) << "Got no standard context"; return; } model = new QuickOpenModel(nullptr); OutlineFilter filter(items); DUChainUtils::collectItems(context, filter); if (noHtmlDestriptionInOutline) { for (int a = 0; a < items.size(); ++a) { items[a].m_noHtmlDestription = true; } } cursorDecl = cursorContextDeclaration(); model->registerProvider(QStringList(), QStringList(), new DeclarationListDataProvider(QuickOpenPlugin::self(), items, true)); dialog = new QuickOpenWidgetDialog(i18n("Outline"), model, QStringList(), QStringList(), true); dialog->widget()->setSortingEnabled(true); model->setParent(dialog->widget()); } void finish() { //Select the declaration that contains the cursor if (cursorDecl && dialog) { int num = 0; foreach (const DUChainItem& item, items) { if (item.m_item.data() == cursorDecl) { QModelIndex index(model->index(num, 0, QModelIndex())); // Need to invoke the scrolling later. If we did it now, then it wouldn't have any effect, // apparently because the widget internals aren't initialized yet properly (although we've // already called 'widget->show()'. auto list = dialog->widget()->ui.list; QMetaObject::invokeMethod(list, "setCurrentIndex", Qt::QueuedConnection, Q_ARG(QModelIndex, index)); QMetaObject::invokeMethod(list, "scrollTo", Qt::QueuedConnection, Q_ARG(QModelIndex, index), Q_ARG(QAbstractItemView::ScrollHint, QAbstractItemView::PositionAtCenter)); } ++num; } } } QPointer dialog; Declaration* cursorDecl; QVector items; QuickOpenModel* model; }; class OutlineQuickopenWidgetCreator : public QuickOpenWidgetCreator { public: OutlineQuickopenWidgetCreator(const QStringList& /*scopes*/, const QStringList& /*items*/) : m_creator(nullptr) { } ~OutlineQuickopenWidgetCreator() override { delete m_creator; } QuickOpenWidget* createWidget() override { delete m_creator; m_creator = new CreateOutlineDialog; m_creator->start(); if (!m_creator->dialog) { return nullptr; } m_creator->dialog->deleteLater(); return m_creator->dialog->widget(); } void widgetShown() override { if (m_creator) { m_creator->finish(); delete m_creator; m_creator = nullptr; } } QString objectNameForLine() override { return QStringLiteral("Outline"); } CreateOutlineDialog* m_creator; }; void QuickOpenPlugin::quickOpenNavigateFunctions() { CreateOutlineDialog create; create.start(); if (!create.dialog) { return; } m_currentWidgetHandler = create.dialog; QuickOpenLineEdit* line = quickOpenLine(QStringLiteral("Outline")); if (!line) { line = quickOpenLine(); } if (line) { line->showWithWidget(create.dialog->widget()); create.dialog->deleteLater(); } else { create.dialog->run(); } create.finish(); } QuickOpenLineEdit::QuickOpenLineEdit(QuickOpenWidgetCreator* creator) : m_widget(nullptr) , m_forceUpdate(false) , m_widgetCreator(creator) { setFont(qApp->font("QToolButton")); setMinimumWidth(200); setMaximumWidth(400); deactivate(); setDefaultText(i18n("Quick Open...")); setToolTip(i18n("Search for files, classes, functions and more," " allowing you to quickly navigate in your source code.")); setObjectName(m_widgetCreator->objectNameForLine()); setFocusPolicy(Qt::ClickFocus); } QuickOpenLineEdit::~QuickOpenLineEdit() { delete m_widget; delete m_widgetCreator; } bool QuickOpenLineEdit::insideThis(QObject* object) { while (object) { qCDebug(PLUGIN_QUICKOPEN) << object; if (object == this || object == m_widget) { return true; } object = object->parent(); } return false; } void QuickOpenLineEdit::widgetDestroyed(QObject* obj) { Q_UNUSED(obj); // need to use a queued connection here, because this function is called in ~QWidget! // => QuickOpenWidget instance is half-destructed => connections are not yet cleared // => clear() will trigger signals which will operate on the invalid QuickOpenWidget // So, just wait until properly destructed QMetaObject::invokeMethod(this, "deactivate", Qt::QueuedConnection); } void QuickOpenLineEdit::showWithWidget(QuickOpenWidget* widget) { connect(widget, &QuickOpenWidget::destroyed, this, &QuickOpenLineEdit::widgetDestroyed); qCDebug(PLUGIN_QUICKOPEN) << "storing widget" << widget; deactivate(); if (m_widget) { qCDebug(PLUGIN_QUICKOPEN) << "deleting" << m_widget; delete m_widget; } m_widget = widget; m_forceUpdate = true; setFocus(); } void QuickOpenLineEdit::focusInEvent(QFocusEvent* ev) { QLineEdit::focusInEvent(ev); // delete m_widget; qCDebug(PLUGIN_QUICKOPEN) << "got focus"; qCDebug(PLUGIN_QUICKOPEN) << "old widget" << m_widget << "force update:" << m_forceUpdate; if (m_widget && !m_forceUpdate) { return; } if (!m_forceUpdate && !QuickOpenPlugin::self()->freeModel()) { deactivate(); return; } m_forceUpdate = false; if (!m_widget) { m_widget = m_widgetCreator->createWidget(); if (!m_widget) { deactivate(); return; } } activate(); m_widget->showStandardButtons(false); m_widget->showSearchField(false); m_widget->setParent(nullptr, Qt::ToolTip); m_widget->setFocusPolicy(Qt::NoFocus); m_widget->setAlternativeSearchField(this); QuickOpenPlugin::self()->m_currentWidgetHandler = m_widget; connect(m_widget.data(), &QuickOpenWidget::ready, this, &QuickOpenLineEdit::deactivate); connect(m_widget.data(), &QuickOpenWidget::scopesChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeScopes); connect(m_widget.data(), &QuickOpenWidget::itemsChanged, QuickOpenPlugin::self(), &QuickOpenPlugin::storeItems); Q_ASSERT(m_widget->ui.searchLine == this); m_widget->prepareShow(); QRect widgetGeometry = QRect(mapToGlobal(QPoint(0, height())), mapToGlobal(QPoint(width(), height() + 400))); widgetGeometry.setWidth(700); ///@todo Waste less space QRect screenGeom = QApplication::desktop()->screenGeometry(this); if (widgetGeometry.right() > screenGeom.right()) { widgetGeometry.moveRight(screenGeom.right()); } if (widgetGeometry.bottom() > screenGeom.bottom()) { widgetGeometry.moveBottom(mapToGlobal(QPoint(0, 0)).y()); } m_widget->setGeometry(widgetGeometry); m_widget->show(); m_widgetCreator->widgetShown(); } void QuickOpenLineEdit::hideEvent(QHideEvent* ev) { QWidget::hideEvent(ev); if (m_widget) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } // deactivate(); } bool QuickOpenLineEdit::eventFilter(QObject* obj, QEvent* e) { if (!m_widget) { return IQuickOpenLine::eventFilter(obj, e); } switch (e->type()) { case QEvent::KeyPress: case QEvent::ShortcutOverride: if (static_cast(e)->key() == Qt::Key_Escape) { deactivate(); e->accept(); return true; // eat event } break; case QEvent::WindowActivate: case QEvent::WindowDeactivate: QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; // handle bug 260657 - "Outline menu doesn't follow main window on its move" case QEvent::Move: { if (QWidget* widget = qobject_cast(obj)) { // close the outline menu in case a parent widget moved if (widget->isAncestorOf(this)) { qCDebug(PLUGIN_QUICKOPEN) << "closing because of parent widget move"; deactivate(); } } break; } case QEvent::FocusIn: if (dynamic_cast(obj)) { QFocusEvent* focusEvent = dynamic_cast(e); Q_ASSERT(focusEvent); //Eat the focus event, keep the focus qCDebug(PLUGIN_QUICKOPEN) << "focus change" << "inside this: " << insideThis(obj) << "this" << this << "obj" << obj; if (obj == this) { break; } qCDebug(PLUGIN_QUICKOPEN) << "reason" << focusEvent->reason(); if (focusEvent->reason() != Qt::MouseFocusReason && focusEvent->reason() != Qt::ActiveWindowFocusReason) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); break; } if (!insideThis(obj)) { deactivate(); } } else if (obj != this) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } break; default: break; } return IQuickOpenLine::eventFilter(obj, e); } void QuickOpenLineEdit::activate() { qCDebug(PLUGIN_QUICKOPEN) << "activating"; setText(QString()); setStyleSheet(QString()); qApp->installEventFilter(this); } void QuickOpenLineEdit::deactivate() { qCDebug(PLUGIN_QUICKOPEN) << "deactivating"; clear(); if (m_widget || hasFocus()) { QMetaObject::invokeMethod(this, "checkFocus", Qt::QueuedConnection); } if (m_widget) { m_widget->deleteLater(); } m_widget = nullptr; qApp->removeEventFilter(this); } void QuickOpenLineEdit::checkFocus() { qCDebug(PLUGIN_QUICKOPEN) << "checking focus" << m_widget; if (m_widget) { QWidget* focusWidget = QApplication::focusWidget(); bool focusWidgetInsideThis = focusWidget ? insideThis(focusWidget) : false; if (QApplication::focusWindow() && isVisible() && !isHidden() && (!focusWidget || (focusWidget && focusWidgetInsideThis))) { qCDebug(PLUGIN_QUICKOPEN) << "setting focus to line edit"; activateWindow(); setFocus(); } else { qCDebug(PLUGIN_QUICKOPEN) << "deactivating because check failed, focusWidget" << focusWidget << "insideThis" << focusWidgetInsideThis; deactivate(); } } else { if (ICore::self()->documentController()->activeDocument()) { ICore::self()->documentController()->activateDocument(ICore::self()->documentController()->activeDocument()); } //Make sure the focus is somewehre else, even if there is no active document setEnabled(false); setEnabled(true); } } IQuickOpenLine* QuickOpenPlugin::createQuickOpenLine(const QStringList& scopes, const QStringList& type, IQuickOpen::QuickOpenType kind) { if (kind == Outline) { return new QuickOpenLineEdit(new OutlineQuickopenWidgetCreator(scopes, type)); } else { return new QuickOpenLineEdit(new StandardQuickOpenWidgetCreator(scopes, type)); } } #include "quickopenplugin.moc"