diff --git a/spec/fuzzy-native-spec.js b/spec/fuzzy-native-spec.js index fe33404..c4290d1 100644 --- a/spec/fuzzy-native-spec.js +++ b/spec/fuzzy-native-spec.js @@ -265,5 +265,79 @@ describe('fuzzy-native', function() { expect(matcher.match('ac').length).toBe(2); matcher.setCandidates([0, 0], ['abc', 'abc']); expect(matcher.match('ac').length).toBe(1); - }) + }); + + it('returns matches when using different path separators', () => { + expect( + values(matcher.match('path1_path2_path3_zzz', {caseSensitive: true})) + ).toEqual([ + '/path1/path2/path3/zzz', + ]); + expect( + values(matcher.match('path1_path2_path3_zzz', {caseSensitive: false})) + ).toEqual([ + '/path1/path2/path3/zzz', + ]); + + expect( + values(matcher.match('\\path1\\path2\\path3\\zzz', {caseSensitive: true})) + ).toEqual([ + '/path1/path2/path3/zzz', + ]); + expect( + values(matcher.match('\\path1\\path2\\path3\\zzz', {caseSensitive: false})) + ).toEqual([ + '/path1/path2/path3/zzz', + ]); + }); + + it('returns exact matches than normalized path separator matches', () => { + matcher.setCandidates( + [0, 1, 2], + ['/path1/path2/path3/zzz', '/path1/path2/path3/zzz_ooo', '/path1/path2/path3/zzz/ooo'] + ); + + expect( + values(matcher.match('path1/path2/path3/zzz', {caseSensitive: true})) + ).toEqual([ + '/path1/path2/path3/zzz', + '/path1/path2/path3/zzz/ooo', + '/path1/path2/path3/zzz_ooo' + ]); + expect( + values(matcher.match('zzz_ooo', {caseSensitive: true})) + ).toEqual([ + '/path1/path2/path3/zzz_ooo', + '/path1/path2/path3/zzz/ooo' + ]); + expect( + values(matcher.match('path1/path2/path3/zzz_ooo', {caseSensitive: true})) + ).toEqual([ + '/path1/path2/path3/zzz_ooo', + '/path1/path2/path3/zzz/ooo' + ]); + expect( + values(matcher.match('path1/path2/path3/zzz_ooo', {caseSensitive: true})) + ).toEqual([ + '/path1/path2/path3/zzz_ooo', + '/path1/path2/path3/zzz/ooo' + ]); + expect( + values(matcher.match('path1_path2_path3_zzz_ooo', {caseSensitive: true})) + ).toEqual([ + '/path1/path2/path3/zzz_ooo', + '/path1/path2/path3/zzz/ooo' + ]); + expect( + values(matcher.match('path1/path2_path3/zzz_ooo', {caseSensitive: false})) + ).toEqual([ + '/path1/path2/path3/zzz_ooo', + '/path1/path2/path3/zzz/ooo' + ]); + expect( + values(matcher.match('zzz/ooo', {caseSensitive: false})) + ).toEqual([ + '/path1/path2/path3/zzz/ooo' + ]); + }); }); diff --git a/src/MatcherBase.cpp b/src/MatcherBase.cpp index 8e54c95..dd30022 100644 --- a/src/MatcherBase.cpp +++ b/src/MatcherBase.cpp @@ -24,8 +24,6 @@ inline uint64_t letter_bitmask(const std::string &str) { result |= count_bit << (index * 2); } else if (c == '-') { result |= (1UL << 52); - } else if (c == '_') { - result |= (1UL << 53); } else if (c >= '0' && c <= '9') { result |= (1UL << (c - '0' + 54)); } diff --git a/src/score_match.cpp b/src/score_match.cpp index 396b872..de43be6 100644 --- a/src/score_match.cpp +++ b/src/score_match.cpp @@ -93,10 +93,12 @@ float recursive_match(const MatchInfo &m, size_t last_slash = 0; for (size_t j = haystack_idx; j <= lim; j++) { char d = m.haystack_case[j]; - if (needle_idx == 0 && (d == '/' || d == '\\')) { + bool is_path_sep = d == '/' || d == '\\'; + + if (needle_idx == 0 && is_path_sep) { last_slash = j; } - if (c == d) { + if (c == d || (is_path_sep && (c == '_' || c == '\\'))) { // calculate score float char_score = 1.0; if (j > haystack_idx) { @@ -123,8 +125,12 @@ float recursive_match(const MatchInfo &m, } // Apply a severe penalty if the case doesn't match. - // This should always hoist the exact case matches above all others. - if (m.smart_case && m.needle[needle_idx] != m.haystack[j]) { + // This will make the exact matches have higher score than the case + // insensitive and the path insensitive matches. + if ( + (m.smart_case || m.haystack[j] == '/') && + m.needle[needle_idx] != m.haystack[j] + ) { char_score *= 0.001; } @@ -200,7 +206,15 @@ float score_match(const char *haystack, for (int i = m.needle_len - 1; i >= 0; i--) { char* ptr = (char*)memrchr(m.haystack_case, m.needle_case[i], hindex); if (ptr == nullptr) { - return 0; + // Since we treat _ and \\ as path separators, we need to re-check if path + // separator exists on the string in case they exact chars are not found. + if (m.needle_case[i] == '_' || m.needle_case[i] == '\\') { + ptr = (char*)memrchr(m.haystack_case, '/', hindex); + } + + if (ptr == nullptr) { + return 0; + } } hindex = ptr - m.haystack_case; last_match[i] = hindex;