diff options
22 files changed, 714 insertions, 25 deletions
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 904fb3b0f39..a5da0d2615f 100644 --- a/clang-tools-extra/clangd/CMakeLists.txt +++ b/clang-tools-extra/clangd/CMakeLists.txt @@ -19,6 +19,7 @@ add_clang_library(clangDaemon Context.cpp Diagnostics.cpp DraftStore.cpp + FindSymbols.cpp FuzzyMatch.cpp GlobalCompilationDatabase.cpp Headers.cpp diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 8187f7830e3..f8895b544d1 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -86,6 +86,14 @@ std::vector<TextEdit> replacementsToEdits(StringRef Code, return Edits; } +SymbolKindBitset defaultSymbolKinds() { + SymbolKindBitset Defaults; + for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array); + ++I) + Defaults.set(I); + return Defaults; +} + } // namespace void ClangdLSPServer::onInitialize(InitializeParams &Params) { @@ -97,6 +105,14 @@ void ClangdLSPServer::onInitialize(InitializeParams &Params) { CCOpts.EnableSnippets = Params.capabilities.textDocument.completion.completionItem.snippetSupport; + if (Params.capabilities.workspace && Params.capabilities.workspace->symbol && + Params.capabilities.workspace->symbol->symbolKind) { + for (SymbolKind Kind : + *Params.capabilities.workspace->symbol->symbolKind->valueSet) { + SupportedSymbolKinds.set(static_cast<size_t>(Kind)); + } + } + reply(json::obj{ {{"capabilities", json::obj{ @@ -122,6 +138,7 @@ void ClangdLSPServer::onInitialize(InitializeParams &Params) { {"documentHighlightProvider", true}, {"hoverProvider", true}, {"renameProvider", true}, + {"workspaceSymbolProvider", true}, {"executeCommandProvider", json::obj{ {"commands", @@ -245,6 +262,20 @@ void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) { } } +void ClangdLSPServer::onWorkspaceSymbol(WorkspaceSymbolParams &Params) { + Server.workspaceSymbols( + Params.query, CCOpts.Limit, + [this](llvm::Expected<std::vector<SymbolInformation>> Items) { + if (!Items) + return replyError(ErrorCode::InternalError, + llvm::toString(Items.takeError())); + for (auto &Sym : *Items) + Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); + + reply(json::ary(*Items)); + }); +} + void ClangdLSPServer::onRename(RenameParams &Params) { Path File = Params.textDocument.uri.file(); llvm::Optional<std::string> Code = DraftMgr.getDraft(File); @@ -422,6 +453,7 @@ ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, llvm::Optional<Path> CompileCommandsDir, const ClangdServer::Options &Opts) : Out(Out), CDB(std::move(CompileCommandsDir)), CCOpts(CCOpts), + SupportedSymbolKinds(defaultSymbolKinds()), Server(CDB, FSProvider, /*DiagConsumer=*/*this, Opts) {} bool ClangdLSPServer::run(std::istream &In, JSONStreamStyle InputStyle) { diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 82cf16f2637..f4884d32d78 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -12,6 +12,7 @@ #include "ClangdServer.h" #include "DraftStore.h" +#include "FindSymbols.h" #include "GlobalCompilationDatabase.h" #include "Path.h" #include "Protocol.h" @@ -69,6 +70,7 @@ private: void onDocumentHighlight(TextDocumentPositionParams &Params) override; void onFileEvent(DidChangeWatchedFilesParams &Params) override; void onCommand(ExecuteCommandParams &Params) override; + void onWorkspaceSymbol(WorkspaceSymbolParams &Params) override; void onRename(RenameParams &Parames) override; void onHover(TextDocumentPositionParams &Params) override; void onChangeConfiguration(DidChangeConfigurationParams &Params) override; @@ -102,6 +104,8 @@ private: RealFileSystemProvider FSProvider; /// Options used for code completion clangd::CodeCompleteOptions CCOpts; + /// The supported kinds of the client. + SymbolKindBitset SupportedSymbolKinds; // Store of the current versions of the open documents. DraftStore DraftMgr; diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index e73834be32d..e72cf40159c 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -9,6 +9,7 @@ #include "ClangdServer.h" #include "CodeComplete.h" +#include "FindSymbols.h" #include "Headers.h" #include "SourceCode.h" #include "XRefs.h" @@ -499,6 +500,11 @@ void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) { // invalidating other caches. } +void ClangdServer::workspaceSymbols( + StringRef Query, int Limit, Callback<std::vector<SymbolInformation>> CB) { + CB(clangd::getWorkspaceSymbols(Query, Limit, Index)); +} + std::vector<std::pair<Path, std::size_t>> ClangdServer::getUsedBytesPerFile() const { return WorkScheduler.getUsedBytesPerFile(); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 34af4b9e29a..6e60705b539 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -157,6 +157,10 @@ public: /// Get code hover for a given position. void findHover(PathRef File, Position Pos, Callback<Hover> CB); + /// Retrieve the top symbols from the workspace matching a query. + void workspaceSymbols(StringRef Query, int Limit, + Callback<std::vector<SymbolInformation>> CB); + /// Run formatting for \p Rng inside \p File with content \p Code. llvm::Expected<tooling::Replacements> formatRange(StringRef Code, PathRef File, Range Rng); diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp new file mode 100644 index 00000000000..f2e7dd254cf --- /dev/null +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -0,0 +1,141 @@ +//===--- FindSymbols.cpp ------------------------------------*- C++-*------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "FindSymbols.h" + +#include "Logger.h" +#include "SourceCode.h" +#include "index/Index.h" +#include "clang/Index/IndexSymbol.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" + +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 + +llvm::Expected<std::vector<SymbolInformation>> +getWorkspaceSymbols(StringRef Query, int Limit, + const SymbolIndex *const Index) { + std::vector<SymbolInformation> Result; + if (Query.empty() || !Index) + return Result; + + auto Names = splitQualifiedName(Query); + + FuzzyFindRequest Req; + Req.Query = Names.second; + + // FuzzyFind doesn't want leading :: qualifier + bool IsGlobalQuery = Names.first.consume_front("::"); + // Restrict results to the scope in the query string if present (global or + // not). + if (IsGlobalQuery || !Names.first.empty()) + Req.Scopes = {Names.first}; + if (Limit) + Req.MaxCandidateCount = Limit; + Index->fuzzyFind(Req, [&Result](const Symbol &Sym) { + // Prefer the definition over e.g. a function declaration in a header + auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration; + auto Uri = URI::parse(CD.FileURI); + if (!Uri) { + log(llvm::formatv( + "Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.", + CD.FileURI, Sym.Name)); + return; + } + // FIXME: Passing no HintPath here will work for "file" and "test" schemes + // because they don't use it but this might not work for other custom ones. + auto Path = URI::resolve(*Uri); + if (!Path) { + log(llvm::formatv("Workspace symbol: Could not resolve path for URI " + "'{0}' for symbol '{1}'.", + (*Uri).toString(), Sym.Name.str())); + return; + } + Location L; + L.uri = URIForFile((*Path)); + Position Start, End; + Start.line = CD.Start.Line; + Start.character = CD.Start.Column; + End.line = CD.End.Line; + End.character = CD.End.Column; + L.range = {Start, End}; + SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind); + std::string Scope = Sym.Scope; + StringRef ScopeRef = Scope; + ScopeRef.consume_back("::"); + Result.push_back({Sym.Name, SK, L, ScopeRef}); + }); + return Result; +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h new file mode 100644 index 00000000000..118917564d2 --- /dev/null +++ b/clang-tools-extra/clangd/FindSymbols.h @@ -0,0 +1,37 @@ +//===--- FindSymbols.h --------------------------------------*- C++-*------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Queries that provide a list of symbols matching a string. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H + +#include "Protocol.h" +#include "llvm/ADT/StringRef.h" + +namespace clang { +namespace clangd { +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 +/// "::". 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). +llvm::Expected<std::vector<SymbolInformation>> +getWorkspaceSymbols(llvm::StringRef Query, int Limit, + const SymbolIndex *const Index); + +} // namespace clangd +} // namespace clang + +#endif diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index ec81a22a8fc..c7e408282c7 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -176,6 +176,63 @@ bool fromJSON(const json::Expr &Params, CompletionClientCapabilities &R) { return true; } +bool fromJSON(const json::Expr &E, SymbolKind &Out) { + if (auto T = E.asInteger()) { + if (*T < static_cast<int>(SymbolKind::File) || + *T > static_cast<int>(SymbolKind::TypeParameter)) + return false; + Out = static_cast<SymbolKind>(*T); + return true; + } + return false; +} + +bool fromJSON(const json::Expr &E, std::vector<SymbolKind> &Out) { + if (auto *A = E.asArray()) { + Out.clear(); + for (size_t I = 0; I < A->size(); ++I) { + SymbolKind KindOut; + if (fromJSON((*A)[I], KindOut)) + Out.push_back(KindOut); + } + return true; + } + return false; +} + +bool fromJSON(const json::Expr &Params, SymbolKindCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("valueSet", R.valueSet); +} + +SymbolKind adjustKindToCapability(SymbolKind Kind, + SymbolKindBitset &supportedSymbolKinds) { + auto KindVal = static_cast<size_t>(Kind); + if (KindVal >= SymbolKindMin && KindVal <= supportedSymbolKinds.size() && + supportedSymbolKinds[KindVal]) + return Kind; + + switch (Kind) { + // Provide some fall backs for common kinds that are close enough. + case SymbolKind::Struct: + return SymbolKind::Class; + case SymbolKind::EnumMember: + return SymbolKind::Enum; + default: + return SymbolKind::String; + } +} + +bool fromJSON(const json::Expr &Params, WorkspaceSymbolCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("symbolKind", R.symbolKind); +} + +bool fromJSON(const json::Expr &Params, WorkspaceClientCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("symbol", R.symbol); +} + bool fromJSON(const json::Expr &Params, TextDocumentClientCapabilities &R) { json::ObjectMapper O(Params); if (!O) @@ -189,6 +246,7 @@ bool fromJSON(const json::Expr &Params, ClientCapabilities &R) { if (!O) return false; O.map("textDocument", R.textDocument); + O.map("workspace", R.workspace); return true; } @@ -351,6 +409,26 @@ bool fromJSON(const json::Expr &Params, ExecuteCommandParams &R) { return false; // Unrecognized command. } +json::Expr toJSON(const SymbolInformation &P) { + return json::obj{ + {"name", P.name}, + {"kind", static_cast<int>(P.kind)}, + {"location", P.location}, + {"containerName", P.containerName}, + }; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const SymbolInformation &SI) { + O << SI.containerName << "::" << SI.name << " - " << toJSON(SI); + return O; +} + +bool fromJSON(const json::Expr &Params, WorkspaceSymbolParams &R) { + json::ObjectMapper O(Params); + return O && O.map("query", R.query); +} + json::Expr toJSON(const Command &C) { auto Cmd = json::obj{{"title", C.title}, {"command", C.command}}; if (C.workspaceEdit) diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index fa57a0c0912..a54179ca3ee 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -27,6 +27,7 @@ #include "JSONExpr.h" #include "URI.h" #include "llvm/ADT/Optional.h" +#include <bitset> #include <string> #include <vector> @@ -237,6 +238,67 @@ struct CompletionClientCapabilities { }; bool fromJSON(const json::Expr &, CompletionClientCapabilities &); +/// A symbol kind. +enum class SymbolKind { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26 +}; + +constexpr auto SymbolKindMin = static_cast<size_t>(SymbolKind::File); +constexpr auto SymbolKindMax = static_cast<size_t>(SymbolKind::TypeParameter); +using SymbolKindBitset = std::bitset<SymbolKindMax + 1>; + +bool fromJSON(const json::Expr &, SymbolKind &); + +struct SymbolKindCapabilities { + /// The SymbolKinds that the client supports. If not set, the client only + /// supports <= SymbolKind::Array and will not fall back to a valid default + /// value. + llvm::Optional<std::vector<SymbolKind>> valueSet; +}; +bool fromJSON(const json::Expr &, std::vector<SymbolKind> &); +bool fromJSON(const json::Expr &, SymbolKindCapabilities &); +SymbolKind adjustKindToCapability(SymbolKind Kind, + SymbolKindBitset &supportedSymbolKinds); + +struct WorkspaceSymbolCapabilities { + /// Capabilities SymbolKind. + llvm::Optional<SymbolKindCapabilities> symbolKind; +}; +bool fromJSON(const json::Expr &, WorkspaceSymbolCapabilities &); + +// FIXME: most of the capabilities are missing from this struct. Only the ones +// used by clangd are currently there. +struct WorkspaceClientCapabilities { + /// Capabilities specific to `workspace/symbol`. + llvm::Optional<WorkspaceSymbolCapabilities> symbol; +}; +bool fromJSON(const json::Expr &, WorkspaceClientCapabilities &); + // FIXME: most of the capabilities are missing from this struct. Only the ones // used by clangd are currently there. struct TextDocumentClientCapabilities { @@ -247,8 +309,7 @@ bool fromJSON(const json::Expr &, TextDocumentClientCapabilities &); struct ClientCapabilities { // Workspace specific client capabilities. - // NOTE: not used by clangd at the moment. - // WorkspaceClientCapabilities workspace; + llvm::Optional<WorkspaceClientCapabilities> workspace; // Text document specific client capabilities. TextDocumentClientCapabilities textDocument; @@ -525,6 +586,31 @@ struct Command : public ExecuteCommandParams { json::Expr toJSON(const Command &C); +/// Represents information about programming constructs like variables, classes, +/// interfaces etc. +struct SymbolInformation { + /// The name of this symbol. + std::string name; + + /// The kind of this symbol. + SymbolKind kind; + + /// The location of this symbol. + Location location; + + /// The name of the symbol containing this symbol. + std::string containerName; +}; +json::Expr toJSON(const SymbolInformation &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const SymbolInformation &); + +/// The parameters of a Workspace Symbol Request. +struct WorkspaceSymbolParams { + /// A non-empty query string + std::string query; +}; +bool fromJSON(const json::Expr &, WorkspaceSymbolParams &); + struct ApplyWorkspaceEditParams { WorkspaceEdit edit; }; diff --git a/clang-tools-extra/clangd/ProtocolHandlers.cpp b/clang-tools-extra/clangd/ProtocolHandlers.cpp index f5b46694763..0432714a5a8 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.cpp +++ b/clang-tools-extra/clangd/ProtocolHandlers.cpp @@ -72,4 +72,5 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, &ProtocolCallbacks::onDocumentHighlight); Register("workspace/didChangeConfiguration", &ProtocolCallbacks::onChangeConfiguration); + Register("workspace/symbol", &ProtocolCallbacks::onWorkspaceSymbol); } diff --git a/clang-tools-extra/clangd/ProtocolHandlers.h b/clang-tools-extra/clangd/ProtocolHandlers.h index 6ed062c6aa9..dd181494043 100644 --- a/clang-tools-extra/clangd/ProtocolHandlers.h +++ b/clang-tools-extra/clangd/ProtocolHandlers.h @@ -49,6 +49,7 @@ public: virtual void onSwitchSourceHeader(TextDocumentIdentifier &Params) = 0; virtual void onFileEvent(DidChangeWatchedFilesParams &Params) = 0; virtual void onCommand(ExecuteCommandParams &Params) = 0; + virtual void onWorkspaceSymbol(WorkspaceSymbolParams &Params) = 0; virtual void onRename(RenameParams &Parames) = 0; virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0; virtual void onHover(TextDocumentPositionParams &Params) = 0; diff --git a/clang-tools-extra/clangd/SourceCode.cpp b/clang-tools-extra/clangd/SourceCode.cpp index 8e1db83ce22..65a7ca987c1 100644 --- a/clang-tools-extra/clangd/SourceCode.cpp +++ b/clang-tools-extra/clangd/SourceCode.cpp @@ -76,5 +76,13 @@ Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) { return {Begin, End}; } +std::pair<llvm::StringRef, llvm::StringRef> +splitQualifiedName(llvm::StringRef QName) { + size_t Pos = QName.rfind("::"); + if (Pos == llvm::StringRef::npos) + return {StringRef(), QName}; + return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)}; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/SourceCode.h b/clang-tools-extra/clangd/SourceCode.h index 6a2efd9a56f..43fd07015ec 100644 --- a/clang-tools-extra/clangd/SourceCode.h +++ b/clang-tools-extra/clangd/SourceCode.h @@ -49,6 +49,11 @@ Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc); // Note that clang also uses closed source ranges, which this can't handle! Range halfOpenToRange(const SourceManager &SM, CharSourceRange R); +/// From "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no +/// qualifier. +std::pair<llvm::StringRef, llvm::StringRef> +splitQualifiedName(llvm::StringRef QName); + } // namespace clangd } // namespace clang #endif diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index c9d156ee166..cef30c715b6 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -11,6 +11,7 @@ #include "../AST.h" #include "../CodeCompletionStrings.h" #include "../Logger.h" +#include "../SourceCode.h" #include "../URI.h" #include "CanonicalIncludes.h" #include "clang/AST/DeclCXX.h" @@ -89,16 +90,6 @@ llvm::Optional<std::string> toURI(const SourceManager &SM, StringRef Path, return llvm::None; } -// "a::b::c", return {"a::b::", "c"}. Scope is empty if there's no qualifier. -std::pair<llvm::StringRef, llvm::StringRef> -splitQualifiedName(llvm::StringRef QName) { - assert(!QName.startswith("::") && "Qualified names should not start with ::"); - size_t Pos = QName.rfind("::"); - if (Pos == llvm::StringRef::npos) - return {StringRef(), QName}; - return {QName.substr(0, Pos + 2), QName.substr(Pos + 2)}; -} - bool shouldFilterDecl(const NamedDecl *ND, ASTContext *ASTCtx, const SymbolCollector::Options &Opts) { using namespace clang::ast_matchers; @@ -321,6 +312,7 @@ const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, Policy.SuppressUnwrittenScope = true; ND.printQualifiedName(OS, Policy); OS.flush(); + assert(!StringRef(QName).startswith("::")); Symbol S; S.ID = std::move(ID); diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp index 4d579b46399..84d8a4e5f68 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -96,9 +96,9 @@ static llvm::cl::opt<PCHStorageFlag> PCHStorage( clEnumValN(PCHStorageFlag::Memory, "memory", "store PCHs in memory")), llvm::cl::init(PCHStorageFlag::Disk)); -static llvm::cl::opt<int> LimitCompletionResult( - "completion-limit", - llvm::cl::desc("Limit the number of completion results returned by clangd. " +static llvm::cl::opt<int> LimitResults( + "limit-results", + llvm::cl::desc("Limit the number of results returned by clangd. " "0 means no limit."), llvm::cl::init(100)); @@ -118,11 +118,11 @@ static llvm::cl::opt<Path> InputMirrorFile( "Mirror all LSP input to the specified file. Useful for debugging."), llvm::cl::init(""), llvm::cl::Hidden); -static llvm::cl::opt<bool> EnableIndexBasedCompletion( - "enable-index-based-completion", - llvm::cl::desc( - "Enable index-based global code completion. " - "Clang uses an index built from symbols in opened files"), +static llvm::cl::opt<bool> EnableIndex( + "index", + llvm::cl::desc("Enable index-based features such as global code completion " + "and searching for symbols." + "Clang uses an index built from symbols in opened files"), llvm::cl::init(true)); static llvm::cl::opt<Path> YamlSymbolFile( @@ -220,9 +220,9 @@ int main(int argc, char *argv[]) { } if (!ResourceDir.empty()) Opts.ResourceDir = ResourceDir; - Opts.BuildDynamicSymbolIndex = EnableIndexBasedCompletion; + Opts.BuildDynamicSymbolIndex = EnableIndex; std::unique_ptr<SymbolIndex> StaticIdx; - if (EnableIndexBasedCompletion && !YamlSymbolFile.empty()) { + if (EnableIndex && !YamlSymbolFile.empty()) { StaticIdx = BuildStaticIndex(YamlSymbolFile); Opts.StaticIndex = StaticIdx.get(); } @@ -230,7 +230,7 @@ int main(int argc, char *argv[]) { clangd::CodeCompleteOptions CCOpts; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; - CCOpts.Limit = LimitCompletionResult; + CCOpts.Limit = LimitResults; // Initialize and run ClangdLSPServer. ClangdLSPServer LSPServer(Out, CCOpts, CompileCommandsDirPath, Opts); diff --git a/clang-tools-extra/test/clangd/initialize-params-invalid.test b/clang-tools-extra/test/clangd/initialize-params-invalid.test index d98e110d462..928d9b69e36 100644 --- a/clang-tools-extra/test/clangd/initialize-params-invalid.test +++ b/clang-tools-extra/test/clangd/initialize-params-invalid.test @@ -36,7 +36,8 @@ # CHECK-NEXT: "," # CHECK-NEXT: ] # CHECK-NEXT: }, -# CHECK-NEXT: "textDocumentSync": 2 +# CHECK-NEXT: "textDocumentSync": 2, +# CHECK-NEXT: "workspaceSymbolProvider": true # CHECK-NEXT: } # CHECK-NEXT: } --- diff --git a/clang-tools-extra/test/clangd/initialize-params.test b/clang-tools-extra/test/clangd/initialize-params.test index d3bf1369dc9..a9ef4baef9f 100644 --- a/clang-tools-extra/test/clangd/initialize-params.test +++ b/clang-tools-extra/test/clangd/initialize-params.test @@ -36,7 +36,8 @@ # CHECK-NEXT: "," # CHECK-NEXT: ] # CHECK-NEXT: }, -# CHECK-NEXT: "textDocumentSync": 2 +# CHECK-NEXT: "textDocumentSync": 2, +# CHECK-NEXT: "workspaceSymbolProvider": true # CHECK-NEXT: } # CHECK-NEXT: } --- diff --git a/clang-tools-extra/test/clangd/symbols.test b/clang-tools-extra/test/clangd/symbols.test new file mode 100644 index 00000000000..adc73fbd3ae --- /dev/null +++ b/clang-tools-extra/test/clangd/symbols.test @@ -0,0 +1,33 @@ +# RUN: clangd -lit-test < %s | FileCheck %s
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"workspace":{"symbol":{"symbolKind":{"valueSet": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]}}}},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"#include <sstream>\nvoid foo(); int main() { foo(); }\n"}}}
+---
+{"jsonrpc":"2.0","id":1,"method":"workspace/symbol","params":{"query":"std::basic_ostringstream"}}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "containerName": "std",
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "location": {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": {{.*}},
+# CHECK-NEXT: "line": {{.*}}
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": {{.*}},
+# CHECK-NEXT: "line": {{.*}}
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file://{{.*}}/sstream"
+# CHECK-NEXT: },
+# CHECK-NEXT: "name": "basic_ostringstream"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT:}
+---
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
diff --git a/clang-tools-extra/unittests/clangd/CMakeLists.txt b/clang-tools-extra/unittests/clangd/CMakeLists.txt index f20350949de..a9aa968a52f 100644 --- a/clang-tools-extra/unittests/clangd/CMakeLists.txt +++ b/clang-tools-extra/unittests/clangd/CMakeLists.txt @@ -17,6 +17,7 @@ add_extra_unittest(ClangdTests ContextTests.cpp DraftStoreTests.cpp FileIndexTests.cpp + FindSymbolsTests.cpp FuzzyMatchTests.cpp GlobalCompilationDatabaseTests.cpp HeadersTests.cpp diff --git a/clang-tools-extra/unittests/clangd/FindSymbolsTests.cpp b/clang-tools-extra/unittests/clangd/FindSymbolsTests.cpp new file mode 100644 index 00000000000..60fe052fff4 --- /dev/null +++ b/clang-tools-extra/unittests/clangd/FindSymbolsTests.cpp @@ -0,0 +1,247 @@ +//===-- FindSymbolsTests.cpp -------------------------*- C++ -*------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "Annotations.h" +#include "ClangdServer.h" +#include "FindSymbols.h" +#include "SyncAPI.h" +#include "TestFS.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { + +namespace { + +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::UnorderedElementsAre; + +class IgnoreDiagnostics : public DiagnosticsConsumer { + void onDiagnosticsReady(PathRef File, + std::vector<Diag> Diagnostics) override {} +}; + +// GMock helpers for matching SymbolInfos items. +MATCHER_P(Named, Name, "") { return arg.name == Name; } +MATCHER_P(InContainer, ContainerName, "") { + return arg.containerName == ContainerName; +} +MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } + +ClangdServer::Options optsForTests() { + auto ServerOpts = ClangdServer::optsForTest(); + ServerOpts.BuildDynamicSymbolIndex = true; + return ServerOpts; +} + +class WorkspaceSymbolsTest : public ::testing::Test { +public: + WorkspaceSymbolsTest() + : Server(CDB, FSProvider, DiagConsumer, optsForTests()) {} + +protected: + MockFSProvider FSProvider; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + ClangdServer Server; + int Limit; + + std::vector<SymbolInformation> getSymbols(StringRef Query) { + EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; + auto SymbolInfos = runWorkspaceSymbols(Server, Query, Limit); + EXPECT_TRUE(bool(SymbolInfos)) << "workspaceSymbols returned an error"; + return *SymbolInfos; + } + + void addFile(StringRef FileName, StringRef Contents) { + auto Path = testPath(FileName); + FSProvider.Files[Path] = Contents; + Server.addDocument(Path, Contents); + } +}; + +} // namespace + +TEST_F(WorkspaceSymbolsTest, NoMacro) { + addFile("foo.cpp", R"cpp( + #define MACRO X + )cpp"); + + // Macros are not in the index. + EXPECT_THAT(getSymbols("macro"), IsEmpty()); +} + +TEST_F(WorkspaceSymbolsTest, NoLocals) { + addFile("foo.cpp", R"cpp( + void test(int FirstParam, int SecondParam) { + struct LocalClass {}; + int local_var; + })cpp"); + EXPECT_THAT(getSymbols("l"), IsEmpty()); + EXPECT_THAT(getSymbols("p"), IsEmpty()); +} + +TEST_F(WorkspaceSymbolsTest, Globals) { + addFile("foo.h", R"cpp( + int global_var; + + int global_func(); + + struct GlobalStruct {};)cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT(getSymbols("global"), + UnorderedElementsAre(AllOf(Named("GlobalStruct"), InContainer(""), + WithKind(SymbolKind::Struct)), + AllOf(Named("global_func"), InContainer(""), + WithKind(SymbolKind::Function)), + AllOf(Named("global_var"), InContainer(""), + WithKind(SymbolKind::Variable)))); +} + +TEST_F(WorkspaceSymbolsTest, Unnamed) { + addFile("foo.h", R"cpp( + struct { + int InUnnamed; + } UnnamedStruct;)cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT(getSymbols("UnnamedStruct"), + ElementsAre(AllOf(Named("UnnamedStruct"), + WithKind(SymbolKind::Variable)))); + EXPECT_THAT(getSymbols("InUnnamed"), IsEmpty()); +} + +TEST_F(WorkspaceSymbolsTest, InMainFile) { + addFile("foo.cpp", R"cpp( + int test() { + } + )cpp"); + EXPECT_THAT(getSymbols("test"), IsEmpty()); +} + +TEST_F(WorkspaceSymbolsTest, Namespaces) { + addFile("foo.h", R"cpp( + namespace ans1 { + int ai1; + namespace ans2 { + int ai2; + } + } + )cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT( + getSymbols("a"), + UnorderedElementsAre(AllOf(Named("ans1"), InContainer("")), + AllOf(Named("ai1"), InContainer("ans1")), + AllOf(Named("ans2"), InContainer("ans1")), + AllOf(Named("ai2"), InContainer("ans1::ans2")))); + EXPECT_THAT(getSymbols("::"), + ElementsAre(AllOf(Named("ans1"), InContainer("")))); + EXPECT_THAT(getSymbols("::a"), + ElementsAre(AllOf(Named("ans1"), InContainer("")))); + EXPECT_THAT(getSymbols("ans1::"), + UnorderedElementsAre(AllOf(Named("ai1"), InContainer("ans1")), + AllOf(Named("ans2"), InContainer("ans1")))); + EXPECT_THAT(getSymbols("::ans1"), + ElementsAre(AllOf(Named("ans1"), InContainer("")))); + EXPECT_THAT(getSymbols("::ans1::"), + UnorderedElementsAre(AllOf(Named("ai1"), InContainer("ans1")), + AllOf(Named("ans2"), InContainer("ans1")))); + EXPECT_THAT(getSymbols("::ans1::ans2"), + ElementsAre(AllOf(Named("ans2"), InContainer("ans1")))); + EXPECT_THAT(getSymbols("::ans1::ans2::"), + ElementsAre(AllOf(Named("ai2"), InContainer("ans1::ans2")))); +} + +TEST_F(WorkspaceSymbolsTest, AnonymousNamespace) { + addFile("foo.h", R"cpp( + namespace { + void test() {} + } + )cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT(getSymbols("test"), IsEmpty()); +} + +TEST_F(WorkspaceSymbolsTest, MultiFile) { + addFile("foo.h", R"cpp( + int foo() { + } + )cpp"); + addFile("foo2.h", R"cpp( + int foo2() { + } + )cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + #include "foo2.h" + )cpp"); + EXPECT_THAT(getSymbols("foo"), + UnorderedElementsAre(AllOf(Named("foo"), InContainer("")), + AllOf(Named("foo2"), InContainer("")))); +} + +TEST_F(WorkspaceSymbolsTest, GlobalNamespaceQueries) { + addFile("foo.h", R"cpp( + int foo() { + } + class Foo { + int a; + }; + namespace ns { + int foo2() { + } + } + )cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT( + getSymbols("::"), + UnorderedElementsAre( + AllOf(Named("Foo"), InContainer(""), WithKind(SymbolKind::Class)), + AllOf(Named("foo"), InContainer(""), WithKind(SymbolKind::Function)), + AllOf(Named("ns"), InContainer(""), + WithKind(SymbolKind::Namespace)))); + EXPECT_THAT(getSymbols(":"), IsEmpty()); + EXPECT_THAT(getSymbols(""), IsEmpty()); +} + +TEST_F(WorkspaceSymbolsTest, WithLimit) { + addFile("foo.h", R"cpp( + int foo; + int foo2; + )cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT(getSymbols("foo"), + ElementsAre(AllOf(Named("foo"), InContainer(""), + WithKind(SymbolKind::Variable)), + AllOf(Named("foo2"), InContainer(""), + WithKind(SymbolKind::Variable)))); + + Limit = 1; + EXPECT_THAT(getSymbols("foo"), + ElementsAre(AnyOf((Named("foo"), InContainer("")), + AllOf(Named("foo2"), InContainer(""))))); +} + +} // namespace clangd +} // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SyncAPI.cpp b/clang-tools-extra/unittests/clangd/SyncAPI.cpp index 28393ee51f9..0a1c5988e7c 100644 --- a/clang-tools-extra/unittests/clangd/SyncAPI.cpp +++ b/clang-tools-extra/unittests/clangd/SyncAPI.cpp @@ -110,5 +110,12 @@ std::string runDumpAST(ClangdServer &Server, PathRef File) { return std::move(*Result); } +llvm::Expected<std::vector<SymbolInformation>> +runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit) { + llvm::Optional<llvm::Expected<std::vector<SymbolInformation>>> Result; + Server.workspaceSymbols(Query, Limit, capture(Result)); + return std::move(*Result); +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/unittests/clangd/SyncAPI.h b/clang-tools-extra/unittests/clangd/SyncAPI.h index c0fcb931ee8..d4d2ac8f901 100644 --- a/clang-tools-extra/unittests/clangd/SyncAPI.h +++ b/clang-tools-extra/unittests/clangd/SyncAPI.h @@ -41,6 +41,9 @@ runRename(ClangdServer &Server, PathRef File, Position Pos, StringRef NewName); std::string runDumpAST(ClangdServer &Server, PathRef File); +llvm::Expected<std::vector<SymbolInformation>> +runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit); + } // namespace clangd } // namespace clang |

