Index: clang-tools-extra/trunk/clangd/ClangdUnit.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ClangdUnit.cpp +++ clang-tools-extra/trunk/clangd/ClangdUnit.cpp @@ -446,6 +446,7 @@ void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, CodeCompletionResult *Results, unsigned NumResults) override final { + StringRef Filter = S.getPreprocessor().getCodeCompletionFilter(); std::priority_queue Candidates; for (unsigned I = 0; I < NumResults; ++I) { auto &Result = Results[I]; @@ -453,6 +454,8 @@ (Result.Availability == CXAvailability_NotAvailable || Result.Availability == CXAvailability_NotAccessible)) continue; + if (!Filter.empty() && !fuzzyMatch(S, Context, Filter, Result)) + continue; Candidates.emplace(Result); if (ClangdOpts.Limit && Candidates.size() > ClangdOpts.Limit) { Candidates.pop(); @@ -476,6 +479,39 @@ 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) { + size_t TPos = 0; + for (char C : Filter) { + TPos = Target.find_lower(C, TPos); + if (TPos == StringRef::npos) + return false; + } + return true; + } + CompletionItem ProcessCodeCompleteResult(const CompletionCandidate &Candidate, const CodeCompletionString &CCS) const { Index: clang-tools-extra/trunk/test/clangd/completion-items-kinds.test =================================================================== --- clang-tools-extra/trunk/test/clangd/completion-items-kinds.test +++ clang-tools-extra/trunk/test/clangd/completion-items-kinds.test @@ -30,7 +30,7 @@ # CHECK-SAME: ]}} Content-Length: 146 -{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"whi"}}} +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"nam"}}} Content-Length: 148 {"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":3}}} Index: clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp +++ clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp @@ -742,6 +742,61 @@ EXPECT_FALSE(ContainsItem(Results, "CCC")); } +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(), + /*StorePreamblesInMemory=*/true, 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;