Add missing sources
This commit is contained in:
parent
62f6ff9d6c
commit
608c416207
259
src/result.go
Normal file
259
src/result.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/curses"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Offset holds two 32-bit integers denoting the offsets of a matched substring
|
||||||
|
type Offset [2]int32
|
||||||
|
|
||||||
|
type colorOffset struct {
|
||||||
|
offset [2]int32
|
||||||
|
color int
|
||||||
|
bold bool
|
||||||
|
index int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type rank struct {
|
||||||
|
index int32
|
||||||
|
// byMatchLen, byBonus, ...
|
||||||
|
points [5]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
item *Item
|
||||||
|
offsets []Offset
|
||||||
|
rank rank
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildResult(item *Item, offsets []Offset, bonus int, trimLen int) *Result {
|
||||||
|
if len(offsets) > 1 {
|
||||||
|
sort.Sort(ByOrder(offsets))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := Result{item: item, offsets: offsets, rank: rank{index: item.index}}
|
||||||
|
|
||||||
|
matchlen := 0
|
||||||
|
prevEnd := 0
|
||||||
|
minBegin := math.MaxInt32
|
||||||
|
numChars := item.text.Length()
|
||||||
|
for _, offset := range offsets {
|
||||||
|
begin := int(offset[0])
|
||||||
|
end := int(offset[1])
|
||||||
|
if prevEnd > begin {
|
||||||
|
begin = prevEnd
|
||||||
|
}
|
||||||
|
if end > prevEnd {
|
||||||
|
prevEnd = end
|
||||||
|
}
|
||||||
|
if end > begin {
|
||||||
|
if begin < minBegin {
|
||||||
|
minBegin = begin
|
||||||
|
}
|
||||||
|
matchlen += end - begin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, criterion := range sortCriteria {
|
||||||
|
var val uint16
|
||||||
|
switch criterion {
|
||||||
|
case byMatchLen:
|
||||||
|
if matchlen == 0 {
|
||||||
|
val = math.MaxUint16
|
||||||
|
} else {
|
||||||
|
val = util.AsUint16(matchlen)
|
||||||
|
}
|
||||||
|
case byBonus:
|
||||||
|
// Higher is better
|
||||||
|
val = math.MaxUint16 - util.AsUint16(bonus)
|
||||||
|
case byLength:
|
||||||
|
// If offsets is empty, trimLen will be 0, but we don't care
|
||||||
|
val = util.AsUint16(trimLen)
|
||||||
|
case byBegin:
|
||||||
|
// We can't just look at item.offsets[0][0] because it can be an inverse term
|
||||||
|
whitePrefixLen := 0
|
||||||
|
for idx := 0; idx < numChars; idx++ {
|
||||||
|
r := item.text.Get(idx)
|
||||||
|
whitePrefixLen = idx
|
||||||
|
if idx == minBegin || r != ' ' && r != '\t' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val = util.AsUint16(minBegin - whitePrefixLen)
|
||||||
|
case byEnd:
|
||||||
|
if prevEnd > 0 {
|
||||||
|
val = util.AsUint16(1 + numChars - prevEnd)
|
||||||
|
} else {
|
||||||
|
// Empty offsets due to inverse terms.
|
||||||
|
val = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.rank.points[idx] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort criteria to use. Never changes once fzf is started.
|
||||||
|
var sortCriteria []criterion
|
||||||
|
|
||||||
|
// Index returns ordinal index of the Item
|
||||||
|
func (result *Result) Index() int32 {
|
||||||
|
return result.item.index
|
||||||
|
}
|
||||||
|
|
||||||
|
func minRank() rank {
|
||||||
|
return rank{index: 0, points: [5]uint16{0, math.MaxUint16, 0, 0, 0}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (result *Result) colorOffsets(color int, bold bool, current bool) []colorOffset {
|
||||||
|
itemColors := result.item.Colors()
|
||||||
|
|
||||||
|
if len(itemColors) == 0 {
|
||||||
|
var offsets []colorOffset
|
||||||
|
for _, off := range result.offsets {
|
||||||
|
|
||||||
|
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, bold: bold})
|
||||||
|
}
|
||||||
|
return offsets
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find max column
|
||||||
|
var maxCol int32
|
||||||
|
for _, off := range result.offsets {
|
||||||
|
if off[1] > maxCol {
|
||||||
|
maxCol = off[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ansi := range itemColors {
|
||||||
|
if ansi.offset[1] > maxCol {
|
||||||
|
maxCol = ansi.offset[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cols := make([]int, maxCol)
|
||||||
|
|
||||||
|
for colorIndex, ansi := range itemColors {
|
||||||
|
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
||||||
|
cols[i] = colorIndex + 1 // XXX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, off := range result.offsets {
|
||||||
|
for i := off[0]; i < off[1]; i++ {
|
||||||
|
cols[i] = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort.Sort(ByOrder(offsets))
|
||||||
|
|
||||||
|
// Merge offsets
|
||||||
|
// ------------ ---- -- ----
|
||||||
|
// ++++++++ ++++++++++
|
||||||
|
// --++++++++-- --++++++++++---
|
||||||
|
curr := 0
|
||||||
|
start := 0
|
||||||
|
var colors []colorOffset
|
||||||
|
add := func(idx int) {
|
||||||
|
if curr != 0 && idx > start {
|
||||||
|
if curr == -1 {
|
||||||
|
colors = append(colors, colorOffset{
|
||||||
|
offset: [2]int32{int32(start), int32(idx)}, color: color, bold: bold})
|
||||||
|
} else {
|
||||||
|
ansi := itemColors[curr-1]
|
||||||
|
fg := ansi.color.fg
|
||||||
|
if fg == -1 {
|
||||||
|
if current {
|
||||||
|
fg = curses.CurrentFG
|
||||||
|
} else {
|
||||||
|
fg = curses.FG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bg := ansi.color.bg
|
||||||
|
if bg == -1 {
|
||||||
|
if current {
|
||||||
|
bg = curses.DarkBG
|
||||||
|
} else {
|
||||||
|
bg = curses.BG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colors = append(colors, colorOffset{
|
||||||
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
|
color: curses.PairFor(fg, bg),
|
||||||
|
bold: ansi.color.bold || bold})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for idx, col := range cols {
|
||||||
|
if col != curr {
|
||||||
|
add(idx)
|
||||||
|
start = idx
|
||||||
|
curr = col
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add(int(maxCol))
|
||||||
|
return colors
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByOrder is for sorting substring offsets
|
||||||
|
type ByOrder []Offset
|
||||||
|
|
||||||
|
func (a ByOrder) Len() int {
|
||||||
|
return len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByOrder) Swap(i, j int) {
|
||||||
|
a[i], a[j] = a[j], a[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByOrder) Less(i, j int) bool {
|
||||||
|
ioff := a[i]
|
||||||
|
joff := a[j]
|
||||||
|
return (ioff[0] < joff[0]) || (ioff[0] == joff[0]) && (ioff[1] <= joff[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByRelevance is for sorting Items
|
||||||
|
type ByRelevance []*Result
|
||||||
|
|
||||||
|
func (a ByRelevance) Len() int {
|
||||||
|
return len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByRelevance) Swap(i, j int) {
|
||||||
|
a[i], a[j] = a[j], a[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByRelevance) Less(i, j int) bool {
|
||||||
|
return compareRanks((*a[i]).rank, (*a[j]).rank, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByRelevanceTac is for sorting Items
|
||||||
|
type ByRelevanceTac []*Result
|
||||||
|
|
||||||
|
func (a ByRelevanceTac) Len() int {
|
||||||
|
return len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByRelevanceTac) Swap(i, j int) {
|
||||||
|
a[i], a[j] = a[j], a[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByRelevanceTac) Less(i, j int) bool {
|
||||||
|
return compareRanks((*a[i]).rank, (*a[j]).rank, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareRanks(irank rank, jrank rank, tac bool) bool {
|
||||||
|
for idx := 0; idx < 5; idx++ {
|
||||||
|
left := irank.points[idx]
|
||||||
|
right := jrank.points[idx]
|
||||||
|
if left < right {
|
||||||
|
return true
|
||||||
|
} else if left > right {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (irank.index <= jrank.index) != tac
|
||||||
|
}
|
114
src/result_test.go
Normal file
114
src/result_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/curses"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOffsetSort(t *testing.T) {
|
||||||
|
offsets := []Offset{
|
||||||
|
Offset{3, 5}, Offset{2, 7},
|
||||||
|
Offset{1, 3}, Offset{2, 9}}
|
||||||
|
sort.Sort(ByOrder(offsets))
|
||||||
|
|
||||||
|
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
|
||||||
|
offsets[1][0] != 2 || offsets[1][1] != 7 ||
|
||||||
|
offsets[2][0] != 2 || offsets[2][1] != 9 ||
|
||||||
|
offsets[3][0] != 3 || offsets[3][1] != 5 {
|
||||||
|
t.Error("Invalid order:", offsets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRankComparison(t *testing.T) {
|
||||||
|
rank := func(vals ...uint16) rank {
|
||||||
|
return rank{
|
||||||
|
points: [5]uint16{vals[0], 0, vals[1], vals[2], vals[3]},
|
||||||
|
index: int32(vals[4])}
|
||||||
|
}
|
||||||
|
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) ||
|
||||||
|
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
|
||||||
|
!compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), false) ||
|
||||||
|
!compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {
|
||||||
|
t.Error("Invalid order")
|
||||||
|
}
|
||||||
|
|
||||||
|
if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), true) ||
|
||||||
|
!compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) ||
|
||||||
|
!compareRanks(rank(1, 2, 0, 0, 3), rank(1, 3, 0, 0, 2), true) ||
|
||||||
|
!compareRanks(rank(0, 0, 0, 0, 0), rank(0, 0, 0, 0, 0), false) {
|
||||||
|
t.Error("Invalid order (tac)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match length, string length, index
|
||||||
|
func TestResultRank(t *testing.T) {
|
||||||
|
// FIXME global
|
||||||
|
sortCriteria = []criterion{byMatchLen, byBonus, byLength}
|
||||||
|
|
||||||
|
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
||||||
|
item1 := buildResult(&Item{text: util.RunesToChars(strs[0]), index: 1}, []Offset{}, 2, 3)
|
||||||
|
if item1.rank.points[0] != math.MaxUint16 || item1.rank.points[1] != math.MaxUint16-2 || item1.rank.points[2] != 3 || item1.item.index != 1 {
|
||||||
|
t.Error(item1.rank)
|
||||||
|
}
|
||||||
|
// Only differ in index
|
||||||
|
item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2, 3)
|
||||||
|
|
||||||
|
items := []*Result{item1, item2}
|
||||||
|
sort.Sort(ByRelevance(items))
|
||||||
|
if items[0] != item2 || items[1] != item1 {
|
||||||
|
t.Error(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
items = []*Result{item2, item1, item1, item2}
|
||||||
|
sort.Sort(ByRelevance(items))
|
||||||
|
if items[0] != item2 || items[1] != item2 ||
|
||||||
|
items[2] != item1 || items[3] != item1 {
|
||||||
|
t.Error(items, item1, item1.item.index, item2, item2.item.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by relevance
|
||||||
|
item3 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 0, 0)
|
||||||
|
item4 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 0, 0)
|
||||||
|
item5 := buildResult(&Item{index: 2}, []Offset{Offset{1, 3}, Offset{5, 7}}, 0, 0)
|
||||||
|
item6 := buildResult(&Item{index: 2}, []Offset{Offset{1, 2}, Offset{6, 7}}, 0, 0)
|
||||||
|
items = []*Result{item1, item2, item3, item4, item5, item6}
|
||||||
|
sort.Sort(ByRelevance(items))
|
||||||
|
if items[0] != item6 || items[1] != item4 ||
|
||||||
|
items[2] != item5 || items[3] != item3 ||
|
||||||
|
items[4] != item2 || items[5] != item1 {
|
||||||
|
t.Error(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestColorOffset(t *testing.T) {
|
||||||
|
// ------------ 20 ---- -- ----
|
||||||
|
// ++++++++ ++++++++++
|
||||||
|
// --++++++++-- --++++++++++---
|
||||||
|
item := Result{
|
||||||
|
offsets: []Offset{Offset{5, 15}, Offset{25, 35}},
|
||||||
|
item: &Item{
|
||||||
|
colors: &[]ansiOffset{
|
||||||
|
ansiOffset{[2]int32{0, 20}, ansiState{1, 5, false}},
|
||||||
|
ansiOffset{[2]int32{22, 27}, ansiState{2, 6, true}},
|
||||||
|
ansiOffset{[2]int32{30, 32}, ansiState{3, 7, false}},
|
||||||
|
ansiOffset{[2]int32{33, 40}, ansiState{4, 8, true}}}}}
|
||||||
|
// [{[0 5] 9 false} {[5 15] 99 false} {[15 20] 9 false} {[22 25] 10 true} {[25 35] 99 false} {[35 40] 11 true}]
|
||||||
|
|
||||||
|
offsets := item.colorOffsets(99, false, true)
|
||||||
|
assert := func(idx int, b int32, e int32, c int, bold bool) {
|
||||||
|
o := offsets[idx]
|
||||||
|
if o.offset[0] != b || o.offset[1] != e || o.color != c || o.bold != bold {
|
||||||
|
t.Error(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(0, 0, 5, curses.ColUser, false)
|
||||||
|
assert(1, 5, 15, 99, false)
|
||||||
|
assert(2, 15, 20, curses.ColUser, false)
|
||||||
|
assert(3, 22, 25, curses.ColUser+1, true)
|
||||||
|
assert(4, 25, 35, 99, false)
|
||||||
|
assert(5, 35, 40, curses.ColUser+2, true)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user