summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2024-08-13 11:19:54 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2024-08-29 17:08:23 +0900
commit6a67712944bbba3bfbb04e6ad02f7ae7d55cd238 (patch)
tree90e9c6c6d4655f26b76f29c73bcc67786ea1f0f1
parente8a690928db0acf8c3eb6887fe92c58fdc3ab167 (diff)
downloadfzf-6a67712944bbba3bfbb04e6ad02f7ae7d55cd238.tar.gz
Implement exact-boundary match type
Close #3963
-rw-r--r--README.md19
-rw-r--r--src/algo/algo.go19
-rw-r--r--src/pattern.go11
3 files changed, 38 insertions, 11 deletions
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