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
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

View File

@ -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();
}

View File

@ -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:

View File

@ -29,6 +29,11 @@
#include <clang-c/Index.h>
#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::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 ],

View File

@ -26,8 +26,13 @@
#include <boost/unordered_set.hpp>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/cxx11/any_of.hpp>
#include <algorithm>
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 );

View File

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

View File

@ -20,8 +20,12 @@
#include "Result.h"
#include "Candidate.h"
#include "CandidateRepository.h"
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/cxx11/any_of.hpp>
#include <vector>
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 );

View File

@ -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

View File

@ -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",