Index: clangd/FuzzyMatch.h =================================================================== --- clangd/FuzzyMatch.h +++ clangd/FuzzyMatch.h @@ -31,7 +31,9 @@ // Characters beyond MaxPat are ignored. FuzzyMatcher(llvm::StringRef Pattern); - // If Word matches the pattern, return a score in [0,1] (higher is better). + // If Word matches the pattern, return a score indicating the quality match. + // Scores usually fall in a [0,1] range, with 1 being a very good score. + // "Super" scores in (1,2] are possible if the pattern is the full word. // Characters beyond MaxWord are ignored. llvm::Optional match(llvm::StringRef Word); Index: clangd/FuzzyMatch.cpp =================================================================== --- clangd/FuzzyMatch.cpp +++ clangd/FuzzyMatch.cpp @@ -101,7 +101,13 @@ Scores[PatN][WordN][Match].Score); if (isAwful(Best)) return None; - return ScoreScale * std::min(PerfectBonus * PatN, std::max(0, Best)); + float Score = + ScoreScale * std::min(PerfectBonus * PatN, std::max(0, Best)); + // If the pattern is as long as the word, we have an exact string match, + // since every pattern character must match something. + if (WordN == PatN) + Score *= 2; // May not be perfect 2 if case differs in a significant way. + return Score; } // Segmentation of words and patterns. Index: clangd/Quality.h =================================================================== --- clangd/Quality.h +++ clangd/Quality.h @@ -70,7 +70,7 @@ /// Attributes of a symbol-query pair that affect how much we like it. struct SymbolRelevanceSignals { - /// 0-1 fuzzy-match score for unqualified name. Must be explicitly assigned. + /// 0-1+ fuzzy-match score for unqualified name. Must be explicitly assigned. float NameMatch = 1; bool Forbidden = false; // Unavailable (e.g const) or inaccessible (private). /// Proximity between best declaration and the query. [0-1], 1 is closest. Index: clangd/Quality.cpp =================================================================== --- clangd/Quality.cpp +++ clangd/Quality.cpp @@ -131,6 +131,8 @@ Score *= 1.1; break; case Namespace: + Score *= 0.8; + break; case Macro: Score *= 0.2; break; Index: unittests/clangd/FindSymbolsTests.cpp =================================================================== --- unittests/clangd/FindSymbolsTests.cpp +++ unittests/clangd/FindSymbolsTests.cpp @@ -273,10 +273,10 @@ #include "foo.h" )cpp"); EXPECT_THAT(getSymbols("foo"), - ElementsAre(AllOf(Named("foo"), InContainer(""), - WithKind(SymbolKind::Variable)), - AllOf(Named("foo2"), InContainer(""), - WithKind(SymbolKind::Variable)))); + UnorderedElementsAre(AllOf(Named("foo"), InContainer(""), + WithKind(SymbolKind::Variable)), + AllOf(Named("foo2"), InContainer(""), + WithKind(SymbolKind::Variable)))); Limit = 1; EXPECT_THAT(getSymbols("foo"), Index: unittests/clangd/FuzzyMatchTests.cpp =================================================================== --- unittests/clangd/FuzzyMatchTests.cpp +++ unittests/clangd/FuzzyMatchTests.cpp @@ -46,10 +46,14 @@ struct MatchesMatcher : public testing::MatcherInterface { ExpectedMatch Candidate; - MatchesMatcher(ExpectedMatch Candidate) : Candidate(std::move(Candidate)) {} + Optional Score; + MatchesMatcher(ExpectedMatch Candidate, Optional Score) + : Candidate(std::move(Candidate)), Score(Score) {} void DescribeTo(::std::ostream *OS) const override { raw_os_ostream(*OS) << "Matches " << Candidate; + if (Score) + *OS << " with score " << *Score; } bool MatchAndExplain(StringRef Pattern, @@ -60,14 +64,15 @@ FuzzyMatcher Matcher(Pattern); auto Result = Matcher.match(Candidate.Word); auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n"); - return Result && Candidate.accepts(AnnotatedMatch); + return Result && Candidate.accepts(AnnotatedMatch) && + (!Score || testing::Value(*Result, testing::FloatEq(*Score))); } }; -// Accepts patterns that match a given word. +// Accepts patterns that match a given word, optionally requiring a score. // Dumps the debug tables on match failure. -testing::Matcher matches(StringRef M) { - return testing::MakeMatcher(new MatchesMatcher(M)); +testing::Matcher matches(StringRef M, Optional Score = {}) { + return testing::MakeMatcher(new MatchesMatcher(M, Score)); } TEST(FuzzyMatch, Matches) { @@ -239,7 +244,7 @@ ranks("[onMess]age", "[onmess]age", "[on]This[M]ega[Es]cape[s]")); EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase")); EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase")); - EXPECT_THAT("p", ranks("[p]arse", "[p]osix", "[p]afdsa", "[p]ath", "[p]")); + EXPECT_THAT("p", ranks("[p]", "[p]arse", "[p]osix", "[p]afdsa", "[p]ath")); EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa")); EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition")); EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement")); @@ -262,6 +267,15 @@ "[c]ss.co[lo]rDecorator[s].[e]nable")); } +// Verify some bounds so we know scores fall in the right range. +// Testing exact scores is fragile, so we prefer Ranking tests. +TEST(FuzzyMatch, Scoring) { + EXPECT_THAT("abs", matches("[a]x[b]y[s]z", 0.f)); + EXPECT_THAT("abs", matches("[abs]l", 1.f)); + EXPECT_THAT("abs", matches("[abs]", 2.f)); + EXPECT_THAT("aBs", matches("[abs]", 2.f)); +} + } // namespace } // namespace clangd } // namespace clang