summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2015-09-12 11:37:55 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2015-09-12 11:37:55 +0900
commit64443221aab288a3069d01cdaf86706c6c1d91f3 (patch)
tree0520ba323fd6be1421d35a8a94b9b5cd42a74189
parent9017e297417bc20c89e1e7c9ce47f1c2fbbfd5fc (diff)
Fix #344 - Backward scan when `--tiebreak=end`
-rw-r--r--src/algo/algo.go60
-rw-r--r--src/algo/algo_test.go56
-rw-r--r--src/core.go3
-rw-r--r--src/pattern.go18
-rw-r--r--src/pattern_test.go22
-rw-r--r--test/test_go.rb11
6 files changed, 112 insertions, 58 deletions
diff --git a/src/algo/algo.go b/src/algo/algo.go
index 03266dd9..ac7bd8b2 100644
--- a/src/algo/algo.go
+++ b/src/algo/algo.go
@@ -15,8 +15,15 @@ import (
* In short: They try to do as little work as possible.
*/
+func runeAt(runes []rune, index int, max int, forward bool) rune {
+ if forward {
+ return runes[index]
+ }
+ return runes[max-index-1]
+}
+
// FuzzyMatch performs fuzzy-match
-func FuzzyMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
+func FuzzyMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) {
if len(pattern) == 0 {
return 0, 0
}
@@ -34,7 +41,11 @@ func FuzzyMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
sidx := -1
eidx := -1
- for index, char := range runes {
+ lenRunes := len(runes)
+ lenPattern := len(pattern)
+
+ for index := range runes {
+ char := runeAt(runes, index, lenRunes, forward)
// This is considerably faster than blindly applying strings.ToLower to the
// whole string
if !caseSensitive {
@@ -47,11 +58,12 @@ func FuzzyMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
char = unicode.To(unicode.LowerCase, char)
}
}
- if char == pattern[pidx] {
+ pchar := runeAt(pattern, pidx, lenPattern, forward)
+ if char == pchar {
if sidx < 0 {
sidx = index
}
- if pidx++; pidx == len(pattern) {
+ if pidx++; pidx == lenPattern {
eidx = index + 1
break
}
@@ -61,7 +73,7 @@ func FuzzyMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
if sidx >= 0 && eidx >= 0 {
pidx--
for index := eidx - 1; index >= sidx; index-- {
- char := runes[index]
+ char := runeAt(runes, index, lenRunes, forward)
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
@@ -69,14 +81,19 @@ func FuzzyMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
char = unicode.To(unicode.LowerCase, char)
}
}
- if char == pattern[pidx] {
+
+ pchar := runeAt(pattern, pidx, lenPattern, forward)
+ if char == pchar {
if pidx--; pidx < 0 {
sidx = index
break
}
}
}
- return sidx, eidx
+ if forward {
+ return sidx, eidx
+ }
+ return lenRunes - eidx, lenRunes - sidx
}
return -1, -1
}
@@ -88,20 +105,21 @@ func FuzzyMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
//
// We might try to implement better algorithms in the future:
// http://en.wikipedia.org/wiki/String_searching_algorithm
-func ExactMatchNaive(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
+func ExactMatchNaive(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) {
if len(pattern) == 0 {
return 0, 0
}
- numRunes := len(runes)
- plen := len(pattern)
- if numRunes < plen {
+ lenRunes := len(runes)
+ lenPattern := len(pattern)
+
+ if lenRunes < lenPattern {
return -1, -1
}
pidx := 0
- for index := 0; index < numRunes; index++ {
- char := runes[index]
+ for index := 0; index < lenRunes; index++ {
+ char := runeAt(runes, index, lenRunes, forward)
if !caseSensitive {
if char >= 'A' && char <= 'Z' {
char += 32
@@ -109,10 +127,14 @@ func ExactMatchNaive(caseSensitive bool, runes []rune, pattern []rune) (int, int
char = unicode.To(unicode.LowerCase, char)
}
}
- if pattern[pidx] == char {
+ pchar := runeAt(pattern, pidx, lenPattern, forward)
+ if pchar == char {
pidx++
- if pidx == plen {
- return index - plen + 1, index + 1
+ if pidx == lenPattern {
+ if forward {
+ return index - lenPattern + 1, index + 1
+ }
+ return lenRunes - (index + 1), lenRunes - (index - lenPattern + 1)
}
} else {
index -= pidx
@@ -123,7 +145,7 @@ func ExactMatchNaive(caseSensitive bool, runes []rune, pattern []rune) (int, int
}
// PrefixMatch performs prefix-match
-func PrefixMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
+func PrefixMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) {
if len(runes) < len(pattern) {
return -1, -1
}
@@ -141,7 +163,7 @@ func PrefixMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
}
// SuffixMatch performs suffix-match
-func SuffixMatch(caseSensitive bool, input []rune, pattern []rune) (int, int) {
+func SuffixMatch(caseSensitive bool, forward bool, input []rune, pattern []rune) (int, int) {
runes := util.TrimRight(input)
trimmedLen := len(runes)
diff := trimmedLen - len(pattern)
@@ -162,7 +184,7 @@ func SuffixMatch(caseSensitive bool, input []rune, pattern []rune) (int, int) {
}
// EqualMatch performs equal-match
-func EqualMatch(caseSensitive bool, runes []rune, pattern []rune) (int, int) {
+func EqualMatch(caseSensitive bool, forward bool, runes []rune, pattern []rune) (int, int) {
if len(runes) != len(pattern) {
return -1, -1
}
diff --git a/src/algo/algo_test.go b/src/algo/algo_test.go
index db241962..95a020b7 100644
--- a/src/algo/algo_test.go
+++ b/src/algo/algo_test.go
@@ -5,11 +5,11 @@ import (
"testing"
)
-func assertMatch(t *testing.T, fun func(bool, []rune, []rune) (int, int), caseSensitive bool, input string, pattern string, sidx int, eidx int) {
+func assertMatch(t *testing.T, fun func(bool, bool, []rune, []rune) (int, int), caseSensitive bool, forward bool, input string, pattern string, sidx int, eidx int) {
if !caseSensitive {
pattern = strings.ToLower(pattern)
}
- s, e := fun(caseSensitive, []rune(input), []rune(pattern))
+ s, e := fun(caseSensitive, forward, []rune(input), []rune(pattern))
if s != sidx {
t.Errorf("Invalid start index: %d (expected: %d, %s / %s)", s, sidx, input, pattern)
}
@@ -19,33 +19,51 @@ func assertMatch(t *testing.T, fun func(bool, []rune, []rune) (int, int), caseSe
}
func TestFuzzyMatch(t *testing.T) {
- assertMatch(t, FuzzyMatch, false, "fooBarbaz", "oBZ", 2, 9)
- assertMatch(t, FuzzyMatch, true, "fooBarbaz", "oBZ", -1, -1)
- assertMatch(t, FuzzyMatch, true, "fooBarbaz", "oBz", 2, 9)
- assertMatch(t, FuzzyMatch, true, "fooBarbaz", "fooBarbazz", -1, -1)
+ assertMatch(t, FuzzyMatch, false, true, "fooBarbaz", "oBZ", 2, 9)
+ assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "oBZ", -1, -1)
+ assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "oBz", 2, 9)
+ assertMatch(t, FuzzyMatch, true, true, "fooBarbaz", "fooBarbazz", -1, -1)
+}
+
+func TestFuzzyMatchBackward(t *testing.T) {
+ assertMatch(t, FuzzyMatch, false, true, "foobar fb", "fb", 0, 4)
+ assertMatch(t, FuzzyMatch, false, false, "foobar fb", "fb", 7, 9)
}
func TestExactMatchNaive(t *testing.T) {
- assertMatch(t, ExactMatchNaive, false, "fooBarbaz", "oBA", 2, 5)
- assertMatch(t, ExactMatchNaive, true, "fooBarbaz", "oBA", -1, -1)
- assertMatch(t, ExactMatchNaive, true, "fooBarbaz", "fooBarbazz", -1, -1)
+ for _, dir := range []bool{true, false} {
+ assertMatch(t, ExactMatchNaive, false, dir, "fooBarbaz", "oBA", 2, 5)
+ assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "oBA", -1, -1)
+ assertMatch(t, ExactMatchNaive, true, dir, "fooBarbaz", "fooBarbazz", -1, -1)
+ }
+}
+
+func TestExactMatchNaiveBackward(t *testing.T) {
+ assertMatch(t, FuzzyMatch, false, true, "foobar foob", "oo", 1, 3)
+ assertMatch(t, FuzzyMatch, false, false, "foobar foob", "oo", 8, 10)
}
func TestPrefixMatch(t *testing.T) {
- assertMatch(t, PrefixMatch, false, "fooBarbaz", "Foo", 0, 3)
- assertMatch(t, PrefixMatch, true, "fooBarbaz", "Foo", -1, -1)
- assertMatch(t, PrefixMatch, false, "fooBarbaz", "baz", -1, -1)
+ for _, dir := range []bool{true, false} {
+ assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "Foo", 0, 3)
+ assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1)
+ assertMatch(t, PrefixMatch, false, dir, "fooBarbaz", "baz", -1, -1)
+ }
}
func TestSuffixMatch(t *testing.T) {
- assertMatch(t, SuffixMatch, false, "fooBarbaz", "Foo", -1, -1)
- assertMatch(t, SuffixMatch, false, "fooBarbaz", "baz", 6, 9)
- assertMatch(t, SuffixMatch, true, "fooBarbaz", "Baz", -1, -1)
+ for _, dir := range []bool{true, false} {
+ assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "Foo", -1, -1)
+ assertMatch(t, SuffixMatch, false, dir, "fooBarbaz", "baz", 6, 9)
+ assertMatch(t, SuffixMatch, true, dir, "fooBarbaz", "Baz", -1, -1)
+ }
}
func TestEmptyPattern(t *testing.T) {
- assertMatch(t, FuzzyMatch, true, "foobar", "", 0, 0)
- assertMatch(t, ExactMatchNaive, true, "foobar", "", 0, 0)
- assertMatch(t, PrefixMatch, true, "foobar", "", 0, 0)
- assertMatch(t, SuffixMatch, true, "foobar", "", 6, 6)
+ for _, dir := range []bool{true, false} {
+ assertMatch(t, FuzzyMatch, true, dir, "foobar", "", 0, 0)
+ assertMatch(t, ExactMatchNaive, true, dir, "foobar", "", 0, 0)
+ assertMatch(t, PrefixMatch, true, dir, "foobar", "", 0, 0)
+ assertMatch(t, SuffixMatch, true, dir, "foobar", "", 6, 6)
+ }
}
diff --git a/src/core.go b/src/core.go
index 4f072151..96bfdd4c 100644
--- a/src/core.go
+++ b/src/core.go
@@ -143,7 +143,8 @@ func Run(opts *Options) {
// Matcher
patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(
- opts.Mode, opts.Case, opts.Nth, opts.Delimiter, runes)
+ opts.Mode, opts.Case, opts.Tiebreak != byEnd,
+ opts.Nth, opts.Delimiter, runes)
}
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
diff --git a/src/pattern.go b/src/pattern.go
index cfeb68dc..5466b862 100644
--- a/src/pattern.go
+++ b/src/pattern.go
@@ -39,12 +39,13 @@ type term struct {
type Pattern struct {
mode Mode
caseSensitive bool
+ forward bool
text []rune
terms []term
hasInvTerm bool
delimiter Delimiter
nth []Range
- procFun map[termType]func(bool, []rune, []rune) (int, int)
+ procFun map[termType]func(bool, bool, []rune, []rune) (int, int)
}
var (
@@ -70,7 +71,7 @@ func clearChunkCache() {
}
// BuildPattern builds Pattern object from the given arguments
-func BuildPattern(mode Mode, caseMode Case,
+func BuildPattern(mode Mode, caseMode Case, forward bool,
nth []Range, delimiter Delimiter, runes []rune) *Pattern {
var asString string
@@ -109,12 +110,13 @@ func BuildPattern(mode Mode, caseMode Case,
ptr := &Pattern{
mode: mode,
caseSensitive: caseSensitive,
+ forward: forward,
text: []rune(asString),
terms: terms,
hasInvTerm: hasInvTerm,
nth: nth,
delimiter: delimiter,
- procFun: make(map[termType]func(bool, []rune, []rune) (int, int))}
+ procFun: make(map[termType]func(bool, bool, []rune, []rune) (int, int))}
ptr.procFun[termFuzzy] = algo.FuzzyMatch
ptr.procFun[termEqual] = algo.EqualMatch
@@ -288,7 +290,7 @@ func dupItem(item *Item, offsets []Offset) *Item {
func (p *Pattern) fuzzyMatch(item *Item) (int, int) {
input := p.prepareInput(item)
- return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.text)
+ return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.forward, p.text)
}
func (p *Pattern) extendedMatch(item *Item) []Offset {
@@ -296,7 +298,7 @@ func (p *Pattern) extendedMatch(item *Item) []Offset {
offsets := []Offset{}
for _, term := range p.terms {
pfun := p.procFun[term.typ]
- if sidx, eidx := p.iter(pfun, input, term.caseSensitive, term.text); sidx >= 0 {
+ if sidx, eidx := p.iter(pfun, input, term.caseSensitive, p.forward, term.text); sidx >= 0 {
if term.inv {
break
}
@@ -324,11 +326,11 @@ func (p *Pattern) prepareInput(item *Item) []Token {
return ret
}
-func (p *Pattern) iter(pfun func(bool, []rune, []rune) (int, int),
- tokens []Token, caseSensitive bool, pattern []rune) (int, int) {
+func (p *Pattern) iter(pfun func(bool, bool, []rune, []rune) (int, int),
+ tokens []Token, caseSensitive bool, forward bool, pattern []rune) (int, int) {
for _, part := range tokens {
prefixLength := part.prefixLength
- if sidx, eidx := pfun(caseSensitive, part.text, pattern); sidx >= 0 {
+ if sidx, eidx := pfun(caseSensitive, forward, part.text, pattern); sidx >= 0 {
return sidx + prefixLength, eidx + prefixLength
}
}
diff --git a/src/pattern_test.go b/src/pattern_test.go
index 66f5d41d..d5086128 100644
--- a/src/pattern_test.go
+++ b/src/pattern_test.go
@@ -58,10 +58,10 @@ func TestParseTermsEmpty(t *testing.T) {
func TestExact(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
- pattern := BuildPattern(ModeExtended, CaseSmart,
+ pattern := BuildPattern(ModeExtended, CaseSmart, true,
[]Range{}, Delimiter{}, []rune("'abc"))
sidx, eidx := algo.ExactMatchNaive(
- pattern.caseSensitive, []rune("aabbcc abc"), pattern.terms[0].text)
+ pattern.caseSensitive, pattern.forward, []rune("aabbcc abc"), pattern.terms[0].text)
if sidx != 7 || eidx != 10 {
t.Errorf("%s / %d / %d", pattern.terms, sidx, eidx)
}
@@ -70,11 +70,11 @@ func TestExact(t *testing.T) {
func TestEqual(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
- pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, Delimiter{}, []rune("^AbC$"))
+ pattern := BuildPattern(ModeExtended, CaseSmart, true, []Range{}, Delimiter{}, []rune("^AbC$"))
match := func(str string, sidxExpected int, eidxExpected int) {
sidx, eidx := algo.EqualMatch(
- pattern.caseSensitive, []rune(str), pattern.terms[0].text)
+ pattern.caseSensitive, pattern.forward, []rune(str), pattern.terms[0].text)
if sidx != sidxExpected || eidx != eidxExpected {
t.Errorf("%s / %d / %d", pattern.terms, sidx, eidx)
}
@@ -86,17 +86,17 @@ func TestEqual(t *testing.T) {
func TestCaseSensitivity(t *testing.T) {
defer clearPatternCache()
clearPatternCache()
- pat1 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, Delimiter{}, []rune("abc"))
+ pat1 := BuildPattern(ModeFuzzy, CaseSmart, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
- pat2 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, Delimiter{}, []rune("Abc"))
+ pat2 := BuildPattern(ModeFuzzy, CaseSmart, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
- pat3 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, Delimiter{}, []rune("abc"))
+ pat3 := BuildPattern(ModeFuzzy, CaseIgnore, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
- pat4 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, Delimiter{}, []rune("Abc"))
+ pat4 := BuildPattern(ModeFuzzy, CaseIgnore, true, []Range{}, Delimiter{}, []rune("Abc"))
clearPatternCache()
- pat5 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, Delimiter{}, []rune("abc"))
+ pat5 := BuildPattern(ModeFuzzy, CaseRespect, true, []Range{}, Delimiter{}, []rune("abc"))
clearPatternCache()
- pat6 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, Delimiter{}, []rune("Abc"))
+ pat6 := BuildPattern(ModeFuzzy, CaseRespect, true, []Range{}, Delimiter{}, []rune("Abc"))
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
@@ -109,7 +109,7 @@ func TestCaseSensitivity(t *testing.T) {
}
func TestOrigTextAndTransformed(t *testing.T) {
- pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, Delimiter{}, []rune("jg"))
+ pattern := BuildPattern(ModeExtended, CaseSmart, true, []Range{}, Delimiter{}, []rune("jg"))
tokens := Tokenize([]rune("junegunn"), Delimiter{})
trans := Transform(tokens, []Range{Range{1, 1}})
diff --git a/test/test_go.rb b/test/test_go.rb
index 377af229..d1f45dc1 100644
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -527,6 +527,17 @@ class TestGoFZF < TestBase
assert_equal output, `cat #{tempname} | #{FZF} -fh -n2 -d:`.split($/)
end
+ def test_tiebreak_end_backward_scan
+ input = %w[
+ foobar-fb
+ fubar
+ ]
+ writelines tempname, input
+
+ assert_equal input.reverse, `cat #{tempname} | #{FZF} -f fb`.split($/)
+ assert_equal input, `cat #{tempname} | #{FZF} -f fb --tiebreak=end`.split($/)
+ end
+
def test_invalid_cache
tmux.send_keys "(echo d; echo D; echo x) | #{fzf '-q d'}", :Enter
tmux.until { |lines| lines[-2].include? '2/3' }