Compare commits

...

13 Commits

Author SHA1 Message Date
Junegunn Choi
f7e7259910 0.32.0 2022-08-02 21:56:14 +09:00
Junegunn Choi
f0bfeba733 Add 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 new scheme works well with tabular input.

  # 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.

Close #2285
Close #2537
- Not the exact solution to --tiebreak=length not taking --nth into account,
  but this should work. And the added benefit is that it works well even
  when --nth is not provided.
- Adding a bonus point to the last character of a word didn't turn out great.
  The order of the result suddenly changes when you type in the last
  character in the word producing a jarring effect.
2022-08-02 21:48:19 +09:00
Junegunn Choi
c3a7a24eea Tweak bonus points to word boundaries
Close https://github.com/junegunn/fzf.vim/issues/1004

  # jobs/latency.js is favored over job_latency.js
  printf 'job_latency.js\njobs/latency.js' | fzf -qlatency
2022-08-02 20:57:13 +09:00
Junegunn Choi
bbbcd780c9 Allow "--version" to be used as the argument to --query, --header, et al.
But why?
2022-08-02 20:57:12 +09:00
kissge
475469a2e7 [zsh] Make awk regex compatible with both GNU and macOS (#2906) 2022-08-02 20:56:25 +09:00
Junegunn Choi
3a7447dcb6 Update FUNDING.yml 2022-07-30 19:35:41 +09:00
Junegunn Choi
e5d8cbd383 [vim] Fix version check on windows/powershell (addendum)
Should handle powershell.exe as well

Fix https://github.com/junegunn/fzf.vim/issues/1411
2022-07-30 19:08:04 +09:00
Junegunn Choi
3c08dca7e7 Fix README examples so that they work both on bash and zsh
Close #2887
2022-07-29 22:03:02 +09:00
Carl Kamholtz
d083f01d22 [vim] Add option to force 24 bit colors on Windows (#2889) 2022-07-29 21:51:35 +09:00
Bob Matcuk
68cf393644 [bash] Fix 'possible retry loop' problem of bash-completion (#2891)
Close #2474
Close #2583
2022-07-29 21:50:59 +09:00
Junegunn Choi
18f7230662 Fix mouse location in --height mode
Fix #2900
2022-07-29 15:42:44 +09:00
Carl Kamholtz
728f735281 [vim] Fix version check on windows/powershell (#2894)
- Replace fzf#shellescape with shellescape
- Prepend command with '&' in powershell to deal with quoted exe
2022-07-29 08:18:06 +09:00
Junegunn Choi
ecc418ba77 0.31.0 2022-07-21 23:04:57 +09:00
19 changed files with 199 additions and 62 deletions

2
.github/FUNDING.yml vendored
View File

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

View File

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

View File

@@ -1,6 +1,40 @@
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
------
- Added support for an alternative preview window layout that is activated
@@ -13,8 +47,8 @@ CHANGELOG
# Or you can just hide it like so
fzf --preview 'cat {}' --preview-window '<50(hidden)'
```
- Use SGR mouse mode to support larger terminals
- You can now use characters that does not satisfy `unicode.IsGraphic` constraint
- fzf now uses SGR mouse mode to properly support mouse on larger terminals
- You can now use characters that do not satisfy `unicode.IsGraphic` constraint
for `--marker`, `--pointer`, and `--ellipsis`. Allows Nerd Fonts and stuff.
Use at your own risk.
- Bug fixes and improvements

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version string = "0.30"
var version string = "0.32"
var revision string = "devel"
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
THE SOFTWARE.
..
.TH fzf-tmux 1 "Apr 2022" "fzf 0.30.0" "fzf-tmux - open fzf in tmux split pane"
.TH fzf-tmux 1 "Aug 2022" "fzf 0.32.0" "fzf-tmux - open fzf in tmux split pane"
.SH NAME
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
THE SOFTWARE.
..
.TH fzf 1 "Jul 2022" "fzf 0.31.0" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Aug 2022" "fzf 0.32.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -95,6 +95,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
.br
.BR length " Prefers line with shorter length"
.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
.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)
return s:versions[a:bin]
end
let command = fzf#shellescape(a:bin) . ' --version --no-height'
let command = (&shell =~ 'powershell' ? '&' : '') . shellescape(a:bin) . ' --version --no-height'
let output = systemlist(command)
if v:shell_error || empty(output)
return ''
@@ -342,7 +342,8 @@ function! s:common_sink(action, lines) abort
endfunction
function! s:get_color(attr, ...)
let gui = !s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
let fam = gui ? 'gui' : 'cterm'
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
for group in a:000

View File

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

View File

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

View File

@@ -89,6 +89,9 @@ import (
var DEBUG bool
const delimiterChars = "/,:;|"
const whiteChars = " \t\n\v\f\r\x85\xA0"
func indexAt(index int, max int, forward bool) int {
if forward {
return index
@@ -117,6 +120,12 @@ const (
// in web2 dictionary and my file system.
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
// for computing bonus points for consecutive chunks starting with a non-word
// character.
@@ -143,7 +152,9 @@ const (
type charClass int
const (
charNonWord charClass = iota
charWhite charClass = iota
charNonWord
charDelimiter
charLower
charUpper
charLetter
@@ -181,6 +192,10 @@ func charClassOfAscii(char rune) charClass {
return charUpper
} else if char >= '0' && char <= '9' {
return charNumber
} else if strings.IndexRune(whiteChars, char) >= 0 {
return charWhite
} else if strings.IndexRune(delimiterChars, char) >= 0 {
return charDelimiter
}
return charNonWord
}
@@ -194,6 +209,10 @@ func charClassOfNonAscii(char rune) charClass {
return charNumber
} else if unicode.IsLetter(char) {
return charLetter
} else if unicode.IsSpace(char) {
return charWhite
} else if strings.IndexRune(delimiterChars, char) >= 0 {
return charDelimiter
}
return charNonWord
}
@@ -206,22 +225,33 @@ func charClassOf(char rune) charClass {
}
func bonusFor(prevClass charClass, class charClass) int16 {
if prevClass == charNonWord && class != charNonWord {
// Word boundary
return bonusBoundary
} else if prevClass == charLower && class == charUpper ||
if class > charNonWord {
if prevClass == charWhite {
// Word boundary after whitespace
return bonusBoundaryWhite
} 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 {
// camelCase letter123
return bonusCamel123
} else if class == charNonWord {
return bonusNonWord
} else if class == charWhite {
return bonusBoundaryWhite
}
return 0
}
func bonusAt(input *util.Chars, idx int) int16 {
if idx == 0 {
return bonusBoundary
return bonusBoundaryWhite
}
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
}
@@ -377,7 +407,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
// Phase 2. Calculate bonus for each point
maxScore, maxScorePos := int16(0), 0
pidx, lastIdx := 0, 0
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charNonWord, false
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charWhite, false
Tsub := T[idx:]
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
for off, char := range Tsub {
@@ -417,7 +447,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
C0sub[off] = 1
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
maxScore, maxScorePos = score, idx+off
if forward && bonus == bonusBoundary {
if forward && bonus >= bonusBoundary {
break
}
}
@@ -486,11 +516,14 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
s1 = Hdiag[off] + scoreMatch
b := Bsub[off]
consecutive = Cdiag[off] + 1
// Break consecutive chunk
if b == bonusBoundary {
consecutive = 1
} else if consecutive > 1 {
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
if consecutive > 1 {
fb := B[col-int(consecutive)+1]
// Break consecutive chunk
if b >= bonusBoundary && b > fb {
consecutive = 1
} else {
b = util.Max16(b, util.Max16(bonusConsecutive, fb))
}
}
if s1+b < s2 {
s1 += Bsub[off]
@@ -555,7 +588,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) {
pidx, score, inGap, consecutive, firstBonus := 0, 0, false, 0, int16(0)
pos := posArray(withPos, len(pattern))
prevClass := charNonWord
prevClass := charWhite
if sidx > 0 {
prevClass = charClassOf(text.Get(sidx - 1))
}
@@ -583,7 +616,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
firstBonus = bonus
} else {
// Break consecutive chunk
if bonus == bonusBoundary {
if bonus >= bonusBoundary && bonus > firstBonus {
firstBonus = bonus
}
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
@@ -741,7 +774,7 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
if bonus > bestBonus {
bestPos, bestBonus = index, bonus
}
if bonus == bonusBoundary {
if bonus >= bonusBoundary {
break
}
index -= pidx - 1
@@ -877,8 +910,8 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
match = runesStr == string(pattern)
}
if match {
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundary)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundaryWhite)*lenPattern +
(bonusFirstCharMultiplier-1)*bonusBoundaryWhite}, 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,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
bonusBoundaryWhite*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
scoreMatch*4+bonusBoundaryDelimiter*bonusFirstCharMultiplier+bonusBoundaryDelimiter*3)
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3+scoreGapStart)
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+bonusBoundaryDelimiter)
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
bonusBoundaryDelimiter*2+2*scoreGapStart+4*scoreGapExtension)
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite+
scoreGapStart*2+scoreGapExtension*3)
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*3)
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
bonusNonWord+bonusBoundary)
@@ -75,14 +75,14 @@ func TestFuzzyMatch(t *testing.T) {
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
scoreMatch*3+bonusBoundary*(bonusFirstCharMultiplier+2)+
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryDelimiter*2+
scoreGapStart*2+scoreGapExtension*4)
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
scoreMatch*3+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusCamel123*2+
scoreGapStart*2+scoreGapExtension*2)
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
util.Max(bonusCamel123, bonusBoundary))
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*2+
util.Max(bonusCamel123, bonusBoundaryWhite))
// Consecutive bonus updated
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) {
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+
scoreGapStart+scoreGapExtension)
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
scoreMatch*2+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite)
}
func TestExactMatchNaive(t *testing.T) {
@@ -114,9 +114,9 @@ func TestExactMatchNaive(t *testing.T) {
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
scoreMatch*4+bonusBoundary*(bonusFirstCharMultiplier+3))
scoreMatch*4+bonusBoundaryDelimiter*(bonusFirstCharMultiplier+3))
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+4))
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+bonusBoundaryDelimiter)
}
}
@@ -128,7 +128,7 @@ func TestExactMatchNaiveBackward(t *testing.T) {
}
func TestPrefixMatch(t *testing.T) {
score := (scoreMatch+bonusBoundary)*3 + bonusBoundary*(bonusFirstCharMultiplier-1)
score := scoreMatch*3 + bonusBoundaryWhite*bonusFirstCharMultiplier + bonusBoundaryWhite*2
for _, dir := range []bool{true, false} {
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
@@ -156,9 +156,10 @@ func TestSuffixMatch(t *testing.T) {
// Strip trailing white space from the string
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz", 6, 9,
scoreMatch*3+bonusConsecutive*2)
// Only when the pattern doesn't end with a space
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
scoreMatch*4+bonusConsecutive*2+bonusNonWord)
scoreMatch*4+bonusConsecutive*2+bonusBoundaryWhite)
}
}
@@ -182,9 +183,9 @@ func TestNormalize(t *testing.T) {
input, pattern, sidx, eidx, score)
}
}
test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
test("Só Danço Samba", "So", 0, 2, 62, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
test("Só Danço Samba", "sodc", 0, 7, 97, FuzzyMatchV1, FuzzyMatchV2)
test("Danço", "danco", 0, 5, 140, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
}
func TestLongString(t *testing.T) {

View File

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

View File

@@ -49,6 +49,21 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
case byScore:
// Higher is better
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:
val = item.TrimLength()
case byBegin, byEnd:

View File

@@ -54,9 +54,9 @@ func TestResultRank(t *testing.T) {
// FIXME global
sortCriteria = []criterion{byScore, byLength}
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
str := []rune("foo")
item1 := buildResult(
withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2)
withIndex(&Item{text: util.RunesToChars(str)}, 1), []Offset{}, 2)
if item1.points[3] != math.MaxUint16-2 || // Bonus
item1.points[2] != 3 || // Length
item1.points[1] != 0 || // Unused
@@ -65,7 +65,7 @@ func TestResultRank(t *testing.T) {
t.Error(item1)
}
// Only differ in index
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2)
item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)
items := []Result{item1, item2}
sort.Sort(ByRelevance(items))
@@ -98,6 +98,23 @@ 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) {
// ------------ 20 ---- -- ----
// ++++++++ ++++++++++

View File

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

View File

@@ -754,6 +754,20 @@ class TestGoFZF < TestBase
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)
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
tmux.send_keys "(echo d; echo D; echo x) | #{fzf('-q d')}", :Enter
tmux.until { |lines| assert_equal ' 2/3', lines[-2] }