Accept comma-separated list of sort criteria

This commit is contained in:
Junegunn Choi 2016-01-13 03:07:42 +09:00
parent d635b3fd3c
commit 1d2d32c847
12 changed files with 298 additions and 120 deletions

View File

@ -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
------

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 "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"

View File

@ -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() {

View File

@ -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)

View File

@ -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 {
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
tiebreak = uint16(lenSum)
val = int32(lenSum)
} else {
tiebreak = uint16(len(item.text))
val = int32(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)
val = int32(minBegin)
case byEnd:
if prevEnd > 0 {
tiebreak = uint16(1 + len(item.text) - prevEnd)
val = int32(1 + len(item.text) - prevEnd)
} else {
// Empty offsets due to inverse terms.
tiebreak = 1
val = 1
}
case byIndex:
tiebreak = 1
val = item.index
}
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 {
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 irank.matchlen > jrank.matchlen {
} else if left > right {
return false
}
if irank.tiebreak < jrank.tiebreak {
}
return true
} else if irank.tiebreak > jrank.tiebreak {
return false
}
return (irank.index <= jrank.index) != tac
}

View File

@ -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 ||

View File

@ -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]

View File

@ -23,7 +23,7 @@ func randItem() *Item {
}
return &Item{
text: []rune(str),
index: rand.Uint32(),
index: rand.Int31(),
offsets: offsets}
}

View File

@ -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
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":
return byIndex
check(&hasIndex, "index")
criteria = append(criteria, byIndex)
case "length":
check(&hasLength, "length")
criteria = append(criteria, byLength)
case "begin":
return byBegin
check(&hasBegin, "begin")
criteria = append(criteria, byBegin)
case "end":
return byEnd
check(&hasEnd, "end")
criteria = append(criteria, byEnd)
default:
errorExit("invalid sort criterion: " + str)
}
return byLength
}
if !hasIndex {
criteria = append(criteria, byIndex)
}
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 {

View File

@ -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) {

View File

@ -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)

View File

@ -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