Accept comma-separated list of sort criteria
This commit is contained in:
parent
d635b3fd3c
commit
1d2d32c847
@ -1,6 +1,15 @@
|
|||||||
CHANGELOG
|
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
|
0.11.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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
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
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@ -68,8 +68,8 @@ Reverse the order of the input
|
|||||||
e.g. \fBhistory | fzf --tac --no-sort\fR
|
e.g. \fBhistory | fzf --tac --no-sort\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--tiebreak=" "CRI"
|
.BI "--tiebreak=" "CRI[,..]"
|
||||||
Sort criterion to use when the scores are tied
|
Comma-separated list of sort criteria to apply when the scores are tied.
|
||||||
.br
|
.br
|
||||||
.R ""
|
.R ""
|
||||||
.br
|
.br
|
||||||
@ -81,6 +81,15 @@ Sort criterion to use when the scores are tied
|
|||||||
.br
|
.br
|
||||||
.BR index " Prefers item that appeared earlier in the input stream"
|
.BR index " Prefers item that appeared earlier in the input stream"
|
||||||
.br
|
.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
|
.SS Interface
|
||||||
.TP
|
.TP
|
||||||
.B "-m, --multi"
|
.B "-m, --multi"
|
||||||
|
@ -6,8 +6,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestChunkList(t *testing.T) {
|
func TestChunkList(t *testing.T) {
|
||||||
|
// FIXME global
|
||||||
|
sortCriteria = []criterion{byMatchLen, byLength, byIndex}
|
||||||
|
|
||||||
cl := NewChunkList(func(s []byte, i int) *Item {
|
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
|
// Snapshot
|
||||||
@ -36,8 +39,11 @@ func TestChunkList(t *testing.T) {
|
|||||||
if len(*chunk1) != 2 {
|
if len(*chunk1) != 2 {
|
||||||
t.Error("Snapshot should contain only two items")
|
t.Error("Snapshot should contain only two items")
|
||||||
}
|
}
|
||||||
if string((*chunk1)[0].text) != "hello" || (*chunk1)[0].rank.index != 0 ||
|
last := func(arr []int32) int32 {
|
||||||
string((*chunk1)[1].text) != "world" || (*chunk1)[1].rank.index != 2 {
|
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")
|
t.Error("Invalid data")
|
||||||
}
|
}
|
||||||
if chunk1.IsFull() {
|
if chunk1.IsFull() {
|
||||||
|
22
src/core.go
22
src/core.go
@ -52,7 +52,7 @@ func Run(opts *Options) {
|
|||||||
initProcs()
|
initProcs()
|
||||||
|
|
||||||
sort := opts.Sort > 0
|
sort := opts.Sort > 0
|
||||||
rankTiebreak = opts.Tiebreak
|
sortCriteria = opts.Criteria
|
||||||
|
|
||||||
if opts.Version {
|
if opts.Version {
|
||||||
fmt.Println(version)
|
fmt.Println(version)
|
||||||
@ -103,9 +103,9 @@ func Run(opts *Options) {
|
|||||||
runes, colors := ansiProcessor(data)
|
runes, colors := ansiProcessor(data)
|
||||||
return &Item{
|
return &Item{
|
||||||
text: runes,
|
text: runes,
|
||||||
index: uint32(index),
|
index: int32(index),
|
||||||
colors: colors,
|
colors: colors,
|
||||||
rank: Rank{0, 0, uint32(index)}}
|
rank: buildEmptyRank(int32(index))}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
||||||
@ -120,9 +120,9 @@ func Run(opts *Options) {
|
|||||||
item := Item{
|
item := Item{
|
||||||
text: joinTokens(trans),
|
text: joinTokens(trans),
|
||||||
origText: &runes,
|
origText: &runes,
|
||||||
index: uint32(index),
|
index: int32(index),
|
||||||
colors: nil,
|
colors: nil,
|
||||||
rank: Rank{0, 0, uint32(index)}}
|
rank: buildEmptyRank(int32(index))}
|
||||||
|
|
||||||
trimmed, colors := ansiProcessorRunes(item.text)
|
trimmed, colors := ansiProcessorRunes(item.text)
|
||||||
item.text = trimmed
|
item.text = trimmed
|
||||||
@ -141,9 +141,19 @@ func Run(opts *Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
|
forward := true
|
||||||
|
for _, cri := range opts.Criteria[1:] {
|
||||||
|
if cri == byEnd {
|
||||||
|
forward = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if cri == byBegin {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(
|
return BuildPattern(
|
||||||
opts.Fuzzy, opts.Extended, opts.Case, opts.Tiebreak != byEnd,
|
opts.Fuzzy, opts.Extended, opts.Case, forward,
|
||||||
opts.Nth, opts.Delimiter, runes)
|
opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
|
||||||
|
110
src/item.go
110
src/item.go
@ -20,25 +20,35 @@ type Item struct {
|
|||||||
text []rune
|
text []rune
|
||||||
origText *[]rune
|
origText *[]rune
|
||||||
transformed []Token
|
transformed []Token
|
||||||
index uint32
|
index int32
|
||||||
offsets []Offset
|
offsets []Offset
|
||||||
colors []ansiOffset
|
colors []ansiOffset
|
||||||
rank Rank
|
rank []int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rank is used to sort the search result
|
// Sort criteria to use. Never changes once fzf is started.
|
||||||
type Rank struct {
|
var sortCriteria []criterion
|
||||||
matchlen uint16
|
|
||||||
tiebreak uint16
|
func isRankValid(rank []int32) bool {
|
||||||
index uint32
|
// 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.
|
func buildEmptyRank(index int32) []int32 {
|
||||||
var rankTiebreak tiebreak
|
len := len(sortCriteria)
|
||||||
|
arr := make([]int32, len)
|
||||||
|
arr[len-1] = index
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
// Rank calculates rank of the Item
|
// Rank calculates rank of the Item
|
||||||
func (item *Item) Rank(cache bool) Rank {
|
func (item *Item) Rank(cache bool) []int32 {
|
||||||
if cache && (item.rank.matchlen > 0 || item.rank.tiebreak > 0) {
|
if cache && isRankValid(item.rank) {
|
||||||
return item.rank
|
return item.rank
|
||||||
}
|
}
|
||||||
matchlen := 0
|
matchlen := 0
|
||||||
@ -64,32 +74,37 @@ func (item *Item) Rank(cache bool) Rank {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if matchlen == 0 {
|
if matchlen == 0 {
|
||||||
matchlen = math.MaxUint16
|
matchlen = math.MaxInt32
|
||||||
}
|
}
|
||||||
var tiebreak uint16
|
rank := make([]int32, len(sortCriteria))
|
||||||
switch rankTiebreak {
|
for idx, criterion := range sortCriteria {
|
||||||
case byLength:
|
var val int32
|
||||||
// It is guaranteed that .transformed in not null in normal execution
|
switch criterion {
|
||||||
if item.transformed != nil {
|
case byMatchLen:
|
||||||
// If offsets is empty, lenSum will be 0, but we don't care
|
val = int32(matchlen)
|
||||||
tiebreak = uint16(lenSum)
|
case byLength:
|
||||||
} else {
|
// It is guaranteed that .transformed in not null in normal execution
|
||||||
tiebreak = uint16(len(item.text))
|
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 byBegin:
|
rank[idx] = val
|
||||||
// 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
|
|
||||||
}
|
|
||||||
case byIndex:
|
|
||||||
tiebreak = 1
|
|
||||||
}
|
}
|
||||||
rank := Rank{uint16(matchlen), tiebreak, item.index}
|
|
||||||
if cache {
|
if cache {
|
||||||
item.rank = rank
|
item.rank = rank
|
||||||
}
|
}
|
||||||
@ -254,18 +269,19 @@ func (a ByRelevanceTac) Less(i, j int) bool {
|
|||||||
return compareRanks(irank, jrank, true)
|
return compareRanks(irank, jrank, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareRanks(irank Rank, jrank Rank, tac bool) bool {
|
func compareRanks(irank []int32, jrank []int32, tac bool) bool {
|
||||||
if irank.matchlen < jrank.matchlen {
|
lastIdx := len(irank) - 1
|
||||||
return true
|
for idx, left := range irank {
|
||||||
} else if irank.matchlen > jrank.matchlen {
|
right := jrank[idx]
|
||||||
return false
|
if tac && idx == lastIdx {
|
||||||
|
left = left * -1
|
||||||
|
right = right * -1
|
||||||
|
}
|
||||||
|
if left < right {
|
||||||
|
return true
|
||||||
|
} else if left > right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
if irank.tiebreak < jrank.tiebreak {
|
|
||||||
return true
|
|
||||||
} else if irank.tiebreak > jrank.tiebreak {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return (irank.index <= jrank.index) != tac
|
|
||||||
}
|
}
|
||||||
|
@ -23,27 +23,30 @@ func TestOffsetSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRankComparison(t *testing.T) {
|
func TestRankComparison(t *testing.T) {
|
||||||
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, false) ||
|
if compareRanks([]int32{3, 0, 5}, []int32{2, 0, 7}, false) ||
|
||||||
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
|
!compareRanks([]int32{3, 0, 5}, []int32{3, 0, 6}, false) ||
|
||||||
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, false) ||
|
!compareRanks([]int32{1, 2, 3}, []int32{1, 3, 2}, false) ||
|
||||||
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
|
!compareRanks([]int32{0, 0, 0}, []int32{0, 0, 0}, false) {
|
||||||
t.Error("Invalid order")
|
t.Error("Invalid order")
|
||||||
}
|
}
|
||||||
|
|
||||||
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, true) ||
|
if compareRanks([]int32{3, 0, 5}, []int32{2, 0, 7}, true) ||
|
||||||
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
|
!compareRanks([]int32{3, 0, 5}, []int32{3, 0, 6}, false) ||
|
||||||
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, true) ||
|
!compareRanks([]int32{1, 2, 3}, []int32{1, 3, 2}, true) ||
|
||||||
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
|
!compareRanks([]int32{0, 0, 0}, []int32{0, 0, 0}, false) {
|
||||||
t.Error("Invalid order (tac)")
|
t.Error("Invalid order (tac)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match length, string length, index
|
// Match length, string length, index
|
||||||
func TestItemRank(t *testing.T) {
|
func TestItemRank(t *testing.T) {
|
||||||
|
// FIXME global
|
||||||
|
sortCriteria = []criterion{byMatchLen, byLength, byIndex}
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||||
item1 := Item{text: strs[0], index: 1, offsets: []Offset{}}
|
item1 := Item{text: strs[0], index: 1, offsets: []Offset{}}
|
||||||
rank1 := item1.Rank(true)
|
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))
|
t.Error(item1.Rank(true))
|
||||||
}
|
}
|
||||||
// Only differ in index
|
// Only differ in index
|
||||||
@ -63,10 +66,10 @@ func TestItemRank(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort by relevance
|
// Sort by relevance
|
||||||
item3 := Item{text: strs[1], rank: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 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: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 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: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 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: Rank{0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 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}
|
items = []*Item{&item1, &item2, &item3, &item4, &item5, &item6}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if items[0] != &item6 || items[1] != &item4 ||
|
if items[0] != &item6 || items[1] != &item4 ||
|
||||||
|
@ -88,7 +88,7 @@ func (mg *Merger) cacheable() bool {
|
|||||||
|
|
||||||
func (mg *Merger) mergedGet(idx int) *Item {
|
func (mg *Merger) mergedGet(idx int) *Item {
|
||||||
for i := len(mg.merged); i <= idx; i++ {
|
for i := len(mg.merged); i <= idx; i++ {
|
||||||
minRank := Rank{0, 0, 0}
|
minRank := buildEmptyRank(0)
|
||||||
minIdx := -1
|
minIdx := -1
|
||||||
for listIdx, list := range mg.lists {
|
for listIdx, list := range mg.lists {
|
||||||
cursor := mg.cursors[listIdx]
|
cursor := mg.cursors[listIdx]
|
||||||
|
@ -23,7 +23,7 @@ func randItem() *Item {
|
|||||||
}
|
}
|
||||||
return &Item{
|
return &Item{
|
||||||
text: []rune(str),
|
text: []rune(str),
|
||||||
index: rand.Uint32(),
|
index: rand.Int31(),
|
||||||
offsets: offsets}
|
offsets: offsets}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,8 @@ const usage = `usage: fzf [options]
|
|||||||
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
||||||
+s, --no-sort Do not sort the result
|
+s, --no-sort Do not sort the result
|
||||||
--tac Reverse the order of the input
|
--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)
|
[length|begin|end|index] (default: length)
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
@ -75,10 +76,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Sort criteria
|
// Sort criteria
|
||||||
type tiebreak int
|
type criterion int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
byLength tiebreak = iota
|
byMatchLen criterion = iota
|
||||||
|
byLength
|
||||||
byBegin
|
byBegin
|
||||||
byEnd
|
byEnd
|
||||||
byIndex
|
byIndex
|
||||||
@ -98,7 +100,7 @@ type Options struct {
|
|||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
Sort int
|
Sort int
|
||||||
Tac bool
|
Tac bool
|
||||||
Tiebreak tiebreak
|
Criteria []criterion
|
||||||
Multi bool
|
Multi bool
|
||||||
Ansi bool
|
Ansi bool
|
||||||
Mouse bool
|
Mouse bool
|
||||||
@ -145,7 +147,7 @@ func defaultOptions() *Options {
|
|||||||
Delimiter: Delimiter{},
|
Delimiter: Delimiter{},
|
||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Tac: false,
|
Tac: false,
|
||||||
Tiebreak: byLength,
|
Criteria: []criterion{byMatchLen, byLength, byIndex},
|
||||||
Multi: false,
|
Multi: false,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
@ -361,20 +363,43 @@ func parseKeyChords(str string, message string) map[int]string {
|
|||||||
return chords
|
return chords
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTiebreak(str string) tiebreak {
|
func parseTiebreak(str string) []criterion {
|
||||||
switch strings.ToLower(str) {
|
criteria := []criterion{byMatchLen}
|
||||||
case "length":
|
hasIndex := false
|
||||||
return byLength
|
hasLength := false
|
||||||
case "index":
|
hasBegin := false
|
||||||
return byIndex
|
hasEnd := false
|
||||||
case "begin":
|
check := func(notExpected *bool, name string) {
|
||||||
return byBegin
|
if *notExpected {
|
||||||
case "end":
|
errorExit("duplicate sort criteria: " + name)
|
||||||
return byEnd
|
}
|
||||||
default:
|
if hasIndex {
|
||||||
errorExit("invalid sort criterion: " + str)
|
errorExit("index should be the last criterion")
|
||||||
|
}
|
||||||
|
*notExpected = true
|
||||||
}
|
}
|
||||||
return byLength
|
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 criteria
|
||||||
}
|
}
|
||||||
|
|
||||||
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
|
func dupeTheme(theme *curses.ColorTheme) *curses.ColorTheme {
|
||||||
@ -715,7 +740,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--expect":
|
case "--expect":
|
||||||
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
|
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||||
case "--bind":
|
case "--bind":
|
||||||
keymap, opts.Execmap, opts.ToggleSort =
|
keymap, opts.Execmap, opts.ToggleSort =
|
||||||
parseKeymap(keymap, opts.Execmap, opts.ToggleSort, nextString(allArgs, &i, "bind expression required"))
|
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 {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
opts.Expect = parseKeyChords(value, "key names required")
|
opts.Expect = parseKeyChords(value, "key names required")
|
||||||
} else if match, value := optString(arg, "--tiebreak="); match {
|
} else if match, value := optString(arg, "--tiebreak="); match {
|
||||||
opts.Tiebreak = parseTiebreak(value)
|
opts.Criteria = parseTiebreak(value)
|
||||||
} else if match, value := optString(arg, "--color="); match {
|
} else if match, value := optString(arg, "--color="); match {
|
||||||
opts.Theme = parseTheme(opts.Theme, value)
|
opts.Theme = parseTheme(opts.Theme, value)
|
||||||
} else if match, value := optString(arg, "--bind="); match {
|
} else if match, value := optString(arg, "--bind="); match {
|
||||||
|
@ -309,7 +309,7 @@ func dupItem(item *Item, offsets []Offset) *Item {
|
|||||||
index: item.index,
|
index: item.index,
|
||||||
offsets: offsets,
|
offsets: offsets,
|
||||||
colors: item.colors,
|
colors: item.colors,
|
||||||
rank: Rank{0, 0, item.index}}
|
rank: buildEmptyRank(item.index)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item) (int, int, int) {
|
func (p *Pattern) basicMatch(item *Item) (int, int, int) {
|
||||||
|
@ -50,7 +50,7 @@ type Terminal struct {
|
|||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
merger *Merger
|
merger *Merger
|
||||||
selected map[uint32]selectedItem
|
selected map[int32]selectedItem
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
@ -223,7 +223,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
reading: true,
|
reading: true,
|
||||||
merger: EmptyMerger,
|
merger: EmptyMerger,
|
||||||
selected: make(map[uint32]selectedItem),
|
selected: make(map[int32]selectedItem),
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
@ -466,7 +466,7 @@ func (t *Terminal) printHeader() {
|
|||||||
text: []rune(trimmed),
|
text: []rune(trimmed),
|
||||||
index: 0,
|
index: 0,
|
||||||
colors: colors,
|
colors: colors,
|
||||||
rank: Rank{0, 0, 0}}
|
rank: buildEmptyRank(0)}
|
||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
t.printHighlighted(item, false, C.ColHeader, 0, false)
|
t.printHighlighted(item, false, C.ColHeader, 0, false)
|
||||||
|
144
test/test_go.rb
144
test/test_go.rb
@ -459,8 +459,8 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_unicode_case
|
def test_unicode_case
|
||||||
writelines tempname, %w[строКА1 СТРОКА2 строка3 Строка4]
|
writelines tempname, %w[строКА1 СТРОКА2 строка3 Строка4]
|
||||||
assert_equal %w[СТРОКА2 Строка4], `cat #{tempname} | #{FZF} -fС`.split($/)
|
assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.split($/)
|
||||||
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `cat #{tempname} | #{FZF} -fс`.split($/)
|
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak
|
def test_tiebreak
|
||||||
@ -472,7 +472,7 @@ class TestGoFZF < TestBase
|
|||||||
]
|
]
|
||||||
writelines tempname, input
|
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[
|
by_length = %w[
|
||||||
----foobar--
|
----foobar--
|
||||||
@ -480,8 +480,8 @@ class TestGoFZF < TestBase
|
|||||||
-------foobar-
|
-------foobar-
|
||||||
--foobar--------
|
--foobar--------
|
||||||
]
|
]
|
||||||
assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar`.split($/)
|
assert_equal by_length, `#{FZF} -ffoobar < #{tempname}`.split($/)
|
||||||
assert_equal by_length, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=length`.split($/)
|
assert_equal by_length, `#{FZF} -ffoobar --tiebreak=length < #{tempname}`.split($/)
|
||||||
|
|
||||||
by_begin = %w[
|
by_begin = %w[
|
||||||
--foobar--------
|
--foobar--------
|
||||||
@ -489,17 +489,117 @@ class TestGoFZF < TestBase
|
|||||||
-----foobar---
|
-----foobar---
|
||||||
-------foobar-
|
-------foobar-
|
||||||
]
|
]
|
||||||
assert_equal by_begin, `cat #{tempname} | #{FZF} -ffoobar --tiebreak=begin`.split($/)
|
assert_equal by_begin, `#{FZF} -ffoobar --tiebreak=begin < #{tempname}`.split($/)
|
||||||
assert_equal by_begin, `cat #{tempname} | #{FZF} -f"!z foobar" -x --tiebreak begin`.split($/)
|
assert_equal by_begin, `#{FZF} -f"!z foobar" -x --tiebreak begin < #{tempname}`.split($/)
|
||||||
|
|
||||||
assert_equal %w[
|
assert_equal %w[
|
||||||
-------foobar-
|
-------foobar-
|
||||||
----foobar--
|
----foobar--
|
||||||
-----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
|
end
|
||||||
|
|
||||||
def test_tiebreak_length_with_nth
|
def test_tiebreak_length_with_nth
|
||||||
@ -517,7 +617,7 @@ class TestGoFZF < TestBase
|
|||||||
123:hello
|
123:hello
|
||||||
1234567:h
|
1234567:h
|
||||||
]
|
]
|
||||||
assert_equal output, `cat #{tempname} | #{FZF} -fh`.split($/)
|
assert_equal output, `#{FZF} -fh < #{tempname}`.split($/)
|
||||||
|
|
||||||
output = %w[
|
output = %w[
|
||||||
1234567:h
|
1234567:h
|
||||||
@ -525,7 +625,7 @@ class TestGoFZF < TestBase
|
|||||||
1:hell
|
1:hell
|
||||||
123:hello
|
123:hello
|
||||||
]
|
]
|
||||||
assert_equal output, `cat #{tempname} | #{FZF} -fh -n2 -d:`.split($/)
|
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_length_with_nth_trim_length
|
def test_tiebreak_length_with_nth_trim_length
|
||||||
@ -544,7 +644,7 @@ class TestGoFZF < TestBase
|
|||||||
"apple juice bottle 1",
|
"apple juice bottle 1",
|
||||||
"apple ui bottle 2",
|
"apple ui bottle 2",
|
||||||
]
|
]
|
||||||
assert_equal output, `cat #{tempname} | #{FZF} -fa -n1`.split($/)
|
assert_equal output, `#{FZF} -fa -n1 < #{tempname}`.split($/)
|
||||||
|
|
||||||
# len(1 ~ 2)
|
# len(1 ~ 2)
|
||||||
output = [
|
output = [
|
||||||
@ -553,7 +653,7 @@ class TestGoFZF < TestBase
|
|||||||
"apple juice bottle 1",
|
"apple juice bottle 1",
|
||||||
"app ice bottle 3",
|
"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)
|
# len(1) + len(2)
|
||||||
output = [
|
output = [
|
||||||
@ -562,7 +662,7 @@ class TestGoFZF < TestBase
|
|||||||
"apple ui bottle 2",
|
"apple ui bottle 2",
|
||||||
"apple juice bottle 1",
|
"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)
|
# len(2)
|
||||||
output = [
|
output = [
|
||||||
@ -571,8 +671,8 @@ class TestGoFZF < TestBase
|
|||||||
"app ice bottle 3",
|
"app ice bottle 3",
|
||||||
"apple juice bottle 1",
|
"apple juice bottle 1",
|
||||||
]
|
]
|
||||||
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2`.split($/)
|
assert_equal output, `#{FZF} -fi -n2 < #{tempname}`.split($/)
|
||||||
assert_equal output, `cat #{tempname} | #{FZF} -fi -n2,1..2`.split($/)
|
assert_equal output, `#{FZF} -fi -n2,1..2 < #{tempname}`.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_tiebreak_end_backward_scan
|
def test_tiebreak_end_backward_scan
|
||||||
@ -582,8 +682,8 @@ class TestGoFZF < TestBase
|
|||||||
]
|
]
|
||||||
writelines tempname, input
|
writelines tempname, input
|
||||||
|
|
||||||
assert_equal input.reverse, `cat #{tempname} | #{FZF} -f fb`.split($/)
|
assert_equal input.reverse, `#{FZF} -f fb < #{tempname}`.split($/)
|
||||||
assert_equal input, `cat #{tempname} | #{FZF} -f fb --tiebreak=end`.split($/)
|
assert_equal input, `#{FZF} -f fb --tiebreak=end < #{tempname}`.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_cache
|
def test_invalid_cache
|
||||||
@ -613,7 +713,7 @@ class TestGoFZF < TestBase
|
|||||||
File.open(tempname, 'w') do |f|
|
File.open(tempname, 'w') do |f|
|
||||||
f << data
|
f << data
|
||||||
end
|
end
|
||||||
assert_equal data, `cat #{tempname} | #{FZF} -f .`.chomp
|
assert_equal data, `#{FZF} -f . < #{tempname}`.chomp
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_read0
|
def test_read0
|
||||||
@ -888,18 +988,18 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_with_nth
|
def test_with_nth
|
||||||
writelines tempname, ['hello world ', 'byebye']
|
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
|
end
|
||||||
|
|
||||||
def test_with_nth_ansi
|
def test_with_nth_ansi
|
||||||
writelines tempname, ["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye']
|
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
|
end
|
||||||
|
|
||||||
def test_with_nth_no_ansi
|
def test_with_nth_no_ansi
|
||||||
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
|
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
|
||||||
writelines tempname, [src, 'byebye']
|
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
|
end
|
||||||
|
|
||||||
def test_exit_0_exit_code
|
def test_exit_0_exit_code
|
||||||
|
Loading…
Reference in New Issue
Block a user