Projects STRLCPY fzf Commits 6fb41a20
🤬
  • ■ ■ ■ ■ ■ ■
    CHANGELOG.md
    1 1  CHANGELOG
    2 2  =========
    3 3   
    4  -0.32.2
     4 +0.33.0
    5 5  ------
     6 +- Added `--scheme=[default|path|history]` option to choose scoring scheme
     7 + - (Experimental)
     8 + - We updated the scoring algorithm in 0.32.0, however we have learned that
     9 + this new scheme (`default`) is not always giving the optimal result
     10 + - `path`: Additional bonus point is only given the the characters after
     11 + path separator. You might want to choose this scheme if you have many
     12 + files with spaces in their paths.
     13 + - `history`: No additional bonus points are given so that we give more
     14 + weight to the chronological ordering. This is equivalent to the scoring
     15 + scheme before 0.32.0. This also sets `--tiebreak=index`.
    6 16  - ANSI color sequences with colon delimiters are now supported.
    7 17   ```sh
    8 18   printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
    skipped 1297 lines
  • ■ ■ ■ ■
    man/man1/fzf-tmux.1
    skipped 20 lines
    21 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    22 22  THE SOFTWARE.
    23 23  ..
    24  -.TH fzf-tmux 1 "Aug 2022" "fzf 0.32.1" "fzf-tmux - open fzf in tmux split pane"
     24 +.TH fzf-tmux 1 "Aug 2022" "fzf 0.33.0" "fzf-tmux - open fzf in tmux split pane"
    25 25   
    26 26  .SH NAME
    27 27  fzf-tmux - open fzf in tmux split pane
    skipped 42 lines
  • ■ ■ ■ ■ ■ ■
    man/man1/fzf.1
    skipped 20 lines
    21 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    22 22  THE SOFTWARE.
    23 23  ..
    24  -.TH fzf 1 "Aug 2022" "fzf 0.32.2" "fzf - a command-line fuzzy finder"
     24 +.TH fzf 1 "Aug 2022" "fzf 0.33.0" "fzf - a command-line fuzzy finder"
    25 25   
    26 26  .SH NAME
    27 27  fzf - a command-line fuzzy finder
    skipped 22 lines
    50 50  .TP
    51 51  .B "--literal"
    52 52  Do not normalize latin script letters for matching.
     53 +.TP
     54 +.BI "--scheme=" SCHEME
     55 +Choose scoring scheme tailored for different types of input.
     56 + 
     57 +.br
     58 +.BR default " Generic scoring scheme designed to work well with any type of input"
     59 +.br
     60 +.BR path " Scoring scheme for paths (additional bonus point only after path separator)
     61 +.br
     62 +.BR history " Scoring scheme for command history (no additional bonus points).
     63 + Sets \fB--tiebreak=index\fR as well.
     64 +.br
    53 65  .TP
    54 66  .BI "--algo=" TYPE
    55 67  Fuzzy matching algorithm (default: v2)
    skipped 980 lines
  • ■ ■ ■ ■
    shell/key-bindings.bash
    skipped 49 lines
    50 50   
    51 51  __fzf_history__() {
    52 52   local output opts script
    53  - opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0"
     53 + opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0"
    54 54   script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
    55 55   output=$(
    56 56   builtin fc -lnr -2147483648 |
    skipped 47 lines
  • ■ ■ ■ ■
    shell/key-bindings.fish
    skipped 52 lines
    53 53   function fzf-history-widget -d "Show command history"
    54 54   test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
    55 55   begin
    56  - set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
     56 + set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
    57 57   
    58 58   set -l FISH_MAJOR (echo $version | cut -f1 -d.)
    59 59   set -l FISH_MINOR (echo $version | cut -f2 -d.)
    skipped 114 lines
  • ■ ■ ■ ■
    shell/key-bindings.zsh
    skipped 97 lines
    98 98   local selected num
    99 99   setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
    100 100   selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
    101  - FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
     101 + FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
    102 102   local ret=$?
    103 103   if [ -n "$selected" ]; then
    104 104   num=$selected[1]
    skipped 17 lines
  • ■ ■ ■ ■ ■
    src/algo/algo.go
    skipped 79 lines
    80 80  import (
    81 81   "bytes"
    82 82   "fmt"
     83 + "os"
    83 84   "strings"
    84 85   "unicode"
    85 86   "unicode/utf8"
    skipped 3 lines
    89 90   
    90 91  var DEBUG bool
    91 92   
    92  -const delimiterChars = "/,:;|"
     93 +var delimiterChars = "/,:;|"
     94 + 
    93 95  const whiteChars = " \t\n\v\f\r\x85\xA0"
    94 96   
    95 97  func indexAt(index int, max int, forward bool) int {
    skipped 24 lines
    120 122   // in web2 dictionary and my file system.
    121 123   bonusBoundary = scoreMatch / 2
    122 124   
    123  - // Extra bonus for word boundary after whitespace character or beginning of the string
    124  - bonusBoundaryWhite = bonusBoundary + 2
    125  - 
    126  - // Extra bonus for word boundary after slash, colon, semi-colon, and comma
    127  - bonusBoundaryDelimiter = bonusBoundary + 1
    128  - 
    129 125   // Although bonus point for non-word characters is non-contextual, we need it
    130 126   // for computing bonus points for consecutive chunks starting with a non-word
    131 127   // character.
    skipped 17 lines
    149 145   bonusFirstCharMultiplier = 2
    150 146  )
    151 147   
     148 +var (
     149 + // Extra bonus for word boundary after whitespace character or beginning of the string
     150 + bonusBoundaryWhite int16 = bonusBoundary + 2
     151 + 
     152 + // Extra bonus for word boundary after slash, colon, semi-colon, and comma
     153 + bonusBoundaryDelimiter int16 = bonusBoundary + 1
     154 + 
     155 + initialCharClass charClass = charWhite
     156 +)
     157 + 
    152 158  type charClass int
    153 159   
    154 160  const (
    skipped 5 lines
    160 166   charLetter
    161 167   charNumber
    162 168  )
     169 + 
     170 +func Init(scheme string) bool {
     171 + switch scheme {
     172 + case "default":
     173 + bonusBoundaryWhite = bonusBoundary + 2
     174 + bonusBoundaryDelimiter = bonusBoundary + 1
     175 + case "path":
     176 + bonusBoundaryWhite = bonusBoundary
     177 + bonusBoundaryDelimiter = bonusBoundary + 1
     178 + if os.PathSeparator == '/' {
     179 + delimiterChars = "/"
     180 + } else {
     181 + delimiterChars = string([]rune{os.PathSeparator, '/'})
     182 + }
     183 + initialCharClass = charDelimiter
     184 + case "history":
     185 + bonusBoundaryWhite = bonusBoundary
     186 + bonusBoundaryDelimiter = bonusBoundary
     187 + default:
     188 + return false
     189 + }
     190 + return true
     191 +}
    163 192   
    164 193  func posArray(withPos bool, len int) *[]int {
    165 194   if withPos {
    skipped 241 lines
    407 436   // Phase 2. Calculate bonus for each point
    408 437   maxScore, maxScorePos := int16(0), 0
    409 438   pidx, lastIdx := 0, 0
    410  - pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charWhite, false
     439 + pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
    411 440   Tsub := T[idx:]
    412 441   H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
    413 442   for off, char := range Tsub {
    skipped 496 lines
    910 939   match = runesStr == string(pattern)
    911 940   }
    912 941   if match {
    913  - return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundaryWhite)*lenPattern +
    914  - (bonusFirstCharMultiplier-1)*bonusBoundaryWhite}, nil
     942 + return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
     943 + (bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
    915 944   }
    916 945   return Result{-1, -1, 0}, nil
    917 946  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    src/algo/algo_test.go
    skipped 44 lines
    45 45   assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
    46 46   scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
    47 47   assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
    48  - scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
    49  - bonusBoundaryWhite*2+2*scoreGapStart+4*scoreGapExtension)
     48 + scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
     49 + int(bonusBoundaryWhite)*2+2*scoreGapStart+4*scoreGapExtension)
    50 50   assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
    51 51   scoreMatch*4+bonusCamel123+bonusConsecutive*2)
    52 52   assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
    53  - scoreMatch*4+bonusBoundaryDelimiter*bonusFirstCharMultiplier+bonusBoundaryDelimiter*3)
     53 + scoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3)
    54 54   assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
    55  - scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+bonusBoundaryDelimiter)
     55 + scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter))
    56 56   assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
    57 57   scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
    58 58   assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
    59 59   scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
    60 60   assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
    61  - scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
    62  - bonusBoundaryDelimiter*2+2*scoreGapStart+4*scoreGapExtension)
     61 + scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
     62 + int(bonusBoundaryDelimiter)*2+2*scoreGapStart+4*scoreGapExtension)
    63 63   assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
    64  - scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
     64 + scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
    65 65   bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
    66 66   assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
    67  - scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite+
     67 + scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)+
    68 68   scoreGapStart*2+scoreGapExtension*3)
    69 69   assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
    70  - scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*3)
     70 + scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*3)
    71 71   assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
    72 72   scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
    73 73   bonusNonWord+bonusBoundary)
    skipped 1 lines
    75 75   assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
    76 76   scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
    77 77   assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
    78  - scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryDelimiter*2+
     78 + scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*2+
    79 79   scoreGapStart*2+scoreGapExtension*4)
    80 80   assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
    81  - scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusCamel123*2+
     81 + scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+bonusCamel123*2+
    82 82   scoreGapStart*2+scoreGapExtension*2)
    83 83   assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
    84  - scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*2+
    85  - util.Max(bonusCamel123, bonusBoundaryWhite))
     84 + scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
     85 + util.Max(bonusCamel123, int(bonusBoundaryWhite)))
    86 86   
    87 87   // Consecutive bonus updated
    88 88   assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
    skipped 9 lines
    98 98   
    99 99  func TestFuzzyMatchBackward(t *testing.T) {
    100 100   assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
    101  - scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+
     101 + scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
    102 102   scoreGapStart+scoreGapExtension)
    103 103   assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
    104  - scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite)
     104 + scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite))
    105 105  }
    106 106   
    107 107  func TestExactMatchNaive(t *testing.T) {
    skipped 6 lines
    114 114   assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
    115 115   scoreMatch*4+bonusCamel123+bonusConsecutive*2)
    116 116   assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
    117  - scoreMatch*4+bonusBoundaryDelimiter*(bonusFirstCharMultiplier+3))
     117 + scoreMatch*4+int(bonusBoundaryDelimiter)*(bonusFirstCharMultiplier+3))
    118 118   assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
    119  - scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+bonusBoundaryDelimiter)
     119 + scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+int(bonusBoundaryDelimiter))
    120 120   }
    121 121  }
    122 122   
    skipped 5 lines
    128 128  }
    129 129   
    130 130  func TestPrefixMatch(t *testing.T) {
    131  - score := scoreMatch*3 + bonusBoundaryWhite*bonusFirstCharMultiplier + bonusBoundaryWhite*2
     131 + score := scoreMatch*3 + int(bonusBoundaryWhite)*bonusFirstCharMultiplier + int(bonusBoundaryWhite)*2
    132 132   
    133 133   for _, dir := range []bool{true, false} {
    134 134   assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
    skipped 24 lines
    159 159   
    160 160   // Only when the pattern doesn't end with a space
    161 161   assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
    162  - scoreMatch*4+bonusConsecutive*2+bonusBoundaryWhite)
     162 + scoreMatch*4+bonusConsecutive*2+int(bonusBoundaryWhite))
    163 163   }
    164 164  }
    165 165   
    skipped 34 lines
  • ■ ■ ■ ■ ■ ■
    src/options.go
    skipped 20 lines
    21 21   -x, --extended Extended-search mode
    22 22   (enabled by default; +x or --no-extended to disable)
    23 23   -e, --exact Enable Exact-match
    24  - --algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
    25 24   -i Case-insensitive match (default: smart-case match)
    26 25   +i Case-sensitive match
     26 + --scheme=SCHEME Scoring scheme [default|path|history]
    27 27   --literal Do not normalize latin script letters before matching
    28 28   -n, --nth=N[,..] Comma-separated list of field index expressions
    29 29   for limiting search scope. Each can be a non-zero
    skipped 164 lines
    194 194  type Options struct {
    195 195   Fuzzy bool
    196 196   FuzzyAlgo algo.Algo
     197 + Scheme string
    197 198   Extended bool
    198 199   Phony bool
    199 200   Case Case
    skipped 59 lines
    259 260   return &Options{
    260 261   Fuzzy: true,
    261 262   FuzzyAlgo: algo.FuzzyMatchV2,
     263 + Scheme: "default",
    262 264   Extended: true,
    263 265   Phony: false,
    264 266   Case: CaseSmart,
    skipped 174 lines
    439 441   errorExit("invalid algorithm (expected: v1 or v2)")
    440 442   }
    441 443   return algo.FuzzyMatchV2
     444 +}
     445 + 
     446 +func processScheme(opts *Options) {
     447 + if !algo.Init(opts.Scheme) {
     448 + errorExit("invalid scoring scheme (expected: default|path|history)")
     449 + }
     450 + if opts.Scheme == "history" {
     451 + opts.Criteria = []criterion{byScore}
     452 + }
    442 453  }
    443 454   
    444 455  func parseBorder(str string, optional bool) tui.BorderShape {
    skipped 900 lines
    1345 1356   opts.Normalize = true
    1346 1357   case "--algo":
    1347 1358   opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
     1359 + case "--scheme":
     1360 + opts.Scheme = strings.ToLower(nextString(allArgs, &i, "scoring scheme required (default|path|history)"))
    1348 1361   case "--expect":
    1349 1362   for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
    1350 1363   opts.Expect[k] = v
    skipped 200 lines
    1551 1564   default:
    1552 1565   if match, value := optString(arg, "--algo="); match {
    1553 1566   opts.FuzzyAlgo = parseAlgo(value)
     1567 + } else if match, value := optString(arg, "--scheme="); match {
     1568 + opts.Scheme = strings.ToLower(value)
    1554 1569   } else if match, value := optString(arg, "-q", "--query="); match {
    1555 1570   opts.Query = value
    1556 1571   } else if match, value := optString(arg, "-f", "--filter="); match {
    skipped 194 lines
    1751 1766   theme.Input = boldify(theme.Input)
    1752 1767   theme.Cursor = boldify(theme.Cursor)
    1753 1768   theme.Spinner = boldify(theme.Spinner)
     1769 + }
     1770 + 
     1771 + if opts.Scheme != "default" {
     1772 + processScheme(opts)
    1754 1773   }
    1755 1774  }
    1756 1775   
    skipped 32 lines
Please wait...
Page is in error, reload to recover