Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added sublime fuzzy search behavior. #12

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fuzzy-min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 70 additions & 19 deletions lib/fuzzy.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,76 @@ fuzzy.match = function(pattern, string, opts) {

pattern = opts.caseSensitive && pattern || pattern.toLowerCase();

// For each character in the string, either add it to the result
// or wrap in template if it's the next string in the pattern
for(var idx = 0; idx < len; idx++) {
ch = string[idx];
if(compareString[idx] === pattern[patternIdx]) {
ch = pre + ch + post;
patternIdx += 1;

// consecutive characters should increase the score more than linearly
currScore += 1 + currScore;
} else {
currScore = 0;
}
totalScore += currScore;
result[result.length] = ch;
var patternCache = fuzzy.traverse(compareString, pattern, 0, 0, []);
if(!patternCache) {
return null;
}

return {rendered: fuzzy.render(string, patternCache.cache, pre, post), score: patternCache.score};
};

fuzzy.traverse = function(string, pattern, stringIndex, patternIndex, patternCache) {

// if the pattern search at end
if(pattern.length === patternIndex) {

// calculate score and copy the cache containing the indices where it's found
return {score : fuzzy.calculateScore(patternCache), cache : patternCache.slice()};
}

// return rendered string if we have a match for every char
if(patternIdx === pattern.length) {
return {rendered: result.join(''), score: totalScore};
// if string at end or remaining pattern > remaining string
if(string.length === stringIndex || pattern.length - patternIndex > string.length - stringIndex) {
return null;
}

return null;
var c = pattern[patternIndex];
var index = string.indexOf(c, stringIndex);
var best, temp;

while(index > -1) {
patternCache.push(index);
temp = fuzzy.traverse(string, pattern, index+1, patternIndex+1, patternCache);
patternCache.pop();

// if downstream traversal failed, return best answer so far
if(!temp) {
return best;
}

if(!best || best.score < temp.score) {
best = temp;
}

index = string.indexOf(c, index+1);
}

return best;
};

fuzzy.calculateScore = function(patternCache) {
var score = 0;
var temp = 1;
patternCache.forEach(function(index, i) {
if(i > 0) {
if(patternCache[i-1] + 1 === index) {
temp += temp + 1;
} else {
temp = 1;
}
}

score += temp;
});
return score;
};

fuzzy.render = function(string, indices, pre, post) {
var rendered = string.substring(0, indices[0]);
indices.forEach(function(index, i) {
rendered += pre + string[index] + post +
string.substring(index + 1, (indices[i+1]) ? indices[i+1] : string.length);
});
return rendered;
};

// The normal entry point. Filters `arr` for matches against `pattern`.
Expand Down Expand Up @@ -108,6 +155,10 @@ fuzzy.filter = function(pattern, arr, opts) {
var str = element;
if(opts.extract) {
str = opts.extract(element);

if(!str) { // take care of undefineds / nulls / etc.
str = '';
}
}
var rendered = fuzzy.match(pattern, str, opts);
if(rendered != null) {
Expand Down
4 changes: 2 additions & 2 deletions test/fuzzy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ describe('fuzzy', function(){
// appear toward the beginning of the string a bit higher
});
// TODO: implement this test
xit('should prefer consecutive characters even if they come after the first match', function(){
it('should prefer consecutive characters even if they come after the first match', function(){
var opts = {pre: '<', post: '>'};
var result = fuzzy.match('bass', 'bodacious bass', opts).rendered;
expect(result).to.equal('bodacious <b><a><s><s>');
});
xit('should prefer consecutive characters in a match even if we need to break up into a substring', function(){
it('should prefer consecutive characters in a match even if we need to break up into a substring', function(){
var opts = {pre: '<', post: '>'};
var result = fuzzy.match('reic', 'reactive rice', opts).rendered;
expect(result).to.equal('<r><e>active r<i><c>e');
Expand Down