diff --git a/README.md b/README.md index 1ee10581..950cf40a 100644 --- a/README.md +++ b/README.md @@ -376,15 +376,16 @@ Unless otherwise specified, fzf starts in "extended-search mode" where you can type in multiple search terms delimited by spaces. e.g. `^music .mp3$ sbtrkt !fire` -| Token | Match type | Description | -| --------- | -------------------------- | ------------------------------------ | -| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` | -| `'wild` | exact-match (quoted) | Items that include `wild` | -| `^music` | prefix-exact-match | Items that start with `music` | -| `.mp3$` | suffix-exact-match | Items that end with `.mp3` | -| `!fire` | inverse-exact-match | Items that do not include `fire` | -| `!^music` | inverse-prefix-exact-match | Items that do not start with `music` | -| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` | +| Token | Match type | Description | +| --------- | -------------------------------------- | ------------------------------------------ | +| `sbtrkt` | fuzzy-match | Items that match `sbtrkt` | +| `'wild` | exact-match (quoted) | Items that include `wild` | +| `'wild'` | exact-boundary-match (quoted both ends) | Items that include `wild` at word boundaries | +| `^music` | prefix-exact-match | Items that start with `music` | +| `.mp3$` | suffix-exact-match | Items that end with `.mp3` | +| `!fire` | inverse-exact-match | Items that do not include `fire` | +| `!^music` | inverse-prefix-exact-match | Items that do not start with `music` | +| `!.mp3$` | inverse-suffix-exact-match | Items that do not end with `.mp3` | If you don't prefer fuzzy matching and do not wish to "quote" every word, start fzf with `-e` or `--exact` option. Note that when `--exact` is set, diff --git a/src/algo/algo.go b/src/algo/algo.go index c85ec82e..a02a0a81 100644 --- a/src/algo/algo.go +++ b/src/algo/algo.go @@ -798,6 +798,14 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C // The solution is much cheaper since there is only one possible alignment of // the pattern. func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) { + return exactMatchNaive(caseSensitive, normalize, forward, false, text, pattern, withPos, slab) +} + +func ExactMatchBoundary(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) { + return exactMatchNaive(caseSensitive, normalize, forward, true, text, pattern, withPos, slab) +} + +func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryCheck bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) { if len(pattern) == 0 { return Result{0, 0, 0}, nil } @@ -832,10 +840,19 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti } pidx_ := indexAt(pidx, lenPattern, forward) pchar := pattern[pidx_] - if pchar == char { + ok := pchar == char + if ok { if pidx_ == 0 { bonus = bonusAt(text, index_) } + if boundaryCheck { + ok = bonus >= bonusBoundary + if ok && pidx_ == len(pattern)-1 { + ok = index_ == lenRunes-1 || charClassOf(text.Get(index_+1)) <= charDelimiter + } + } + } + if ok { pidx++ if pidx == lenPattern { if bonus > bestBonus { diff --git a/src/pattern.go b/src/pattern.go index ee1b88a5..c736be3c 100644 --- a/src/pattern.go +++ b/src/pattern.go @@ -23,6 +23,7 @@ type termType int const ( termFuzzy termType = iota termExact + termExactBoundary termPrefix termSuffix termEqual @@ -147,6 +148,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo ptr.procFun[termFuzzy] = fuzzyAlgo ptr.procFun[termEqual] = algo.EqualMatch ptr.procFun[termExact] = algo.ExactMatchNaive + ptr.procFun[termExactBoundary] = algo.ExactMatchBoundary ptr.procFun[termPrefix] = algo.PrefixMatch ptr.procFun[termSuffix] = algo.SuffixMatch @@ -193,7 +195,14 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet text = text[:len(text)-1] } - if strings.HasPrefix(text, "'") { + if fuzzy && len(text) > 2 && strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") || + !fuzzy && !strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") { + typ = termExactBoundary + if fuzzy { + text = text[1:] + } + text = text[:len(text)-1] + } else if strings.HasPrefix(text, "'") { // Flip exactness if fuzzy && !inv { typ = termExact