summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2016-01-13 03:07:42 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2016-01-13 21:27:43 +0900
commit1d2d32c847e39818bedae5f86ca75e6b70b60444 (patch)
treec4920d669241446466448d5bb4d14162cc2085db
parentd635b3fd3ca34143b203eacc4308ed35628ac2f8 (diff)
Accept comma-separated list of sort criteria
-rw-r--r--CHANGELOG.md9
-rw-r--r--man/man1/fzf.115
-rw-r--r--src/chunklist_test.go12
-rw-r--r--src/core.go22
-rw-r--r--src/item.go110
-rw-r--r--src/item_test.go29
-rw-r--r--src/merger.go2
-rw-r--r--src/merger_test.go2
-rw-r--r--src/options.go65
-rw-r--r--src/pattern.go2
-rw-r--r--src/terminal.go6
-rw-r--r--test/test_go.rb144
12 files changed, 298 insertions, 120 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1f4b70a..87f78273 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
CHANGELOG
=========
+0.11.2
+------
+
+- `--tiebreak` now accepts comma-separated list of sort criteria.
+ - Each criterion should appear only once in the list
+ - `index` is only allowed at the end of the list
+ - `index` is implicitly appended to the list when not specified
+ - Default is `length` (or equivalently `length,index`)
+
0.11.1
------
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index 416482a4..feca9728 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -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 "Dec 2015" "fzf 0.11.1" "fzf - a command-line fuzzy finder"
+.TH fzf 1 "Jan 2016" "fzf 0.11.2" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -68,8 +68,8 @@ Reverse the order of the input
e.g. \fBhistory | fzf --tac --no-sort\fR
.RE
.TP
-.BI "--tiebreak=" "CRI"
-Sort criterion to use when the scores are tied
+.BI "--tiebreak=" "CRI[,..]"
+Comma-separated list of sort criteria to apply when the scores are tied.
.br
.R ""
.br
@@ -81,6 +81,15 @@ Sort criterion to use when the scores are tied
.br
.BR index " Prefers item that appeared earlier in the input stream"
.br
+.R ""
+.br
+- Each criterion should appear only once in the list
+.br
+- \fBindex\fR is only allowed at the end of the list
+.br
+- \fBindex\fR is implicitly appended to the list when not specified
+.br
+- Default is \fBlength\fR (or equivalently \fBlength\fR,index)
.SS Interface
.TP
.B "-m, --multi"
diff --git a/src/chunklist_test.go b/src/chunklist_test.go
index 26795ef2..6ddd336f 100644
--- a/src/chunklist_test.go
+++ b/src/chunklist_test.go
@@ -6,8 +6,11 @@ import (
)
func TestChunkList(t *testing.T) {
+ // FIXME global
+ sortCriteria = []criterion{byMatchLen, byLength, byIndex}
+
cl := NewChunkList(func(s []byte, i int) *Item {
- return &Item{text: []rune(string(s)), rank: Rank{0, 0, uint32(i * 2)}}
+ return &Item{text: []rune(string(s)), rank: buildEmptyRank(int32(i * 2))}
})
// Snapshot
@@ -36,8 +39,11 @@ func TestChunkList(t *testing.T) {
if len(*chunk1) != 2 {
t.Error("Snapshot should contain only two items")
}
- if string((*chunk1)[0].text) != "hello" || (*chunk1)[0].rank.index != 0 ||
- string((*chunk1)[1].text) != "world" || (*chunk1)[1].rank.index != 2 {
+ last := func(arr []int32) int32 {
+ return arr[len(arr)-1]
+ }
+ if string((*chunk1)[0].text) != "hello" || last((*chunk1)[0].rank) != 0 ||
+ string((*chunk1)[1].text) != "world" || last((*chunk1)[1].rank) != 2 {
t.Error("Invalid data")
}
if chunk1.IsFull() {
diff --git a/src/core.go b/src/core.go
index dcba7ecf..1906c508 100644
--- a/src/core.go
+++ b/src/core.go
@@ -52,7 +52,7 @@ func Run(opts *Options) {
initProcs()
sort := opts.Sort > 0
- rankTiebreak = opts.Tiebreak
+ sortCriteria = opts.Criteria
if opts.Version {
fmt.Println(version)
@@ -103,9 +103,9 @@ func Run(opts *Options) {
runes, colors := ansiProcessor(data)
return &Item{
text: runes,
- index: uint32(index),
+ index: int32(index),
colors: colors,
- rank: Rank{0, 0, uint32(index)}}
+ rank: buildEmptyRank(int32(index))}
})
} else {
chunkList = NewChunkList(func(data []byte, index int) *Item {
@@ -120,9 +120,9 @@ func Run(opts *Options) {
item := Item{
text: joinTokens(trans),
origText: &runes,
- index: uint32(index),
+ index: int32(index),
colors: nil,
- rank: Rank{0, 0, uint32(index)}}
+ rank: buildEmptyRank(int32(index))}
trimmed, colors := ansiProcessorRunes(item.text)
item.text = trimmed
@@ -141,9 +141,19 @@ func Run(opts *Options) {
}
// Matcher
+ forward := true
+ for _, cri := range opts.Criteria[1:] {
+ if cri == byEnd {
+ forward = false
+ break
+ }
+ if cri == byBegin {
+ break
+ }
+ }
patternBuilder := func(runes []rune) *Pattern {
return BuildPattern(
- opts.Fuzzy, opts.Extended, opts.Case, opts.Tiebreak != byEnd,
+ opts.Fuzzy, opts.Extended, opts.Case, forward,
opts.Nth, opts.Delimiter, runes)
}
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
diff --git a/src/item.go b/src/item.go
index 5ce25c71..a4fa609b 100644
--- a/src/item.go
+++ b/src/item.go
@@ -20,25 +20,35 @@ type Item struct {
text []rune
origText *[]rune
transformed []Token
- index uint32
+ index int32
offsets []Offset
colors []ansiOffset
- rank Rank
+ rank []int32
}
-// Rank is used to sort the search result
-type Rank struct {
- matchlen uint16
- tiebreak uint16
- index uint32
+// Sort criteria to use. Never changes once fzf is started.
+var sortCriteria []criterion
+
+func isRankValid(rank []int32) bool {
+ // Exclude ordinal index
+ for i := 0; i < len(rank)-1; i++ {
+ if rank[i] > 0 {
+ return true
+ }
+ }
+ return false
}
-// Tiebreak criterion to use. Never changes once fzf is started.
-var rankTiebreak tiebreak
+func buildEmptyRank(index int32) []int32 {
+ len := len(sortCriteria)
+ arr := make([]int32, len)
+ arr[len-1] = index
+ return arr
+}
// Rank calculates rank of the Item
-func (item *Item) Rank(cache bool) Rank {
- if cache && (item.rank.matchlen > 0 || item.rank.tiebreak > 0) {
+func (item *Item) Rank(cache bool) []int32 {
+ if cache && isRankValid(item.rank) {
return item.rank
}
matchlen := 0
@@ -64,32 +74,37 @@ func (item *Item) Rank(cache bool) Rank {
}
}
if matchlen == 0 {
- matchlen = math.MaxUint16
+ matchlen = math.MaxInt32
}
- var tiebreak uint16
- switch rankTiebreak {
- case byLength:
- // It is guaranteed that .transformed in not null in normal execution
- if item.transformed != nil {
- // If offsets is empty, lenSum will be 0, but we don't care
- tiebreak = uint16(lenSum)
- } else {
- tiebreak = uint16(len(item.text))
- }
- case byBegin:
- // We can't just look at item.offsets[0][0] because it can be an inverse term
- tiebreak = uint16(minBegin)
- case byEnd:
- if prevEnd > 0 {
- tiebreak = uint16(1 + len(item.text) - prevEnd)
- } else {
- // Empty offsets due to inverse terms.
- tiebreak = 1
+ rank := make([]int32, len(sortCriteria))
+ for idx, criterion := range sortCriteria {
+ var val int32
+ switch criterion {
+ case byMatchLen:
+ val = int32(matchlen)
+ case byLength:
+ // It is guaranteed that .transformed in not null in normal execution
+ if item.transformed != nil {
+ // If offsets is empty, lenSum will be 0, but we don't care
+ val = int32(lenSum)
+ } else {
+ val = int32(len(item.text))
+ }
+ case byBegin:
+ // We can't just look at item.offsets[0][0] because it can be an inverse term
+ val = int32(minBegin)
+ case byEnd:
+ if prevEnd > 0 {
+ val = int32(1 + len(item.text) - prevEnd)
+ } else {
+ // Empty offsets due to inverse terms.
+ val = 1
+ }
+ case byIndex:
+ val = item.index
}
- case byIndex:
- tiebreak = 1
+ rank[idx] = val
}
- rank := Rank{uint16(matchlen), tiebreak, item.index}
if cache {
item.rank = rank
}
@@ -254,18 +269,19 @@ func (a ByRelevanceTac) Less(i, j int) bool {
return compareRanks(irank, jrank, true)
}
-func compareRanks(irank Rank, jrank Rank, tac bool) bool {
- if irank.matchlen < jrank.matchlen {
- return true
- } else if irank.matchlen > jrank.matchlen {
- return false
- }
-
- if irank.tiebreak < jrank.tiebreak {
- return true
- } else if irank.tiebreak > jrank.tiebreak {
- return false
+func compareRanks(irank []int32, jrank []int32, tac bool) bool {
+ lastIdx := len(irank) - 1
+ for idx, left := range irank {
+ right := jrank[idx]
+ if tac && idx == lastIdx {
+ left = left * -1
+ right = right * -1
+ }
+ if left < right {
+ return true
+ } else if left > right {
+ return false
+ }
}
-
- return (irank.index <= jrank.index) != tac
+ return true
}
diff --git a/src/item_test.go b/src/item_test.go
index 50d6851e..f26f8370 100644
--- a/src/item_test.go
+++ b/src/item_test.go
@@ -23,27 +23,30 @@ func TestOffsetSort(t *testing.T) {
}
func TestRankComparison(t *testing.T) {
- if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, false) ||
- !compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
- !compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, false) ||
- !compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
+ if compareRanks([]int32{3, 0, 5}, []int32{2, 0, 7}, false) ||
+ !compareRanks([]int32{3, 0, 5}, []int32{3, 0, 6}, false) ||
+ !compareRanks([]int32{1, 2, 3}, []int32{1, 3, 2}, false) ||
+ !compareRanks([]int32{0, 0, 0}, []int32{0, 0, 0}, false) {
t.Error("Invalid order")
}
- if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, true) ||
- !compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
- !compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, true) ||
- !compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
+ if compareRanks([]int32{3, 0, 5}, []int32{2, 0, 7}, true) ||
+ !compareRanks([]int32{3, 0, 5}, []int32{3, 0, 6}, false) ||
+ !compareRanks([]int32{1, 2, 3}, []int32{1, 3, 2}, true) ||
+ !compareRanks([]int32{0, 0, 0}, []int32{0, 0, 0}, false) {
t.Error("Invalid order (tac)")
}
}
// Match length, string length, index
func TestItemRank(t *testing.T) {
+ // FIXME global
+ sortCriteria = []criterion{byMatchLen, byLength, byIndex}
+
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
item1 := Item{text: strs[0], index: 1, offsets: []Offset{}}
rank1 := item1.Rank(true)
- if rank1.matchlen != math.MaxUint16 || rank1.tiebreak != 3 || rank1.index != 1 {
+ if rank1[0] != math.MaxInt32 || rank1[1] != 3 || rank1[2] != 1 {
t.Error(item1.Rank(true))
}
// Only differ in index
@@ -63,10 +66,10 @@ func TestItemRank(t *testing.T) {
}
// Sort by relevance
- item3 := Item{text: strs[1], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
- item4 := Item{text: strs[1], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
- item5 := Item{text: strs[2], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
- item6 := Item{text: strs[2], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
+ item3 := Item{text: strs[1], rank: []int32{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
+ item4 := Item{text: strs[1], rank: []int32{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
+ item5 := Item{text: strs[2], rank: []int32{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
+ item6 := Item{text: strs[2], rank: []int32{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
items = []*Item{&item1, &item2, &item3, &item4, &item5, &item6}
sort.Sort(ByRelevance(items))
if items[0] != &item6 || items[1] != &item4 ||
diff --git a/src/merger.go b/src/merger.go
index cce8a947..26ed17b5 100644
--- a/src/merger.go
+++ b/src/merger.go
@@ -88,7 +88,7 @@ func (mg *Merger) cacheable() bool {
func (mg *Merger) mergedGet(idx int) *Item {
for i := len(mg.merged); i <= idx; i++ {
- minRank := Rank{0, 0, 0}
+ minRank := buildEmptyRank(0)
minIdx := -1
for listIdx, list := range mg.lists {
cursor := mg.cursors[listIdx]
diff --git a/src/merger_test.go b/src/merger_test.go
index b7a2993a..34efc84d 100644
--- a/src/merger_test.go
+++ b/src/merger_test.go
@@ -23,7 +23,7 @@ func randItem() *Item {
}
return &Item{
text: []rune(str),
- index: rand.Uint32(),
+ index: rand.Int31(),
offsets: offsets}
}
diff --git a/src/options.go b/src/options.go
index ad05213c..30e00160 100644
--- a/src/options.go
+++ b/src/options.go
@@ -27,7 +27,8 @@ const usage = `usage: fzf [options]
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
- --tiebreak=CRITERION Sort criterion when the scores are tied;
+ --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
+ when the scores are tied;
[length|begin|end|index] (default: length)
Interface
@@ -75,10 +76,11 @@ const (
)
// Sort criteria
-type tiebreak int
+type criterion int
const (
- byLength tiebreak = iota
+ byMatchLen criterion = iota
+ byLength
byBegin
byEnd
byIndex
@@ -98,7 +100,7 @@ type Options struct {
Delimiter Delimiter
Sort int
Tac bool
- Tiebreak tiebreak
+ Criteria []criterion
Multi bool
Ansi bool
Mouse bool
@@ -145,7 +147,7 @@ func defaultOptions() *Options {
Delimiter: Delimiter{},
Sort: 1000,
Tac: false,
- Tiebreak: byLength,
+ Criteria: []criterion{byMatchLen, byLength, byIndex},
Multi: false,
Ansi: false,
Mouse: true,
@@ -361,20 +363,43 @@ func parseKeyChords(str string, message string) map[int]string {
return chords
}
-func parseTiebreak(str string) tiebreak {
- switch strings.ToLower(str) {
- case "length":
- return byLength
- case "index":
- return byIndex
- case "begin":
- return byBegin
- case "end":
- return byEnd
- default:
- errorExit("invalid sort criterion: " + str)
+func parseTiebreak(str string) []criterion {
+ criteria := []criterion{byMatchLen}
+ hasIndex := false
+ hasLength := false
+ hasBegin := false
+ hasEnd := false
+ check := func(notExpected *bool, name string) {
+ if *notExpected {
+ errorExit("duplicate sort criteria: " + name)
+ }
+ if hasIndex {
+ errorExit("index should be the last criterion")
+ }
+ *notExpected = true
+ }
+ for _, str := range strings.Split(strings.ToLower(str), ",") {
+ switch str {
+ case "index":
+ check(&hasIndex, "index")
+ criteria = append(criteria, byIndex)
+ case "length":
+ check(&hasLength, "length")
+ criteria = append(criteria, byLength)
+ case "begin":
+ check(&hasBegin, "begin")
+ criteria = append(criteria, byBegin)
+ case "end":
+ check(&hasEnd, "end")
+ criteria = append(criteria, byEnd)
+ default:
+ errorExit("invalid sort criterion: " + str)
+ }
+ }
+ if !hasIndex {
+ criteria = append(criteria, byIndex)
}
- return byLength
+ return criteria
}
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
@@ -715,7 +740,7 @@ func parseOptions(opts *Options, allArgs []string) {
case "--expect":
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
case "--tiebreak":
- opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
+ opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
case "--bind":
keymap, opts.Execmap, opts.ToggleSort =
parseKeymap(keymap, opts.Execmap, opts.ToggleSort, nextString(allArgs, &i, "bind expression required"))
@@ -850,7 +875,7 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--expect="); match {
opts.Expect = parseKeyChords(value, "key names required")
} else if match, value := optString(arg, "--tiebreak="); match {
- opts.Tiebreak = parseTiebreak(value)
+ opts.Criteria = parseTiebreak(value)
} else if match, value := optString(arg, "--color="); match {
opts.Theme = parseTheme(opts.Theme, value)
} else if match, value := optString(arg, "--bind="); match {
diff --git a/src/pattern.go b/src/pattern.go
index 2abcf439..4c61b87d 100644
--- a/src/pattern.go
+++ b/src/pattern.go
@@ -309,7 +309,7 @@ func dupItem(item *Item, offsets []Offset) *Item {
index: item.index,
offsets: offsets,
colors: item.colors,
- rank: Rank{0, 0, item.index}}
+ rank: buildEmptyRank(item.index)}
}
func (p *Pattern) basicMatch(item *Item) (int, int, int) {
diff --git a/src/terminal.go b/src/terminal.go
index a19f41d8..c9b80565 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -50,7 +50,7 @@ type Terminal struct {
progress int
reading bool
merger *Merger
- selected map[uint32]selectedItem
+ selected map[int32]selectedItem
reqBox *util.EventBox
eventBox *util.EventBox
mutex sync.Mutex
@@ -223,7 +223,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
ansi: opts.Ansi,
reading: true,
merger: EmptyMerger,
- selected: make(map[uint32]selectedItem),
+ selected: make(map[int32]selectedItem),
reqBox: util.NewEventBox(),
eventBox: eventBox,
mutex: sync.Mutex{},
@@ -466,7 +466,7 @@ func (t *Terminal) printHeader() {
text: []rune(trimmed),
index: 0,
colors: colors,
- rank: Rank{0, 0, 0}}
+ rank: buildEmptyRank(0)}
t.move(line, 2, true)
t.printHighlighted(item, false, C.ColHeader, 0, false)
diff --git a/test/test_go.rb b/test/test_go.rb
index fcf43401..85e3d838 100644
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -459,8 +459,8 @@ class TestGoFZF < TestBase
def test_unicode_case
writelines tempname, %w[строКА1 СТРОКА2 строка3 Строка4]
- assert_equal %w[СТРОКА2 Строка4], `cat #{tempname} | #{FZF} -fС`.split($/)
- assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `cat #{tempname} | #{FZF} -fс`.split($/)
+ assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.split($/)
+ assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.split($/)
end
def test_tiebreak
@@ -472,7 +472,7 @@ class TestGoFZF < TestBase
]
writelines tempname, input
- assert_equal input, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=index`.split($/)
+ assert_equal input, `#{FZF} -ffoobar --tiebreak=index < #{tempname}`.split($/)
by_length = %w[
----foobar--
@@ -480,8 +480,8 @@ class TestGoFZF < TestBase
-------foobar-
--foobar--------
]
- assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar`.split($/)
- assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=length`.split($/)
+ assert_equal by_length, `#{FZF} -ffoobar < #{tempname}`.split($/)
+ assert_equal by_length, `#{FZF} -ffoobar --tiebreak=length < #{tempname}`.split($/)
by_begin = %w[
--foobar--------
@@ -489,17 +489,117 @@ class TestGoFZF < TestBase
-----foobar---
-------foobar-
]
- assert_equal by_begin, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=begin`.split($/)
- assert_equal by_begin, `cat #{tempname} | #{FZF} -f"!z foobar" -x --tiebreak begin`.split($/)
+ assert_equal by_begin, `#{FZF} -ffoobar --tiebreak=begin < #{tempname}`.split($/)
+ assert_equal by_begin, `#{FZF} -f"!z foobar" -x --tiebreak begin < #{tempname}`.split($/)
assert_equal %w[
-------foobar-
----foobar--
-----foobar---
--foobar--------
- ], `cat #{tempname} | #{FZF} -ffoobar --tiebreak end`.split($/)
+ ], `#{FZF} -ffoobar --tiebreak end < #{tempname}`.split($/)
- assert_equal input, `cat #{tempname} | #{FZF} -f"!z" -x --tiebreak end`.split($/)
+ assert_equal input, `#{FZF} -f"!z" -x --tiebreak end < #{tempname}`.split($/)
+ end
+
+ # Since 0.11.2
+ def test_tiebreak_list
+ input = %w[
+ f-o-o-b-a-r
+ foobar----
+ --foobar
+ ----foobar
+ foobar--
+ --foobar--
+ foobar
+ ]
+ writelines tempname, input
+
+ assert_equal %w[
+ foobar----
+ --foobar
+ ----foobar
+ foobar--
+ --foobar--
+ foobar
+ f-o-o-b-a-r
+ ], `#{FZF} -ffb --tiebreak=index < #{tempname}`.split($/)
+
+ by_length = %w[
+ foobar
+ --foobar
+ foobar--
+ foobar----
+ ----foobar
+ --foobar--
+ f-o-o-b-a-r
+ ]
+ assert_equal by_length, `#{FZF} -ffb < #{tempname}`.split($/)
+ assert_equal by_length, `#{FZF} -ffb --tiebreak=length < #{tempname}`.split($/)
+
+ assert_equal %w[
+ foobar
+ foobar--
+ --foobar
+ foobar----
+ --foobar--
+ ----foobar
+ f-o-o-b-a-r
+ ], `#{FZF} -ffb --tiebreak=length,begin < #{tempname}`.split($/)
+
+ assert_equal %w[
+ foobar
+ --foobar
+ foobar--
+ ----foobar
+ --foobar--
+ foobar----
+ f-o-o-b-a-r
+ ], `#{FZF} -ffb --tiebreak=length,end < #{tempname}`.split($/)
+
+ assert_equal %w[
+ foobar----
+ foobar--
+ foobar
+ --foobar
+ --foobar--
+ ----foobar
+ f-o-o-b-a-r
+ ], `#{FZF} -ffb --tiebreak=begin < #{tempname}`.split($/)
+
+ by_begin_end = %w[
+ foobar
+ foobar--
+ foobar----
+ --foobar
+ --foobar--
+ ----foobar
+ f-o-o-b-a-r
+ ]
+ assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=begin,length < #{tempname}`.split($/)
+ assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=begin,end < #{tempname}`.split($/)
+
+ assert_equal %w[
+ --foobar
+ ----foobar
+ foobar
+ foobar--
+ --foobar--
+ foobar----
+ f-o-o-b-a-r
+ ], `#{FZF} -ffb --tiebreak=end < #{tempname}`.split($/)
+
+ by_begin_end = %w[
+ foobar
+ --foobar
+ ----foobar
+ foobar--
+ --foobar--
+ foobar----
+ f-o-o-b-a-r
+ ]
+ assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=end,begin < #{tempname}`.split($/)
+ assert_equal by_begin_end, `#{FZF} -ffb --tiebreak=end,length < #{tempname}`.split($/)
end
def test_tiebreak_length_with_nth
@@ -517,7 +617,7 @@ class TestGoFZF < TestBase
123:hello
1234567:h
]
- assert_equal output, `cat #{tempname} | #{FZF} -fh`.split($/)
+ assert_equal output, `#{FZF} -fh < #{tempname}`.split($/)
output = %w[
1234567:h
@@ -525,7 +625,7 @@ class TestGoFZF < TestBase
1:hell
123:hello
]
- assert_equal output, `cat #{tempname} | #{FZF} -fh -n2 -d:`.split($/)
+ assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.split($/)
end
def test_tiebreak_length_with_nth_trim_length
@@ -544,7 +644,7 @@ class TestGoFZF < TestBase
"apple juice bottle 1",
"apple ui bottle 2",
]
- assert_equal output, `cat #{tempname} | #{FZF} -fa -n1`.split($/)
+ assert_equal output, `#{FZF} -fa -n1 < #{tempname}`.split($/)
# len(1 ~ 2)
output = [
@@ -553,7 +653,7 @@ class TestGoFZF < TestBase
"apple juice bottle 1",
"app ice bottle 3",
]
- assert_equal output, `cat #{tempname} | #{FZF} -fai -n1..2`.split($/)
+ assert_equal output, `#{FZF} -fai -n1..2 < #{tempname}`.split($/)
# len(1) + len(2)
output = [
@@ -562,7 +662,7 @@ class TestGoFZF < TestBase
"apple ui bottle 2",
"apple juice bottle 1",
]
- assert_equal output, `cat #{tempname} | #{FZF} -x -f"a i" -n1,2`.split($/)
+ assert_equal output, `#{FZF} -x -f"a i" -n1,2 < #{tempname}`.split($/)
# len(2)
output = [
@@ -571,8 +671,8 @@ class TestGoFZF < TestBase
"app ice bottle 3",
"apple juice bottle 1",
]
- assert_equal output, `cat #{tempname} | #{FZF} -fi -n2`.split($/)
- assert_equal output, `cat #{tempname} | #{FZF} -fi -n2,1..2`.split($/)
+ assert_equal output, `#{FZF} -fi -n2 < #{tempname}`.split($/)
+ assert_equal output, `#{FZF} -fi -n2,1..2 < #{tempname}`.split($/)
end
def test_tiebreak_end_backward_scan
@@ -582,8 +682,8 @@ class TestGoFZF < TestBase
]
writelines tempname, input
- assert_equal input.reverse, `cat #{tempname} | #{FZF} -f fb`.split($/)
- assert_equal input, `cat #{tempname} | #{FZF} -f fb --tiebreak=end`.split($/)
+ assert_equal input.reverse, `#{FZF} -f fb < #{tempname}`.split($/)
+ assert_equal input, `#{FZF} -f fb --tiebreak=end < #{tempname}`.split($/)
end
def test_invalid_cache
@@ -613,7 +713,7 @@ class TestGoFZF < TestBase
File.open(tempname, 'w') do |f|
f << data
end
- assert_equal data, `cat #{tempname} | #{FZF} -f .`.chomp
+ assert_equal data, `#{FZF} -f . < #{tempname}`.chomp
end
def test_read0
@@ -888,18 +988,18 @@ class TestGoFZF < TestBase
def test_with_nth
writelines tempname, ['hello world ', 'byebye']
- assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1`.chomp
+ assert_equal 'hello world ', `#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp
end
def test_with_nth_ansi
writelines tempname, ["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye']
- assert_equal 'hello world ', `cat #{tempname} | #{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi`.chomp
+ assert_equal 'hello world ', `#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi < #{tempname}`.chomp
end
def test_with_nth_no_ansi
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
writelines tempname, [src, 'byebye']
- assert_equal src, `cat #{tempname} | #{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi`.chomp
+ assert_equal src, `#{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{tempname}`.chomp
end
def test_exit_0_exit_code