forked from qt-creator/qt-creator
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>
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
@@ -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
|
||||
|
@@ -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"};
|
||||
|
@@ -44,6 +44,8 @@ QtcLibrary {
|
||||
"shutdownmessages.h",
|
||||
"textsynchronization.cpp",
|
||||
"textsynchronization.h",
|
||||
"typehierarchy.cpp",
|
||||
"typehierarchy.h",
|
||||
"workspace.cpp",
|
||||
"workspace.h",
|
||||
]
|
||||
|
@@ -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
|
||||
{
|
||||
|
@@ -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);
|
||||
|
28
src/libs/languageserverprotocol/typehierarchy.cpp
Normal file
28
src/libs/languageserverprotocol/typehierarchy.cpp
Normal file
@@ -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 ¶ms)
|
||||
: Request(methodName, params)
|
||||
{}
|
||||
TypeHierarchySupertypesRequest::TypeHierarchySupertypesRequest(const TypeHierarchyParams ¶ms)
|
||||
: Request(methodName, params)
|
||||
{}
|
||||
TypeHierarchySubtypesRequest::TypeHierarchySubtypesRequest(const TypeHierarchyParams ¶ms)
|
||||
: Request(methodName, params)
|
||||
{}
|
||||
|
||||
} // namespace LanguageServerProtocol
|
85
src/libs/languageserverprotocol/typehierarchy.h
Normal file
85
src/libs/languageserverprotocol/typehierarchy.h
Normal file
@@ -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 ¶ms);
|
||||
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 ¶ms);
|
||||
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 ¶ms);
|
||||
using Request::Request;
|
||||
constexpr static const char methodName[] = "typeHierarchy/subtypes";
|
||||
};
|
||||
|
||||
} // namespace LanguageServerProtocol
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
508
src/plugins/languageclient/callandtypehierarchy.cpp
Normal file
508
src/plugins/languageclient/callandtypehierarchy.cpp
Normal file
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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",
|
||||
|
@@ -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);
|
||||
|
||||
|
Reference in New Issue
Block a user