From 608c41620755041d6fc216ae43de8b21d56c969c Mon Sep 17 00:00:00 2001
From: Junegunn Choi <junegunn.c@gmail.com>
Date: Fri, 19 Aug 2016 03:27:42 +0900
Subject: [PATCH] Add missing sources

---
 src/result.go      | 259 +++++++++++++++++++++++++++++++++++++++++++++
 src/result_test.go | 114 ++++++++++++++++++++
 2 files changed, 373 insertions(+)
 create mode 100644 src/result.go
 create mode 100644 src/result_test.go

diff --git a/src/result.go b/src/result.go
new file mode 100644
index 00000000..c295e7ac
--- /dev/null
+++ b/src/result.go
@@ -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
+}
diff --git a/src/result_test.go b/src/result_test.go
new file mode 100644
index 00000000..c8478fd9
--- /dev/null
+++ b/src/result_test.go
@@ -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)
+}
-- 
GitLab