aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2024-03-01 17:19:48 +0100
committerChristian Kandeler <christian.kandeler@qt.io>2024-03-15 08:49:20 +0000
commit45de53d353a37a6d134cdbbd40d4a850a47e6748 (patch)
tree8eb4a83220007cef6d582e287cbe0d7cb94b0e08
parent7e01d9082438aa758d17c583a07c750565f8f205 (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>
-rw-r--r--src/libs/languageserverprotocol/CMakeLists.txt1
-rw-r--r--src/libs/languageserverprotocol/clientcapabilities.h6
-rw-r--r--src/libs/languageserverprotocol/jsonkeys.h2
-rw-r--r--src/libs/languageserverprotocol/languageserverprotocol.qbs2
-rw-r--r--src/libs/languageserverprotocol/servercapabilities.cpp22
-rw-r--r--src/libs/languageserverprotocol/servercapabilities.h4
-rw-r--r--src/libs/languageserverprotocol/typehierarchy.cpp28
-rw-r--r--src/libs/languageserverprotocol/typehierarchy.h85
-rw-r--r--src/plugins/cppeditor/cpptypehierarchy.cpp2
-rw-r--r--src/plugins/languageclient/CMakeLists.txt2
-rw-r--r--src/plugins/languageclient/callandtypehierarchy.cpp508
-rw-r--r--src/plugins/languageclient/callandtypehierarchy.h (renamed from src/plugins/languageclient/callhierarchy.h)5
-rw-r--r--src/plugins/languageclient/callhierarchy.cpp318
-rw-r--r--src/plugins/languageclient/client.cpp5
-rw-r--r--src/plugins/languageclient/languageclient.qbs7
-rw-r--r--src/plugins/languageclient/languageclientplugin.cpp3
16 files changed, 674 insertions, 326 deletions
diff --git a/src/libs/languageserverprotocol/CMakeLists.txt b/src/libs/languageserverprotocol/CMakeLists.txt
index a3031413a4..0261046016 100644
--- a/src/libs/languageserverprotocol/CMakeLists.txt
+++ b/src/libs/languageserverprotocol/CMakeLists.txt
@@ -22,5 +22,6 @@ add_qtc_library(LanguageServerProtocol
servercapabilities.cpp servercapabilities.h
shutdownmessages.cpp shutdownmessages.h
textsynchronization.cpp textsynchronization.h
+ typehierarchy.cpp typehierarchy.h
workspace.cpp workspace.h
)
diff --git a/src/libs/languageserverprotocol/clientcapabilities.h b/src/libs/languageserverprotocol/clientcapabilities.h
index b2311d7e72..91c9954cd7 100644
--- a/src/libs/languageserverprotocol/clientcapabilities.h
+++ b/src/libs/languageserverprotocol/clientcapabilities.h
@@ -529,6 +529,12 @@ public:
void setCallHierarchy(const DynamicRegistrationCapabilities &callHierarchy)
{ insert(callHierarchyKey, callHierarchy); }
void clearCallHierarchy() { remove(callHierarchyKey); }
+
+ std::optional<DynamicRegistrationCapabilities> typeHierarchy() const
+ { return optionalValue<DynamicRegistrationCapabilities>(typeHierarchyKey); }
+ void setTypeHierarchy(const DynamicRegistrationCapabilities &typeHierarchy)
+ { insert(typeHierarchyKey, typeHierarchy); }
+ void clearTypeHierarchy() { remove(typeHierarchyKey); }
};
class LANGUAGESERVERPROTOCOL_EXPORT SemanticTokensWorkspaceClientCapabilities : public JsonObject
diff --git a/src/libs/languageserverprotocol/jsonkeys.h b/src/libs/languageserverprotocol/jsonkeys.h
index c2b5e545f2..37894f166e 100644
--- a/src/libs/languageserverprotocol/jsonkeys.h
+++ b/src/libs/languageserverprotocol/jsonkeys.h
@@ -218,6 +218,8 @@ constexpr Key trimTrailingWhitespaceKey{"trimTrailingWhitespace"};
constexpr Key typeDefinitionKey{"typeDefinition"};
constexpr Key typeDefinitionProviderKey{"typeDefinitionProvider"};
constexpr Key typeKey{"type"};
+constexpr Key typeHierarchyKey{"typeHierarchy"};
+constexpr Key typeHierarchyProviderKey{"typeHierarchyProvider"};
constexpr Key unregistrationsKey{"unregistrations"};
constexpr Key uriKey{"uri"};
constexpr Key valueKey{"value"};
diff --git a/src/libs/languageserverprotocol/languageserverprotocol.qbs b/src/libs/languageserverprotocol/languageserverprotocol.qbs
index 0368ceafee..2bf717c293 100644
--- a/src/libs/languageserverprotocol/languageserverprotocol.qbs
+++ b/src/libs/languageserverprotocol/languageserverprotocol.qbs
@@ -44,6 +44,8 @@ QtcLibrary {
"shutdownmessages.h",
"textsynchronization.cpp",
"textsynchronization.h",
+ "typehierarchy.cpp",
+ "typehierarchy.h",
"workspace.cpp",
"workspace.h",
]
diff --git a/src/libs/languageserverprotocol/servercapabilities.cpp b/src/libs/languageserverprotocol/servercapabilities.cpp
index 885198d12e..0c57f47131 100644
--- a/src/libs/languageserverprotocol/servercapabilities.cpp
+++ b/src/libs/languageserverprotocol/servercapabilities.cpp
@@ -196,6 +196,28 @@ void ServerCapabilities::setCallHierarchyProvider(
insert(callHierarchyProviderKey, val);
}
+std::optional<std::variant<bool, WorkDoneProgressOptions>> ServerCapabilities::typeHierarchyProvider()
+ const
+{
+ const QJsonValue &provider = value(typeHierarchyProviderKey);
+ if (provider.isBool())
+ return provider.toBool();
+ else if (provider.isObject())
+ return WorkDoneProgressOptions(provider.toObject());
+ return std::nullopt;
+}
+
+void ServerCapabilities::setTypeHierarchyProvider(
+ const std::variant<bool, WorkDoneProgressOptions> &typeHierarchyProvider)
+{
+ QJsonValue val;
+ if (std::holds_alternative<bool>(typeHierarchyProvider))
+ val = std::get<bool>(typeHierarchyProvider);
+ else if (std::holds_alternative<WorkDoneProgressOptions>(typeHierarchyProvider))
+ val = QJsonObject(std::get<WorkDoneProgressOptions>(typeHierarchyProvider));
+ insert(typeHierarchyProviderKey, val);
+}
+
std::optional<std::variant<bool, WorkDoneProgressOptions>>
ServerCapabilities::workspaceSymbolProvider() const
{
diff --git a/src/libs/languageserverprotocol/servercapabilities.h b/src/libs/languageserverprotocol/servercapabilities.h
index c3b50dd590..32598d2afa 100644
--- a/src/libs/languageserverprotocol/servercapabilities.h
+++ b/src/libs/languageserverprotocol/servercapabilities.h
@@ -330,6 +330,10 @@ public:
void setCallHierarchyProvider(const std::variant<bool, WorkDoneProgressOptions> &callHierarchyProvider);
void clearCallHierarchyProvider() { remove(callHierarchyProviderKey); }
+ std::optional<std::variant<bool, WorkDoneProgressOptions>> typeHierarchyProvider() const;
+ void setTypeHierarchyProvider(const std::variant<bool, WorkDoneProgressOptions> &typeHierarchyProvider);
+ void clearTypeHierarchyProvider() { remove(typeHierarchyProviderKey); }
+
// The server provides workspace symbol support.
std::optional<std::variant<bool, WorkDoneProgressOptions>> workspaceSymbolProvider() const;
void setWorkspaceSymbolProvider(std::variant<bool, WorkDoneProgressOptions> workspaceSymbolProvider);
diff --git a/src/libs/languageserverprotocol/typehierarchy.cpp b/src/libs/languageserverprotocol/typehierarchy.cpp
new file mode 100644
index 0000000000..1734e25a6b
--- /dev/null
+++ b/src/libs/languageserverprotocol/typehierarchy.cpp
@@ -0,0 +1,28 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "typehierarchy.h"
+
+namespace LanguageServerProtocol {
+
+bool TypeHierarchyItem::isValid() const
+{
+ return contains(nameKey) && contains(kindKey) && contains(uriKey) && contains(rangeKey)
+ && contains(selectionRangeKey);
+}
+std::optional<QList<SymbolTag>> TypeHierarchyItem::symbolTags() const
+{
+ return Internal::getSymbolTags(*this);
+}
+
+PrepareTypeHierarchyRequest::PrepareTypeHierarchyRequest(const TextDocumentPositionParams &params)
+ : Request(methodName, params)
+{}
+TypeHierarchySupertypesRequest::TypeHierarchySupertypesRequest(const TypeHierarchyParams &params)
+ : Request(methodName, params)
+{}
+TypeHierarchySubtypesRequest::TypeHierarchySubtypesRequest(const TypeHierarchyParams &params)
+ : Request(methodName, params)
+{}
+
+} // namespace LanguageServerProtocol
diff --git a/src/libs/languageserverprotocol/typehierarchy.h b/src/libs/languageserverprotocol/typehierarchy.h
new file mode 100644
index 0000000000..d5e88c8cfd
--- /dev/null
+++ b/src/libs/languageserverprotocol/typehierarchy.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "jsonrpcmessages.h"
+
+namespace LanguageServerProtocol {
+
+class LANGUAGESERVERPROTOCOL_EXPORT TypeHierarchyItem : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ QString name() const { return typedValue<QString>(nameKey); }
+ void setName(const QString &name) { insert(nameKey, name); }
+
+ SymbolKind symbolKind() const { return SymbolKind(typedValue<int>(kindKey)); }
+ void setSymbolKind(const SymbolKind &symbolKind) { insert(kindKey, int(symbolKind)); }
+
+ std::optional<QList<SymbolTag>> symbolTags() const;
+
+ std::optional<QString> detail() const { return optionalValue<QString>(detailKey); }
+ void setDetail(const QString &detail) { insert(detailKey, detail); }
+ void clearDetail() { remove(detailKey); }
+
+ DocumentUri uri() const { return DocumentUri::fromProtocol(typedValue<QString>(uriKey)); }
+ void setUri(const DocumentUri &uri) { insert(uriKey, uri); }
+
+ Range range() const { return typedValue<Range>(rangeKey); }
+ void setRange(const Range &range) { insert(rangeKey, range); }
+
+ Range selectionRange() const { return typedValue<Range>(selectionRangeKey); }
+ void setSelectionRange(Range selectionRange) { insert(selectionRangeKey, selectionRange); }
+
+ /*
+ * A data entry field that is preserved between a type hierarchy prepare and
+ * supertypes or subtypes requests. It could also be used to identify the
+ * type hierarchy in the server, helping improve the performance on
+ * resolving supertypes and subtypes.
+ */
+ std::optional<QJsonValue> data() const { return optionalValue<QJsonValue>(dataKey); }
+
+ bool isValid() const override;
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT PrepareTypeHierarchyRequest : public Request<
+ LanguageClientArray<TypeHierarchyItem>, std::nullptr_t, TextDocumentPositionParams>
+{
+public:
+ explicit PrepareTypeHierarchyRequest(const TextDocumentPositionParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "textDocument/prepareTypeHierarchy";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TypeHierarchyParams : public JsonObject
+{
+public:
+ using JsonObject::JsonObject;
+
+ TypeHierarchyItem item() const { return typedValue<TypeHierarchyItem>(itemKey); }
+ void setItem(const TypeHierarchyItem &item) { insert(itemKey, item); }
+
+ bool isValid() const override { return contains(itemKey); }
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TypeHierarchySupertypesRequest : public Request<
+ LanguageClientArray<TypeHierarchyItem>, std::nullptr_t, TypeHierarchyParams>
+{
+public:
+ explicit TypeHierarchySupertypesRequest(const TypeHierarchyParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "typeHierarchy/supertypes";
+};
+
+class LANGUAGESERVERPROTOCOL_EXPORT TypeHierarchySubtypesRequest
+ : public Request<LanguageClientArray<TypeHierarchyItem>, std::nullptr_t, TypeHierarchyParams>
+{
+public:
+ explicit TypeHierarchySubtypesRequest(const TypeHierarchyParams &params);
+ using Request::Request;
+ constexpr static const char methodName[] = "typeHierarchy/subtypes";
+};
+
+} // namespace LanguageServerProtocol
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 &params,
+ 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 &params,
+ 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 &params,
+ 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);