Warning, file /kdevelop/kdevelop/plugins/switchtobuddy/switchtobuddyplugin.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2012 André Stein <andre.stein@rwth-aachen.de> 0003 SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "switchtobuddyplugin.h" 0009 0010 #include <KLocalizedString> 0011 #include <KPluginFactory> 0012 #include <KActionCollection> 0013 #include <QAction> 0014 #include <QFile> 0015 #include <QMimeDatabase> 0016 #include <QMimeType> 0017 0018 #include <interfaces/icore.h> 0019 #include <interfaces/idocumentcontroller.h> 0020 #include <interfaces/ibuddydocumentfinder.h> 0021 #include <interfaces/contextmenuextension.h> 0022 #include <language/interfaces/editorcontext.h> 0023 #include <language/duchain/duchainutils.h> 0024 #include <language/duchain/functiondefinition.h> 0025 #include <language/duchain/parsingenvironment.h> 0026 #include <language/duchain/classfunctiondeclaration.h> 0027 #include <debug.h> 0028 0029 #include <KTextEditor/Document> 0030 #include <KTextEditor/View> 0031 0032 using namespace KDevelop; 0033 0034 namespace { 0035 0036 KTextEditor::Cursor normalizeCursor(KTextEditor::Cursor c) 0037 { 0038 c.setColumn(0); 0039 return c; 0040 } 0041 0042 ///Tries to find a definition for the declaration at given cursor-position and document-url. DUChain must be locked. 0043 Declaration* definitionForCursorDeclaration(const KTextEditor::Cursor& cursor, const QUrl& url) 0044 { 0045 const QList<TopDUContext*> topContexts = DUChain::self()->chainsForDocument(url); 0046 for (TopDUContext* ctx : topContexts) { 0047 Declaration* decl = DUChainUtils::declarationInLine(cursor, ctx); 0048 if (!decl) { 0049 continue; 0050 } 0051 if (auto definition = FunctionDefinition::definition(decl)) { 0052 return definition; 0053 } 0054 } 0055 return nullptr; 0056 } 0057 0058 QString findSwitchCandidate(const QUrl& docUrl) 0059 { 0060 QMimeDatabase db; 0061 IBuddyDocumentFinder* finder = IBuddyDocumentFinder::finderForMimeType(db.mimeTypeForUrl(docUrl).name()); 0062 if (finder) { 0063 // get the first entry that exists, use that as candidate 0064 const auto potentialBuddies = finder->potentialBuddies(docUrl); 0065 for (const QUrl& buddyUrl : potentialBuddies) { 0066 if (!QFile::exists(buddyUrl.toLocalFile())) { 0067 continue; 0068 } 0069 0070 return buddyUrl.toLocalFile(); 0071 } 0072 } 0073 return QString(); 0074 } 0075 0076 } 0077 0078 K_PLUGIN_FACTORY_WITH_JSON(SwitchToBuddyPluginFactory, "kdevswitchtobuddy.json", registerPlugin<SwitchToBuddyPlugin>(); ) 0079 0080 SwitchToBuddyPlugin::SwitchToBuddyPlugin ( QObject* parent, const QVariantList& ) 0081 : IPlugin ( QStringLiteral("kdevswitchtobuddy"), parent ) 0082 { 0083 setXMLFile(QStringLiteral("kdevswitchtobuddy.rc")); 0084 } 0085 0086 SwitchToBuddyPlugin::~SwitchToBuddyPlugin() 0087 { 0088 } 0089 0090 ContextMenuExtension SwitchToBuddyPlugin::contextMenuExtension(Context* context, QWidget* parent) 0091 { 0092 auto* ctx = dynamic_cast<EditorContext*>(context); 0093 if (!ctx) { 0094 return ContextMenuExtension(); 0095 } 0096 0097 QUrl currentUrl = ctx->url(); 0098 IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(QMimeDatabase().mimeTypeForUrl(currentUrl).name()); 0099 if (!buddyFinder) 0100 return ContextMenuExtension(); 0101 0102 // Get all potential buddies for the current document and add a switch-to action 0103 // for each buddy who really exists in the file system. Note: if no buddies could be calculated 0104 // no extension actions are generated. 0105 const QVector<QUrl>& potentialBuddies = buddyFinder->potentialBuddies(currentUrl); 0106 0107 ContextMenuExtension extension; 0108 0109 for (const QUrl& url : potentialBuddies) { 0110 if (!QFile::exists(url.toLocalFile())) { 0111 continue; 0112 } 0113 0114 auto* action = new QAction(i18nc("@action:inmenu", "Switch to '%1'", url.fileName()), parent); 0115 const QString surl = url.toLocalFile(); 0116 connect(action, &QAction::triggered, this, [this, surl](){ switchToBuddy(surl); }, Qt::QueuedConnection); 0117 extension.addAction(ContextMenuExtension::NavigationGroup, action); 0118 } 0119 0120 return extension; 0121 } 0122 0123 void SwitchToBuddyPlugin::createActionsForMainWindow(Sublime::MainWindow* /*window*/, QString& xmlFile, KActionCollection& actions) 0124 { 0125 xmlFile = this->xmlFile(); 0126 0127 QAction* switchDefinitionDeclaration = actions.addAction(QStringLiteral("switch_definition_declaration")); 0128 switchDefinitionDeclaration->setText(i18nc("@action", "&Switch Definition/Declaration")); 0129 actions.setDefaultShortcut(switchDefinitionDeclaration, Qt::CTRL | Qt::SHIFT | Qt::Key_C); 0130 connect(switchDefinitionDeclaration, &QAction::triggered, this, &SwitchToBuddyPlugin::switchDefinitionDeclaration); 0131 0132 QAction* switchHeaderSource = actions.addAction(QStringLiteral("switch_header_source")); 0133 switchHeaderSource->setText(i18nc("@action", "Switch Header/Source")); 0134 actions.setDefaultShortcut(switchHeaderSource, Qt::CTRL | Qt::Key_Slash); 0135 connect(switchHeaderSource, &QAction::triggered, this, &SwitchToBuddyPlugin::switchHeaderSource); 0136 } 0137 0138 void SwitchToBuddyPlugin::switchHeaderSource() 0139 { 0140 qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching header/source"; 0141 0142 auto doc = ICore::self()->documentController()->activeDocument(); 0143 if (!doc) 0144 return; 0145 0146 QString buddyUrl = findSwitchCandidate(doc->url()); 0147 if (!buddyUrl.isEmpty()) 0148 switchToBuddy(buddyUrl); 0149 } 0150 0151 void SwitchToBuddyPlugin::switchToBuddy(const QString& url) 0152 { 0153 KDevelop::ICore::self()->documentController()->openDocument(QUrl::fromLocalFile(url)); 0154 } 0155 0156 void SwitchToBuddyPlugin::switchDefinitionDeclaration() 0157 { 0158 qCDebug(PLUGIN_SWITCHTOBUDDY) << "switching definition/declaration"; 0159 0160 QUrl docUrl; 0161 KTextEditor::Cursor cursor; 0162 0163 ///Step 1: Find the current top-level context of type DUContext::Other(the highest code-context). 0164 ///-- If it belongs to a function-declaration or definition, it can be retrieved through owner(), and we are in a definition. 0165 ///-- If no such context could be found, search for a declaration on the same line as the cursor, and switch to the according definition 0166 { 0167 auto view = ICore::self()->documentController()->activeTextDocumentView(); 0168 if (!view) { 0169 qCDebug(PLUGIN_SWITCHTOBUDDY) << "No active document"; 0170 return; 0171 } 0172 0173 docUrl = view->document()->url(); 0174 cursor = view->cursorPosition(); 0175 } 0176 0177 QString switchCandidate = findSwitchCandidate(docUrl); 0178 if(!switchCandidate.isEmpty()) { 0179 0180 DUChainReadLocker lock; 0181 0182 //If the file has not been parsed yet, update it 0183 TopDUContext* ctx = DUChainUtils::standardContextForUrl(docUrl); 0184 //At least 'VisibleDeclarationsAndContexts' is required so we can do a switch 0185 if (!ctx || (ctx->parsingEnvironmentFile() && !ctx->parsingEnvironmentFile()->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses))) { 0186 lock.unlock(); 0187 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Parsing switch-candidate before switching" << switchCandidate; 0188 ReferencedTopDUContext updatedContext = DUChain::self()->waitForUpdate(IndexedString(switchCandidate), TopDUContext::AllDeclarationsContextsAndUses); 0189 if (!updatedContext) { 0190 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Failed to update document:" << switchCandidate; 0191 return; 0192 } 0193 } 0194 } 0195 0196 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Document:" << docUrl; 0197 0198 DUChainReadLocker lock; 0199 0200 TopDUContext* standardCtx = DUChainUtils::standardContextForUrl(docUrl); 0201 0202 bool wasSignal = false; 0203 if (standardCtx) { 0204 Declaration* definition = nullptr; 0205 0206 DUContext* ctx = standardCtx->findContext(standardCtx->transformToLocalRevision(cursor)); 0207 if (!ctx) { 0208 ctx = standardCtx; 0209 } 0210 0211 while (ctx && ctx->parentContext() && (ctx->parentContext()->type() == DUContext::Other || ctx->parentContext()->type() == DUContext::Function)) { 0212 ctx = ctx->parentContext(); 0213 } 0214 0215 if (ctx && ctx->owner() && (ctx->type() == DUContext::Other || ctx->type() == DUContext::Function) && ctx->owner()->isDefinition()) { 0216 definition = ctx->owner(); 0217 qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition while traversing:" << definition->toString(); 0218 } 0219 0220 if (!definition && ctx) { 0221 definition = DUChainUtils::declarationInLine(cursor, ctx); 0222 } 0223 0224 if (auto* cDef = dynamic_cast<ClassFunctionDeclaration*>(definition)) { 0225 if (cDef->isSignal()) { 0226 qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition is a signal, not switching to .moc implementation"; 0227 definition = nullptr; 0228 wasSignal = true; 0229 } 0230 } 0231 0232 auto* def = dynamic_cast<FunctionDefinition*>(definition); 0233 if (def && def->declaration()) { 0234 Declaration* declaration = def->declaration(); 0235 KTextEditor::Range targetRange = declaration->rangeInCurrentRevision(); 0236 const auto url = declaration->url().toUrl(); 0237 qCDebug(PLUGIN_SWITCHTOBUDDY) << "found definition that has declaration: " << definition->toString() << "range" << targetRange << "url" << url; 0238 lock.unlock(); 0239 0240 auto view = ICore::self()->documentController()->activeTextDocumentView(); 0241 if (view && !targetRange.contains(view->cursorPosition())) { 0242 const auto pos = normalizeCursor(targetRange.start()); 0243 ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); 0244 } else { 0245 ICore::self()->documentController()->openDocument(url); 0246 } 0247 return; 0248 } else { 0249 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Definition has no assigned declaration"; 0250 } 0251 0252 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Could not get definition/declaration from context"; 0253 } else { 0254 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Got no context for the current document"; 0255 } 0256 0257 Declaration* def = nullptr; 0258 if (!wasSignal) { 0259 def = definitionForCursorDeclaration(cursor, docUrl); 0260 } 0261 0262 if (def) { 0263 const auto url = def->url().toUrl(); 0264 KTextEditor::Range targetRange = def->rangeInCurrentRevision(); 0265 0266 if (def->internalContext()) { 0267 targetRange.end() = def->internalContext()->rangeInCurrentRevision().end(); 0268 } else { 0269 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Declaration does not have internal context"; 0270 } 0271 lock.unlock(); 0272 0273 auto view = ICore::self()->documentController()->activeTextDocumentView(); 0274 if (view && !targetRange.contains(view->cursorPosition())) { 0275 KTextEditor::Cursor pos(normalizeCursor(targetRange.start())); 0276 ICore::self()->documentController()->openDocument(url, KTextEditor::Range(pos, pos)); 0277 } else { 0278 //The cursor is already in the target range, only open the document 0279 ICore::self()->documentController()->openDocument(url); 0280 } 0281 return; 0282 } else if (!wasSignal) { 0283 qCWarning(PLUGIN_SWITCHTOBUDDY) << "Found no definition assigned to cursor position"; 0284 } 0285 0286 lock.unlock(); 0287 0288 ///- If no definition/declaration could be found to switch to, just switch the document using normal header/source heuristic by file-extension 0289 if (!switchCandidate.isEmpty()) { 0290 ICore::self()->documentController()->openDocument(QUrl::fromUserInput(switchCandidate)); 0291 } else { 0292 qCDebug(PLUGIN_SWITCHTOBUDDY) << "Found no source/header candidate to switch"; 0293 } 0294 } 0295 0296 #include "switchtobuddyplugin.moc" 0297 #include "moc_switchtobuddyplugin.cpp"