Index: clangd/CodeComplete.h =================================================================== --- clangd/CodeComplete.h +++ clangd/CodeComplete.h @@ -101,6 +101,11 @@ /// Whether to generate snippets for function arguments on code-completion. /// Needs snippets to be enabled as well. bool EnableFunctionArgSnippets = true; + + /// Whether to include index symbols that are not defined in the scopes + /// visible from the code completion point (e.g. enclosing namespaces). Such + /// completions can insert scope qualifiers. + bool IncludeIndexSymbolsFromAllScopes = false; }; // Semi-structured representation of a code-complete suggestion for our C++ API. Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -44,6 +44,7 @@ #include "clang/Sema/CodeCompleteConsumer.h" #include "clang/Sema/Sema.h" #include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Error.h" @@ -330,6 +331,7 @@ struct CodeCompletionBuilder { CodeCompletionBuilder(ASTContext &ASTCtx, const CompletionCandidate &C, CodeCompletionString *SemaCCS, + llvm::ArrayRef QueryScopes, const IncludeInserter &Includes, StringRef FileName, CodeCompletionContext::Kind ContextKind, const CodeCompleteOptions &Opts) @@ -362,13 +364,29 @@ (C.SemaResult->Availability == CXAvailability_Deprecated); } if (C.IndexResult) { - Completion.Origin |= C.IndexResult->Origin; + const auto &Sym = *C.IndexResult; + Completion.Origin |= Sym.Origin; if (Completion.Scope.empty()) - Completion.Scope = C.IndexResult->Scope; + Completion.Scope = Sym.Scope; if (Completion.Kind == CompletionItemKind::Missing) - Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind); + Completion.Kind = toCompletionItemKind(Sym.SymInfo.Kind); if (Completion.Name.empty()) - Completion.Name = C.IndexResult->Name; + Completion.Name = Sym.Name; + // To avoid inserting unnecessary qualifiers (e.g. no need for qualifier + // when symbol is accessed via using shadow like "using ns::X;"), only + // insert qualifier if the symbol is not already available form Sema. + // FIXME(ioeric): find a better way to avoid inserting redundant + // qualifiers. + if (Completion.RequiredQualifier.empty() && !C.SemaResult) { + StringRef ShortestQualifier = Sym.Scope; + for (StringRef Scope: QueryScopes) { + StringRef Qualifier = Sym.Scope; + if (Qualifier.consume_front(Scope) && + Qualifier.size() < ShortestQualifier.size()) + ShortestQualifier = Qualifier; + } + Completion.RequiredQualifier = ShortestQualifier; + } Completion.Deprecated |= (C.IndexResult->Flags & Symbol::Deprecated); } @@ -571,7 +589,8 @@ // Get all scopes that will be queried in indexes. std::vector getQueryScopes(CodeCompletionContext &CCContext, - const SourceManager &SM) { + const SourceManager &SM, + const CodeCompleteOptions &Opts) { auto GetAllAccessibleScopes = [](CodeCompletionContext &CCContext) { SpecifiedScope Info; for (auto *Context : CCContext.getVisitedContexts()) { @@ -592,7 +611,10 @@ // FIXME: Capture scopes and use for scoring, for example, // "using namespace std; namespace foo {v^}" => // foo::value > std::vector > boost::variant - return GetAllAccessibleScopes(CCContext).scopesForIndexQuery(); + auto Scopes = GetAllAccessibleScopes(CCContext).scopesForIndexQuery(); + if (Opts.IncludeIndexSymbolsFromAllScopes) + Scopes.push_back("*"); + return Scopes; } // Qualified completion ("std::vec^"), we have two cases depending on whether @@ -1353,7 +1375,7 @@ Filter = FuzzyMatcher( Recorder->CCSema->getPreprocessor().getCodeCompletionFilter()); QueryScopes = getQueryScopes(Recorder->CCContext, - Recorder->CCSema->getSourceManager()); + Recorder->CCSema->getSourceManager(), Opts); // Sema provides the needed context to query the index. // FIXME: in addition to querying for extra/overlapping symbols, we should // explicitly request symbols corresponding to Sema results. @@ -1503,6 +1525,8 @@ Relevance.Context = Recorder->CCContext.getKind(); Relevance.Query = SymbolRelevanceSignals::CodeComplete; Relevance.FileProximityMatch = FileProximity.getPointer(); + // FIXME: incorparate scope proximity into relevance score. + auto &First = Bundle.front(); if (auto FuzzyScore = fuzzyScore(First)) Relevance.NameMatch = *FuzzyScore; @@ -1552,8 +1576,8 @@ : nullptr; if (!Builder) Builder.emplace(Recorder->CCSema->getASTContext(), Item, SemaCCS, - *Inserter, FileName, Recorder->CCContext.getKind(), - Opts); + QueryScopes, *Inserter, FileName, + Recorder->CCContext.getKind(), Opts); else Builder->add(Item, SemaCCS); } Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -427,7 +427,9 @@ /// is provided, the matched symbols must be defined in namespace xyz but not /// namespace xyz::abc. /// - /// The global scope is "", a top level scope is "foo::", etc. + /// The global scope is "", a top level scope is "foo::", etc. "*" is + /// wildcard. + /// FIXME: support assigning different weight to each scope. std::vector Scopes; /// \brief The number of top candidates to return. The index may choose to /// return more than this, e.g. if it doesn't know which candidates are best. Index: clangd/index/MemIndex.cpp =================================================================== --- clangd/index/MemIndex.cpp +++ clangd/index/MemIndex.cpp @@ -37,7 +37,8 @@ const Symbol *Sym = Pair.second; // Exact match against all possible scopes. - if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope)) + if (!Req.Scopes.empty() && !llvm::is_contained(Req.Scopes, Sym->Scope) && + !llvm::is_contained(Req.Scopes, "*")) continue; if (Req.RestrictForCodeCompletion && !(Sym->Flags & Symbol::IndexedForCodeCompletion)) Index: clangd/index/dex/Dex.cpp =================================================================== --- clangd/index/dex/Dex.cpp +++ clangd/index/dex/Dex.cpp @@ -12,6 +12,7 @@ #include "FuzzyMatch.h" #include "Logger.h" #include "Quality.h" +#include "index/Index.h" #include "llvm/ADT/StringSet.h" #include #include @@ -156,6 +157,10 @@ // Generate scope tokens for search query. std::vector> ScopeIterators; for (const auto &Scope : Req.Scopes) { + if (Scope == "*") { + ScopeIterators.push_back(createTrue(Symbols.size())); + continue; + } const auto It = InvertedIndex.find(Token(Token::Kind::Scope, Scope)); if (It != InvertedIndex.end()) ScopeIterators.push_back(It->second.iterator()); Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -136,6 +136,15 @@ "enabled separatedly."), llvm::cl::init(true), llvm::cl::Hidden); +static llvm::cl::opt CrossNamespaceCompletion( + "cross-namespace-completion", + llvm::cl::desc( + "This is an experimental feature. If set to true, code completion will " + "include index symbols that are not defined in the scopes (e.g. " + "namespaces) visible from the code completion point. Such completions " + "can insert scope qualifiers."), + llvm::cl::init(false), llvm::cl::Hidden); + static llvm::cl::opt ShowOrigins("debug-origin", llvm::cl::desc("Show origins of completion items"), @@ -304,6 +313,7 @@ } CCOpts.SpeculativeIndexRequest = Opts.StaticIndex; CCOpts.EnableFunctionArgSnippets = EnableFunctionArgSnippets; + CCOpts.IncludeIndexSymbolsFromAllScopes = CrossNamespaceCompletion; // Initialize and run ClangdLSPServer. ClangdLSPServer LSPServer( Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -2040,6 +2040,41 @@ } } +TEST(CompletionTest, CrossNamespaceCompletion) { + clangd::CodeCompleteOptions Opts = {}; + Opts.IncludeIndexSymbolsFromAllScopes = true; + + auto Results = completions( + R"cpp( + namespace na { + void f() { Clangd^ } + } + )cpp", + {cls("nx::Clangd1"), cls("ny::Clangd2"), cls("Clangd3"), + cls("na::nb::Clangd4")}, + Opts); + EXPECT_THAT(Results.Completions, + UnorderedElementsAre(AllOf(Qualifier("nx::"), Named("Clangd1")), + AllOf(Qualifier("ny::"), Named("Clangd2")), + AllOf(Qualifier(""), Named("Clangd3")), + AllOf(Qualifier("nb::"), Named("Clangd4")))); +} + +TEST(CompletionTest, NoQualifierIfShadowed) { + clangd::CodeCompleteOptions Opts = {}; + Opts.IncludeIndexSymbolsFromAllScopes = true; + + auto Results = completions(R"cpp( + namespace nx { class Clangd1 {}; } + using nx::Clangd1; + void f() { Clangd^ } + )cpp", + {cls("nx::Clangd1"), cls("nx::Clangd2")}, Opts); + EXPECT_THAT(Results.Completions, + UnorderedElementsAre(AllOf(Qualifier(""), Named("Clangd1")), + AllOf(Qualifier("nx::"), Named("Clangd2")))); +} + } // namespace } // namespace clangd } // namespace clang Index: unittests/clangd/DexTests.cpp =================================================================== --- unittests/clangd/DexTests.cpp +++ unittests/clangd/DexTests.cpp @@ -565,6 +565,16 @@ EXPECT_THAT(match(*I, Req), UnorderedElementsAre("a::y1")); } +TEST(DexTest, WildcardScope) { + auto I = + Dex::build(generateSymbols({"a::y1", "a::b::y2", "c::y3"}), URISchemes); + FuzzyFindRequest Req; + Req.Query = "y"; + Req.Scopes = {"a::", "*"}; + EXPECT_THAT(match(*I, Req), + UnorderedElementsAre("a::y1", "a::b::y2", "c::y3")); +} + TEST(DexTest, IgnoreCases) { auto I = Dex::build(generateSymbols({"ns::ABC", "ns::abc"}), URISchemes); FuzzyFindRequest Req;