Smart-case sensitive filtering

Fixes #120
This commit is contained in:
Strahinja Val Markovic 2013-03-01 22:18:43 -08:00
parent 831c122d06
commit a6e83bfe76
9 changed files with 150 additions and 48 deletions

View File

@ -285,6 +285,10 @@ User Guide
- If the offered completions are too broad, keep typing characters; YCM will - If the offered completions are too broad, keep typing characters; YCM will
continue refining the offered completions based on your input. 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 - 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 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 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 ### General Semantic Completion Engine Usage
- You can use Ctrl+Space to trigger the completion suggestions anywhere, even - 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. available for use.
### C-family Semantic Completion Engine Usage ### C-family Semantic Completion Engine Usage

View File

@ -43,6 +43,28 @@ std::string GetWordBoundaryChars( const std::string &text ) {
return result; 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 } // 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(); LetterNode *node = root_node_.get();
int index_sum = 0; int index_sum = 0;
@ -76,7 +99,17 @@ Result Candidate::QueryMatchResult( const std::string &query ) const {
if ( !list ) if ( !list )
return Result( false ); 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(); index_sum += node->Index();
} }

View File

@ -49,7 +49,8 @@ public:
return ( letters_present_ & query_bitset ) == query_bitset; 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: private:

View File

@ -29,6 +29,11 @@
#include <clang-c/Index.h> #include <clang-c/Index.h>
#include <boost/make_shared.hpp> #include <boost/make_shared.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/cxx11/any_of.hpp>
using boost::algorithm::any_of;
using boost::algorithm::is_upper;
using boost::bind; using boost::bind;
using boost::cref; using boost::cref;
@ -387,6 +392,7 @@ std::vector< CompletionData > ClangCompleter::SortCandidatesForQuery(
const std::string &query, const std::string &query,
const std::vector< CompletionData > &completion_datas ) { const std::vector< CompletionData > &completion_datas ) {
Bitset query_bitset = LetterBitsetFromString( query ); Bitset query_bitset = LetterBitsetFromString( query );
bool query_has_uppercase_letters = any_of( query, is_upper() );
std::vector< const Candidate * > repository_candidates = std::vector< const Candidate * > repository_candidates =
candidate_repository_.GetCandidatesForStrings( completion_datas ); candidate_repository_.GetCandidatesForStrings( completion_datas );
@ -399,7 +405,8 @@ std::vector< CompletionData > ClangCompleter::SortCandidatesForQuery(
if ( !candidate->MatchesQueryBitset( query_bitset ) ) if ( !candidate->MatchesQueryBitset( query_bitset ) )
continue; continue;
Result result = candidate->QueryMatchResult( query ); Result result = candidate->QueryMatchResult( query,
query_has_uppercase_letters );
if ( result.IsSubsequence() ) { if ( result.IsSubsequence() ) {
ResultAnd< CompletionData* > data_and_result( &completion_datas[ i ], ResultAnd< CompletionData* > data_and_result( &completion_datas[ i ],

View File

@ -26,8 +26,13 @@
#include <boost/unordered_set.hpp> #include <boost/unordered_set.hpp>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <boost/make_shared.hpp> #include <boost/make_shared.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/cxx11/any_of.hpp>
#include <algorithm> #include <algorithm>
using boost::algorithm::any_of;
using boost::algorithm::is_upper;
using boost::packaged_task; using boost::packaged_task;
using boost::unique_future; using boost::unique_future;
using boost::shared_ptr; using boost::shared_ptr;
@ -228,6 +233,7 @@ void IdentifierCompleter::ResultsForQueryAndType(
return; return;
Bitset query_bitset = LetterBitsetFromString( query ); Bitset query_bitset = LetterBitsetFromString( query );
bool query_has_uppercase_letters = any_of( query, is_upper() );
boost::unordered_set< const Candidate * > seen_candidates; boost::unordered_set< const Candidate * > seen_candidates;
seen_candidates.reserve( candidate_repository_.NumStoredCandidates() ); seen_candidates.reserve( candidate_repository_.NumStoredCandidates() );
@ -243,7 +249,8 @@ void IdentifierCompleter::ResultsForQueryAndType(
if ( !candidate->MatchesQueryBitset( query_bitset ) ) if ( !candidate->MatchesQueryBitset( query_bitset ) )
continue; continue;
Result result = candidate->QueryMatchResult( query ); Result result = candidate->QueryMatchResult(
query, query_has_uppercase_letters );
if ( result.IsSubsequence() ) if ( result.IsSubsequence() )
results.push_back( result ); results.push_back( result );

View File

@ -51,7 +51,7 @@ public:
letters_[ letter ].push_front( node ); letters_[ letter ].push_front( node );
} }
inline int Index() { inline int Index() const {
return index_; return index_;
} }

View File

@ -20,8 +20,12 @@
#include "Result.h" #include "Result.h"
#include "Candidate.h" #include "Candidate.h"
#include "CandidateRepository.h" #include "CandidateRepository.h"
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/cxx11/any_of.hpp>
#include <vector> #include <vector>
using boost::algorithm::any_of;
using boost::algorithm::is_upper;
using boost::python::len; using boost::python::len;
using boost::python::extract; using boost::python::extract;
using boost::python::object; using boost::python::object;
@ -67,6 +71,8 @@ boost::python::list FilterAndSortCandidates(
CandidatesFromObjectList( candidates, candidate_property ); CandidatesFromObjectList( candidates, candidate_property );
Bitset query_bitset = LetterBitsetFromString( query ); Bitset query_bitset = LetterBitsetFromString( query );
bool query_has_uppercase_letters = any_of( query, is_upper() );
int num_candidates = len( candidates ); int num_candidates = len( candidates );
std::vector< ResultAnd< int > > object_and_results; std::vector< ResultAnd< int > > object_and_results;
@ -76,7 +82,8 @@ boost::python::list FilterAndSortCandidates(
if ( !candidate->MatchesQueryBitset( query_bitset ) ) if ( !candidate->MatchesQueryBitset( query_bitset ) )
continue; continue;
Result result = candidate->QueryMatchResult( query ); Result result = candidate->QueryMatchResult( query,
query_has_uppercase_letters );
if ( result.IsSubsequence() ) { if ( result.IsSubsequence() ) {
ResultAnd< int > object_and_result( i, result ); ResultAnd< int > object_and_result( i, result );

View File

@ -64,45 +64,87 @@ TEST( CandidateTest, DoesntMatchQueryBitset ) {
LetterBitsetFromString( "fbrmmm" ) ) ); LetterBitsetFromString( "fbrmmm" ) ) );
} }
TEST( CandidateTest, QueryMatchResultIsSubsequence ) { TEST( CandidateTest, QueryMatchResultCaseInsensitiveIsSubsequence ) {
Candidate candidate( "foobaaar" ); Candidate candidate( "foobaaar" );
EXPECT_TRUE( candidate.QueryMatchResult( "foobaaar" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "foobaaar", false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "fobar" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "foOBAaar", false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "fbr" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "FOOBAAAR", false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "f" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "fobar" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "o" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "fbr" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "a" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "f" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "r" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "F" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "b" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "o" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "bar" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "O" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "oa" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "a" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "obr" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "r" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "oo" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "b" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "aaa" ).IsSubsequence() ); EXPECT_TRUE( candidate.QueryMatchResult( "bar" , false ).IsSubsequence() );
EXPECT_TRUE( candidate.QueryMatchResult( "" ).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" ); Candidate candidate( "foobaaar" );
EXPECT_FALSE( candidate.QueryMatchResult( "foobra" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "foobra" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "frb" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "frb" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "brf" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "brf" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "x" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "x" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "9" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "9" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "-" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "-" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "~" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "~" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( " " ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( " " , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "rabof" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "rabof" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "oabfr" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "oabfr" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "ooo" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "ooo" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "baaara" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "baaara" , false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "ffoobaaar" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "ffoobaaar", false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "xfoobaaar" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "xfoobaaar", false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( " foobaaar" ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( " foobaaar", false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "foobaaar " ).IsSubsequence() ); EXPECT_FALSE( candidate.QueryMatchResult( "foobaaar ", false ).IsSubsequence() );
EXPECT_FALSE( candidate.QueryMatchResult( "ff" ).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 } // namespace YouCompleteMe

View File

@ -64,6 +64,14 @@ TEST( IdentifierCompleterTest, ManyCandidateSimple ) {
"foobartest" ) ) ); "foobartest" ) ) );
} }
TEST( IdentifierCompleterTest, SmartCaseFiltering ) {
EXPECT_THAT( IdentifierCompleter(
StringVector(
"fooBar",
"fooBaR" ) ).CandidatesForQuery( "fBr" ),
ElementsAre( "fooBar" ) );
}
TEST( IdentifierCompleterTest, FirstCharSameAsQueryWins ) { TEST( IdentifierCompleterTest, FirstCharSameAsQueryWins ) {
EXPECT_THAT( IdentifierCompleter( EXPECT_THAT( IdentifierCompleter(
StringVector( StringVector(
@ -119,13 +127,6 @@ TEST( IdentifierCompleterTest, RatioUtilizationTieBreak ) {
ElementsAre( "acaaCaaFooGxx", ElementsAre( "acaaCaaFooGxx",
"aCaafoog" ) ); "aCaafoog" ) );
EXPECT_THAT( IdentifierCompleter(
StringVector(
"acaaCaaFooGxx",
"aCaafoog" ) ).CandidatesForQuery( "caaFoo" ),
ElementsAre( "acaaCaaFooGxx",
"aCaafoog" ) );
EXPECT_THAT( IdentifierCompleter( EXPECT_THAT( IdentifierCompleter(
StringVector( StringVector(
"FooBarQux", "FooBarQux",