From a6e83bfe76f0d38611a19b1792c59f3263bddd3a Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Fri, 1 Mar 2013 22:18:43 -0800 Subject: [PATCH] Smart-case sensitive filtering Fixes #120 --- README.md | 6 +- cpp/ycm/Candidate.cpp | 37 ++++++- cpp/ycm/Candidate.h | 3 +- cpp/ycm/ClangCompleter/ClangCompleter.cpp | 9 +- cpp/ycm/IdentifierCompleter.cpp | 9 +- cpp/ycm/LetterNode.h | 2 +- cpp/ycm/PythonSupport.cpp | 9 +- cpp/ycm/tests/Candidate_test.cpp | 108 ++++++++++++++------- cpp/ycm/tests/IdentifierCompleter_test.cpp | 15 +-- 9 files changed, 150 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index af464505..9a1a382b 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,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". - 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 @@ -306,7 +310,7 @@ string. ### General Semantic Completion Engine Usage - You can use Ctrl+Space to trigger the completion suggestions anywhere, even - without a string prefix. This is useful to see which top-level functions are + without a string prefix. This is useful to see which top-level functions are available for use. ### C-family Semantic Completion Engine Usage diff --git a/cpp/ycm/Candidate.cpp b/cpp/ycm/Candidate.cpp index fce0e5d4..f50e9b08 100644 --- a/cpp/ycm/Candidate.cpp +++ b/cpp/ycm/Candidate.cpp @@ -43,6 +43,28 @@ std::string GetWordBoundaryChars( const std::string &text ) { return result; } +LetterNode* FirstUppercaseNode( const std::list< LetterNode *> &list ) { + LetterNode *node = NULL; + foreach( LetterNode *current_node, list ) { + if ( current_node->LetterIsUppercase() ) { + node = current_node; + break; + } + } + 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 @@ -66,7 +88,8 @@ Candidate::Candidate( const std::string &text ) } -Result Candidate::QueryMatchResult( const std::string &query ) const { +Result Candidate::QueryMatchResult( const std::string &query, + bool case_sensitive ) const { LetterNode *node = root_node_.get(); int index_sum = 0; @@ -76,7 +99,17 @@ Result Candidate::QueryMatchResult( const std::string &query ) const { if ( !list ) return Result( false ); - node = list->front(); + if ( case_sensitive ) { + node = IsUppercase( letter ) ? + FirstUppercaseNode( *list ) : + FirstLowercaseNode( *list ); + + if ( !node ) + return Result( false ); + } else { + node = list->front(); + } + index_sum += node->Index(); } diff --git a/cpp/ycm/Candidate.h b/cpp/ycm/Candidate.h index 2ce8084e..e68dfdac 100644 --- a/cpp/ycm/Candidate.h +++ b/cpp/ycm/Candidate.h @@ -49,7 +49,8 @@ public: return ( letters_present_ & query_bitset ) == query_bitset; } - Result QueryMatchResult( const std::string &query ) const; + Result QueryMatchResult( const std::string &query, + bool case_sensitive ) const; private: diff --git a/cpp/ycm/ClangCompleter/ClangCompleter.cpp b/cpp/ycm/ClangCompleter/ClangCompleter.cpp index f73be368..86dc21d9 100644 --- a/cpp/ycm/ClangCompleter/ClangCompleter.cpp +++ b/cpp/ycm/ClangCompleter/ClangCompleter.cpp @@ -29,6 +29,11 @@ #include #include +#include +#include + +using boost::algorithm::any_of; +using boost::algorithm::is_upper; using boost::bind; using boost::cref; @@ -387,6 +392,7 @@ std::vector< CompletionData > ClangCompleter::SortCandidatesForQuery( const std::string &query, const std::vector< CompletionData > &completion_datas ) { Bitset query_bitset = LetterBitsetFromString( query ); + bool query_has_uppercase_letters = any_of( query, is_upper() ); std::vector< const Candidate * > repository_candidates = candidate_repository_.GetCandidatesForStrings( completion_datas ); @@ -399,7 +405,8 @@ std::vector< CompletionData > ClangCompleter::SortCandidatesForQuery( if ( !candidate->MatchesQueryBitset( query_bitset ) ) continue; - Result result = candidate->QueryMatchResult( query ); + Result result = candidate->QueryMatchResult( query, + query_has_uppercase_letters ); if ( result.IsSubsequence() ) { ResultAnd< CompletionData* > data_and_result( &completion_datas[ i ], diff --git a/cpp/ycm/IdentifierCompleter.cpp b/cpp/ycm/IdentifierCompleter.cpp index 8ee93603..50361925 100644 --- a/cpp/ycm/IdentifierCompleter.cpp +++ b/cpp/ycm/IdentifierCompleter.cpp @@ -26,8 +26,13 @@ #include #include #include +#include +#include #include +using boost::algorithm::any_of; +using boost::algorithm::is_upper; + using boost::packaged_task; using boost::unique_future; using boost::shared_ptr; @@ -228,6 +233,7 @@ void IdentifierCompleter::ResultsForQueryAndType( return; Bitset query_bitset = LetterBitsetFromString( query ); + bool query_has_uppercase_letters = any_of( query, is_upper() ); boost::unordered_set< const Candidate * > seen_candidates; seen_candidates.reserve( candidate_repository_.NumStoredCandidates() ); @@ -243,7 +249,8 @@ void IdentifierCompleter::ResultsForQueryAndType( if ( !candidate->MatchesQueryBitset( query_bitset ) ) continue; - Result result = candidate->QueryMatchResult( query ); + Result result = candidate->QueryMatchResult( + query, query_has_uppercase_letters ); if ( result.IsSubsequence() ) results.push_back( result ); diff --git a/cpp/ycm/LetterNode.h b/cpp/ycm/LetterNode.h index 28b299fa..e7650837 100644 --- a/cpp/ycm/LetterNode.h +++ b/cpp/ycm/LetterNode.h @@ -51,7 +51,7 @@ public: letters_[ letter ].push_front( node ); } - inline int Index() { + inline int Index() const { return index_; } diff --git a/cpp/ycm/PythonSupport.cpp b/cpp/ycm/PythonSupport.cpp index 0897afc8..71048b9f 100644 --- a/cpp/ycm/PythonSupport.cpp +++ b/cpp/ycm/PythonSupport.cpp @@ -20,8 +20,12 @@ #include "Result.h" #include "Candidate.h" #include "CandidateRepository.h" +#include +#include #include +using boost::algorithm::any_of; +using boost::algorithm::is_upper; using boost::python::len; using boost::python::extract; using boost::python::object; @@ -67,6 +71,8 @@ boost::python::list FilterAndSortCandidates( CandidatesFromObjectList( candidates, candidate_property ); Bitset query_bitset = LetterBitsetFromString( query ); + bool query_has_uppercase_letters = any_of( query, is_upper() ); + int num_candidates = len( candidates ); std::vector< ResultAnd< int > > object_and_results; @@ -76,7 +82,8 @@ boost::python::list FilterAndSortCandidates( if ( !candidate->MatchesQueryBitset( query_bitset ) ) continue; - Result result = candidate->QueryMatchResult( query ); + Result result = candidate->QueryMatchResult( query, + query_has_uppercase_letters ); if ( result.IsSubsequence() ) { ResultAnd< int > object_and_result( i, result ); diff --git a/cpp/ycm/tests/Candidate_test.cpp b/cpp/ycm/tests/Candidate_test.cpp index d772c089..ef6c2222 100644 --- a/cpp/ycm/tests/Candidate_test.cpp +++ b/cpp/ycm/tests/Candidate_test.cpp @@ -64,45 +64,87 @@ TEST( CandidateTest, DoesntMatchQueryBitset ) { LetterBitsetFromString( "fbrmmm" ) ) ); } -TEST( CandidateTest, QueryMatchResultIsSubsequence ) { +TEST( CandidateTest, QueryMatchResultCaseInsensitiveIsSubsequence ) { Candidate candidate( "foobaaar" ); - EXPECT_TRUE( candidate.QueryMatchResult( "foobaaar" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "fobar" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "fbr" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "f" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "o" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "a" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "r" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "b" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "bar" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "oa" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "obr" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "oo" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "aaa" ).IsSubsequence() ); - EXPECT_TRUE( candidate.QueryMatchResult( "" ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "foobaaar", false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "foOBAaar", false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "FOOBAAAR", false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "fobar" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "fbr" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "f" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "F" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "o" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "O" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "a" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "r" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "b" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "bar" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "oa" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "obr" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "oar" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "oo" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "aaa" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "AAA" , false ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "" , false ).IsSubsequence() ); } -TEST( CandidateTest, QueryMatchResultIsntSubsequence ) { +TEST( CandidateTest, QueryMatchResultCaseInsensitiveIsntSubsequence ) { Candidate candidate( "foobaaar" ); - EXPECT_FALSE( candidate.QueryMatchResult( "foobra" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "frb" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "brf" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "x" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "9" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "-" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "~" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( " " ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "rabof" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "oabfr" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "ooo" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "baaara" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "ffoobaaar" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "xfoobaaar" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( " foobaaar" ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "foobaaar " ).IsSubsequence() ); - EXPECT_FALSE( candidate.QueryMatchResult( "ff" ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "foobra" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "frb" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "brf" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "x" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "9" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "-" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "~" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( " " , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "rabof" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "oabfr" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "ooo" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "baaara" , false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "ffoobaaar", false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "xfoobaaar", false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( " foobaaar", false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "foobaaar ", false ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "ff" , false ).IsSubsequence() ); +} + +TEST( CandidateTest, QueryMatchResultCaseSensitiveIsSubsequence ) { + Candidate candidate( "FooBaAAr" ); + + EXPECT_TRUE( candidate.QueryMatchResult( "FooBaAAr", true ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "FBAA" , true ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "F" , true ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "AA" , true ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "A" , true ).IsSubsequence() ); + EXPECT_TRUE( candidate.QueryMatchResult( "B" , 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( "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() ); + EXPECT_FALSE( candidate.QueryMatchResult( "fOobaaar", true ).IsSubsequence() ); + EXPECT_FALSE( candidate.QueryMatchResult( "foobaaaR", true ).IsSubsequence() ); } } // namespace YouCompleteMe diff --git a/cpp/ycm/tests/IdentifierCompleter_test.cpp b/cpp/ycm/tests/IdentifierCompleter_test.cpp index 8edcd59d..d8a519a8 100644 --- a/cpp/ycm/tests/IdentifierCompleter_test.cpp +++ b/cpp/ycm/tests/IdentifierCompleter_test.cpp @@ -64,6 +64,14 @@ TEST( IdentifierCompleterTest, ManyCandidateSimple ) { "foobartest" ) ) ); } +TEST( IdentifierCompleterTest, SmartCaseFiltering ) { + EXPECT_THAT( IdentifierCompleter( + StringVector( + "fooBar", + "fooBaR" ) ).CandidatesForQuery( "fBr" ), + ElementsAre( "fooBar" ) ); +} + TEST( IdentifierCompleterTest, FirstCharSameAsQueryWins ) { EXPECT_THAT( IdentifierCompleter( StringVector( @@ -119,13 +127,6 @@ TEST( IdentifierCompleterTest, RatioUtilizationTieBreak ) { ElementsAre( "acaaCaaFooGxx", "aCaafoog" ) ); - EXPECT_THAT( IdentifierCompleter( - StringVector( - "acaaCaaFooGxx", - "aCaafoog" ) ).CandidatesForQuery( "caaFoo" ), - ElementsAre( "acaaCaaFooGxx", - "aCaafoog" ) ); - EXPECT_THAT( IdentifierCompleter( StringVector( "FooBarQux",