<div dir="ltr">r319606 should fix that one, if I've understood the problem right.<br></div><div class="gmail_extra"><br><div class="gmail_quote">On Sat, Dec 2, 2017 at 3:50 AM, Yung, Douglas <span dir="ltr"><<a href="mailto:douglas.yung@sony.com" target="_blank">douglas.yung@sony.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi Sam, the FuzzyMatch tests you added in this commit seem to be failing on the Windows bot:<br>
<br>
<a href="http://lab.llvm.org:8011/builders/llvm-clang-lld-x86_64-scei-ps4-windows10pro-fast/builds/13869" rel="noreferrer" target="_blank">http://lab.llvm.org:8011/<wbr>builders/llvm-clang-lld-x86_<wbr>64-scei-ps4-windows10pro-fast/<wbr>builds/13869</a><br>
<br>
Can you take a look?<br>
<br>
Douglas Yung<br>
<span class=""><br>
> -----Original Message-----<br>
> From: cfe-commits [mailto:<a href="mailto:cfe-commits-bounces@lists.llvm.org">cfe-commits-bounces@<wbr>lists.llvm.org</a>] On Behalf Of Sam<br>
> McCall via cfe-commits<br>
> Sent: Friday, December 01, 2017 9:08<br>
> To: <a href="mailto:cfe-commits@lists.llvm.org">cfe-commits@lists.llvm.org</a><br>
> Subject: [clang-tools-extra] r319557 - [clangd] Fuzzy match scorer<br>
><br>
> Author: sammccall<br>
> Date: Fri Dec  1 09:08:02 2017<br>
> New Revision: 319557<br>
><br>
> URL: <a href="http://llvm.org/viewvc/llvm-project?rev=319557&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-<wbr>project?rev=319557&view=rev</a><br>
> Log:<br>
> [clangd] Fuzzy match scorer<br>
><br>
> Summary:<br>
> This will be used for rescoring code completion results based on partial<br>
> identifiers.<br>
> Short-term use:<br>
>   - we want to limit the number of code completion results returned to<br>
>   improve performance of global completion. The scorer will be used to<br>
>   rerank the results to return when the user has applied a filter.<br>
</span><span class="">>   - ranking of completion results from in-memory index<br>
>   - merging of completion results from multiple sources (merging usually<br>
</span>> Long-term use case:<br>
<span class="">>   works best when done at the component-score level, rescoring the<br>
>   fuzzy-match quality avoids different backends needing to have<br>
>   comparable scores)<br>
><br>
> Reviewers: ilya-biryukov<br>
><br>
> Subscribers: cfe-commits, mgorny<br>
><br>
> Differential Revision: <a href="https://reviews.llvm.org/D40060" rel="noreferrer" target="_blank">https://reviews.llvm.org/<wbr>D40060</a><br>
><br>
> Added:<br>
>     clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.cpp<br>
>     clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.h<br>
>     clang-tools-extra/trunk/<wbr>unittests/clangd/<wbr>FuzzyMatchTests.cpp<br>
> Modified:<br>
>     clang-tools-extra/trunk/<wbr>clangd/CMakeLists.txt<br>
>     clang-tools-extra/trunk/<wbr>unittests/clangd/CMakeLists.<wbr>txt<br>
><br>
> Modified: clang-tools-extra/trunk/<wbr>clangd/CMakeLists.txt<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-<wbr>project/clang-tools-</a><br>
> extra/trunk/clangd/CMakeLists.<wbr>txt?rev=319557&r1=319556&r2=<wbr>319557&view=diff<br>
> ==============================<wbr>==============================<wbr>==================<br>
> --- clang-tools-extra/trunk/<wbr>clangd/CMakeLists.txt (original)<br>
> +++ clang-tools-extra/trunk/<wbr>clangd/CMakeLists.txt Fri Dec  1 09:08:02<br>
</span>> +++ 2017<br>
<span class="">> @@ -8,6 +8,7 @@ add_clang_library(clangDaemon<br>
>    ClangdUnit.cpp<br>
>    ClangdUnitStore.cpp<br>
>    DraftStore.cpp<br>
> +  FuzzyMatch.cpp<br>
>    GlobalCompilationDatabase.cpp<br>
>    JSONExpr.cpp<br>
>    JSONRPCDispatcher.cpp<br>
><br>
> Added: clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.cpp<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-<wbr>project/clang-tools-</a><br>
> extra/trunk/clangd/FuzzyMatch.<wbr>cpp?rev=319557&view=auto<br>
> ==============================<wbr>==============================<wbr>==================<br>
> --- clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.cpp (added)<br>
> +++ clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.cpp Fri Dec  1 09:08:02<br>
</span>> +++ 2017<br>
<span class="">> @@ -0,0 +1,373 @@<br>
> +//===--- FuzzyMatch.h - Approximate identifier matching  ---------*-<br>
</span>> +C++-*-===// //<br>
<span class="">> +//                     The LLVM Compiler Infrastructure<br>
> +//<br>
> +// This file is distributed under the University of Illinois Open<br>
</span>> +Source // License. See LICENSE.TXT for details.<br>
> +//<br>
> +//===------------------------<wbr>------------------------------<wbr>------------<br>
> +----===//<br>
<span class="">> +//<br>
> +// To check for a match between a Pattern ('u_p') and a Word<br>
</span>> +('unique_ptr'), // we consider the possible partial match states:<br>
<span class="">> +//<br>
> +//     u n i q u e _ p t r<br>
> +//   +---------------------<br>
> +//   |A . . . . . . . . . .<br>
> +//  u|<br>
> +//   |. . . . . . . . . . .<br>
> +//  _|<br>
> +//   |. . . . . . . O . . .<br>
> +//  p|<br>
> +//   |. . . . . . . . . . B<br>
> +//<br>
> +// Each dot represents some prefix of the pattern being matched against<br>
</span>> +some // prefix of the word.<br>
<span class="">> +//   - A is the initial state: '' matched against ''<br>
> +//   - O is an intermediate state: 'u_' matched against 'unique_'<br>
> +//   - B is the target state: 'u_p' matched against 'unique_ptr'<br>
> +//<br>
> +// We aim to find the best path from A->B.<br>
> +//  - Moving right (consuming a word character)<br>
> +//    Always legal: not all word characters must match.<br>
> +//  - Moving diagonally (consuming both a word and pattern character)<br>
> +//    Legal if the characters match.<br>
> +//  - Moving down (consuming a pattern character) is never legal.<br>
> +//    Never legal: all pattern characters must match something.<br>
> +//<br>
> +// The scoring is based on heuristics:<br>
> +//  - when matching a character, apply a bonus or penalty depending on the<br>
> +//    match quality (does case match, do word segments align, etc)<br>
> +//  - when skipping a character, apply a penalty if it hurts the match<br>
> +//    (it starts a word segment, or splits the matched region, etc)<br>
> +//<br>
> +// These heuristics require the ability to "look backward" one<br>
</span>> +character, to // see whether it was matched or not. Therefore the<br>
> +dynamic-programming matrix // has an extra dimension (last character<br>
<span class="">> matched).<br>
> +// Each entry also has an additional flag indicating whether the<br>
</span>> +last-but-one // character matched, which is needed to trace back<br>
> +through the scoring table // and reconstruct the match.<br>
<span class="">> +//<br>
> +// We treat strings as byte-sequences, so only ASCII has first-class support.<br>
> +//<br>
> +// This algorithm was inspired by VS code's client-side filtering, and<br>
</span>> +aims // to be mostly-compatible.<br>
> +//<br>
> +//===------------------------<wbr>------------------------------<wbr>------------<br>
> +----===//<br>
<span class="">> +<br>
> +#include "FuzzyMatch.h"<br>
> +#include "llvm/ADT/Optional.h"<br>
> +#include "llvm/Support/Format.h"<br>
> +<br>
> +using namespace llvm;<br>
> +using namespace clang::clangd;<br>
> +<br>
> +const int FuzzyMatcher::MaxPat;<br>
> +const int FuzzyMatcher::MaxWord;<br>
> +<br>
> +static char lower(char C) { return C >= 'A' && C <= 'Z' ? C + ('a' -<br>
</span>> +'A') : C; } // A "negative infinity" score that won't overflow.<br>
<span class="">> +// We use this to mark unreachable states and forbidden solutions.<br>
> +// Score field is 15 bits wide, min value is -2^14, we use half of that.<br>
</span>> +static constexpr int AwfulScore = -(1 << 13); static bool isAwful(int<br>
> +S) { return S < AwfulScore / 2; } static constexpr int PerfectBonus =<br>
> +3; // Perfect per-pattern-char score.<br>
<div><div class="h5">> +<br>
> +FuzzyMatcher::FuzzyMatcher(<wbr>StringRef Pattern)<br>
> +    : PatN(std::min<int>(MaxPat, Pattern.size())), CaseSensitive(false),<br>
> +      ScoreScale(float{1} / (PerfectBonus * PatN)), WordN(0) {<br>
> +  memcpy(Pat, Pattern.data(), PatN);<br>
> +  for (int I = 0; I < PatN; ++I) {<br>
> +    LowPat[I] = lower(Pat[I]);<br>
> +    CaseSensitive |= LowPat[I] != Pat[I];<br>
> +  }<br>
> +  Scores[0][0][Miss] = {0, Miss};<br>
> +  Scores[0][0][Match] = {AwfulScore, Miss};<br>
> +  for (int P = 0; P <= PatN; ++P)<br>
> +    for (int W = 0; W < P; ++W)<br>
> +      for (Action A : {Miss, Match})<br>
> +        Scores[P][W][A] = {AwfulScore, Miss};<br>
> +  calculateRoles(Pat, PatRole, PatN);<br>
> +}<br>
> +<br>
> +Optional<float> FuzzyMatcher::match(StringRef Word) {<br>
> +  if (!PatN)<br>
> +    return 1;<br>
> +  if (!(WordContainsPattern = init(Word)))<br>
> +    return None;<br>
> +  buildGraph();<br>
> +  auto Best = std::max(Scores[PatN][WordN][<wbr>Miss].Score,<br>
> +                       Scores[PatN][WordN][Match].<wbr>Score);<br>
> +  if (isAwful(Best))<br>
> +    return None;<br>
> +  return ScoreScale * std::min(PerfectBonus * PatN, std::max<int>(0,<br>
</div></div>> +Best)); }<br>
<span class="">> +<br>
> +// Segmentation of words and patterns.<br>
> +// A name like "fooBar_baz" consists of several parts foo, bar, baz.<br>
> +// Aligning segmentation of word and pattern improves the fuzzy-match.<br>
> +// For example: [lol] matches "LaughingOutLoud" better than "LionPopulation"<br>
> +//<br>
> +// First we classify each character into types (uppercase, lowercase, etc).<br>
> +// Then we look at the sequence: e.g. [upper, lower] is the start of a<br>
> segment.<br>
> +<br>
> +// We only distinguish the types of characters that affect segmentation.<br>
> +// It's not obvious how to segment digits, we treat them as lowercase<br>
> letters.<br>
> +// As we don't decode UTF-8, we treat bytes over 127 as lowercase too.<br>
> +// This means we require exact (case-sensitive) match.<br>
> +enum FuzzyMatcher::CharType : char {<br>
> +  Empty = 0,       // Before-the-start and after-the-end (and control chars).<br>
> +  Lower = 1,       // Lowercase letters, digits, and non-ASCII bytes.<br>
> +  Upper = 2,       // Uppercase letters.<br>
> +  Punctuation = 3, // ASCII punctuation (including Space) };<br>
> +<br>
</span><span class="">> +// We get CharTypes from a lookup table. Each is 2 bits, 4 fit in each byte.<br>
> +// The top 6 bits of the char select the byte, the bottom 2 select the<br>
> offset.<br>
> +// e.g. 'q' = 010100 01 = byte 28 (55), bits 3-2 (01) -> Lower.<br>
> +constexpr static uint8_t CharTypes[] = {<br>
> +    0x00, 0x00, 0x00, 0x00, // Control characters<br>
> +    0x00, 0x00, 0x00, 0x00, // Control characters<br>
> +    0xff, 0xff, 0xff, 0xff, // Punctuation<br>
> +    0x55, 0x55, 0xf5, 0xff, // Numbers->Lower, more Punctuation.<br>
> +    0xab, 0xaa, 0xaa, 0xaa, // @ and A-O<br>
> +    0xaa, 0xaa, 0xea, 0xff, // P-Z, more Punctuation.<br>
> +    0x57, 0x55, 0x55, 0x55, // ` and a-o<br>
> +    0x55, 0x55, 0xd5, 0x3f, // p-z, Punctuation, DEL.<br>
> +    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // Bytes over 127 -><br>
> Lower.<br>
> +    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, // (probably UTF-8).<br>
> +    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,<br>
> +    0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, };<br>
> +<br>
</span><div><div class="h5">> +// Each character's Role is the Head or Tail of a segment, or a Separator.<br>
> +// e.g. XMLHttpRequest_Async<br>
> +//      +--+---+------ +----<br>
> +//      ^Head   ^Tail ^Separator<br>
> +enum FuzzyMatcher::CharRole : char {<br>
> +  Unknown = 0,   // Stray control characters or impossible states.<br>
> +  Tail = 1,      // Part of a word segment, but not the first character.<br>
> +  Head = 2,      // The first character of a word segment.<br>
> +  Separator = 3, // Punctuation characters that separate word segments.<br>
> +};<br>
> +<br>
> +// The Role can be determined from the Type of a character and its neighbors:<br>
> +//<br>
> +//   Example  | Chars | Type | Role<br>
> +//   ---------+--------------+-----<br>
> +//   F(o)oBar | Foo   | Ull  | Tail<br>
> +//   Foo(B)ar | oBa   | lUl  | Head<br>
> +//   (f)oo    | ^fo   | Ell  | Head<br>
> +//   H(T)TP   | HTT   | UUU  | Tail<br>
> +//<br>
> +// Our lookup table maps a 6 bit key (Prev, Curr, Next) to a 2-bit Role.<br>
> +// A byte packs 4 Roles. (Prev, Curr) selects a byte, Next selects the<br>
> offset.<br>
> +// e.g. Lower, Upper, Lower -> 01 10 01 -> byte 6 (aa), bits 3-2 (10) -><br>
> Head.<br>
> +constexpr static uint8_t CharRoles[] = {<br>
> +    // clang-format off<br>
> +    //         Curr= Empty Lower Upper Separ<br>
> +    /* Prev=Empty */ 0x00, 0xaa, 0xaa, 0xff, // At start, Lower|Upper->Head<br>
> +    /* Prev=Lower */ 0x00, 0x55, 0xaa, 0xff, // In word, Upper->Head;Lower-<br>
> >Tail<br>
> +    /* Prev=Upper */ 0x00, 0x55, 0x59, 0xff, // Ditto, but U(U)U->Tail<br>
> +    /* Prev=Separ */ 0x00, 0xaa, 0xaa, 0xff, // After separator, like at<br>
> start<br>
> +    // clang-format on<br>
> +};<br>
> +<br>
> +template <typename T> static T packedLookup(const uint8_t *Data, int I)<br>
</div></div>> +{<br>
> +  return static_cast<T>((Data[I >> 2] >> ((I & 3) * 2)) & 3); } void<br>
> +FuzzyMatcher::calculateRoles(<wbr>const char *Text, CharRole *Out, int N) {<br>
<span class="">> +  // Types holds a sliding window of (Prev, Curr, Next) types.<br>
> +  // Initial value is (Empty, Empty, type of Text[0]).<br>
> +  int Types = packedLookup<CharType>(<wbr>CharTypes, Text[0]);<br>
> +  // Rotate slides in the type of the next character.<br>
> +  auto Rotate = [&](CharType T) { Types = ((Types << 2) | T) & 0x3f; };<br>
> +  for (int I = 0; I < N - 1; ++I) {<br>
> +    // For each character, rotate in the next, and look up the role.<br>
> +    Rotate(packedLookup<CharType>(<wbr>CharTypes, Text[I + 1]));<br>
> +    *Out++ = packedLookup<CharRole>(<wbr>CharRoles, Types);<br>
> +  }<br>
> +  // For the last character, the "next character" is Empty.<br>
> +  Rotate(Empty);<br>
> +  *Out++ = packedLookup<CharRole>(<wbr>CharRoles, Types); }<br>
> +<br>
</span><span class="">> +// Sets up the data structures matching Word.<br>
> +// Returns false if we can cheaply determine that no match is possible.<br>
> +bool FuzzyMatcher::init(StringRef NewWord) {<br>
> +  WordN = std::min<int>(MaxWord, NewWord.size());<br>
> +  if (PatN > WordN)<br>
> +    return false;<br>
> +  memcpy(Word, NewWord.data(), WordN);<br>
> +  for (int I = 0; I < WordN; ++I)<br>
> +    LowWord[I] = lower(Word[I]);<br>
> +<br>
> +  // Cheap subsequence check.<br>
> +  for (int W = 0, P = 0; P != PatN; ++W) {<br>
> +    if (W == WordN)<br>
> +      return false;<br>
> +    if (LowWord[W] == LowPat[P])<br>
> +      ++P;<br>
> +  }<br>
> +<br>
> +  calculateRoles(Word, WordRole, WordN);<br>
> +  return true;<br>
> +}<br>
> +<br>
> +// The forwards pass finds the mappings of Pattern onto Word.<br>
> +// Score = best score achieved matching Word[..W] against Pat[..P].<br>
</span>> +// Unlike other tables, indices range from 0 to N *inclusive* //<br>
> +Matched = whether we chose to match Word[W] with Pat[P] or not.<br>
<span class="">> +//<br>
> +// Points are mostly assigned to matched characters, with 1 being a<br>
</span>> +good score // and 3 being a great one. So we treat the score range as [0, 3 *<br>
<span class="">> PatN].<br>
> +// This range is not strict: we can apply larger bonuses/penalties, or<br>
</span>> +penalize // non-matched characters.<br>
<div><div class="h5">> +void FuzzyMatcher::buildGraph() {<br>
> +  for (int W = 0; W < WordN; ++W) {<br>
> +    Scores[0][W + 1][Miss] = {Scores[0][W][Miss].Score - skipPenalty(W,<br>
> Miss),<br>
> +                              Miss};<br>
> +    Scores[0][W + 1][Match] = {AwfulScore, Miss};<br>
> +  }<br>
> +  for (int P = 0; P < PatN; ++P) {<br>
> +    for (int W = P; W < WordN; ++W) {<br>
> +      auto &Score = Scores[P + 1][W + 1], &PreMiss = Scores[P + 1][W];<br>
> +<br>
> +      auto MatchMissScore = PreMiss[Match].Score;<br>
> +      auto MissMissScore = PreMiss[Miss].Score;<br>
> +      if (P < PatN - 1) { // Skipping trailing characters is always free.<br>
> +        MatchMissScore -= skipPenalty(W, Match);<br>
> +        MissMissScore -= skipPenalty(W, Miss);<br>
> +      }<br>
> +      Score[Miss] = (MatchMissScore > MissMissScore)<br>
> +                        ? ScoreInfo{MatchMissScore, Match}<br>
> +                        : ScoreInfo{MissMissScore, Miss};<br>
> +<br>
> +      if (LowPat[P] != LowWord[W]) { // No match possible.<br>
> +        Score[Match] = {AwfulScore, Miss};<br>
> +      } else {<br>
> +        auto &PreMatch = Scores[P][W];<br>
> +        auto MatchMatchScore = PreMatch[Match].Score + matchBonus(P, W,<br>
> Match);<br>
> +        auto MissMatchScore = PreMatch[Miss].Score + matchBonus(P, W, Miss);<br>
> +        Score[Match] = (MatchMatchScore > MissMatchScore)<br>
> +                           ? ScoreInfo{MatchMatchScore, Match}<br>
> +                           : ScoreInfo{MissMatchScore, Miss};<br>
> +      }<br>
> +    }<br>
> +  }<br>
> +}<br>
> +<br>
> +int FuzzyMatcher::skipPenalty(int W, Action Last) {<br>
> +  int S = 0;<br>
> +  if (WordRole[W] == Head) // Skipping a segment.<br>
> +    S += 1;<br>
> +  if (Last == Match) // Non-consecutive match.<br>
> +    S += 2;          // We'd rather skip a segment than split our match.<br>
> +  return S;<br>
> +}<br>
> +<br>
> +int FuzzyMatcher::matchBonus(int P, int W, Action Last) {<br>
> +  assert(LowPat[P] == LowWord[W]);<br>
> +  int S = 1;<br>
> +  // Bonus: pattern so far is a (case-insensitive) prefix of the word.<br>
> +  if (P == W) // We can't skip pattern characters, so we must have matched<br>
> all.<br>
> +    ++S;<br>
> +  // Bonus: case matches, or a Head in the pattern aligns with one in the<br>
> word.<br>
> +  if ((Pat[P] == Word[W] && (CaseSensitive || P == W)) ||<br>
> +      (PatRole[P] == Head && WordRole[W] == Head))<br>
> +    ++S;<br>
> +  // Penalty: matching inside a segment (and previous char wasn't matched).<br>
> +  if (WordRole[W] == Tail && P && Last == Miss)<br>
> +    S -= 3;<br>
> +  // Penalty: a Head in the pattern matches in the middle of a word segment.<br>
> +  if (PatRole[P] == Head && WordRole[W] == Tail)<br>
> +    --S;<br>
> +  // Penalty: matching the first pattern character in the middle of a<br>
> segment.<br>
> +  if (P == 0 && WordRole[W] == Tail)<br>
> +    S -= 4;<br>
> +  assert(S <= PerfectBonus);<br>
> +  return S;<br>
> +}<br>
> +<br>
> +llvm::SmallString<256> FuzzyMatcher::dumpLast(llvm::<wbr>raw_ostream &OS)<br>
</div></div>> +const {<br>
<span class="">> +  llvm::SmallString<256> Result;<br>
> +  OS << "=== Match \"" << StringRef(Word, WordN) << "\" against ["<br>
> +     << StringRef(Pat, PatN) << "] ===\n";<br>
> +  if (!WordContainsPattern) {<br>
> +    OS << "Substring check failed.\n";<br>
> +    return Result;<br>
> +  } else if (isAwful(std::max(Scores[PatN]<wbr>[WordN][Match].Score,<br>
> +                              Scores[PatN][WordN][Miss].<wbr>Score))) {<br>
> +    OS << "Substring check passed, but all matches are forbidden\n";<br>
> +  }<br>
> +  if (!CaseSensitive)<br>
> +    OS << "Lowercase query, so scoring ignores case\n";<br>
> +<br>
> +  // Traverse Matched table backwards to reconstruct the Pattern/Word<br>
> mapping.<br>
> +  // The Score table has cumulative scores, subtracting along this path<br>
</span>> + gives  // us the per-letter scores.<br>
<div><div class="h5">> +  Action Last =<br>
> +      (Scores[PatN][WordN][Match].<wbr>Score > Scores[PatN][WordN][Miss].<wbr>Score)<br>
> +          ? Match<br>
> +          : Miss;<br>
> +  int S[MaxWord];<br>
> +  Action A[MaxWord];<br>
> +  for (int W = WordN - 1, P = PatN - 1; W >= 0; --W) {<br>
> +    A[W] = Last;<br>
> +    const auto &Cell = Scores[P + 1][W + 1][Last];<br>
> +    if (Last == Match)<br>
> +      --P;<br>
> +    const auto &Prev = Scores[P + 1][W][Cell.Prev];<br>
> +    S[W] = Cell.Score - Prev.Score;<br>
> +    Last = Cell.Prev;<br>
> +  }<br>
> +  for (int I = 0; I < WordN; ++I) {<br>
> +    if (A[I] == Match && (I == 0 || A[I - 1] == Miss))<br>
> +      Result.push_back('[');<br>
> +    if (A[I] == Miss && I > 0 && A[I - 1] == Match)<br>
> +      Result.push_back(']');<br>
> +    Result.push_back(Word[I]);<br>
> +  }<br>
> +  if (A[WordN - 1] == Match)<br>
> +    Result.push_back(']');<br>
> +<br>
> +  for (char C : StringRef(Word, WordN))<br>
> +    OS << " " << C << " ";<br>
> +  OS << "\n";<br>
> +  for (int I = 0, J = 0; I < WordN; I++)<br>
</div></div>> +    OS << " " << (A[I] == Match ? Pat[J++] : ' ') << " ";  OS << "\n";<br>
<span class="">> + for (int I = 0; I < WordN; I++)<br>
> +    OS << format("%2d ", S[I]);<br>
> +  OS << "\n";<br>
> +<br>
> +  OS << "\nSegmentation:";<br>
</span>> +  OS << "\n'" << StringRef(Word, WordN) << "'\n ";  for (int I = 0; I <<br>
> + WordN; ++I)<br>
> +    OS << "?-+ "[static_cast<int>(WordRole[I]<wbr>)];  OS << "\n[" <<<br>
> + StringRef(Pat, PatN) << "]\n ";  for (int I = 0; I < PatN; ++I)<br>
> +    OS << "?-+ "[static_cast<int>(PatRole[I])<wbr>];  OS << "\n";<br>
<span class="">> +<br>
> +  OS << "\nScoring table (last-Miss, last-Match):\n";<br>
> +  OS << " |    ";<br>
> +  for (char C : StringRef(Word, WordN))<br>
> +    OS << "  " << C << " ";<br>
> +  OS << "\n";<br>
</span>> +  OS << "-+----" << std::string(WordN * 4, '-') << "\n";  for (int I =<br>
> + 0; I <= PatN; ++I) {<br>
<span class="">> +    for (Action A : {Miss, Match}) {<br>
> +      OS << ((I && A == Miss) ? Pat[I - 1] : ' ') << "|";<br>
> +      for (int J = 0; J <= WordN; ++J) {<br>
> +        if (!isAwful(Scores[I][J][A].<wbr>Score))<br>
> +          OS << format("%3d%c", Scores[I][J][A].Score,<br>
> +                       Scores[I][J][A].Prev == Match ? '*' : ' ');<br>
> +        else<br>
> +          OS << "    ";<br>
> +      }<br>
> +      OS << "\n";<br>
> +    }<br>
> +  }<br>
> +<br>
> +  return Result;<br>
> +}<br>
><br>
> Added: clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.h<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-<wbr>project/clang-tools-</a><br>
> extra/trunk/clangd/FuzzyMatch.<wbr>h?rev=319557&view=auto<br>
> ==============================<wbr>==============================<wbr>==================<br>
> --- clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.h (added)<br>
> +++ clang-tools-extra/trunk/<wbr>clangd/FuzzyMatch.h Fri Dec  1 09:08:02 2017<br>
> @@ -0,0 +1,84 @@<br>
> +//===--- FuzzyMatch.h - Approximate identifier matching  ---------*-<br>
</span>> +C++-*-===// //<br>
<span class="">> +//                     The LLVM Compiler Infrastructure<br>
> +//<br>
> +// This file is distributed under the University of Illinois Open<br>
</span>> +Source // License. See LICENSE.TXT for details.<br>
> +//<br>
> +//===------------------------<wbr>------------------------------<wbr>------------<br>
> +----===//<br>
<span class="">> +//<br>
> +// This file implements fuzzy-matching of strings against identifiers.<br>
> +// It indicates both the existence and quality of a match:<br>
> +// 'eb' matches both 'emplace_back' and 'embed', the former has a better<br>
> score.<br>
> +//<br>
> +//===------------------------<wbr>------------------------------<wbr>------------<br>
</span>> +----===//<br>
<div><div class="h5">> +<br>
> +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_<wbr>FUZZYMATCH_H<br>
> +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_<wbr>FUZZYMATCH_H<br>
> +<br>
> +#include "llvm/ADT/Optional.h"<br>
> +#include "llvm/ADT/SmallString.h"<br>
> +#include "llvm/ADT/StringRef.h"<br>
> +#include "llvm/Support/raw_ostream.h"<br>
> +<br>
> +namespace clang {<br>
> +namespace clangd {<br>
> +<br>
> +// A matcher capable of matching and scoring strings against a single<br>
> pattern.<br>
> +// It's optimized for matching against many strings - match() does not<br>
> allocate.<br>
> +class FuzzyMatcher {<br>
> +public:<br>
> +  // Characters beyond MaxPat are ignored.<br>
> +  FuzzyMatcher(llvm::StringRef Pattern);<br>
> +<br>
> +  // If Word matches the pattern, return a score in [0,1] (higher is better).<br>
> +  // Characters beyond MaxWord are ignored.<br>
> +  llvm::Optional<float> match(llvm::StringRef Word);<br>
> +<br>
> +  // Dump internal state from the last match() to the stream, for debugging.<br>
> +  // Returns the pattern with [] around matched characters, e.g.<br>
> +  //   [u_p] + "unique_ptr" --> "[u]nique[_p]tr"<br>
> +  llvm::SmallString<256> dumpLast(llvm::raw_ostream &) const;<br>
> +<br>
> +private:<br>
> +  // We truncate the pattern and the word to bound the cost of matching.<br>
> +  constexpr static int MaxPat = 63, MaxWord = 127;<br>
> +  enum CharRole : char; // For segmentation.<br>
> +  enum CharType : char; // For segmentation.<br>
> +  enum Action { Miss = 0, Match = 1 };<br>
> +<br>
> +  bool init(llvm::StringRef Word);<br>
> +  void buildGraph();<br>
</div></div>> +  void calculateRoles(const char *Text, CharRole *Out, int N);  int<br>
> + skipPenalty(int W, Action Last);  int matchBonus(int P, int W, Action<br>
> + Last);<br>
<div><div class="h5">> +<br>
> +  // Pattern data is initialized by the constructor, then constant.<br>
> +  char Pat[MaxPat];         // Pattern data<br>
> +  int PatN;                 // Length<br>
> +  char LowPat[MaxPat];      // Pattern in lowercase<br>
> +  CharRole PatRole[MaxPat]; // Pattern segmentation info<br>
> +  bool CaseSensitive;       // Case-sensitive match if pattern has uppercase<br>
> +  float ScoreScale;         // Normalizes scores for the pattern length.<br>
> +<br>
> +  // Word data is initialized on each call to match(), mostly by init().<br>
> +  char Word[MaxWord];         // Word data<br>
> +  int WordN;                  // Length<br>
> +  char LowWord[MaxWord];      // Word in lowercase<br>
> +  CharRole WordRole[MaxWord]; // Word segmentation info<br>
> +  bool WordContainsPattern;   // Simple substring check<br>
> +<br>
> +  // Cumulative best-match score table.<br>
> +  // Boundary conditions are filled in by the constructor.<br>
> +  // The rest is repopulated for each match(), by buildGraph().<br>
> +  struct ScoreInfo {<br>
> +    signed int Score : 15;<br>
> +    Action Prev : 1;<br>
> +  };<br>
> +  ScoreInfo Scores[MaxPat + 1][MaxWord + 1][/* Last Action */ 2]; };<br>
> +<br>
> +} // namespace clangd<br>
> +} // namespace clang<br>
> +<br>
> +#endif<br>
><br>
> Modified: clang-tools-extra/trunk/<wbr>unittests/clangd/CMakeLists.<wbr>txt<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-<wbr>project/clang-tools-</a><br>
> extra/trunk/unittests/clangd/<wbr>CMakeLists.txt?rev=319557&r1=<wbr>319556&r2=319557&vie<br>
> w=diff<br>
> ==============================<wbr>==============================<wbr>==================<br>
> --- clang-tools-extra/trunk/<wbr>unittests/clangd/CMakeLists.<wbr>txt (original)<br>
> +++ clang-tools-extra/trunk/<wbr>unittests/clangd/CMakeLists.<wbr>txt Fri Dec  1<br>
</div></div>> +++ 09:08:02 2017<br>
<span class="">> @@ -10,6 +10,7 @@ include_directories(<br>
><br>
>  add_extra_unittest(ClangdTests<br>
>    ClangdTests.cpp<br>
> +  FuzzyMatchTests.cpp<br>
>    JSONExprTests.cpp<br>
>    TraceTests.cpp<br>
>    )<br>
><br>
> Added: clang-tools-extra/trunk/<wbr>unittests/clangd/<wbr>FuzzyMatchTests.cpp<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/clang-tools-" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-<wbr>project/clang-tools-</a><br>
> extra/trunk/unittests/clangd/<wbr>FuzzyMatchTests.cpp?rev=<wbr>319557&view=auto<br>
> ==============================<wbr>==============================<wbr>==================<br>
> --- clang-tools-extra/trunk/<wbr>unittests/clangd/<wbr>FuzzyMatchTests.cpp (added)<br>
> +++ clang-tools-extra/trunk/<wbr>unittests/clangd/<wbr>FuzzyMatchTests.cpp Fri Dec<br>
</span>> +++ 1 09:08:02 2017<br>
<span class="">> @@ -0,0 +1,252 @@<br>
> +//===-- FuzzyMatchTests.cpp - String fuzzy matcher tests --------*- C++<br>
</span>> +-*-===// //<br>
<span class="">> +//                     The LLVM Compiler Infrastructure<br>
> +//<br>
> +// This file is distributed under the University of Illinois Open<br>
</span>> +Source // License. See LICENSE.TXT for details.<br>
> +//<br>
> +//===------------------------<wbr>------------------------------<wbr>------------<br>
> +----===//<br>
<span class="">> +<br>
> +#include "FuzzyMatch.h"<br>
> +<br>
> +#include "llvm/ADT/StringExtras.h"<br>
> +#include "gmock/gmock.h"<br>
> +#include "gtest/gtest.h"<br>
> +<br>
> +namespace clang {<br>
> +namespace clangd {<br>
> +namespace {<br>
> +using namespace llvm;<br>
> +using testing::Not;<br>
> +<br>
> +struct ExpectedMatch {<br>
> +  ExpectedMatch(StringRef Annotated) : Word(Annotated), Annotated(Annotated)<br>
> {<br>
> +    for (char C : "[]")<br>
> +      Word.erase(std::remove(Word.<wbr>begin(), Word.end(), C), Word.end());<br>
> +  }<br>
> +  std::string Word;<br>
> +  StringRef Annotated;<br>
> +};<br>
> +raw_ostream &operator<<(raw_ostream &OS, const ExpectedMatch &M) {<br>
> +  return OS << "'" << M.Word << "' as " << M.Annotated; }<br>
> +<br>
</span><span class="">> +struct MatchesMatcher : public testing::MatcherInterface<<wbr>StringRef> {<br>
> +  ExpectedMatch Candidate;<br>
> +  MatchesMatcher(ExpectedMatch Candidate) :<br>
</span>> +Candidate(std::move(<wbr>Candidate)) {}<br>
<span class="">> +<br>
> +  void DescribeTo(::std::ostream *OS) const override {<br>
> +    raw_os_ostream(*OS) << "Matches " << Candidate;  }<br>
> +<br>
</span>> +  bool MatchAndExplain(StringRef Pattern,<br>
<span class="">> +                       testing::MatchResultListener *L) const override {<br>
> +    std::unique_ptr<raw_ostream> OS(<br>
> +        L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream()))<br>
> +                    : new raw_null_ostream());<br>
> +    FuzzyMatcher Matcher(Pattern);<br>
> +    auto Result = Matcher.match(Candidate.Word);<br>
> +    auto AnnotatedMatch = Matcher.dumpLast(*OS << "\n");<br>
> +    return Result && AnnotatedMatch == Candidate.Annotated;<br>
> +  }<br>
> +};<br>
> +<br>
> +// Accepts patterns that match a given word.<br>
> +// Dumps the debug tables on match failure.<br>
> +testing::Matcher<StringRef> matches(StringRef M) {<br>
> +  return testing::MakeMatcher<<wbr>StringRef>(new MatchesMatcher(M)); }<br>
> +<br>
</span><span class="">> +TEST(FuzzyMatch, Matches) {<br>
> +  EXPECT_THAT("u_p", matches("[u]nique[_p]tr"));<br>
> +  EXPECT_THAT("up", matches("[u]nique_[p]tr"));<br>
> +  EXPECT_THAT("uq", matches("[u]ni[q]ue_ptr"));<br>
> +  EXPECT_THAT("qp", Not(matches("unique_ptr")));<br>
> +  EXPECT_THAT("log", Not(matches("<wbr>SVGFEMorphologyElement")));<br>
> +<br>
</span>> +  EXPECT_THAT("tit", matches("win.[tit]"));  EXPECT_THAT("title",<br>
> + matches("win.[title]"));  EXPECT_THAT("WordCla",<br>
> + matches("[Word]Character[Cla]<wbr>ssifier"));<br>
<span class="">> +  EXPECT_THAT("WordCCla", matches("[WordC]haracter[Cla]<wbr>ssifier"));<br>
> +<br>
> +  EXPECT_THAT("dete", Not(matches("editor.<wbr>quickSuggestionsDelay")));<br>
> +<br>
> +  EXPECT_THAT("highlight", matches("editorHover[<wbr>Highlight]"));<br>
> +  EXPECT_THAT("hhighlight", matches("editor[H]over[<wbr>Highlight]"));<br>
> +  EXPECT_THAT("dhhighlight", Not(matches("<wbr>editorHoverHighlight")));<br>
> +<br>
</span>> +  EXPECT_THAT("-moz", matches("[-moz]-foo"));  EXPECT_THAT("moz",<br>
> + matches("-[moz]-foo"));  EXPECT_THAT("moza",<br>
> + matches("-[moz]-[a]nimation"))<wbr>;<br>
<span class="">> +<br>
> +  EXPECT_THAT("ab", matches("[ab]A"));<br>
</span>> +  EXPECT_THAT("ccm", matches("[c]a[cm]elCase"));  EXPECT_THAT("bti",<br>
> + Not(matches("the_black_knight"<wbr>)));<br>
> +  EXPECT_THAT("ccm", Not(matches("camelCase")));  EXPECT_THAT("cmcm",<br>
> + Not(matches("camelCase")));  EXPECT_THAT("BK",<br>
> + matches("the_[b]lack_[k]night"<wbr>));  EXPECT_THAT("KeyboardLayout=",<br>
> + Not(matches("KeyboardLayout"))<wbr>);  EXPECT_THAT("LLL",<br>
> + matches("SVisual[L]ogger[L]<wbr>ogs[L]ist"));<br>
<span class="">> +  EXPECT_THAT("LLLL", Not(matches("SVilLoLosLi")));<br>
> + EXPECT_THAT("LLLL", Not(matches("<wbr>SVisualLoggerLogsList")));<br>
</span>> +  EXPECT_THAT("TEdit", matches("[T]ext[Edit]"));  EXPECT_THAT("TEdit",<br>
> + matches("[T]ext[Edit]or"));  EXPECT_THAT("TEdit",<br>
> + matches("[Te]xte[dit]"));  EXPECT_THAT("TEdit",<br>
> + matches("[t]ext_[edit]"));  EXPECT_THAT("TEditDit",<br>
> + matches("[T]ext[Edit]or[D]<wbr>ecorat[i]on[T]ype"));<br>
<span class="">> +  EXPECT_THAT("TEdit", matches("[T]ext[Edit]<wbr>orDecorationType"));<br>
</span>> +  EXPECT_THAT("Tedit", matches("[T]ext[Edit]"));  EXPECT_THAT("ba",<br>
> + Not(matches("?AB?")));  EXPECT_THAT("bkn",<br>
> + matches("the_[b]lack_[kn]ight"<wbr>));  EXPECT_THAT("bt",<br>
> + matches("the_[b]lack_knigh[t]"<wbr>));  EXPECT_THAT("ccm",<br>
> + matches("[c]amelCase[cm]"));  EXPECT_THAT("fdm",<br>
> + matches("[f]in[dM]odel"));  EXPECT_THAT("fob", matches("[fo]o[b]ar"));<br>
> + EXPECT_THAT("fobz", Not(matches("foobar")));  EXPECT_THAT("foobar",<br>
> + matches("[foobar]"));  EXPECT_THAT("form",<br>
> + matches("editor.[form]<wbr>atOnSave"));<br>
> +  EXPECT_THAT("g p", matches("[G]it:[ P]ull"));  EXPECT_THAT("g p",<br>
> + matches("[G]it:[ P]ull"));  EXPECT_THAT("gip", matches("[Gi]t:<br>
> + [P]ull"));  EXPECT_THAT("gip", matches("[Gi]t: [P]ull"));<br>
> + EXPECT_THAT("gp", matches("[G]it: [P]ull"));  EXPECT_THAT("gp",<br>
> + matches("[G]it_Git_[P]ull"));  EXPECT_THAT("is",<br>
> + matches("[I]mport[S]tatement")<wbr>);  EXPECT_THAT("is",<br>
> + matches("[is]Valid"));  EXPECT_THAT("lowrd", matches("[low]Wo[rd]"));<br>
> + EXPECT_THAT("myvable", matches("[myva]ria[ble]"));  EXPECT_THAT("no",<br>
> + Not(matches("")));  EXPECT_THAT("no", Not(matches("match")));<br>
> + EXPECT_THAT("ob", Not(matches("foobar")));  EXPECT_THAT("sl",<br>
> + matches("[S]Visual[L]<wbr>oggerLogsList"));<br>
<span class="">> +  EXPECT_THAT("sllll", matches("[S]Visua[lL]ogger[L]<wbr>ogs[L]ist"));<br>
> +  EXPECT_THAT("Three", matches("H[T]ML[HRE]l[e]ment")<wbr>);<br>
</span>> + EXPECT_THAT("Three", matches("[Three]"));  EXPECT_THAT("fo",<br>
> + Not(matches("barfoo")));  EXPECT_THAT("fo", matches("bar_[fo]o"));<br>
> + EXPECT_THAT("fo", matches("bar_[Fo]o"));  EXPECT_THAT("fo",<br>
> + matches("bar [fo]o"));  EXPECT_THAT("fo", matches("bar.[fo]o"));<br>
> + EXPECT_THAT("fo", matches("bar/[fo]o"));  EXPECT_THAT("fo",<br>
> + matches("bar\\[fo]o"));<br>
<span class="">> +<br>
> +  EXPECT_THAT(<br>
> +      "aaaaaa",<br>
> +<br>
> matches("[aaaaaa]<wbr>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<wbr>aaaaaaaaaaaaaaaaaaaaaaaaa"<br>
> +<br>
> + "<wbr>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<wbr>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<wbr>a"));<br>
> +  EXPECT_THAT("baba", Not(matches("ababababab")));<br>
> + EXPECT_THAT("fsfsfs",<br>
</span>> + Not(matches("<wbr>dsafdsafdsafdsafdsafdsafdsafas<wbr>dfdsa")));<br>
<span class="">> +  EXPECT_THAT("fsfsfsfsfsfsfsf",<br>
> +<br>
> Not(matches("<wbr>dsafdsafdsafdsafdsafdsafdsafas<wbr>dfdsafdsafdsafdsafdsfd"<br>
> +<br>
> + "<wbr>safdsfdfdfasdnfdsajfndsjnafjnd<wbr>sajlknfdsa")));<br>
> +<br>
</span>> +  EXPECT_THAT("  g", matches("[  g]roup"));  EXPECT_THAT("g", matches("<br>
> + [g]roup"));  EXPECT_THAT("g g", Not(matches("  groupGroup")));<br>
> + EXPECT_THAT("g g", matches("  [g]roup[ G]roup"));  EXPECT_THAT(" g g",<br>
> + matches("[ ] [g]roup[ G]roup"));  EXPECT_THAT("zz",<br>
> + matches("[zz]Group"));  EXPECT_THAT("zzg", matches("[zzG]roup"));<br>
<span class="">> + EXPECT_THAT("g", matches("zz[G]roup"));<br>
> +<br>
> +  EXPECT_THAT("aaaa", matches("_a_[aaaa]")); // Prefer consecutive.<br>
> +  EXPECT_THAT("printf", matches("s[printf]"));<br>
> +  EXPECT_THAT("str", matches("o[str]eam")); }<br>
> +<br>
</span><div><div class="h5">> +struct RankMatcher : public testing::MatcherInterface<<wbr>StringRef> {<br>
> +  std::vector<ExpectedMatch> RankedStrings;<br>
> +  RankMatcher(std::initializer_<wbr>list<ExpectedMatch> RankedStrings)<br>
> +      : RankedStrings(RankedStrings) {}<br>
> +<br>
> +  void DescribeTo(::std::ostream *OS) const override {<br>
> +    raw_os_ostream O(*OS);<br>
> +    O << "Ranks strings in order: [";<br>
> +    for (const auto &Str : RankedStrings)<br>
> +      O << "\n\t" << Str;<br>
> +    O << "\n]";<br>
> +  }<br>
> +<br>
> +  bool MatchAndExplain(StringRef Pattern,<br>
> +                       testing::MatchResultListener *L) const override {<br>
> +    std::unique_ptr<raw_ostream> OS(<br>
> +        L->stream() ? (raw_ostream *)(new raw_os_ostream(*L->stream()))<br>
> +                    : new raw_null_ostream());<br>
> +    FuzzyMatcher Matcher(Pattern);<br>
> +    const ExpectedMatch *LastMatch;<br>
> +    Optional<float> LastScore;<br>
> +    bool Ok = true;<br>
> +    for (const auto &Str : RankedStrings) {<br>
> +      auto Score = Matcher.match(Str.Word);<br>
> +      if (!Score) {<br>
> +        *OS << "\nDoesn't match '" << Str.Word << "'";<br>
> +        Matcher.dumpLast(*OS << "\n");<br>
> +        Ok = false;<br>
> +      } else {<br>
> +        std::string Buf;<br>
> +        llvm::raw_string_ostream Info(Buf);<br>
> +        auto AnnotatedMatch = Matcher.dumpLast(Info);<br>
> +<br>
> +        if (AnnotatedMatch != Str.Annotated) {<br>
> +          *OS << "\nMatched " << Str.Word << " as " << AnnotatedMatch<br>
> +              << " instead of " << Str.Annotated << "\n"<br>
> +              << Info.str();<br>
> +          Ok = false;<br>
> +        } else if (LastScore && *LastScore < *Score) {<br>
> +          *OS << "\nRanks '" << Str.Word << "'=" << *Score << " above '"<br>
> +              << LastMatch->Word << "'=" << *LastScore << "\n"<br>
> +              << Info.str();<br>
> +          Matcher.match(LastMatch->Word)<wbr>;<br>
> +          Matcher.dumpLast(*OS << "\n");<br>
> +          Ok = false;<br>
> +        }<br>
> +      }<br>
> +      LastMatch = &Str;<br>
> +      LastScore = Score;<br>
> +    }<br>
> +    return Ok;<br>
> +  }<br>
> +};<br>
> +<br>
> +// Accepts patterns that match all the strings and rank them in the given<br>
> order.<br>
> +// Dumps the debug tables on match failure.<br>
> +template <typename... T> testing::Matcher<StringRef> ranks(T...<br>
</div></div>> +RankedStrings) {<br>
<span class="">> +  return testing::MakeMatcher<<wbr>StringRef>(<br>
> +      new RankMatcher{ExpectedMatch(<wbr>RankedStrings)...});<br>
> +}<br>
> +<br>
> +TEST(FuzzyMatch, Ranking) {<br>
> +  EXPECT_THAT("eb", ranks("[e]mplace_[b]ack", "[e]m[b]ed"));<br>
> +  EXPECT_THAT("cons",<br>
> +              ranks("[cons]ole", "[Cons]ole",<br>
</span>> +"ArrayBuffer[Cons]tructor"));<br>
<span class="">> +  EXPECT_THAT("foo", ranks("[foo]", "[Foo]"));<br>
> +  EXPECT_THAT("onMess",<br>
> +              ranks("[onMess]age", "[onmess]age",<br>
</span>> +"[on]This[M]ega[Es]cape[s]"))<wbr>;<br>
<span class="">> +  EXPECT_THAT("CC", ranks("[C]amel[C]ase", "[c]amel[C]ase"));<br>
> +  EXPECT_THAT("cC", ranks("[c]amel[C]ase", "[C]amel[C]ase"));<br>
> +  EXPECT_THAT("p", ranks("[p]arse", "[p]osix", "[p]afdsa", "[p]ath",<br>
</span>> +"[p]"));<br>
<span class="">> +  EXPECT_THAT("pa", ranks("[pa]rse", "[pa]th", "[pa]fdsa"));<br>
> +  EXPECT_THAT("log", ranks("[log]", "Scroll[Log]icalPosition"));<br>
> +  EXPECT_THAT("e", ranks("[e]lse", "Abstract[E]lement"));<br>
> +  EXPECT_THAT("workbench.sideb",<br>
> +              ranks("[workbench.sideB]ar.<wbr>location",<br>
> +                    "[workbench.]editor.default[<wbr>SideB]ySideLayout"));<br>
> +  EXPECT_THAT("editor.r", ranks("[editor.r]<wbr>enderControlCharacter",<br>
> +                                "[editor.]overview[R]<wbr>ulerlanes",<br>
> +                                "diff[Editor.r]<wbr>enderSideBySide"));<br>
> +  EXPECT_THAT("-mo", ranks("[-mo]z-columns", "[-]ms-ime-[mo]de"));<br>
> +  EXPECT_THAT("<wbr>convertModelPosition",<br>
> +              ranks("[convertModelPosition]<wbr>ToViewPosition",<br>
> +                    "[convert]ViewTo[<wbr>ModelPosition]"));<br>
> +  EXPECT_THAT("is", ranks("[is]ValidViewletId", "[i]mport<br>
</span>> +[s]tatement"));<br>
<span class="">> +  EXPECT_THAT("title", ranks("window.[title]",<br>
> +<br>
> +"files.[t]r[i]m[T]rai[l]<wbr>ingWhit[e]space"));<br>
> +  EXPECT_THAT("strcpy", ranks("[strcpy]", "[strcpy]_s",<br>
</span>> +"[str]n[cpy]"));<br>
<div class="HOEnZb"><div class="h5">> +  EXPECT_THAT("close", ranks("workbench.quickOpen.[<wbr>close]OnFocusOut",<br>
> +                             "[c]ss.[l]int.imp[o]rt[S]tat[<wbr>e]ment",<br>
> +                             "[c]<a href="http://ss.co" rel="noreferrer" target="_blank">ss.co</a>[lo]rDecorator[s].[e]<wbr>nable"));<br>
> +}<br>
> +<br>
> +} // namespace<br>
> +} // namespace clangd<br>
> +} // namespace clang<br>
><br>
><br>
> ______________________________<wbr>_________________<br>
> cfe-commits mailing list<br>
> <a href="mailto:cfe-commits@lists.llvm.org">cfe-commits@lists.llvm.org</a><br>
> <a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/<wbr>mailman/listinfo/cfe-commits</a><br>
</div></div></blockquote></div><br></div>