Compare commits

..

1 Commits

Author SHA1 Message Date
Junegunn Choi
a0ef8987fb 0.31.0 2022-07-21 22:46:34 +09:00
19 changed files with 60 additions and 197 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1 +1 @@
github: junegunn custom: ["https://paypal.me/junegunn", "https://www.buymeacoffee.com/junegunn"]

View File

@@ -18,7 +18,7 @@ builds:
post: | post: |
sh -c ' sh -c '
cat > /tmp/fzf-gon-amd64.hcl << EOF cat > /tmp/fzf-gon-amd64.hcl << EOF
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"] source = ["./dist/fzf-macos_darwin_amd64/fzf"]
bundle_id = "kr.junegunn.fzf" bundle_id = "kr.junegunn.fzf"
apple_id { apple_id {
username = "junegunn.c@gmail.com" username = "junegunn.c@gmail.com"

View File

@@ -1,40 +1,6 @@
CHANGELOG CHANGELOG
========= =========
0.32.0
------
- Updated the scoring algorithm
- Different bonus points to different categories of word boundaries
(listed higher to lower bonus point)
- Word after whitespace characters or beginning of the string
- Word after common delimiter characters (`/,:;|`)
- Word after other non-word characters
```sh
# foo/bar.sh` is preferred over `foo-bar.sh` on `bar`
fzf --query=bar --height=4 << EOF
foo-bar.sh
foo/bar.sh
EOF
```
- Added a new tiebreak `chunk`
- Favors the line with shorter matched chunk. A chunk is a set of
consecutive non-whitespace characters.
- Unlike the default `length`, this scheme works well with tabular input
```sh
# length prefers item #1, because the whole line is shorter,
# chunk prefers item #2, because the matched chunk ("foo") is shorter
fzf --height=6 --header-lines=2 --tiebreak=chunk --reverse --query=fo << "EOF"
N | Field1 | Field2 | Field3
- | ------ | ------ | ------
1 | hello | foobar | baz
2 | world | foo | bazbaz
EOF
```
- If the input does not contain any spaces, `chunk` is equivalent to
`length`. But we're not going to set it as the default because it is
computationally more expensive.
- Bug fixes and improvements
0.31.0 0.31.0
------ ------
- Added support for an alternative preview window layout that is activated - Added support for an alternative preview window layout that is activated

View File

@@ -557,7 +557,7 @@ more details.
```sh ```sh
FZF_DEFAULT_COMMAND='ps -ef' \ FZF_DEFAULT_COMMAND='ps -ef' \
fzf --bind 'ctrl-r:reload(eval "$FZF_DEFAULT_COMMAND")' \ fzf --bind 'ctrl-r:reload($FZF_DEFAULT_COMMAND)' \
--header 'Press CTRL-R to reload' --header-lines=1 \ --header 'Press CTRL-R to reload' --header-lines=1 \
--height=50% --layout=reverse --height=50% --layout=reverse
``` ```
@@ -566,7 +566,7 @@ FZF_DEFAULT_COMMAND='ps -ef' \
```sh ```sh
FZF_DEFAULT_COMMAND='find . -type f' \ FZF_DEFAULT_COMMAND='find . -type f' \
fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload(eval "$FZF_DEFAULT_COMMAND")' \ fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload($FZF_DEFAULT_COMMAND)' \
--height=50% --layout=reverse --height=50% --layout=reverse
``` ```

View File

@@ -2,7 +2,7 @@
set -u set -u
version=0.32.0 version=0.31.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2

View File

@@ -1,4 +1,4 @@
$version="0.32.0" $version="0.31.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version string = "0.32" var version string = "0.31"
var revision string = "devel" var revision string = "devel"
func main() { func main() {

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf-tmux 1 "Aug 2022" "fzf 0.32.0" "fzf-tmux - open fzf in tmux split pane" .TH fzf-tmux 1 "Jul 2022" "fzf 0.31.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf-tmux - open fzf in tmux split pane fzf-tmux - open fzf in tmux split pane

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Aug 2022" "fzf 0.32.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Jul 2022" "fzf 0.31.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -95,8 +95,6 @@ Comma-separated list of sort criteria to apply when the scores are tied.
.br .br
.BR length " Prefers line with shorter length" .BR length " Prefers line with shorter length"
.br .br
.BR chunk " Prefers line with shorter matched chunk (delimited by whitespaces)"
.br
.BR begin " Prefers line with matched substring closer to the beginning" .BR begin " Prefers line with matched substring closer to the beginning"
.br .br
.BR end " Prefers line with matched substring closer to the end" .BR end " Prefers line with matched substring closer to the end"

View File

@@ -164,7 +164,7 @@ function s:get_version(bin)
if has_key(s:versions, a:bin) if has_key(s:versions, a:bin)
return s:versions[a:bin] return s:versions[a:bin]
end end
let command = (&shell =~ 'powershell' ? '&' : '') . shellescape(a:bin) . ' --version --no-height' let command = fzf#shellescape(a:bin) . ' --version --no-height'
let output = systemlist(command) let output = systemlist(command)
if v:shell_error || empty(output) if v:shell_error || empty(output)
return '' return ''
@@ -342,8 +342,7 @@ function! s:common_sink(action, lines) abort
endfunction endfunction
function! s:get_color(attr, ...) function! s:get_color(attr, ...)
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152) let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
let fam = gui ? 'gui' : 'cterm' let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$' let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000 for group in a:000

View File

@@ -161,11 +161,7 @@ _fzf_handle_dynamic_completion() {
__fzf_generic_path_completion() { __fzf_generic_path_completion() {
local cur base dir leftover matches trigger cmd local cur base dir leftover matches trigger cmd
cmd="${COMP_WORDS[0]}" cmd="${COMP_WORDS[0]//[^A-Za-z0-9_=]/_}"
if [[ $cmd == \\* ]]; then
cmd="${cmd:1}"
fi
cmd="${cmd//[^A-Za-z0-9_=]/_}"
COMPREPLY=() COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'} trigger=${FZF_COMPLETION_TRIGGER-'**'}
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"

View File

@@ -97,7 +97,7 @@ bindkey -M viins '\ec' fzf-cd-widget
fzf-history-widget() { fzf-history-widget() {
local selected num local selected num
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' | selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^\s*[0-9]+\**\s+/, "", cmd); if (!seen[cmd]++) print $0 }' |
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)) ) 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)) )
local ret=$? local ret=$?
if [ -n "$selected" ]; then if [ -n "$selected" ]; then

View File

@@ -89,9 +89,6 @@ import (
var DEBUG bool var DEBUG bool
const delimiterChars = "/,:;|"
const whiteChars = " \t\n\v\f\r\x85\xA0"
func indexAt(index int, max int, forward bool) int { func indexAt(index int, max int, forward bool) int {
if forward { if forward {
return index return index
@@ -120,12 +117,6 @@ const (
// in web2 dictionary and my file system. // in web2 dictionary and my file system.
bonusBoundary = scoreMatch / 2 bonusBoundary = scoreMatch / 2
// Extra bonus for word boundary after whitespace character or beginning of the string
bonusBoundaryWhite = bonusBoundary + 2
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
bonusBoundaryDelimiter = bonusBoundary + 1
// Although bonus point for non-word characters is non-contextual, we need it // Although bonus point for non-word characters is non-contextual, we need it
// for computing bonus points for consecutive chunks starting with a non-word // for computing bonus points for consecutive chunks starting with a non-word
// character. // character.
@@ -152,9 +143,7 @@ const (
type charClass int type charClass int
const ( const (
charWhite charClass = iota charNonWord charClass = iota
charNonWord
charDelimiter
charLower charLower
charUpper charUpper
charLetter charLetter
@@ -192,10 +181,6 @@ func charClassOfAscii(char rune) charClass {
return charUpper return charUpper
} else if char >= '0' && char <= '9' { } else if char >= '0' && char <= '9' {
return charNumber return charNumber
} else if strings.IndexRune(whiteChars, char) >= 0 {
return charWhite
} else if strings.IndexRune(delimiterChars, char) >= 0 {
return charDelimiter
} }
return charNonWord return charNonWord
} }
@@ -209,10 +194,6 @@ func charClassOfNonAscii(char rune) charClass {
return charNumber return charNumber
} else if unicode.IsLetter(char) { } else if unicode.IsLetter(char) {
return charLetter return charLetter
} else if unicode.IsSpace(char) {
return charWhite
} else if strings.IndexRune(delimiterChars, char) >= 0 {
return charDelimiter
} }
return charNonWord return charNonWord
} }
@@ -225,33 +206,22 @@ func charClassOf(char rune) charClass {
} }
func bonusFor(prevClass charClass, class charClass) int16 { func bonusFor(prevClass charClass, class charClass) int16 {
if class > charNonWord { if prevClass == charNonWord && class != charNonWord {
if prevClass == charWhite { // Word boundary
// Word boundary after whitespace return bonusBoundary
return bonusBoundaryWhite } else if prevClass == charLower && class == charUpper ||
} else if prevClass == charDelimiter {
// Word boundary after a delimiter character
return bonusBoundaryDelimiter
} else if prevClass == charNonWord {
// Word boundary
return bonusBoundary
}
}
if prevClass == charLower && class == charUpper ||
prevClass != charNumber && class == charNumber { prevClass != charNumber && class == charNumber {
// camelCase letter123 // camelCase letter123
return bonusCamel123 return bonusCamel123
} else if class == charNonWord { } else if class == charNonWord {
return bonusNonWord return bonusNonWord
} else if class == charWhite {
return bonusBoundaryWhite
} }
return 0 return 0
} }
func bonusAt(input *util.Chars, idx int) int16 { func bonusAt(input *util.Chars, idx int) int16 {
if idx == 0 { if idx == 0 {
return bonusBoundaryWhite return bonusBoundary
} }
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx))) return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
} }
@@ -407,7 +377,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
// Phase 2. Calculate bonus for each point // Phase 2. Calculate bonus for each point
maxScore, maxScorePos := int16(0), 0 maxScore, maxScorePos := int16(0), 0
pidx, lastIdx := 0, 0 pidx, lastIdx := 0, 0
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charWhite, false pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
Tsub := T[idx:] Tsub := T[idx:]
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)] H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
for off, char := range Tsub { for off, char := range Tsub {
@@ -447,7 +417,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
C0sub[off] = 1 C0sub[off] = 1
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) { if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, idx+off maxScore, maxScorePos = score, idx+off
if forward && bonus >= bonusBoundary { if forward && bonus == bonusBoundary {
break break
} }
} }
@@ -516,14 +486,11 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
s1 = Hdiag[off] + scoreMatch s1 = Hdiag[off] + scoreMatch
b := Bsub[off] b := Bsub[off]
consecutive = Cdiag[off] + 1 consecutive = Cdiag[off] + 1
if consecutive > 1 { // Break consecutive chunk
fb := B[col-int(consecutive)+1] if b == bonusBoundary {
// Break consecutive chunk consecutive = 1
if b >= bonusBoundary && b > fb { } else if consecutive > 1 {
consecutive = 1 b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
} else {
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
}
} }
if s1+b < s2 { if s1+b < s2 {
s1 += Bsub[off] s1 += Bsub[off]
@@ -588,7 +555,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) { func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, pattern []rune, sidx int, eidx int, withPos bool) (int, *[]int) {
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0) pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
pos := posArray(withPos, len(pattern)) pos := posArray(withPos, len(pattern))
prevClass := charWhite prevClass := charNonWord
if sidx > 0 { if sidx > 0 {
prevClass = charClassOf(text.Get(sidx - 1)) prevClass = charClassOf(text.Get(sidx - 1))
} }
@@ -616,7 +583,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
firstBonus = bonus firstBonus = bonus
} else { } else {
// Break consecutive chunk // Break consecutive chunk
if bonus >= bonusBoundary && bonus > firstBonus { if bonus == bonusBoundary {
firstBonus = bonus firstBonus = bonus
} }
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive) bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
@@ -774,7 +741,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
if bonus > bestBonus { if bonus > bestBonus {
bestPos, bestBonus = index, bonus bestPos, bestBonus = index, bonus
} }
if bonus >= bonusBoundary { if bonus == bonusBoundary {
break break
} }
index -= pidx - 1 index -= pidx - 1
@@ -910,8 +877,8 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
match = runesStr == string(pattern) match = runesStr == string(pattern)
} }
if match { if match {
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundaryWhite)*lenPattern + return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundaryWhite}, nil (bonusFirstCharMultiplier-1)*bonusBoundary}, nil
} }
return Result{-1, -1, 0}, nil return Result{-1, -1, 0}, nil
} }

View File

@@ -45,29 +45,29 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9, assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3) scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9, assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundaryWhite*2+2*scoreGapStart+4*scoreGapExtension) bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13, assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2) scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10, assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundaryDelimiter*bonusFirstCharMultiplier+bonusBoundaryDelimiter*3) scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13, assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+bonusBoundaryDelimiter) scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3+scoreGapStart)
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10, assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension) scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10, assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension) scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9, assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundaryDelimiter*2+2*scoreGapStart+4*scoreGapExtension) bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7, assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension) bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8, assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite+ scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
scoreGapStart*2+scoreGapExtension*3) scoreGapStart*2+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4, assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*3) scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6, assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+ scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
bonusNonWord+bonusBoundary) bonusNonWord+bonusBoundary)
@@ -75,14 +75,14 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9, assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3) scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9, assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryDelimiter*2+ scoreMatch*3+bonusBoundary*(bonusFirstCharMultiplier+2)+
scoreGapStart*2+scoreGapExtension*4) scoreGapStart*2+scoreGapExtension*4)
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7, assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusCamel123*2+ scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
scoreGapStart*2+scoreGapExtension*2) scoreGapStart*2+scoreGapExtension*2)
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4, assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*2+ scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
util.Max(bonusCamel123, bonusBoundaryWhite)) util.Max(bonusCamel123, bonusBoundary))
// Consecutive bonus updated // Consecutive bonus updated
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6, assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
@@ -98,10 +98,10 @@ func TestFuzzyMatch(t *testing.T) {
func TestFuzzyMatchBackward(t *testing.T) { func TestFuzzyMatchBackward(t *testing.T) {
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4, assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+ scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+
scoreGapStart+scoreGapExtension) scoreGapStart+scoreGapExtension)
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9, assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite) scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
} }
func TestExactMatchNaive(t *testing.T) { func TestExactMatchNaive(t *testing.T) {
@@ -114,9 +114,9 @@ func TestExactMatchNaive(t *testing.T) {
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13, assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2) scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10, assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundaryDelimiter*(bonusFirstCharMultiplier+3)) scoreMatch*4+bonusBoundary*(bonusFirstCharMultiplier+3))
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13, assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+bonusBoundaryDelimiter) scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+4))
} }
} }
@@ -128,7 +128,7 @@ func TestExactMatchNaiveBackward(t *testing.T) {
} }
func TestPrefixMatch(t *testing.T) { func TestPrefixMatch(t *testing.T) {
score := scoreMatch*3 + bonusBoundaryWhite*bonusFirstCharMultiplier + bonusBoundaryWhite*2 score := (scoreMatch+bonusBoundary)*3 + bonusBoundary*(bonusFirstCharMultiplier-1)
for _, dir := range []bool{true, false} { for _, dir := range []bool{true, false} {
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0) assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
@@ -156,10 +156,9 @@ func TestSuffixMatch(t *testing.T) {
// Strip trailing white space from the string // Strip trailing white space from the string
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9, assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
scoreMatch*3+bonusConsecutive*2) scoreMatch*3+bonusConsecutive*2)
// Only when the pattern doesn't end with a space // Only when the pattern doesn't end with a space
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10, assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
scoreMatch*4+bonusConsecutive*2+bonusBoundaryWhite) scoreMatch*4+bonusConsecutive*2+bonusNonWord)
} }
} }
@@ -183,9 +182,9 @@ func TestNormalize(t *testing.T) {
input, pattern, sidx, eidx, score) input, pattern, sidx, eidx, score)
} }
} }
test("Só Danço Samba", "So", 0, 2, 62, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive) test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
test("Só Danço Samba", "sodc", 0, 7, 97, FuzzyMatchV1, FuzzyMatchV2) test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
test("Danço", "danco", 0, 5, 140, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch) test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
} }
func TestLongString(t *testing.T) { func TestLongString(t *testing.T) {

View File

@@ -35,7 +35,7 @@ const usage = `usage: fzf [options]
--tac Reverse the order of the input --tac Reverse the order of the input
--disabled Do not perform search --disabled Do not perform search
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
when the scores are tied [length|chunk|begin|end|index] when the scores are tied [length|begin|end|index]
(default: length) (default: length)
Interface Interface
@@ -125,7 +125,6 @@ type criterion int
const ( const (
byScore criterion = iota byScore criterion = iota
byChunk
byLength byLength
byBegin byBegin
byEnd byEnd
@@ -612,7 +611,6 @@ func parseKeyChords(str string, message string) map[tui.Event]string {
func parseTiebreak(str string) []criterion { func parseTiebreak(str string) []criterion {
criteria := []criterion{byScore} criteria := []criterion{byScore}
hasIndex := false hasIndex := false
hasChunk := false
hasLength := false hasLength := false
hasBegin := false hasBegin := false
hasEnd := false hasEnd := false
@@ -629,9 +627,6 @@ func parseTiebreak(str string) []criterion {
switch str { switch str {
case "index": case "index":
check(&hasIndex, "index") check(&hasIndex, "index")
case "chunk":
check(&hasChunk, "chunk")
criteria = append(criteria, byChunk)
case "length": case "length":
check(&hasLength, "length") check(&hasLength, "length")
criteria = append(criteria, byLength) criteria = append(criteria, byLength)
@@ -645,9 +640,6 @@ func parseTiebreak(str string) []criterion {
errorExit("invalid sort criterion: " + str) errorExit("invalid sort criterion: " + str)
} }
} }
if len(criteria) > 4 {
errorExit("at most 3 tiebreaks are allowed: " + str)
}
return criteria return criteria
} }
@@ -1750,20 +1742,12 @@ func postProcessOptions(opts *Options) {
} }
} }
func expectsArbitraryString(opt string) bool {
switch opt {
case "-q", "--query", "-f", "--filter", "--header", "--prompt":
return true
}
return false
}
// ParseOptions parses command-line options // ParseOptions parses command-line options
func ParseOptions() *Options { func ParseOptions() *Options {
opts := defaultOptions() opts := defaultOptions()
for idx, arg := range os.Args[1:] { for _, arg := range os.Args[1:] {
if arg == "--version" && (idx == 0 || idx > 0 && !expectsArbitraryString(os.Args[idx])) { if arg == "--version" {
opts.Version = true opts.Version = true
return opts return opts
} }

View File

@@ -49,21 +49,6 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
case byScore: case byScore:
// Higher is better // Higher is better
val = math.MaxUint16 - util.AsUint16(score) val = math.MaxUint16 - util.AsUint16(score)
case byChunk:
b := minBegin
e := maxEnd
l := item.text.Length()
for ; b >= 1; b-- {
if unicode.IsSpace(item.text.Get(b - 1)) {
break
}
}
for ; e < l; e++ {
if unicode.IsSpace(item.text.Get(e)) {
break
}
}
val = util.AsUint16(e - b)
case byLength: case byLength:
val = item.TrimLength() val = item.TrimLength()
case byBegin, byEnd: case byBegin, byEnd:

View File

@@ -54,9 +54,9 @@ func TestResultRank(t *testing.T) {
// FIXME global // FIXME global
sortCriteria = []criterion{byScore, byLength} sortCriteria = []criterion{byScore, byLength}
str := []rune("foo") strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
item1 := buildResult( item1 := buildResult(
withIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2) withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
if item1.points[3] != math.MaxUint16-2 || // Bonus if item1.points[3] != math.MaxUint16-2 || // Bonus
item1.points[2] != 3 || // Length item1.points[2] != 3 || // Length
item1.points[1] != 0 || // Unused item1.points[1] != 0 || // Unused
@@ -65,7 +65,7 @@ func TestResultRank(t *testing.T) {
t.Error(item1) t.Error(item1)
} }
// Only differ in index // Only differ in index
item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2) item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
items := []Result{item1, item2} items := []Result{item1, item2}
sort.Sort(ByRelevance(items)) sort.Sort(ByRelevance(items))
@@ -98,23 +98,6 @@ func TestResultRank(t *testing.T) {
} }
} }
func TestChunkTiebreak(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byChunk}
score := 100
test := func(input string, offset Offset, chunk string) {
item := buildResult(withIndex(&Item{text: util.RunesToChars([]rune(input))}, 1), []Offset{offset}, score)
if !(item.points[3] == math.MaxUint16-uint16(score) && item.points[2] == uint16(len(chunk))) {
t.Error(item.points)
}
}
test("hello foobar goodbye", Offset{8, 9}, "foobar")
test("hello foobar goodbye", Offset{7, 18}, "foobar goodbye")
test("hello foobar goodbye", Offset{0, 1}, "hello")
test("hello foobar goodbye", Offset{5, 7}, "hello foobar") // TBD
}
func TestColorOffset(t *testing.T) { func TestColorOffset(t *testing.T) {
// ------------ 20 ---- -- ---- // ------------ 20 ---- -- ----
// ++++++++ ++++++++++ // ++++++++ ++++++++++

View File

@@ -540,7 +540,7 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
t := atoi(elems[0], -1) t := atoi(elems[0], -1)
x := atoi(elems[1], -1) - 1 x := atoi(elems[1], -1) - 1
y := atoi(elems[2], -1) - 1 - r.yoffset y := atoi(elems[2], -1) - 1
if t < 0 || x < 0 || y < 0 { if t < 0 || x < 0 || y < 0 {
return Event{Invalid, 0, nil} return Event{Invalid, 0, nil}
} }

View File

@@ -754,20 +754,6 @@ class TestGoFZF < TestBase
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true) assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)
end end
def test_tiebreak_chunk
writelines(tempname, [
'1 foobarbaz baz',
'2 foobar baz',
'3 foo barbaz'
])
assert_equal [
'3 foo barbaz',
'2 foobar baz',
'1 foobarbaz baz'
], `#{FZF} -fo --tiebreak=chunk < #{tempname}`.lines(chomp: true)
end
def test_invalid_cache def test_invalid_cache
tmux.send_keys "(echo d; echo D; echo x) | #{fzf('-q d')}", :Enter tmux.send_keys "(echo d; echo D; echo x) | #{fzf('-q d')}", :Enter
tmux.until { |lines| assert_equal ' 2/3', lines[-2] } tmux.until { |lines| assert_equal ' 2/3', lines[-2] }