Skip to content

Commit

Permalink
[clangd] Add support for type hierarchy (super types only for now)
Browse files Browse the repository at this point in the history
Summary:
Patch by Nathan Ridge(@nridge)!

This is an LSP extension proposed here:
microsoft/vscode-languageserver-node#426

An example client implementation can be found here:
eclipse-theia/theia#3802

Reviewers: kadircet, sammccall

Reviewed By: kadircet

Subscribers: jdoerfert, sammccall, cfe-commits, mgorny, dschaefer, simark, ilya-biryukov, ioeric, MaskRay, jkorous, arphaman, kadircet

Tags: #clang

Differential Revision: https://reviews.llvm.org/D56370

llvm-svn: 356445
  • Loading branch information
kadircet committed Mar 19, 2019
1 parent ad78768 commit 8665802
Show file tree
Hide file tree
Showing 17 changed files with 991 additions and 71 deletions.
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
{ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
ExecuteCommandParams::CLANGD_APPLY_TWEAK}},
}},
{"typeHierarchyProvider", true},
}}}});
}

Expand Down Expand Up @@ -806,6 +807,13 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
std::move(Reply));
}

void ClangdLSPServer::onTypeHierarchy(
const TypeHierarchyParams &Params,
Callback<Optional<TypeHierarchyItem>> Reply) {
Server->typeHierarchy(Params.textDocument.uri.file(), Params.position,
Params.resolve, Params.direction, std::move(Reply));
}

void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
Expand Down Expand Up @@ -885,6 +893,7 @@ ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent);
MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration);
MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo);
MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy);
// clang-format on
}

Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class ClangdLSPServer : private DiagnosticsConsumer {
void onRename(const RenameParams &, Callback<WorkspaceEdit>);
void onHover(const TextDocumentPositionParams &,
Callback<llvm::Optional<Hover>>);
void onTypeHierarchy(const TypeHierarchyParams &,
Callback<llvm::Optional<TypeHierarchyItem>>);
void onChangeConfiguration(const DidChangeConfigurationParams &);
void onSymbolInfo(const TextDocumentPositionParams &,
Callback<std::vector<SymbolDetails>>);
Expand Down
18 changes: 15 additions & 3 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,9 +362,8 @@ void ClangdServer::enumerateTweaks(PathRef File, Range Sel,

void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,
Callback<tooling::Replacements> CB) {
auto Action = [Sel](decltype(CB) CB, std::string File,
std::string TweakID,
Expected<InputsAndAST> InpAST) {
auto Action = [Sel](decltype(CB) CB, std::string File, std::string TweakID,
Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
auto Selection = tweakSelection(Sel, *InpAST);
Expand Down Expand Up @@ -523,6 +522,19 @@ void ClangdServer::findHover(PathRef File, Position Pos,
WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB)));
}

void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
TypeHierarchyDirection Direction,
Callback<Optional<TypeHierarchyItem>> CB) {
auto Action = [Pos, Resolve, Direction](decltype(CB) CB,
Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction));
};

WorkScheduler.runWithAST("Type Hierarchy", File, Bind(Action, std::move(CB)));
}

tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) {
trace::Span Span("GetCompileCommand");
llvm::Optional<tooling::CompileCommand> C = CDB.getCompileCommand(File);
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/clangd/ClangdServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@ class ClangdServer {
void findHover(PathRef File, Position Pos,
Callback<llvm::Optional<Hover>> CB);

/// Get information about type hierarchy for a given position.
void typeHierarchy(PathRef File, Position Pos, int Resolve,
TypeHierarchyDirection Direction,
Callback<llvm::Optional<TypeHierarchyItem>> CB);

/// Retrieve the top symbols from the workspace matching a query.
void workspaceSymbols(StringRef Query, int Limit,
Callback<std::vector<SymbolInformation>> CB);
Expand Down
61 changes: 1 addition & 60 deletions clang-tools-extra/clangd/FindSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,67 +26,8 @@

namespace clang {
namespace clangd {
namespace {

// Convert a index::SymbolKind to clangd::SymbolKind (LSP)
// Note, some are not perfect matches and should be improved when this LSP
// issue is addressed:
// https://github.com/Microsoft/language-server-protocol/issues/344
SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
switch (Kind) {
case index::SymbolKind::Unknown:
return SymbolKind::Variable;
case index::SymbolKind::Module:
return SymbolKind::Module;
case index::SymbolKind::Namespace:
return SymbolKind::Namespace;
case index::SymbolKind::NamespaceAlias:
return SymbolKind::Namespace;
case index::SymbolKind::Macro:
return SymbolKind::String;
case index::SymbolKind::Enum:
return SymbolKind::Enum;
case index::SymbolKind::Struct:
return SymbolKind::Struct;
case index::SymbolKind::Class:
return SymbolKind::Class;
case index::SymbolKind::Protocol:
return SymbolKind::Interface;
case index::SymbolKind::Extension:
return SymbolKind::Interface;
case index::SymbolKind::Union:
return SymbolKind::Class;
case index::SymbolKind::TypeAlias:
return SymbolKind::Class;
case index::SymbolKind::Function:
return SymbolKind::Function;
case index::SymbolKind::Variable:
return SymbolKind::Variable;
case index::SymbolKind::Field:
return SymbolKind::Field;
case index::SymbolKind::EnumConstant:
return SymbolKind::EnumMember;
case index::SymbolKind::InstanceMethod:
case index::SymbolKind::ClassMethod:
case index::SymbolKind::StaticMethod:
return SymbolKind::Method;
case index::SymbolKind::InstanceProperty:
case index::SymbolKind::ClassProperty:
case index::SymbolKind::StaticProperty:
return SymbolKind::Property;
case index::SymbolKind::Constructor:
case index::SymbolKind::Destructor:
return SymbolKind::Method;
case index::SymbolKind::ConversionFunction:
return SymbolKind::Function;
case index::SymbolKind::Parameter:
return SymbolKind::Variable;
case index::SymbolKind::Using:
return SymbolKind::Namespace;
}
llvm_unreachable("invalid symbol kind");
}

namespace {
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
struct ScoredSymbolGreater {
bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
Expand Down
7 changes: 4 additions & 3 deletions clang-tools-extra/clangd/FindSymbols.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ class ParsedAST;
class SymbolIndex;

/// Searches for the symbols matching \p Query. The syntax of \p Query can be
/// the non-qualified name or fully qualified of a symbol. For example, "vector"
/// will match the symbol std::vector and "std::vector" would also match it.
/// Direct children of scopes (namepaces, etc) can be listed with a trailing
/// the non-qualified name or fully qualified of a symbol. For example,
/// "vector" will match the symbol std::vector and "std::vector" would also
/// match it. Direct children of scopes (namepaces, etc) can be listed with a
/// trailing
/// "::". For example, "std::" will list all children of the std namespace and
/// "::" alone will list all children of the global namespace.
/// \p Limit limits the number of results returned (0 means no limit).
Expand Down
115 changes: 115 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,61 @@ SymbolKind adjustKindToCapability(SymbolKind Kind,
}
}

SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
switch (Kind) {
case index::SymbolKind::Unknown:
return SymbolKind::Variable;
case index::SymbolKind::Module:
return SymbolKind::Module;
case index::SymbolKind::Namespace:
return SymbolKind::Namespace;
case index::SymbolKind::NamespaceAlias:
return SymbolKind::Namespace;
case index::SymbolKind::Macro:
return SymbolKind::String;
case index::SymbolKind::Enum:
return SymbolKind::Enum;
case index::SymbolKind::Struct:
return SymbolKind::Struct;
case index::SymbolKind::Class:
return SymbolKind::Class;
case index::SymbolKind::Protocol:
return SymbolKind::Interface;
case index::SymbolKind::Extension:
return SymbolKind::Interface;
case index::SymbolKind::Union:
return SymbolKind::Class;
case index::SymbolKind::TypeAlias:
return SymbolKind::Class;
case index::SymbolKind::Function:
return SymbolKind::Function;
case index::SymbolKind::Variable:
return SymbolKind::Variable;
case index::SymbolKind::Field:
return SymbolKind::Field;
case index::SymbolKind::EnumConstant:
return SymbolKind::EnumMember;
case index::SymbolKind::InstanceMethod:
case index::SymbolKind::ClassMethod:
case index::SymbolKind::StaticMethod:
return SymbolKind::Method;
case index::SymbolKind::InstanceProperty:
case index::SymbolKind::ClassProperty:
case index::SymbolKind::StaticProperty:
return SymbolKind::Property;
case index::SymbolKind::Constructor:
case index::SymbolKind::Destructor:
return SymbolKind::Method;
case index::SymbolKind::ConversionFunction:
return SymbolKind::Function;
case index::SymbolKind::Parameter:
return SymbolKind::Variable;
case index::SymbolKind::Using:
return SymbolKind::Namespace;
}
llvm_unreachable("invalid symbol kind");
}

bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R) {
const llvm::json::Object *O = Params.getAsObject();
if (!O)
Expand Down Expand Up @@ -812,6 +867,66 @@ bool fromJSON(const llvm::json::Value &Params, InitializationOptions &Opts) {
return true;
}

bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out) {
auto T = E.getAsInteger();
if (!T)
return false;
if (*T < static_cast<int>(TypeHierarchyDirection::Children) ||
*T > static_cast<int>(TypeHierarchyDirection::Both))
return false;
Out = static_cast<TypeHierarchyDirection>(*T);
return true;
}

bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R) {
llvm::json::ObjectMapper O(Params);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("resolve", R.resolve) &&
O.map("direction", R.direction);
}

llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
const TypeHierarchyItem &I) {
return O << I.name << " - " << toJSON(I);
}

llvm::json::Value toJSON(const TypeHierarchyItem &I) {
llvm::json::Object Result{{"name", I.name},
{"kind", static_cast<int>(I.kind)},
{"range", I.range},
{"selectionRange", I.selectionRange},
{"uri", I.uri}};

if (I.detail)
Result["detail"] = I.detail;
if (I.deprecated)
Result["deprecated"] = I.deprecated;
if (I.parents)
Result["parents"] = I.parents;
if (I.children)
Result["children"] = I.children;
return std::move(Result);
}

bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I) {
llvm::json::ObjectMapper O(Params);

// Required fields.
if (!(O && O.map("name", I.name) && O.map("kind", I.kind) &&
O.map("uri", I.uri) && O.map("range", I.range) &&
O.map("selectionRange", I.selectionRange))) {
return false;
}

// Optional fields.
O.map("detail", I.detail);
O.map("deprecated", I.deprecated);
O.map("parents", I.parents);
O.map("children", I.children);

return true;
}

bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R) {
TextDocumentPositionParams &Base = R;
return fromJSON(Params, Base);
Expand Down
68 changes: 68 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "URI.h"
#include "index/SymbolID.h"
#include "clang/Index/IndexSymbol.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/JSON.h"
#include <bitset>
Expand Down Expand Up @@ -331,6 +332,12 @@ bool fromJSON(const llvm::json::Value &, SymbolKindBitset &);
SymbolKind adjustKindToCapability(SymbolKind Kind,
SymbolKindBitset &supportedSymbolKinds);

// Convert a index::SymbolKind to clangd::SymbolKind (LSP)
// Note, some are not perfect matches and should be improved when this LSP
// issue is addressed:
// https://github.com/Microsoft/language-server-protocol/issues/344
SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind);

// This struct doesn't mirror LSP!
// The protocol defines deeply nested structures for client capabilities.
// Instead of mapping them all, this just parses out the bits we care about.
Expand Down Expand Up @@ -1014,6 +1021,67 @@ struct DocumentHighlight {
llvm::json::Value toJSON(const DocumentHighlight &DH);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const DocumentHighlight &);

enum class TypeHierarchyDirection { Children = 0, Parents = 1, Both = 2 };
bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out);

/// The type hierarchy params is an extension of the
/// `TextDocumentPositionsParams` with optional properties which can be used to
/// eagerly resolve the item when requesting from the server.
struct TypeHierarchyParams : public TextDocumentPositionParams {
/// The hierarchy levels to resolve. `0` indicates no level.
int resolve = 0;

/// The direction of the hierarchy levels to resolve.
TypeHierarchyDirection direction = TypeHierarchyDirection::Parents;
};
bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &);

struct TypeHierarchyItem {
/// The human readable name of the hierarchy item.
std::string name;

/// Optional detail for the hierarchy item. It can be, for instance, the
/// signature of a function or method.
llvm::Optional<std::string> detail;

/// The kind of the hierarchy item. For instance, class or interface.
SymbolKind kind;

/// `true` if the hierarchy item is deprecated. Otherwise, `false`.
bool deprecated;

/// The URI of the text document where this type hierarchy item belongs to.
URIForFile uri;

/// The range enclosing this type hierarchy item not including
/// leading/trailing whitespace but everything else like comments. This
/// information is typically used to determine if the client's cursor is
/// inside the type hierarch item to reveal in the symbol in the UI.
Range range;

/// The range that should be selected and revealed when this type hierarchy
/// item is being picked, e.g. the name of a function. Must be contained by
/// the `range`.
Range selectionRange;

/// If this type hierarchy item is resolved, it contains the direct parents.
/// Could be empty if the item does not have direct parents. If not defined,
/// the parents have not been resolved yet.
llvm::Optional<std::vector<TypeHierarchyItem>> parents;

/// If this type hierarchy item is resolved, it contains the direct children
/// of the current item. Could be empty if the item does not have any
/// descendants. If not defined, the children have not been resolved.
llvm::Optional<std::vector<TypeHierarchyItem>> children;

/// The protocol has a slot here for an optional 'data' filed, which can
/// be used to identify a type hierarchy item in a resolve request. We don't
/// need this (the item itself is sufficient to identify what to resolve)
/// so don't declare it.
};
llvm::json::Value toJSON(const TypeHierarchyItem &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &);

struct ReferenceParams : public TextDocumentPositionParams {
// For now, no options like context.includeDeclaration are supported.
};
Expand Down
Loading

0 comments on commit 8665802

Please sign in to comment.