Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -380,9 +380,12 @@ void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, CodeCompletionResult *Results, unsigned NumResults) override final { + StringRef Filter = S.getPreprocessor().getCodeCompletionFilter(); Items.reserve(NumResults); for (unsigned I = 0; I < NumResults; ++I) { auto &Result = Results[I]; + if (!Filter.empty() && !fuzzyMatch(S, Context, Filter, Result)) + continue; const auto *CCS = Result.CreateCodeCompletionString( S, Context, *Allocator, CCTUInfo, CodeCompleteOpts.IncludeBriefComments); @@ -397,6 +400,41 @@ CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } private: + bool fuzzyMatch(Sema &S, const CodeCompletionContext &CCCtx, StringRef Filter, + CodeCompletionResult Result) { + switch (Result.Kind) { + case CodeCompletionResult::RK_Declaration: + if (auto *ID = Result.Declaration->getIdentifier()) + return fuzzyMatch(Filter, ID->getName()); + break; + case CodeCompletionResult::RK_Keyword: + return fuzzyMatch(Filter, Result.Keyword); + case CodeCompletionResult::RK_Macro: + return fuzzyMatch(Filter, Result.Macro->getName()); + case CodeCompletionResult::RK_Pattern: + return fuzzyMatch(Filter, Result.Pattern->getTypedText()); + } + auto *CCS = Result.CreateCodeCompletionString( + S, CCCtx, *Allocator, CCTUInfo, /*IncludeBriefComments=*/false); + return fuzzyMatch(Filter, CCS->getTypedText()); + } + + // Checks whether Target matches the Filter. + // Currently just requires a case-insensitive subsequence match. + // FIXME: make stricter and word-based: 'unique_ptr' should not match 'que'. + // FIXME: return a score to be incorporated into ranking. + static bool fuzzyMatch(StringRef Filter, StringRef Target) { + llvm::errs() << "match " << Target << " against " << Filter << "\n"; + size_t TPos = 0; + for (char C : Filter) { + TPos = Target.find_lower(C, TPos); + if (TPos == StringRef::npos) + return false; + } + llvm::errs() << "yeah\n"; + return true; + } + CompletionItem ProcessCodeCompleteResult(const CodeCompletionResult &Result, const CodeCompletionString &CCS) const { Index: unittests/clangd/ClangdTests.cpp =================================================================== --- unittests/clangd/ClangdTests.cpp +++ unittests/clangd/ClangdTests.cpp @@ -694,6 +694,61 @@ } } +TEST_F(ClangdCompletionTest, Filter) { + MockFSProvider FS; + MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true); + CDB.ExtraClangFlags.push_back("-xc++"); + ErrorCheckingDiagConsumer DiagConsumer; + clangd::CodeCompleteOptions Opts; + ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(), + Opts, EmptyLogger::getInstance()); + + auto FooCpp = getVirtualTestFilePath("foo.cpp"); + FS.Files[FooCpp] = ""; + FS.ExpectedFile = FooCpp; + + const char *Body = R"cpp( + int Abracadabra; + int Alakazam; + struct S { + int FooBar; + int FooBaz; + int Qux; + }; + )cpp"; + auto Complete = [&](StringRef Query) { + StringWithPos Completion = parseTextMarker( + formatv("{0} int main() { {1}{{complete}} }", Body, Query).str(), + "complete"); + Server.addDocument(FooCpp, Completion.Text); + return Server + .codeComplete(FooCpp, Completion.MarkerPos, StringRef(Completion.Text)) + .get() + .Value; + }; + + auto Foba = Complete("S().Foba"); + EXPECT_TRUE(ContainsItem(Foba, "FooBar")); + EXPECT_TRUE(ContainsItem(Foba, "FooBaz")); + EXPECT_FALSE(ContainsItem(Foba, "Qux")); + + auto FR = Complete("S().FR"); + EXPECT_TRUE(ContainsItem(FR, "FooBar")); + EXPECT_FALSE(ContainsItem(FR, "FooBaz")); + EXPECT_FALSE(ContainsItem(FR, "Qux")); + + auto Op = Complete("S().opr"); + EXPECT_TRUE(ContainsItem(Op, "operator=")); + + auto Aaa = Complete("aaa"); + EXPECT_TRUE(ContainsItem(Aaa, "Abracadabra")); + EXPECT_TRUE(ContainsItem(Aaa, "Alakazam")); + + auto UA = Complete("_a"); + EXPECT_TRUE(ContainsItem(UA, "static_cast")); + EXPECT_FALSE(ContainsItem(UA, "Abracadabra")); +} + TEST_F(ClangdCompletionTest, CompletionOptions) { MockFSProvider FS; ErrorCheckingDiagConsumer DiagConsumer;