From eb7ccfd8893607d0b8a46c17ed444233c353bf8e Mon Sep 17 00:00:00 2001 From: David Schulz Date: Wed, 22 Mar 2023 10:39:09 +0100 Subject: [PATCH] LanguageClient: add action to open call hierarchy Fixes: QTCREATORBUG-28839 Fixes: QTCREATORBUG-28842 Change-Id: Icb70412282c0c2c36241559d942a58ffddab5664 Reviewed-by: Christian Stenger --- src/plugins/cppeditor/cppeditorplugin.cpp | 4 ++ src/plugins/languageclient/callhierarchy.cpp | 47 +++++++++++-------- src/plugins/languageclient/callhierarchy.h | 6 +++ src/plugins/languageclient/client.cpp | 3 ++ .../languageclient/languageclient_global.h | 2 + .../languageclient/languageclientmanager.cpp | 11 +++++ .../languageclient/languageclientmanager.h | 1 + src/plugins/texteditor/texteditor.cpp | 10 ++++ src/plugins/texteditor/texteditor.h | 2 + .../texteditor/texteditoractionhandler.cpp | 5 ++ .../texteditor/texteditoractionhandler.h | 3 +- src/plugins/texteditor/texteditorconstants.h | 1 + 12 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/plugins/cppeditor/cppeditorplugin.cpp b/src/plugins/cppeditor/cppeditorplugin.cpp index f1fc63b49ab..2d4cabe726e 100644 --- a/src/plugins/cppeditor/cppeditorplugin.cpp +++ b/src/plugins/cppeditor/cppeditorplugin.cpp @@ -431,6 +431,10 @@ void CppEditorPlugin::initialize() contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); cppToolsMenu->addAction(cmd); + cmd = ActionManager::command(TextEditor::Constants::OPEN_CALL_HIERARCHY); + contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST); + cppToolsMenu->addAction(cmd); + // Refactoring sub-menu Command *sep = contextMenu->addSeparator(); sep->action()->setObjectName(QLatin1String(Constants::M_REFACTORING_MENU_INSERTION_POINT)); diff --git a/src/plugins/languageclient/callhierarchy.cpp b/src/plugins/languageclient/callhierarchy.cpp index 069e31b163b..5a8cb9e88e3 100644 --- a/src/plugins/languageclient/callhierarchy.cpp +++ b/src/plugins/languageclient/callhierarchy.cpp @@ -23,8 +23,6 @@ using namespace LanguageServerProtocol; namespace LanguageClient { -const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy"; - namespace { enum Direction { Incoming, Outgoing }; @@ -186,6 +184,9 @@ public: layout()->setSpacing(0); connect(m_view, &NavigationTreeView::activated, this, &CallHierarchy::onItemActivated); + + connect(LanguageClientManager::instance(), &LanguageClientManager::openCallHierarchy, + this, &CallHierarchy::updateHierarchyAtCursorPosition); } void onItemActivated(const QModelIndex &index) @@ -211,26 +212,14 @@ void CallHierarchy::updateHierarchyAtCursorPosition() BaseTextEditor *editor = BaseTextEditor::currentTextEditor(); if (!editor) return; - Client *client = LanguageClientManager::clientForFilePath(editor->document()->filePath()); + + Core::IDocument *document = editor->document(); + + Client *client = LanguageClientManager::clientForFilePath(document->filePath()); if (!client) return; - const QString methodName = PrepareCallHierarchyRequest::methodName; - std::optional registered = client->dynamicCapabilities().isRegistered(methodName); - bool supported = registered.value_or(false); - const Core::IDocument *document = editor->document(); - if (registered) { - if (supported) { - const QJsonValue &options = client->dynamicCapabilities().option(methodName); - const TextDocumentRegistrationOptions docOptions(options); - supported = docOptions.filterApplies(document->filePath(), - Utils::mimeTypeForName(document->mimeType())); - } - } else { - supported = client->capabilities().callHierarchyProvider().has_value(); - } - - if (!supported) + if (!CallHierarchyFactory::supportsCallHierarchy(client, document)) return; TextDocumentPositionParams params; @@ -273,7 +262,25 @@ CallHierarchyFactory::CallHierarchyFactory() { setDisplayName(Tr::tr("Call Hierarchy")); setPriority(650); - setId(CALL_HIERARCHY_FACTORY_ID); + setId(Constants::CALL_HIERARCHY_FACTORY_ID); +} + +bool CallHierarchyFactory::supportsCallHierarchy(Client *client, const Core::IDocument *document) +{ + const QString methodName = PrepareCallHierarchyRequest::methodName; + std::optional registered = client->dynamicCapabilities().isRegistered(methodName); + bool supported = registered.value_or(false); + if (registered) { + if (supported) { + const QJsonValue &options = client->dynamicCapabilities().option(methodName); + const TextDocumentRegistrationOptions docOptions(options); + supported = docOptions.filterApplies(document->filePath(), + Utils::mimeTypeForName(document->mimeType())); + } + } else { + supported = client->capabilities().callHierarchyProvider().has_value(); + } + return supported; } Core::NavigationView CallHierarchyFactory::createWidget() diff --git a/src/plugins/languageclient/callhierarchy.h b/src/plugins/languageclient/callhierarchy.h index f707c4fbcbb..bbc15b09712 100644 --- a/src/plugins/languageclient/callhierarchy.h +++ b/src/plugins/languageclient/callhierarchy.h @@ -5,8 +5,12 @@ #pragma once +namespace Core { class IDocument; } + namespace LanguageClient { +class Client; + class CallHierarchyFactory : public Core::INavigationWidgetFactory { Q_OBJECT @@ -14,6 +18,8 @@ class CallHierarchyFactory : public Core::INavigationWidgetFactory public: CallHierarchyFactory(); + static bool supportsCallHierarchy(Client *client, const Core::IDocument *document); + Core::NavigationView createWidget() override; }; diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 461e908bcf9..17248c7ca29 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -3,6 +3,7 @@ #include "client.h" +#include "callhierarchy.h" #include "diagnosticmanager.h" #include "documentsymbolcache.h" #include "languageclientcompletionassist.h" @@ -879,6 +880,8 @@ void Client::activateEditor(Core::IEditor *editor) optionalActions |= TextEditor::TextEditorActionHandler::FindUsage; if (symbolSupport().supportsRename(widget->textDocument())) optionalActions |= TextEditor::TextEditorActionHandler::RenameSymbol; + if (CallHierarchyFactory::supportsCallHierarchy(this, textEditor->document())) + optionalActions |= TextEditor::TextEditorActionHandler::CallHierarchy; widget->setOptionalActions(optionalActions); } } diff --git a/src/plugins/languageclient/languageclient_global.h b/src/plugins/languageclient/languageclient_global.h index 0dca3e6bbfc..d115d10a252 100644 --- a/src/plugins/languageclient/languageclient_global.h +++ b/src/plugins/languageclient/languageclient_global.h @@ -29,5 +29,7 @@ const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_N const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID[] = "Workspace Functions and Methods"; const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Functions and Methods in Workspace"); +const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy"; + } // namespace Constants } // namespace LanguageClient diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp index 56ef4a1c3a6..f21a951607d 100644 --- a/src/plugins/languageclient/languageclientmanager.cpp +++ b/src/plugins/languageclient/languageclientmanager.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -448,6 +449,8 @@ QList LanguageClientManager::reachableClients() void LanguageClientManager::editorOpened(Core::IEditor *editor) { using namespace TextEditor; + using namespace Core; + if (auto *textEditor = qobject_cast(editor)) { if (TextEditorWidget *widget = textEditor->editorWidget()) { connect(widget, &TextEditorWidget::requestLinkAt, this, @@ -466,6 +469,14 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor) if (auto client = clientForDocument(document)) client->symbolSupport().renameSymbol(document, cursor); }); + connect(widget, &TextEditorWidget::requestCallHierarchy, this, + [this, textEditor](const QTextCursor &cursor) { + if (auto client = clientForDocument(textEditor->textDocument())) { + emit openCallHierarchy(); + NavigationWidget::activateSubWidget(Constants::CALL_HIERARCHY_FACTORY_ID, + Side::Left); + } + }); connect(widget, &TextEditorWidget::cursorPositionChanged, this, [widget]() { if (Client *client = clientForDocument(widget->textDocument())) if (client->reachable()) diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h index db6357ee9e1..cf90a2f7468 100644 --- a/src/plugins/languageclient/languageclientmanager.h +++ b/src/plugins/languageclient/languageclientmanager.h @@ -83,6 +83,7 @@ signals: void clientInitialized(Client *client); void clientRemoved(Client *client); void shutdownFinished(); + void openCallHierarchy(); private: LanguageClientManager(QObject *parent); diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 6d3593aab7b..ccbdb0467d0 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -2348,6 +2348,11 @@ void TextEditorWidget::renameSymbolUnderCursor() emit requestRename(textCursor()); } +void TextEditorWidget::openCallHierarchy() +{ + emit requestCallHierarchy(textCursor()); +} + void TextEditorWidget::abortAssist() { d->m_codeAssistant.destroyContext(); @@ -8225,6 +8230,11 @@ void TextEditorWidget::appendStandardContextMenuActions(QMenu *menu) if (!menu->actions().contains(findUsage)) menu->addAction(findUsage); } + if (optionalActions() & TextEditorActionHandler::CallHierarchy) { + const auto callHierarchy = ActionManager::command(Constants::OPEN_CALL_HIERARCHY)->action(); + if (!menu->actions().contains(callHierarchy)) + menu->addAction(callHierarchy); + } menu->addSeparator(); appendMenuActionsFromContext(menu, Constants::M_STANDARDCONTEXTMENU); diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h index 31c4c9d6bf4..4234645d50a 100644 --- a/src/plugins/texteditor/texteditor.h +++ b/src/plugins/texteditor/texteditor.h @@ -437,6 +437,7 @@ public: virtual void findUsages(); virtual void renameSymbolUnderCursor(); + virtual void openCallHierarchy(); /// Abort code assistant if it is running. void abortAssist(); @@ -487,6 +488,7 @@ signals: bool resolveTarget, bool inNextSplit); void requestUsages(const QTextCursor &cursor); void requestRename(const QTextCursor &cursor); + void requestCallHierarchy(const QTextCursor &cursor); void optionalActionMaskChanged(); void toolbarOutlineChanged(QWidget *newOutline); diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp index 25116729c99..00ed94a5ee7 100644 --- a/src/plugins/texteditor/texteditoractionhandler.cpp +++ b/src/plugins/texteditor/texteditoractionhandler.cpp @@ -116,6 +116,7 @@ public: QAction *m_followSymbolAction = nullptr; QAction *m_followSymbolInNextSplitAction = nullptr; QAction *m_findUsageAction = nullptr; + QAction *m_openCallHierarchyAction = nullptr; QAction *m_renameSymbolAction = nullptr; QAction *m_jumpToFileAction = nullptr; QAction *m_jumpToFileInNextSplitAction = nullptr; @@ -228,6 +229,8 @@ void TextEditorActionHandlerPrivate::createActions() m_jumpToFileInNextSplitAction = registerAction(JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT, [] (TextEditorWidget *w) { w->openLinkUnderCursorInNextSplit(); }, true, Tr::tr("Jump to File Under Cursor in Next Split"), QKeySequence(Utils::HostOsInfo::isMacHost() ? Tr::tr("Meta+E, F2") : Tr::tr("Ctrl+E, F2")).toString()); + m_openCallHierarchyAction = registerAction(OPEN_CALL_HIERARCHY, + [] (TextEditorWidget *w) { w->openCallHierarchy(); }, true, Tr::tr("Open Call Hierarchy")); registerAction(VIEW_PAGE_UP, [] (TextEditorWidget *w) { w->viewPageUp(); }, true, Tr::tr("Move the View a Page Up and Keep the Cursor Position"), @@ -484,6 +487,8 @@ void TextEditorActionHandlerPrivate::updateOptionalActions() optionalActions & TextEditorActionHandler::UnCollapseAll); m_renameSymbolAction->setEnabled( optionalActions & TextEditorActionHandler::RenameSymbol); + m_openCallHierarchyAction->setEnabled( + optionalActions & TextEditorActionHandler::CallHierarchy); bool formatEnabled = (optionalActions & TextEditorActionHandler::Format) && m_currentEditorWidget && !m_currentEditorWidget->isReadOnly(); diff --git a/src/plugins/texteditor/texteditoractionhandler.h b/src/plugins/texteditor/texteditoractionhandler.h index 277c2cf66f4..2bdb8efff3e 100644 --- a/src/plugins/texteditor/texteditoractionhandler.h +++ b/src/plugins/texteditor/texteditoractionhandler.h @@ -36,7 +36,8 @@ public: FollowSymbolUnderCursor = 8, JumpToFileUnderCursor = 16, RenameSymbol = 32, - FindUsage = 64 + FindUsage = 64, + CallHierarchy = 128 }; using TextEditorWidgetResolver = std::function; diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h index 0560dae9d82..f422630f0d2 100644 --- a/src/plugins/texteditor/texteditorconstants.h +++ b/src/plugins/texteditor/texteditorconstants.h @@ -208,6 +208,7 @@ const char FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.FollowSymbol const char FIND_USAGES[] = "TextEditor.FindUsages"; // moved from CppEditor to TextEditor avoid breaking the setting by using the old key const char RENAME_SYMBOL[] = "CppEditor.RenameSymbolUnderCursor"; +const char OPEN_CALL_HIERARCHY[] = "TextEditor.OpenCallHierarchy"; const char JUMP_TO_FILE_UNDER_CURSOR[] = "TextEditor.JumpToFileUnderCursor"; const char JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.JumpToFileUnderCursorInNextSplit";