فهرست منبع

Better smart-case query-candidate matching

Previously, "Foo" would match "Foo" but not "FOO". Now it matches both. Bottom
line, an uppercase char matches only uppercase chars but lowercase chars can
match either. We used to insist that lowercase chars in the query match
lowercase chars in the candidate strings IF the query also had uppercase
letters; this was dumb and a poor user experience.

Fixes #501.
Strahinja Val Markovic 11 سال پیش
والد
کامیت
16d1365d12
4فایلهای تغییر یافته به همراه19 افزوده شده و 24 حذف شده
  1. 4 3
      README.md
  2. 5 12
      cpp/ycm/Candidate.cpp
  3. 8 8
      cpp/ycm/tests/Candidate_test.cpp
  4. 2 1
      cpp/ycm/tests/IdentifierCompleter_test.cpp

+ 4 - 3
README.md

@@ -293,9 +293,10 @@ User Guide
 - If the offered completions are too broad, keep typing characters; YCM will
   continue refining the offered completions based on your input.
 - Filtering is "smart-case" sensitive; if you are typing only lowercase letters,
-  then it's case-insensitive. If your input involves uppercase letters, then
-  it's case-sensitive. So "foo" matches "Foo" and "foo", but "Foo" matches "Foo"
-  but not "foo".
+  then it's case-insensitive. If your input contains uppercase letters, then the
+  uppercase letters in your query must match uppercase letters in the completion
+  strings (the lowercase letters still match both). So, "foo" matches "Foo" and
+  "foo", "Foo" matches "Foo" and "FOO" but not "foo".
 - Use the TAB key to accept a completion and continue pressing TAB to cycle
   through the completions. Use Shift-TAB to cycle backwards. Note that if you're
   using console Vim (that is, not Gvim or MacVim) then it's likely that the

+ 5 - 12
cpp/ycm/Candidate.cpp

@@ -39,17 +39,6 @@ LetterNode *FirstUppercaseNode( const std::list< LetterNode *> &list ) {
   return node;
 }
 
-LetterNode *FirstLowercaseNode( const std::list< LetterNode *> &list ) {
-  LetterNode *node = NULL;
-  foreach( LetterNode * current_node, list ) {
-    if ( !current_node->LetterIsUppercase() ) {
-      node = current_node;
-      break;
-    }
-  }
-  return node;
-}
-
 } // unnamed namespace
 
 std::string GetWordBoundaryChars( const std::string &text ) {
@@ -107,9 +96,13 @@ Result Candidate::QueryMatchResult( const std::string &query,
       return Result( false );
 
     if ( case_sensitive ) {
+      // When the query letter is uppercase, then we force an uppercase match
+      // but when the query letter is lowercase, then it can match both an
+      // uppercase and a lowercase letter. This is by design and it's much
+      // better than forcing lowercase letter matches.
       node = IsUppercase( letter ) ?
              FirstUppercaseNode( *list ) :
-             FirstLowercaseNode( *list );
+             list->front();
 
       if ( !node )
         return Result( false );

+ 8 - 8
cpp/ycm/tests/Candidate_test.cpp

@@ -192,26 +192,26 @@ TEST( CandidateTest, QueryMatchResultCaseSensitiveIsSubsequence ) {
   EXPECT_TRUE( candidate.QueryMatchResult( "AA"      , true ).IsSubsequence() );
   EXPECT_TRUE( candidate.QueryMatchResult( "A"       , true ).IsSubsequence() );
   EXPECT_TRUE( candidate.QueryMatchResult( "B"       , true ).IsSubsequence() );
+  EXPECT_TRUE( candidate.QueryMatchResult( "foobaaar", true ).IsSubsequence() );
+  EXPECT_TRUE( candidate.QueryMatchResult( "foobaAAr", true ).IsSubsequence() );
+  EXPECT_TRUE( candidate.QueryMatchResult( "fbAA"    , true ).IsSubsequence() );
+  EXPECT_TRUE( candidate.QueryMatchResult( "fbaa"    , true ).IsSubsequence() );
+  EXPECT_TRUE( candidate.QueryMatchResult( "b"       , true ).IsSubsequence() );
+  EXPECT_TRUE( candidate.QueryMatchResult( "f"       , true ).IsSubsequence() );
+  EXPECT_TRUE( candidate.QueryMatchResult( "fbar"    , true ).IsSubsequence() );
 }
 
 TEST( CandidateTest, QueryMatchResultCaseSensitiveIsntSubsequence ) {
   Candidate candidate( "FooBaAAr" );
 
-  EXPECT_FALSE( candidate.QueryMatchResult( "foobaaar", true ).IsSubsequence() );
-  EXPECT_FALSE( candidate.QueryMatchResult( "foobaAAr", true ).IsSubsequence() );
-  EXPECT_FALSE( candidate.QueryMatchResult( "fbAA"    , true ).IsSubsequence() );
-  EXPECT_FALSE( candidate.QueryMatchResult( "fbaa"    , true ).IsSubsequence() );
+  EXPECT_FALSE( candidate.QueryMatchResult( "goo"     , true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "R"       , true ).IsSubsequence() );
-  EXPECT_FALSE( candidate.QueryMatchResult( "b"       , true ).IsSubsequence() );
-  EXPECT_FALSE( candidate.QueryMatchResult( "f"       , true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "O"       , true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "OO"      , true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "OBA"     , true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "FBAR"    , true ).IsSubsequence() );
-  EXPECT_FALSE( candidate.QueryMatchResult( "fbar"    , true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "FBAAR"   , true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "Oar"     , true ).IsSubsequence() );
-  EXPECT_FALSE( candidate.QueryMatchResult( "FooBaAAR", true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "FooBAAAr", true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "FOoBaAAr", true ).IsSubsequence() );
   EXPECT_FALSE( candidate.QueryMatchResult( "FOobaaar", true ).IsSubsequence() );

+ 2 - 1
cpp/ycm/tests/IdentifierCompleter_test.cpp

@@ -69,7 +69,8 @@ TEST( IdentifierCompleterTest, SmartCaseFiltering ) {
                  StringVector(
                    "fooBar",
                    "fooBaR" ) ).CandidatesForQuery( "fBr" ),
-               ElementsAre( "fooBar" ) );
+               ElementsAre( "fooBaR",
+                            "fooBar" ) );
 }
 
 TEST( IdentifierCompleterTest, FirstCharSameAsQueryWins ) {