Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -124,8 +124,14 @@ struct FuzzyFindRequest { /// \brief A query string for the fuzzy find. This is matched against symbols' - /// qualfified names. + /// qualified names. If any scope below is provided, \p Query is only matched + /// against symbols in the scope (excl. symbols in nested scopes). std::string Query; + /// \brief If this is non-empty, symbols must be in at least one of the scopes + /// (e.g. namespaces) excluding nested scopes. For example, if a scope "xyz" + /// is provided, the matched symbols must be defined in scope "xyz" but not + /// "xyz::abc". "" and "::" are interpreted as the global namespace. + std::vector Scopes; /// \brief The maxinum number of candidates to return. size_t MaxCandidateCount = UINT_MAX; }; Index: clangd/index/MemIndex.cpp =================================================================== --- clangd/index/MemIndex.cpp +++ clangd/index/MemIndex.cpp @@ -27,21 +27,41 @@ bool MemIndex::fuzzyFind(Context & /*Ctx*/, const FuzzyFindRequest &Req, std::function Callback) const { - std::string LoweredQuery = llvm::StringRef(Req.Query).lower(); + std::vector FixedPrefixes; + for (StringRef Scope : Req.Scopes) + FixedPrefixes.push_back( + (Scope == "::" || Scope.empty()) ? "" : (Scope + "::").str()); + unsigned Matched = 0; { std::lock_guard Lock(Mutex); for (const auto Pair : Index) { + if (Matched >= Req.MaxCandidateCount) + return false; const Symbol *Sym = Pair.second; - // Find all symbols that contain the query, igoring cases. - // FIXME: consider matching chunks in qualified names instead the whole - // string. - // FIXME: use better matching algorithm, e.g. fuzzy matcher. - if (StringRef(StringRef(Sym->QualifiedName).lower()) - .contains(LoweredQuery)) { - if (++Matched > Req.MaxCandidateCount) - return false; - Callback(*Sym); + + auto Match = [&](llvm::StringRef Name, llvm::StringRef Filter) { + // FIXME(ioeric): use fuzzy matcher. + if (StringRef(Name.lower()).contains(Req.Query)) { + Matched++; + Callback(*Sym); + } + }; + + StringRef QName = Sym->QualifiedName; + if (FixedPrefixes.empty()) { + // No prefix is specified. Simply match the entire qualified name. + Match(QName, Req.Query); + } else { + // Try (exact-)matching against all possible scopes/prefixes. + for (StringRef Prefix : FixedPrefixes) { + if (!QName.consume_front(Prefix)) + continue; + // Only match symbols that are directly defined in the scope. + if (QName.contains("::")) + continue; + Match(QName, Req.Query); + } } } } Index: unittests/clangd/IndexTests.cpp =================================================================== --- unittests/clangd/IndexTests.cpp +++ unittests/clangd/IndexTests.cpp @@ -31,19 +31,19 @@ std::vector Pointers; }; -// Create a slab of symbols with IDs and names [Begin, End]. The life time of -// the slab is managed by the returned shared pointer. If \p WeakSymbols is -// provided, it will be pointed to the managed object in the returned shared -// pointer. +// Create a slab of symbols with the given qualified names as both IDs and +// names. The life time of the slab is managed by the returned shared pointer. +// If \p WeakSymbols is provided, it will be pointed to the managed object in +// the returned shared pointer. std::shared_ptr> -generateNumSymbols(int Begin, int End, - std::weak_ptr *WeakSymbols = nullptr) { +generateSymbols(std::vector QualifiedNames, + std::weak_ptr *WeakSymbols = nullptr) { auto Slab = std::make_shared(); if (WeakSymbols) *WeakSymbols = Slab; - for (int i = Begin; i <= End; i++) - Slab->Slab.insert(symbol(std::to_string(i))); + for (llvm::StringRef QName : QualifiedNames) + Slab->Slab.insert(symbol(QName)); for (const auto &Sym : Slab->Slab) Slab->Pointers.push_back(&Sym.second); @@ -52,6 +52,17 @@ return {std::move(Slab), Pointers}; } +// Create a slab of symbols with IDs and names [Begin, End], otherwise identical +// to the `generateSymbols` above. +std::shared_ptr> +generateNumSymbols(int Begin, int End, + std::weak_ptr *WeakSymbols = nullptr) { + std::vector Names; + for (int i = Begin; i <= End; i++) + Names.push_back(std::to_string(i)); + return generateSymbols(Names, WeakSymbols); +} + std::vector match(const SymbolIndex &I, const FuzzyFindRequest &Req) { std::vector Matches; @@ -110,6 +121,56 @@ EXPECT_EQ(Matches.size(), Req.MaxCandidateCount); } +TEST(MemIndexTest, MatchQualifiedNamesWithoutSpecificScope) { + MemIndex I; + I.build(generateSymbols({"a::xyz", "b::yz", "yz"})); + FuzzyFindRequest Req; + Req.Query = "y"; + auto Matches = match(I, Req); + EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "b::yz", "yz")); +} + +TEST(MemIndexTest, MatchQualifiedNamesWithGlobalScope) { + MemIndex I; + I.build(generateSymbols({"a::xyz", "b::yz", "yz"})); + FuzzyFindRequest Req; + Req.Query = "y"; + Req.Scopes.push_back("::"); + auto Matches = match(I, Req); + EXPECT_THAT(match(I, Req), UnorderedElementsAre("yz")); +} + +TEST(MemIndexTest, MatchQualifiedNamesWithOneScope) { + MemIndex I; + I.build(generateSymbols({"a::xyz", "a::yy", "a::xz", "b::yz", "yz"})); + FuzzyFindRequest Req; + Req.Query = "y"; + Req.Scopes.push_back("a"); + auto Matches = match(I, Req); + EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "a::yy")); +} + +TEST(MemIndexTest, MatchQualifiedNamesWithMultipleScopes) { + MemIndex I; + I.build(generateSymbols({"a::xyz", "a::yy", "a::xz", "b::yz", "yz"})); + FuzzyFindRequest Req; + Req.Query = "y"; + Req.Scopes.push_back("a"); + Req.Scopes.push_back("b"); + auto Matches = match(I, Req); + EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz", "a::yy", "b::yz")); +} + +TEST(MemIndexTest, NoMatchNestedScopes) { + MemIndex I; + I.build(generateSymbols({"a::xyz", "a::b::yy"})); + FuzzyFindRequest Req; + Req.Query = "y"; + Req.Scopes.push_back("a"); + auto Matches = match(I, Req); + EXPECT_THAT(match(I, Req), UnorderedElementsAre("a::xyz")); +} + } // namespace } // namespace clangd } // namespace clang