Index: ClangdLSPServer.h =================================================================== --- ClangdLSPServer.h +++ ClangdLSPServer.h @@ -88,6 +88,8 @@ void onHover(const TextDocumentPositionParams &, Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); + void onSymbolInfo(const TextDocumentPositionParams &, + Callback>); std::vector getFixes(StringRef File, const clangd::Diagnostic &D); Index: ClangdLSPServer.cpp =================================================================== --- ClangdLSPServer.cpp +++ ClangdLSPServer.cpp @@ -685,6 +685,12 @@ std::move(Reply)); } +void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams &Params, + Callback> Reply) { + Server->getSymbolInfo(Params.textDocument.uri.file(), Params.position, + std::move(Reply)); +} + ClangdLSPServer::ClangdLSPServer(class Transport &Transp, const clangd::CodeCompleteOptions &CCOpts, Optional CompileCommandsDir, @@ -719,6 +725,7 @@ MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange); MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); + MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); // clang-format on } Index: ClangdServer.h =================================================================== --- ClangdServer.h +++ ClangdServer.h @@ -201,6 +201,10 @@ /// Called when an event occurs for a watched file in the workspace. void onFileEvent(const DidChangeWatchedFilesParams &Params); + /// Get symbol info for given position. + void getSymbolInfo(PathRef File, Position Pos, + Callback> CB); + /// Returns estimated memory usage for each of the currently open files. /// The order of results is unspecified. /// Overall memory usage of clangd may be significantly more than reported Index: ClangdServer.cpp =================================================================== --- ClangdServer.cpp +++ ClangdServer.cpp @@ -517,6 +517,18 @@ WorkScheduler.runWithAST("References", File, Bind(Action, std::move(CB))); } +void ClangdServer::getSymbolInfo(PathRef File, Position Pos, + Callback> CB) { + auto Action = [Pos](Callback> CB, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getSymbolInfo(InpAST->AST, Pos)); + }; + + WorkScheduler.runWithAST("CursorInfo", File, Bind(Action, std::move(CB))); +} + std::vector> ClangdServer::getUsedBytesPerFile() const { return WorkScheduler.getUsedBytesPerFile(); Index: Protocol.h =================================================================== --- Protocol.h +++ Protocol.h @@ -673,6 +673,62 @@ llvm::json::Value toJSON(const SymbolInformation &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolInformation &); +// The class identifies a particular C++ symbol (class, function, method, etc). +// +// As USRs (Unified Symbol Resolution) could be large, especially for functions +// with long type arguments, SymbolID is using truncated SHA1(USR) values to +// guarantee the uniqueness of symbols while using a relatively small amount of +// memory (vs storing USRs directly). +// +// SymbolID can be used as key in the symbol indexes to lookup the symbol. +class SymbolID { +public: + SymbolID() = default; + explicit SymbolID(llvm::StringRef USR); + + bool operator==(const SymbolID &Sym) const { + return HashValue == Sym.HashValue; + } + bool operator<(const SymbolID &Sym) const { + return HashValue < Sym.HashValue; + } + + // The stored hash is truncated to RawSize bytes. + // This trades off memory against the number of symbols we can handle. + // FIXME: can we reduce this further to 8 bytes? + constexpr static size_t RawSize = 16; + llvm::StringRef raw() const; + + static SymbolID fromRaw(llvm::StringRef); + + // Returns a hex encoded string. + std::string str() const; + static llvm::Expected fromStr(llvm::StringRef); + +private: + std::array HashValue; +}; + +llvm::hash_code hash_value(const SymbolID &ID); + +// Write SymbolID into the given stream. SymbolID is encoded as ID.str(). +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolID &ID); + +/// Represents information about identifier. +/// This is returned from textDocument/symbolInfo, which is a clangd extension. +struct SymbolDetails { + std::string name; + + std::string containerName; + + /// Unified Symbol Resolution identifier + std::string USR; + + SymbolID ID; +}; +llvm::json::Value toJSON(const SymbolDetails &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolDetails &); + /// The parameters of a Workspace Symbol Request. struct WorkspaceSymbolParams { /// A non-empty query string Index: Protocol.cpp =================================================================== --- Protocol.cpp +++ Protocol.cpp @@ -14,11 +14,14 @@ #include "Protocol.h" #include "Logger.h" #include "URI.h" +#include "index/Index.h" #include "clang/Basic/LLVM.h" +#include "llvm/ADT/Hashing.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" +#include "llvm/Support/SHA1.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; @@ -422,6 +425,62 @@ return O; } +SymbolID::SymbolID(StringRef USR) { + auto Hash = llvm::SHA1::hash(arrayRefFromStringRef(USR)); + static_assert(sizeof(Hash) >= RawSize, "RawSize larger than SHA1"); + memcpy(HashValue.data(), Hash.data(), RawSize); +} + +llvm::StringRef SymbolID::raw() const { + return StringRef(reinterpret_cast(HashValue.data()), RawSize); +} + +SymbolID SymbolID::fromRaw(StringRef Raw) { + SymbolID ID; + assert(Raw.size() == RawSize); + memcpy(ID.HashValue.data(), Raw.data(), RawSize); + return ID; +} + +std::string SymbolID::str() const { return toHex(raw()); } + +Expected SymbolID::fromStr(StringRef Str) { + if (Str.size() != RawSize * 2) + return createStringError(inconvertibleErrorCode(), "Bad ID length"); + for (char C : Str) + if (!isHexDigit(C)) + return createStringError(inconvertibleErrorCode(), "Bad hex ID"); + return fromRaw(fromHex(Str)); +} + +raw_ostream &operator<<(raw_ostream &OS, const SymbolID &ID) { + return OS << toHex(ID.raw()); +} + +llvm::hash_code hash_value(const SymbolID &ID) { + // We already have a good hash, just return the first bytes. + assert(sizeof(size_t) <= SymbolID::RawSize && "size_t longer than SHA1!"); + size_t Result; + memcpy(&Result, ID.raw().data(), sizeof(size_t)); + return llvm::hash_code(Result); +} + +llvm::json::Value toJSON(const SymbolDetails &P) { + return json::Object{ + {"name", P.name}, + {"containerName", P.containerName}, + {"usr", P.USR}, + {"id", P.ID.str()}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const SymbolDetails &S) { + if (!S.containerName.empty()) + O << S.containerName << "::"; + O << S.name << " - " << toJSON(S); + return O; +} + bool fromJSON(const json::Value &Params, WorkspaceSymbolParams &R) { json::ObjectMapper O(Params); return O && O.map("query", R.query); Index: XRefs.h =================================================================== --- XRefs.h +++ XRefs.h @@ -38,6 +38,9 @@ std::vector findReferences(ParsedAST &AST, Position Pos, const SymbolIndex *Index = nullptr); +/// Get info about symbol at \p Cursor. +llvm::Optional getSymbolInfo(ParsedAST &AST, Position Pos); + } // namespace clangd } // namespace clang Index: XRefs.cpp =================================================================== --- XRefs.cpp +++ XRefs.cpp @@ -92,11 +92,14 @@ const SourceLocation &SearchedLocation; const ASTContext &AST; Preprocessor &PP; + const bool StopOnFirstDeclFound; public: DeclarationAndMacrosFinder(const SourceLocation &SearchedLocation, - ASTContext &AST, Preprocessor &PP) - : SearchedLocation(SearchedLocation), AST(AST), PP(PP) {} + ASTContext &AST, Preprocessor &PP, + bool StopOnFirstDeclFound) + : SearchedLocation(SearchedLocation), AST(AST), PP(PP), + StopOnFirstDeclFound(StopOnFirstDeclFound) {} // Get all DeclInfo of the found declarations. // The results are sorted by "IsReferencedExplicitly" and declaration @@ -153,6 +156,12 @@ }; bool IsExplicit = !hasImplicitExpr(ASTNode.OrigE); + + if (StopOnFirstDeclFound && IsExplicit) { + Decls[D] |= IsExplicit; + return false; + } + // Find and add definition declarations (for GoToDefinition). // We don't use parameter `D`, as Parameter `D` is the canonical // declaration, which is the first declaration of a redeclarable @@ -203,9 +212,10 @@ std::vector Macros; }; -IdentifiedSymbol getSymbolAtPosition(ParsedAST &AST, SourceLocation Pos) { - auto DeclMacrosFinder = DeclarationAndMacrosFinder(Pos, AST.getASTContext(), - AST.getPreprocessor()); +IdentifiedSymbol getSymbolAtPosition(ParsedAST &AST, SourceLocation Pos, + bool ReturnFirstDeclFound = false) { + auto DeclMacrosFinder = DeclarationAndMacrosFinder( + Pos, AST.getASTContext(), AST.getPreprocessor(), ReturnFirstDeclFound); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -749,5 +759,51 @@ return Results; } +llvm::Optional getSymbolInfo(ParsedAST &AST, Position Pos) { + const SourceManager &SM = AST.getASTContext().getSourceManager(); + + auto Loc = getBeginningOfIdentifier(AST, Pos, SM.getMainFileID()); + auto Symbols = getSymbolAtPosition(AST, Loc, true); + + SymbolDetails Result; + + if (!Symbols.Decls.empty()) { + if (const NamedDecl *ND = dyn_cast(Symbols.Decls.front().D)) { + std::string QName = printQualifiedName(*ND); + std::tie(Result.containerName, Result.name) = splitQualifiedName(QName); + + if (!Result.containerName.empty()) { + StringRef ContainerNameRef = Result.containerName; + ContainerNameRef.consume_back("::"); + } else { + const auto *DC = ND->getDeclContext(); + if (DC) { + if (const NamedDecl *ParentND = dyn_cast(DC)) { + Result.containerName = printQualifiedName(*ParentND); + } + } + } + } + llvm::SmallString<32> USR; + if (!index::generateUSRForDecl(Symbols.Decls.front().D, USR)) { + Result.USR = USR.str(); + Result.ID = SymbolID(Result.USR); + return Result; + } + } + + if (!Symbols.Macros.empty()) { + Result.name = Symbols.Macros.front().Name; + llvm::SmallString<32> USR; + if (!index::generateUSRForMacro(Result.name, Loc, SM, USR)) { + Result.USR = USR.str(); + Result.ID = SymbolID(Result.USR); + return Result; + } + } + + return llvm::None; +} + } // namespace clangd } // namespace clang Index: clangd/cursor-info.test =================================================================== --- /dev/null +++ clangd/cursor-info.test @@ -0,0 +1,46 @@ +# RUN: clangd -lit-test < %s | FileCheck %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///simple.cpp","languageId":"cpp","version":1,"text":"void foo(); int main() { foo(); }\n"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///simple.cpp"},"position":{"line":0,"character":27}}} +# CHECK: "containerName": "", +# CHECK-NEXT: "id": "CA2EBE44A1D76D2A1547D47BC6D51EBF", +# CHECK-NEXT: "name": "foo", +# CHECK-NEXT: "usr": "c:@F@foo#" +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///nested-decl.cpp","languageId":"cpp","version":1,"text":"namespace nnn { struct aaa {}; void foo() { aaa a; }; }"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///nested-decl.cpp"},"position":{"line":0,"character":46}}} +# CHECK: "containerName": "nnn::", +# CHECK-NEXT: "id": "20237FF18EB405D842456DC5D578426D", +# CHECK-NEXT: "name": "aaa", +# CHECK-NEXT: "usr": "c:@N@nnn@S@aaa" +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///reference.cpp","languageId":"cpp","version":1,"text":"int value; void foo(int) {} void bar() { foo(value); }"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///reference.cpp"},"position":{"line":0,"character":48}}} +# CHECK: "containerName": "", +# CHECK-NEXT: "id": "844613FB2393C9D40A2AFF25D5D316A1", +# CHECK-NEXT: "name": "value", +# CHECK-NEXT: "usr": "c:@value" +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///local.cpp","languageId":"cpp","version":1,"text":"void foo() { int aaa; int bbb = aaa; }"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///local.cpp"},"position":{"line":0,"character":33}}} +# CHECK: "containerName": "foo", +# CHECK-NEXT: "id": "C05589F2664B06F392C2C438568E55E0", +# CHECK-NEXT: "name": "aaa", +# CHECK-NEXT: "usr": "c:local.cpp@13@F@foo#@aaa" +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///macro.cpp","languageId":"cpp","version":1,"text":"#define MACRO 5\nint i = MACRO;"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/symbolInfo","params":{"textDocument":{"uri":"test:///macro.cpp"},"position":{"line":1,"character":11}}} +# CHECK: "containerName": "", +# CHECK-NEXT: "id": "29EB506CBDF1BA6D1B6EC203FF03B384", +# CHECK-NEXT: "name": "MACRO", +# CHECK-NEXT: "usr": "c:macro.cpp@24@macro@MACRO" +--- +{"jsonrpc":"2.0","id":3,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} Index: index/Index.h =================================================================== --- index/Index.h +++ index/Index.h @@ -10,11 +10,11 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_INDEX_INDEX_H +#include "Protocol.h" #include "clang/Index/IndexSymbol.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/Hashing.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" @@ -94,54 +94,6 @@ } llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolLocation &); -// The class identifies a particular C++ symbol (class, function, method, etc). -// -// As USRs (Unified Symbol Resolution) could be large, especially for functions -// with long type arguments, SymbolID is using truncated SHA1(USR) values to -// guarantee the uniqueness of symbols while using a relatively small amount of -// memory (vs storing USRs directly). -// -// SymbolID can be used as key in the symbol indexes to lookup the symbol. -class SymbolID { -public: - SymbolID() = default; - explicit SymbolID(llvm::StringRef USR); - - bool operator==(const SymbolID &Sym) const { - return HashValue == Sym.HashValue; - } - bool operator<(const SymbolID &Sym) const { - return HashValue < Sym.HashValue; - } - - // The stored hash is truncated to RawSize bytes. - // This trades off memory against the number of symbols we can handle. - // FIXME: can we reduce this further to 8 bytes? - constexpr static size_t RawSize = 16; - llvm::StringRef raw() const { - return StringRef(reinterpret_cast(HashValue.data()), RawSize); - } - static SymbolID fromRaw(llvm::StringRef); - - // Returns a hex encoded string. - std::string str() const; - static llvm::Expected fromStr(llvm::StringRef); - -private: - std::array HashValue; -}; - -inline llvm::hash_code hash_value(const SymbolID &ID) { - // We already have a good hash, just return the first bytes. - assert(sizeof(size_t) <= SymbolID::RawSize && "size_t longer than SHA1!"); - size_t Result; - memcpy(&Result, ID.raw().data(), sizeof(size_t)); - return llvm::hash_code(Result); -} - -// Write SymbolID into the given stream. SymbolID is encoded as ID.str(). -llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const SymbolID &ID); - } // namespace clangd } // namespace clang namespace llvm { Index: index/Index.cpp =================================================================== --- index/Index.cpp +++ index/Index.cpp @@ -12,7 +12,6 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" -#include "llvm/Support/SHA1.h" #include "llvm/Support/raw_ostream.h" using namespace llvm; @@ -43,34 +42,6 @@ << "-" << L.End.line() << ":" << L.End.column() << ")"; } -SymbolID::SymbolID(StringRef USR) { - auto Hash = SHA1::hash(arrayRefFromStringRef(USR)); - static_assert(sizeof(Hash) >= RawSize, "RawSize larger than SHA1"); - memcpy(HashValue.data(), Hash.data(), RawSize); -} - -raw_ostream &operator<<(raw_ostream &OS, const SymbolID &ID) { - return OS << toHex(ID.raw()); -} - -SymbolID SymbolID::fromRaw(StringRef Raw) { - SymbolID ID; - assert(Raw.size() == RawSize); - memcpy(ID.HashValue.data(), Raw.data(), RawSize); - return ID; -} - -std::string SymbolID::str() const { return toHex(raw()); } - -Expected SymbolID::fromStr(StringRef Str) { - if (Str.size() != RawSize * 2) - return createStringError(inconvertibleErrorCode(), "Bad ID length"); - for (char C : Str) - if (!isHexDigit(C)) - return createStringError(inconvertibleErrorCode(), "Bad hex ID"); - return fromRaw(fromHex(Str)); -} - raw_ostream &operator<<(raw_ostream &OS, SymbolOrigin O) { if (O == SymbolOrigin::Unknown) return OS << "unknown";