mirror of
https://github.com/junegunn/fzf.git
synced 2025-12-07 21:34:28 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e7259910 | ||
|
|
f0bfeba733 | ||
|
|
c3a7a24eea | ||
|
|
bbbcd780c9 | ||
|
|
475469a2e7 | ||
|
|
3a7447dcb6 | ||
|
|
e5d8cbd383 | ||
|
|
3c08dca7e7 | ||
|
|
d083f01d22 | ||
|
|
68cf393644 | ||
|
|
18f7230662 | ||
|
|
728f735281 | ||
|
|
ecc418ba77 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
|||||||
custom: ["https://paypal.me/junegunn", "https://www.buymeacoffee.com/junegunn"]
|
github: junegunn
|
||||||
|
|||||||
@@ -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/fzf"]
|
source = ["./dist/fzf-macos_darwin_amd64_v1/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"
|
||||||
|
|||||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,6 +1,40 @@
|
|||||||
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
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ more details.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
FZF_DEFAULT_COMMAND='ps -ef' \
|
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 \
|
--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($FZF_DEFAULT_COMMAND)' \
|
fzf --bind 'ctrl-d:reload(find . -type d),ctrl-f:reload(eval "$FZF_DEFAULT_COMMAND")' \
|
||||||
--height=50% --layout=reverse
|
--height=50% --layout=reverse
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.31.0
|
version=0.32.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.31.0"
|
$version="0.32.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.31"
|
var version string = "0.32"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -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 "Jul 2022" "fzf 0.31.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
|
.SH NAME
|
||||||
fzf-tmux - open fzf in tmux split pane
|
fzf-tmux - open fzf in tmux split pane
|
||||||
|
|||||||
@@ -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 "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
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
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
|
||||||
.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"
|
||||||
|
|||||||
@@ -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 = fzf#shellescape(a:bin) . ' --version --no-height'
|
let command = (&shell =~ 'powershell' ? '&' : '') . 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,7 +342,8 @@ function! s:common_sink(action, lines) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:get_color(attr, ...)
|
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 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
|
||||||
|
|||||||
@@ -161,7 +161,11 @@ _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]//[^A-Za-z0-9_=]/_}"
|
cmd="${COMP_WORDS[0]}"
|
||||||
|
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]}"
|
||||||
|
|||||||
@@ -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(/^\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)) )
|
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
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ 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
|
||||||
@@ -117,6 +120,12 @@ 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.
|
||||||
@@ -143,7 +152,9 @@ const (
|
|||||||
type charClass int
|
type charClass int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
charNonWord charClass = iota
|
charWhite charClass = iota
|
||||||
|
charNonWord
|
||||||
|
charDelimiter
|
||||||
charLower
|
charLower
|
||||||
charUpper
|
charUpper
|
||||||
charLetter
|
charLetter
|
||||||
@@ -181,6 +192,10 @@ 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
|
||||||
}
|
}
|
||||||
@@ -194,6 +209,10 @@ 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
|
||||||
}
|
}
|
||||||
@@ -206,22 +225,33 @@ func charClassOf(char rune) charClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bonusFor(prevClass charClass, class charClass) int16 {
|
func bonusFor(prevClass charClass, class charClass) int16 {
|
||||||
if prevClass == charNonWord && class != charNonWord {
|
if class > charNonWord {
|
||||||
// Word boundary
|
if prevClass == charWhite {
|
||||||
return bonusBoundary
|
// Word boundary after whitespace
|
||||||
} else if prevClass == charLower && class == charUpper ||
|
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 {
|
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 bonusBoundary
|
return bonusBoundaryWhite
|
||||||
}
|
}
|
||||||
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
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
|
// 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), charNonWord, false
|
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charWhite, 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 {
|
||||||
@@ -417,7 +447,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,11 +516,14 @@ 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
|
||||||
// Break consecutive chunk
|
if consecutive > 1 {
|
||||||
if b == bonusBoundary {
|
fb := B[col-int(consecutive)+1]
|
||||||
consecutive = 1
|
// Break consecutive chunk
|
||||||
} else if consecutive > 1 {
|
if b >= bonusBoundary && b > fb {
|
||||||
b = util.Max16(b, util.Max16(bonusConsecutive, B[col-int(consecutive)+1]))
|
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]
|
||||||
@@ -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) {
|
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 := charNonWord
|
prevClass := charWhite
|
||||||
if sidx > 0 {
|
if sidx > 0 {
|
||||||
prevClass = charClassOf(text.Get(sidx - 1))
|
prevClass = charClassOf(text.Get(sidx - 1))
|
||||||
}
|
}
|
||||||
@@ -583,7 +616,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 {
|
if bonus >= bonusBoundary && bonus > firstBonus {
|
||||||
firstBonus = bonus
|
firstBonus = bonus
|
||||||
}
|
}
|
||||||
bonus = util.Max16(util.Max16(bonus, firstBonus), bonusConsecutive)
|
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 {
|
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
|
||||||
@@ -877,8 +910,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+bonusBoundary)*lenPattern +
|
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundaryWhite)*lenPattern +
|
||||||
(bonusFirstCharMultiplier-1)*bonusBoundary}, nil
|
(bonusFirstCharMultiplier-1)*bonusBoundaryWhite}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
|
bonusBoundaryWhite*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
|
scoreMatch*4+bonusBoundaryDelimiter*bonusFirstCharMultiplier+bonusBoundaryDelimiter*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*3+scoreGapStart)
|
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+bonusBoundaryDelimiter)
|
||||||
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+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
|
||||||
bonusBoundary*2+2*scoreGapStart+4*scoreGapExtension)
|
bonusBoundaryDelimiter*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+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*3+bonusBoundaryWhite*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary+
|
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite+
|
||||||
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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*3)
|
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*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+bonusBoundary*(bonusFirstCharMultiplier+2)+
|
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryDelimiter*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+bonusBoundary*bonusFirstCharMultiplier+bonusCamel123*2+
|
scoreMatch*3+bonusBoundaryWhite*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+
|
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*2+
|
||||||
util.Max(bonusCamel123, bonusBoundary))
|
util.Max(bonusCamel123, bonusBoundaryWhite))
|
||||||
|
|
||||||
// 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+bonusBoundary*bonusFirstCharMultiplier+
|
scoreMatch*2+bonusBoundaryWhite*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+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary)
|
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite)
|
||||||
}
|
}
|
||||||
|
|
||||||
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+bonusBoundary*(bonusFirstCharMultiplier+3))
|
scoreMatch*4+bonusBoundaryDelimiter*(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+4))
|
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+bonusBoundaryDelimiter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ func TestExactMatchNaiveBackward(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixMatch(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} {
|
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,9 +156,10 @@ 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+bonusNonWord)
|
scoreMatch*4+bonusConsecutive*2+bonusBoundaryWhite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,9 +183,9 @@ func TestNormalize(t *testing.T) {
|
|||||||
input, pattern, sidx, eidx, score)
|
input, pattern, sidx, eidx, score)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
test("Só Danço Samba", "So", 0, 2, 56, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
|
test("Só Danço Samba", "So", 0, 2, 62, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, ExactMatchNaive)
|
||||||
test("Só Danço Samba", "sodc", 0, 7, 89, FuzzyMatchV1, FuzzyMatchV2)
|
test("Só Danço Samba", "sodc", 0, 7, 97, FuzzyMatchV1, FuzzyMatchV2)
|
||||||
test("Danço", "danco", 0, 5, 128, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
|
test("Danço", "danco", 0, 5, 140, FuzzyMatchV1, FuzzyMatchV2, PrefixMatch, SuffixMatch, ExactMatchNaive, EqualMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLongString(t *testing.T) {
|
func TestLongString(t *testing.T) {
|
||||||
|
|||||||
@@ -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|begin|end|index]
|
when the scores are tied [length|chunk|begin|end|index]
|
||||||
(default: length)
|
(default: length)
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
@@ -125,6 +125,7 @@ type criterion int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
byScore criterion = iota
|
byScore criterion = iota
|
||||||
|
byChunk
|
||||||
byLength
|
byLength
|
||||||
byBegin
|
byBegin
|
||||||
byEnd
|
byEnd
|
||||||
@@ -611,6 +612,7 @@ 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
|
||||||
@@ -627,6 +629,9 @@ 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)
|
||||||
@@ -640,6 +645,9 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
// ParseOptions parses command-line options
|
||||||
func ParseOptions() *Options {
|
func ParseOptions() *Options {
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
|
|
||||||
for _, arg := range os.Args[1:] {
|
for idx, arg := range os.Args[1:] {
|
||||||
if arg == "--version" {
|
if arg == "--version" && (idx == 0 || idx > 0 && !expectsArbitraryString(os.Args[idx])) {
|
||||||
opts.Version = true
|
opts.Version = true
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,21 @@ 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:
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ func TestResultRank(t *testing.T) {
|
|||||||
// FIXME global
|
// FIXME global
|
||||||
sortCriteria = []criterion{byScore, byLength}
|
sortCriteria = []criterion{byScore, byLength}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
str := []rune("foo")
|
||||||
item1 := buildResult(
|
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
|
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(strs[0])}, []Offset{}, 2)
|
item2 := buildResult(&Item{text: util.RunesToChars(str)}, []Offset{}, 2)
|
||||||
|
|
||||||
items := []Result{item1, item2}
|
items := []Result{item1, item2}
|
||||||
sort.Sort(ByRelevance(items))
|
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) {
|
func TestColorOffset(t *testing.T) {
|
||||||
// ------------ 20 ---- -- ----
|
// ------------ 20 ---- -- ----
|
||||||
// ++++++++ ++++++++++
|
// ++++++++ ++++++++++
|
||||||
|
|||||||
@@ -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
|
y := atoi(elems[2], -1) - 1 - r.yoffset
|
||||||
if t < 0 || x < 0 || y < 0 {
|
if t < 0 || x < 0 || y < 0 {
|
||||||
return Event{Invalid, 0, nil}
|
return Event{Invalid, 0, nil}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -754,6 +754,20 @@ 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] }
|
||||||
|
|||||||
Reference in New Issue
Block a user