diff options
author | Christian Kandeler <christian.kandeler@qt.io> | 2024-03-01 17:19:48 +0100 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@qt.io> | 2024-03-15 08:49:20 +0000 |
commit | 45de53d353a37a6d134cdbbd40d4a850a47e6748 (patch) | |
tree | 8eb4a83220007cef6d582e287cbe0d7cb94b0e08 /src/plugins | |
parent | 7e01d9082438aa758d17c583a07c750565f8f205 (diff) |
LSP: Add type hierarchy support
Fixes: QTCREATORBUG-28116
Change-Id: Ibaed23144f63fa84fa97ae9106d0f0baf8f53118
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Diffstat (limited to 'src/plugins')
-rw-r--r-- | src/plugins/cppeditor/cpptypehierarchy.cpp | 2 | ||||
-rw-r--r-- | src/plugins/languageclient/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/plugins/languageclient/callandtypehierarchy.cpp | 508 | ||||
-rw-r--r-- | src/plugins/languageclient/callandtypehierarchy.h (renamed from src/plugins/languageclient/callhierarchy.h) | 5 | ||||
-rw-r--r-- | src/plugins/languageclient/callhierarchy.cpp | 318 | ||||
-rw-r--r-- | src/plugins/languageclient/client.cpp | 5 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclient.qbs | 7 | ||||
-rw-r--r-- | src/plugins/languageclient/languageclientplugin.cpp | 3 |
8 files changed, 524 insertions, 326 deletions
diff --git a/src/plugins/cppeditor/cpptypehierarchy.cpp b/src/plugins/cppeditor/cpptypehierarchy.cpp index d0a523ce13..ab4bc0525e 100644 --- a/src/plugins/cppeditor/cpptypehierarchy.cpp +++ b/src/plugins/cppeditor/cpptypehierarchy.cpp @@ -420,7 +420,7 @@ class CppTypeHierarchyFactory final : public TextEditor::TypeHierarchyWidgetFact if (!textEditor) return nullptr; const auto cppDoc = qobject_cast<CppEditorDocument *>(textEditor->textDocument()); - if (!cppDoc /* || cppDoc->usesClangd() */) + if (!cppDoc || cppDoc->usesClangd()) return nullptr; return new CppTypeHierarchyWidget; diff --git a/src/plugins/languageclient/CMakeLists.txt b/src/plugins/languageclient/CMakeLists.txt index a74bc33026..e098168a1c 100644 --- a/src/plugins/languageclient/CMakeLists.txt +++ b/src/plugins/languageclient/CMakeLists.txt @@ -8,7 +8,7 @@ add_qtc_plugin(LanguageClient PUBLIC_DEPENDS LanguageServerProtocol Qt::Core PLUGIN_DEPENDS ProjectExplorer Core TextEditor SOURCES - callhierarchy.cpp callhierarchy.h + callandtypehierarchy.cpp callandtypehierarchy.h client.cpp client.h clientrequest.cpp clientrequest.h currentdocumentsymbolsrequest.cpp currentdocumentsymbolsrequest.h diff --git a/src/plugins/languageclient/callandtypehierarchy.cpp b/src/plugins/languageclient/callandtypehierarchy.cpp new file mode 100644 index 0000000000..14e5935c6a --- /dev/null +++ b/src/plugins/languageclient/callandtypehierarchy.cpp @@ -0,0 +1,508 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "callandtypehierarchy.h" + +#include "languageclientmanager.h" +#include "languageclienttr.h" + +#include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/inavigationwidgetfactory.h> + +#include <languageserverprotocol/callhierarchy.h> +#include <languageserverprotocol/typehierarchy.h> + +#include <texteditor/texteditor.h> +#include <texteditor/typehierarchy.h> + +#include <utils/delegates.h> +#include <utils/navigationtreeview.h> +#include <utils/treemodel.h> +#include <utils/utilsicons.h> + +#include <QLayout> +#include <QToolButton> + +using namespace Utils; +using namespace TextEditor; +using namespace LanguageServerProtocol; + +namespace LanguageClient { + +namespace { +enum { + AnnotationRole = Qt::UserRole + 1, + LinkRole +}; +} + +template<class Item, class Params, class Request, class Result> +class HierarchyItem : public TreeItem +{ +public: + HierarchyItem(const Item &item, Client *client) + : m_item(item) + , m_client(client) + {} + +protected: + QVariant data(int column, int role) const override + { + switch (role) { + case Qt::DecorationRole: + if (hasTag(SymbolTag::Deprecated)) + return Utils::Icons::WARNING.icon(); + return symbolIcon(int(m_item.symbolKind())); + case Qt::DisplayRole: + return m_item.name(); + case Qt::ToolTipRole: + if (hasTag(SymbolTag::Deprecated)) + return Tr::tr("Deprecated"); + return {}; + case LinkRole: { + if (!m_client) + return QVariant(); + const Position start = m_item.selectionRange().start(); + return QVariant::fromValue( + Link(m_client->serverUriToHostPath(m_item.uri()), start.line() + 1, start.character())); + } + case AnnotationRole: + if (const std::optional<QString> detail = m_item.detail()) + return *detail; + return {}; + default: + return TreeItem::data(column, role); + } + } + +private: + bool canFetchMore() const override { return m_client && !m_fetchedChildren; } + + void fetchMore() override + { + m_fetchedChildren = true; + if (!m_client) + return; + + Params params; + params.setItem(m_item); + Request request(params); + request.setResponseCallback( + [this](const typename Request::Response &response) { + const std::optional<LanguageClientArray<Result>> result = response.result(); + if (result && !result->isNull()) { + for (const Result &item : result->toList()) { + if (item.isValid()) + appendChild(new HierarchyItem(getSourceItem(item), m_client)); + } + } + if (!hasChildren()) + update(); + }); + m_client->sendMessage(request); + } + + Item getSourceItem(const Result &result) + { + if constexpr (std::is_same_v<Result, CallHierarchyIncomingCall>) + return result.from(); + if constexpr (std::is_same_v<Result, CallHierarchyOutgoingCall>) + return result.to(); + if constexpr (std::is_same_v<Result, TypeHierarchyItem>) + return result; + } + + bool hasTag(const SymbolTag tag) const + { + if (const std::optional<QList<SymbolTag>> tags = m_item.symbolTags()) + return tags->contains(tag); + return false; + } + + const Item m_item; + bool m_fetchedChildren = false; + QPointer<Client> m_client; +}; + +class CallHierarchyIncomingItem : public HierarchyItem<CallHierarchyItem, + CallHierarchyCallsParams, + CallHierarchyIncomingCallsRequest, + CallHierarchyIncomingCall> +{ +public: + CallHierarchyIncomingItem(const LanguageServerProtocol::CallHierarchyItem &item, Client *client) + : HierarchyItem(item, client) + {} + +private: + QVariant data(int column, int role) const override + { + if (role == Qt::DisplayRole) + return Tr::tr("Incoming"); + if (role == Qt::DecorationRole) + return {}; + return HierarchyItem::data(column, role); + } +}; + +class CallHierarchyOutgoingItem : public HierarchyItem<CallHierarchyItem, + CallHierarchyCallsParams, + CallHierarchyOutgoingCallsRequest, + CallHierarchyOutgoingCall> +{ +public: + CallHierarchyOutgoingItem(const LanguageServerProtocol::CallHierarchyItem &item, Client *client) + : HierarchyItem(item, client) + {} + +private: + QVariant data(int column, int role) const override + { + if (role == Qt::DisplayRole) + return Tr::tr("Outgoing"); + if (role == Qt::DecorationRole) + return {}; + return HierarchyItem::data(column, role); + } +}; + +template<class Item> class HierarchyRootItem : public TreeItem +{ +public: + HierarchyRootItem(const Item &item) + : m_item(item) + {} + +private: + QVariant data(int column, int role) const override + { + switch (role) { + case Qt::DecorationRole: + if (m_item.symbolTags().value_or(QList<SymbolTag>()).contains(SymbolTag::Deprecated)) + return Utils::Icons::WARNING.icon(); + return symbolIcon(int(m_item.symbolKind())); + case Qt::DisplayRole: + return m_item.name(); + default: + return TreeItem::data(column, role); + } + } + + const Item m_item; +}; + + +class CallHierarchyRootItem : public HierarchyRootItem<LanguageServerProtocol::CallHierarchyItem> +{ +public: + CallHierarchyRootItem(const LanguageServerProtocol::CallHierarchyItem &item, Client *client) + : HierarchyRootItem(item) + { + appendChild(new CallHierarchyIncomingItem(item, client)); + appendChild(new CallHierarchyOutgoingItem(item, client)); + } +}; + +class TypeHierarchyBasesItem : public HierarchyItem<TypeHierarchyItem, + TypeHierarchyParams, + TypeHierarchySupertypesRequest, + TypeHierarchyItem> +{ +public: + TypeHierarchyBasesItem(const LanguageServerProtocol::TypeHierarchyItem &item, Client *client) + : HierarchyItem(item, client) + {} + +private: + QVariant data(int column, int role) const override + { + if (role == Qt::DisplayRole) + return Tr::tr("Bases"); + if (role == Qt::DecorationRole) + return {}; + return HierarchyItem::data(column, role); + } +}; + +class TypeHierarchyDerivedItem : public HierarchyItem<TypeHierarchyItem, + TypeHierarchyParams, + TypeHierarchySubtypesRequest, + TypeHierarchyItem> +{ +public: + TypeHierarchyDerivedItem(const LanguageServerProtocol::TypeHierarchyItem &item, Client *client) + : HierarchyItem(item, client) + {} + +private: + QVariant data(int column, int role) const override + { + if (role == Qt::DisplayRole) + return Tr::tr("Derived"); + if (role == Qt::DecorationRole) + return {}; + return HierarchyItem::data(column, role); + } +}; + +class TypeHierarchyRootItem : public HierarchyRootItem<LanguageServerProtocol::TypeHierarchyItem> +{ +public: + TypeHierarchyRootItem(const LanguageServerProtocol::TypeHierarchyItem &item, Client *client) + : HierarchyRootItem(item) + { + appendChild(new TypeHierarchyBasesItem(item, client)); + appendChild(new TypeHierarchyDerivedItem(item, client)); + } +}; + +class HierarchyWidgetHelper +{ +public: + HierarchyWidgetHelper(QWidget *theWidget) : m_view(new NavigationTreeView(theWidget)) + { + m_delegate.setDelimiter(" "); + m_delegate.setAnnotationRole(AnnotationRole); + + m_view->setModel(&m_model); + m_view->setActivationMode(SingleClickActivation); + m_view->setItemDelegate(&m_delegate); + + theWidget->setLayout(new QVBoxLayout); + theWidget->layout()->addWidget(m_view); + theWidget->layout()->setContentsMargins(0, 0, 0, 0); + theWidget->layout()->setSpacing(0); + + QObject::connect(m_view, &NavigationTreeView::activated, + theWidget, [this](const QModelIndex &index) { onItemActivated(index); }); + QObject::connect(m_view, &QTreeView::doubleClicked, + theWidget, [this](const QModelIndex &index) { onItemDoubleClicked(index); }); + } + + void updateHierarchyAtCursorPosition() + { + m_model.clear(); + + BaseTextEditor *editor = BaseTextEditor::currentTextEditor(); + if (!editor) + return; + + Core::IDocument *document = editor->document(); + + Client *client = LanguageClientManager::clientForFilePath(document->filePath()); + if (!client) + return; + + TextDocumentPositionParams params; + params.setTextDocument(TextDocumentIdentifier(client->hostPathToServerUri(document->filePath()))); + params.setPosition(Position(editor->editorWidget()->textCursor())); + sendRequest(client, params, document); + } + +protected: + void addItem(TreeItem *item) + { + m_model.rootItem()->appendChild(item); + m_view->expand(item->index()); + item->forChildrenAtLevel(1, [&](const TreeItem *child) { m_view->expand(child->index()); }); + } + +private: + virtual void sendRequest(Client *client, const TextDocumentPositionParams ¶ms, + const Core::IDocument *document) = 0; + + virtual void onItemDoubleClicked(const QModelIndex &index) { Q_UNUSED(index) } + + void onItemActivated(const QModelIndex &index) + { + const auto link = index.data(LinkRole).value<Utils::Link>(); + if (link.hasValidTarget()) + Core::EditorManager::openEditorAt(link); + } + + AnnotatedItemDelegate m_delegate; + NavigationTreeView * const m_view; + TreeModel<TreeItem> m_model; +}; + +class CallHierarchy : public QWidget, public HierarchyWidgetHelper +{ +public: + CallHierarchy() : HierarchyWidgetHelper(this) + { + connect(LanguageClientManager::instance(), &LanguageClientManager::openCallHierarchy, + this, [this] { updateHierarchyAtCursorPosition(); }); + } + +private: + void sendRequest(Client *client, const TextDocumentPositionParams ¶ms, + const Core::IDocument *document) override + { + if (!supportsCallHierarchy(client, document)) + return; + + PrepareCallHierarchyRequest request(params); + request.setResponseCallback([this, client = QPointer<Client>(client)]( + const PrepareCallHierarchyRequest::Response &response) { + handlePrepareResponse(client, response); + }); + client->sendMessage(request); + } + + void handlePrepareResponse(Client *client, + const PrepareCallHierarchyRequest::Response &response) + { + if (!client) + return; + const std::optional<PrepareCallHierarchyRequest::Response::Error> error = response.error(); + if (error) + client->log(*error); + + const std::optional<LanguageClientArray<LanguageServerProtocol::CallHierarchyItem>> + result = response.result(); + if (result && !result->isNull()) { + for (const LanguageServerProtocol::CallHierarchyItem &item : result->toList()) + addItem(new CallHierarchyRootItem(item, client)); + } + } +}; + +class TypeHierarchy : public TypeHierarchyWidget, public HierarchyWidgetHelper +{ +public: + TypeHierarchy() : HierarchyWidgetHelper(this) {} + +private: + void reload() override + { + updateHierarchyAtCursorPosition(); + } + + void sendRequest(Client *client, const TextDocumentPositionParams ¶ms, + const Core::IDocument *document) override + { + if (!supportsTypeHierarchy(client, document)) + return; + + PrepareTypeHierarchyRequest request(params); + request.setResponseCallback([this, client = QPointer<Client>(client)]( + const PrepareTypeHierarchyRequest::Response &response) { + handlePrepareResponse(client, response); + }); + client->sendMessage(request); + } + + void onItemDoubleClicked(const QModelIndex &index) override + { + if (const auto link = index.data(LinkRole).value<Link>(); link.hasValidTarget()) + reload(); + } + + void handlePrepareResponse(Client *client, + const PrepareTypeHierarchyRequest::Response &response) + { + if (!client) + return; + const std::optional<PrepareTypeHierarchyRequest::Response::Error> error = response.error(); + if (error) + client->log(*error); + + const std::optional<LanguageClientArray<LanguageServerProtocol::TypeHierarchyItem>> + result = response.result(); + if (result && !result->isNull()) { + for (const LanguageServerProtocol::TypeHierarchyItem &item : result->toList()) + addItem(new TypeHierarchyRootItem(item, client)); + } + } +}; + +class CallHierarchyFactory : public Core::INavigationWidgetFactory +{ +public: + CallHierarchyFactory() + { + setDisplayName(Tr::tr("Call Hierarchy")); + setPriority(650); + setId(Constants::CALL_HIERARCHY_FACTORY_ID); + } + + Core::NavigationView createWidget() final + { + auto h = new CallHierarchy; + h->updateHierarchyAtCursorPosition(); + + Icons::RELOAD_TOOLBAR.icon(); + auto button = new QToolButton; + button->setIcon(Icons::RELOAD_TOOLBAR.icon()); + button->setToolTip(LanguageClient::Tr::tr( + "Reloads the call hierarchy for the symbol under cursor position.")); + connect(button, &QToolButton::clicked, this, [h] { h->updateHierarchyAtCursorPosition(); }); + return {h, {button}}; + } +}; + +class TypeHierarchyFactory final : public TypeHierarchyWidgetFactory +{ + TypeHierarchyWidget *createWidget(Core::IEditor *editor) final + { + const auto textEditor = qobject_cast<BaseTextEditor *>(editor); + if (!textEditor) + return nullptr; + + Client *const client = LanguageClientManager::clientForFilePath( + textEditor->document()->filePath()); + if (!client || !supportsTypeHierarchy(client, textEditor->document())) + return nullptr; + + return new TypeHierarchy; + } +}; + +void setupCallHierarchyFactory() +{ + static CallHierarchyFactory theCallHierarchyFactory; +} + +static bool supportsHierarchy( + Client *client, + const Core::IDocument *document, + const QString &methodName, + const std::optional<std::variant<bool, WorkDoneProgressOptions>> &provider) +{ + std::optional<bool> 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 = provider.has_value(); + } + return supported; +} + +bool supportsCallHierarchy(Client *client, const Core::IDocument *document) +{ + return supportsHierarchy(client, + document, + PrepareCallHierarchyRequest::methodName, + client->capabilities().callHierarchyProvider()); +} + +void setupTypeHierarchyFactory() +{ + static TypeHierarchyFactory theTypeHierarchyFactory; +} + +bool supportsTypeHierarchy(Client *client, const Core::IDocument *document) +{ + return supportsHierarchy(client, + document, + PrepareTypeHierarchyRequest::methodName, + client->capabilities().typeHierarchyProvider()); +} + +} // namespace LanguageClient diff --git a/src/plugins/languageclient/callhierarchy.h b/src/plugins/languageclient/callandtypehierarchy.h index 860919b2b9..b49684d65a 100644 --- a/src/plugins/languageclient/callhierarchy.h +++ b/src/plugins/languageclient/callandtypehierarchy.h @@ -1,4 +1,4 @@ -// Copyright (C) 2022 The Qt Company Ltd. +// Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 #pragma once @@ -12,4 +12,7 @@ class Client; void setupCallHierarchyFactory(); bool supportsCallHierarchy(Client *client, const Core::IDocument *document); +void setupTypeHierarchyFactory(); +bool supportsTypeHierarchy(Client *client, const Core::IDocument *document); + } // namespace LanguageClient diff --git a/src/plugins/languageclient/callhierarchy.cpp b/src/plugins/languageclient/callhierarchy.cpp deleted file mode 100644 index 2f53a120eb..0000000000 --- a/src/plugins/languageclient/callhierarchy.cpp +++ /dev/null @@ -1,318 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 - -#include "callhierarchy.h" - -#include "languageclientmanager.h" -#include "languageclienttr.h" - -#include <coreplugin/editormanager/editormanager.h> -#include <coreplugin/inavigationwidgetfactory.h> - -#include <languageserverprotocol/callhierarchy.h> - -#include <texteditor/texteditor.h> - -#include <utils/delegates.h> -#include <utils/navigationtreeview.h> -#include <utils/treemodel.h> -#include <utils/utilsicons.h> - -#include <QLayout> -#include <QToolButton> - -using namespace Utils; -using namespace TextEditor; -using namespace LanguageServerProtocol; - -namespace LanguageClient { - -namespace { -enum Direction { Incoming, Outgoing }; - -enum { - AnnotationRole = Qt::UserRole + 1, - LinkRole -}; -} - -class CallHierarchyRootItem : public TreeItem -{ -public: - CallHierarchyRootItem(const CallHierarchyItem &item) - : m_item(item) - {} - - QVariant data(int column, int role) const override - { - switch (role) { - case Qt::DecorationRole: - if (hasTag(SymbolTag::Deprecated)) - return Utils::Icons::WARNING.icon(); - return symbolIcon(int(m_item.symbolKind())); - case Qt::DisplayRole: - return m_item.name(); - case Qt::ToolTipRole: - if (hasTag(SymbolTag::Deprecated)) - return Tr::tr("Deprecated"); - break; - default: - break; - } - return TreeItem::data(column, role); - } - -protected: - const CallHierarchyItem m_item; - - bool hasTag(const SymbolTag tag) const - { - if (const std::optional<QList<SymbolTag>> tags = m_item.symbolTags()) - return tags->contains(tag); - return false; - } -}; - -class CallHierarchyTreeItem : public CallHierarchyRootItem -{ -public: - CallHierarchyTreeItem(const CallHierarchyItem &item, const Direction direction, Client *client) - : CallHierarchyRootItem(item) - , m_direction(direction) - , m_client(client) - { - } - - QVariant data(int column, int role) const override - { - switch (role) { - case LinkRole: { - if (!m_client) - return QVariant(); - const Position start = m_item.selectionRange().start(); - return QVariant::fromValue( - Link(m_client->serverUriToHostPath(m_item.uri()), start.line() + 1, start.character())); - } - case AnnotationRole: - if (const std::optional<QString> detail = m_item.detail()) - return *detail; - return {}; - default: - break; - } - return CallHierarchyRootItem::data(column, role); - } - bool canFetchMore() const override - { - return m_client && !m_fetchedChildren; - } - - void fetchMore() override - { - m_fetchedChildren = true; - if (!m_client) - return; - - CallHierarchyCallsParams params; - params.setItem(m_item); - - if (m_direction == Incoming) { - CallHierarchyIncomingCallsRequest request(params); - request.setResponseCallback( - [this](const CallHierarchyIncomingCallsRequest::Response &response) { - const std::optional<LanguageClientArray<CallHierarchyIncomingCall>> result - = response.result(); - if (result && !result->isNull()) { - for (const CallHierarchyIncomingCall &item : result->toList()) { - if (item.isValid()) - appendChild(new CallHierarchyTreeItem(item.from(), m_direction, m_client)); - } - } - if (!hasChildren()) - update(); - }); - m_client->sendMessage(request); - } else { - CallHierarchyOutgoingCallsRequest request(params); - request.setResponseCallback( - [this](const CallHierarchyOutgoingCallsRequest::Response &response) { - const std::optional<LanguageClientArray<CallHierarchyOutgoingCall>> result - = response.result(); - if (result && !result->isNull()) { - for (const CallHierarchyOutgoingCall &item : result->toList()) { - if (item.isValid()) - appendChild(new CallHierarchyTreeItem(item.to(), m_direction, m_client)); - } - } - if (!hasChildren()) - update(); - }); - m_client->sendMessage(request); - } - } - -protected: - const Direction m_direction; - bool m_fetchedChildren = false; - QPointer<Client> m_client; -}; - -class CallHierarchyDirectionItem : public CallHierarchyTreeItem -{ -public: - CallHierarchyDirectionItem(const CallHierarchyItem &item, - const Direction direction, - Client *client) - : CallHierarchyTreeItem(item, direction, client) - {} - - QVariant data(int column, int role) const override - { - if (role == Qt::DisplayRole) - return m_direction == Incoming ? Tr::tr("Incoming") : Tr::tr("Outgoing"); - return TreeItem::data(column, role); - } -}; - -class CallHierarchy : public QWidget -{ -public: - CallHierarchy() : m_view(new NavigationTreeView(this)) - { - m_delegate.setDelimiter(" "); - m_delegate.setAnnotationRole(AnnotationRole); - - m_view->setModel(&m_model); - m_view->setActivationMode(SingleClickActivation); - m_view->setItemDelegate(&m_delegate); - - setLayout(new QVBoxLayout); - layout()->addWidget(m_view); - layout()->setContentsMargins(0, 0, 0, 0); - layout()->setSpacing(0); - - connect(m_view, &NavigationTreeView::activated, this, &CallHierarchy::onItemActivated); - - connect(LanguageClientManager::instance(), &LanguageClientManager::openCallHierarchy, - this, &CallHierarchy::updateHierarchyAtCursorPosition); - } - - void onItemActivated(const QModelIndex &index) - { - const auto link = index.data(LinkRole).value<Utils::Link>(); - if (link.hasValidTarget()) - Core::EditorManager::openEditorAt(link); - } - - void updateHierarchyAtCursorPosition(); - void handlePrepareResponse(Client *client, - const PrepareCallHierarchyRequest::Response &response); - - AnnotatedItemDelegate m_delegate; - NavigationTreeView *m_view; - TreeModel<TreeItem, CallHierarchyRootItem, CallHierarchyTreeItem> m_model; -}; - -void CallHierarchy::updateHierarchyAtCursorPosition() -{ - m_model.clear(); - - BaseTextEditor *editor = BaseTextEditor::currentTextEditor(); - if (!editor) - return; - - Core::IDocument *document = editor->document(); - - Client *client = LanguageClientManager::clientForFilePath(document->filePath()); - if (!client) - return; - - if (!supportsCallHierarchy(client, document)) - return; - - TextDocumentPositionParams params; - params.setTextDocument(TextDocumentIdentifier(client->hostPathToServerUri(document->filePath()))); - params.setPosition(Position(editor->editorWidget()->textCursor())); - - PrepareCallHierarchyRequest request(params); - request.setResponseCallback([this, client = QPointer<Client>(client)]( - const PrepareCallHierarchyRequest::Response &response) { - handlePrepareResponse(client, response); - }); - - client->sendMessage(request); -} - -void CallHierarchy::handlePrepareResponse(Client *client, - const PrepareCallHierarchyRequest::Response &response) -{ - if (!client) - return; - const std::optional<PrepareCallHierarchyRequest::Response::Error> error = response.error(); - if (error) - client->log(*error); - - const std::optional<LanguageClientArray<CallHierarchyItem>> - result = response.result(); - if (result && !result->isNull()) { - for (const CallHierarchyItem &item : result->toList()) { - auto newItem = new CallHierarchyRootItem(item); - newItem->appendChild(new CallHierarchyDirectionItem(item, Incoming, client)); - newItem->appendChild(new CallHierarchyDirectionItem(item, Outgoing, client)); - m_model.rootItem()->appendChild(newItem); - m_view->expand(newItem->index()); - newItem->forChildrenAtLevel(1, [&](const TreeItem *child) { - m_view->expand(child->index()); - }); - } - } -} - -class CallHierarchyFactory final : public Core::INavigationWidgetFactory -{ -public: - CallHierarchyFactory() - { - setDisplayName(Tr::tr("Call Hierarchy")); - setPriority(650); - setId(Constants::CALL_HIERARCHY_FACTORY_ID); - } - - Core::NavigationView createWidget() final - { - auto h = new CallHierarchy; - h->updateHierarchyAtCursorPosition(); - - Icons::RELOAD_TOOLBAR.icon(); - auto button = new QToolButton; - button->setIcon(Icons::RELOAD_TOOLBAR.icon()); - button->setToolTip(::LanguageClient::Tr::tr("Reloads the call hierarchy for the symbol under cursor position.")); - connect(button, &QToolButton::clicked, this, [h] { h->updateHierarchyAtCursorPosition(); }); - return {h, {button}}; - } -}; - -void setupCallHierarchyFactory() -{ - static CallHierarchyFactory theCallHierarchyFactory; -} - -bool supportsCallHierarchy(Client *client, const Core::IDocument *document) -{ - const QString methodName = PrepareCallHierarchyRequest::methodName; - std::optional<bool> 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; -} - -} // namespace LanguageClient diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index 15a71adae3..5e3d240f8c 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -3,7 +3,7 @@ #include "client.h" -#include "callhierarchy.h" +#include "callandtypehierarchy.h" #include "diagnosticmanager.h" #include "documentsymbolcache.h" #include "languageclientcompletionassist.h" @@ -527,6 +527,7 @@ static ClientCapabilities generateClientCapabilities() tokens.setFormats({"relative"}); documentCapabilities.setSemanticTokens(tokens); documentCapabilities.setCallHierarchy(allowDynamicRegistration); + documentCapabilities.setTypeHierarchy(allowDynamicRegistration); capabilities.setTextDocument(documentCapabilities); WindowClientClientCapabilities window; @@ -1013,6 +1014,8 @@ void Client::activateEditor(Core::IEditor *editor) optionalActions |= TextEditor::TextEditorActionHandler::FollowTypeUnderCursor; if (supportsCallHierarchy(this, textEditor->document())) optionalActions |= TextEditor::TextEditorActionHandler::CallHierarchy; + if (supportsTypeHierarchy(this, textEditor->document())) + optionalActions |= TextEditor::TextEditorActionHandler::TypeHierarchy; widget->setOptionalActions(optionalActions); } } diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs index 96a88629f0..04c2f0f326 100644 --- a/src/plugins/languageclient/languageclient.qbs +++ b/src/plugins/languageclient/languageclient.qbs @@ -15,8 +15,8 @@ QtcPlugin { Depends { name: "TextEditor" } files: [ - "callhierarchy.cpp", - "callhierarchy.h", + "callandtypehierarchy.cpp", + "callandtypehierarchy.h", "client.cpp", "client.h", "clientrequest.cpp", @@ -30,7 +30,8 @@ QtcPlugin { "dynamiccapabilities.cpp", "dynamiccapabilities.h", "languageclient.qrc", - "languageclient_global.h", "languageclienttr.h", + "languageclient_global.h", + "languageclienttr.h", "languageclientformatter.cpp", "languageclientformatter.h", "languageclienthoverhandler.cpp", diff --git a/src/plugins/languageclient/languageclientplugin.cpp b/src/plugins/languageclient/languageclientplugin.cpp index 83a1512253..985f00da1e 100644 --- a/src/plugins/languageclient/languageclientplugin.cpp +++ b/src/plugins/languageclient/languageclientplugin.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "callhierarchy.h" +#include "callandtypehierarchy.h" #include "languageclientmanager.h" #include "languageclientoutline.h" #include "languageclientsettings.h" @@ -43,6 +43,7 @@ void LanguageClientPlugin::initialize() using namespace Core; setupCallHierarchyFactory(); + setupTypeHierarchyFactory(); setupLanguageClientProjectPanel(); setupLanguageClientManager(this); |