2015-01-01 19:49:30 +00:00
|
|
|
package fzf
|
|
|
|
|
2015-03-18 16:59:14 +00:00
|
|
|
import (
|
2015-04-16 05:19:28 +00:00
|
|
|
"math"
|
|
|
|
|
2015-03-18 16:59:14 +00:00
|
|
|
"github.com/junegunn/fzf/src/curses"
|
2016-08-13 15:39:44 +00:00
|
|
|
"github.com/junegunn/fzf/src/util"
|
2015-03-18 16:59:14 +00:00
|
|
|
)
|
|
|
|
|
2015-10-02 09:40:20 +00:00
|
|
|
// Offset holds three 32-bit integers denoting the offsets of a matched substring
|
|
|
|
type Offset [3]int32
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
type colorOffset struct {
|
2015-03-18 16:59:14 +00:00
|
|
|
offset [2]int32
|
|
|
|
color int
|
|
|
|
bold bool
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Item represents each input line
|
2015-01-01 19:49:30 +00:00
|
|
|
type Item struct {
|
2016-08-13 15:39:44 +00:00
|
|
|
text util.Chars
|
|
|
|
origText *[]byte
|
2015-08-02 05:00:18 +00:00
|
|
|
transformed []Token
|
2015-01-01 19:49:30 +00:00
|
|
|
offsets []Offset
|
2015-03-22 07:05:54 +00:00
|
|
|
colors []ansiOffset
|
2016-01-13 12:36:44 +00:00
|
|
|
rank [5]int32
|
2016-04-16 05:02:43 +00:00
|
|
|
bonus int32
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2016-01-12 18:07:42 +00:00
|
|
|
// Sort criteria to use. Never changes once fzf is started.
|
|
|
|
var sortCriteria []criterion
|
|
|
|
|
2016-01-13 12:36:44 +00:00
|
|
|
func isRankValid(rank [5]int32) bool {
|
2016-01-12 18:07:42 +00:00
|
|
|
// Exclude ordinal index
|
2016-01-13 12:36:44 +00:00
|
|
|
for _, r := range rank[:4] {
|
|
|
|
if r > 0 {
|
2016-01-12 18:07:42 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2015-01-08 17:37:08 +00:00
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2016-01-13 12:36:44 +00:00
|
|
|
func buildEmptyRank(index int32) [5]int32 {
|
|
|
|
return [5]int32{0, 0, 0, 0, index}
|
|
|
|
}
|
|
|
|
|
2016-08-14 08:51:34 +00:00
|
|
|
// Index returns ordinal index of the Item
|
2016-01-13 12:36:44 +00:00
|
|
|
func (item *Item) Index() int32 {
|
|
|
|
return item.rank[4]
|
2016-01-12 18:07:42 +00:00
|
|
|
}
|
2015-04-16 05:19:28 +00:00
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// Rank calculates rank of the Item
|
2016-01-13 12:36:44 +00:00
|
|
|
func (item *Item) Rank(cache bool) [5]int32 {
|
2016-01-12 18:07:42 +00:00
|
|
|
if cache && isRankValid(item.rank) {
|
2015-08-02 14:54:53 +00:00
|
|
|
return item.rank
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
matchlen := 0
|
|
|
|
prevEnd := 0
|
2015-10-02 09:40:20 +00:00
|
|
|
lenSum := 0
|
2016-01-13 12:36:44 +00:00
|
|
|
minBegin := math.MaxInt32
|
2015-08-02 14:54:53 +00:00
|
|
|
for _, offset := range item.offsets {
|
2015-01-08 17:37:08 +00:00
|
|
|
begin := int(offset[0])
|
|
|
|
end := int(offset[1])
|
2015-10-02 09:40:20 +00:00
|
|
|
trimLen := int(offset[2])
|
|
|
|
lenSum += trimLen
|
2015-01-01 19:49:30 +00:00
|
|
|
if prevEnd > begin {
|
|
|
|
begin = prevEnd
|
|
|
|
}
|
|
|
|
if end > prevEnd {
|
|
|
|
prevEnd = end
|
|
|
|
}
|
|
|
|
if end > begin {
|
2015-04-16 05:19:28 +00:00
|
|
|
if begin < minBegin {
|
|
|
|
minBegin = begin
|
|
|
|
}
|
2015-01-01 19:49:30 +00:00
|
|
|
matchlen += end - begin
|
|
|
|
}
|
|
|
|
}
|
2016-01-13 12:36:44 +00:00
|
|
|
rank := buildEmptyRank(item.Index())
|
2016-01-12 18:07:42 +00:00
|
|
|
for idx, criterion := range sortCriteria {
|
|
|
|
var val int32
|
|
|
|
switch criterion {
|
|
|
|
case byMatchLen:
|
2016-04-16 05:02:43 +00:00
|
|
|
if matchlen == 0 {
|
|
|
|
val = math.MaxInt32
|
|
|
|
} else {
|
|
|
|
// It is extremely unlikely that bonus exceeds 128
|
|
|
|
val = 128*int32(matchlen) - item.bonus
|
|
|
|
}
|
2016-01-12 18:07:42 +00:00
|
|
|
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 {
|
2016-08-13 15:39:44 +00:00
|
|
|
val = int32(item.text.Length())
|
2016-01-12 18:07:42 +00:00
|
|
|
}
|
|
|
|
case byBegin:
|
|
|
|
// We can't just look at item.offsets[0][0] because it can be an inverse term
|
2016-01-13 16:32:03 +00:00
|
|
|
whitePrefixLen := 0
|
2016-08-13 15:39:44 +00:00
|
|
|
numChars := item.text.Length()
|
|
|
|
for idx := 0; idx < numChars; idx++ {
|
|
|
|
r := item.text.Get(idx)
|
2016-01-13 16:32:03 +00:00
|
|
|
whitePrefixLen = idx
|
|
|
|
if idx == minBegin || r != ' ' && r != '\t' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val = int32(minBegin - whitePrefixLen)
|
2016-01-12 18:07:42 +00:00
|
|
|
case byEnd:
|
|
|
|
if prevEnd > 0 {
|
2016-08-13 15:39:44 +00:00
|
|
|
val = int32(1 + item.text.Length() - prevEnd)
|
2016-01-12 18:07:42 +00:00
|
|
|
} else {
|
|
|
|
// Empty offsets due to inverse terms.
|
|
|
|
val = 1
|
|
|
|
}
|
2015-04-16 05:19:28 +00:00
|
|
|
}
|
2016-01-12 18:07:42 +00:00
|
|
|
rank[idx] = val
|
2015-04-16 05:19:28 +00:00
|
|
|
}
|
2015-01-11 14:49:12 +00:00
|
|
|
if cache {
|
2015-08-02 14:54:53 +00:00
|
|
|
item.rank = rank
|
2015-01-11 14:49:12 +00:00
|
|
|
}
|
|
|
|
return rank
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// AsString returns the original string
|
2015-08-28 12:23:10 +00:00
|
|
|
func (item *Item) AsString(stripAnsi bool) string {
|
|
|
|
return *item.StringPtr(stripAnsi)
|
2015-06-14 03:25:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// StringPtr returns the pointer to the original string
|
2015-08-28 12:23:10 +00:00
|
|
|
func (item *Item) StringPtr(stripAnsi bool) *string {
|
2015-08-02 14:54:53 +00:00
|
|
|
if item.origText != nil {
|
2015-08-28 12:23:10 +00:00
|
|
|
if stripAnsi {
|
2016-06-14 12:52:47 +00:00
|
|
|
trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
|
2015-08-28 12:23:10 +00:00
|
|
|
return &trimmed
|
|
|
|
}
|
|
|
|
orig := string(*item.origText)
|
|
|
|
return &orig
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
2016-08-13 15:39:44 +00:00
|
|
|
str := item.text.ToString()
|
2015-08-02 05:00:18 +00:00
|
|
|
return &str
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 07:05:54 +00:00
|
|
|
func (item *Item) colorOffsets(color int, bold bool, current bool) []colorOffset {
|
2015-03-18 16:59:14 +00:00
|
|
|
if len(item.colors) == 0 {
|
2015-03-22 07:05:54 +00:00
|
|
|
var offsets []colorOffset
|
2015-03-18 16:59:14 +00:00
|
|
|
for _, off := range item.offsets {
|
2015-10-02 09:40:20 +00:00
|
|
|
|
|
|
|
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: color, bold: bold})
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
|
|
|
return offsets
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find max column
|
2015-03-22 07:05:54 +00:00
|
|
|
var maxCol int32
|
2015-03-18 16:59:14 +00:00
|
|
|
for _, off := range item.offsets {
|
|
|
|
if off[1] > maxCol {
|
|
|
|
maxCol = off[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, ansi := range item.colors {
|
|
|
|
if ansi.offset[1] > maxCol {
|
|
|
|
maxCol = ansi.offset[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cols := make([]int, maxCol)
|
|
|
|
|
|
|
|
for colorIndex, ansi := range item.colors {
|
|
|
|
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
|
|
|
cols[i] = colorIndex + 1 // XXX
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, off := range item.offsets {
|
|
|
|
for i := off[0]; i < off[1]; i++ {
|
|
|
|
cols[i] = -1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort.Sort(ByOrder(offsets))
|
|
|
|
|
|
|
|
// Merge offsets
|
|
|
|
// ------------ ---- -- ----
|
|
|
|
// ++++++++ ++++++++++
|
|
|
|
// --++++++++-- --++++++++++---
|
|
|
|
curr := 0
|
|
|
|
start := 0
|
2015-03-22 07:05:54 +00:00
|
|
|
var offsets []colorOffset
|
2015-03-18 16:59:14 +00:00
|
|
|
add := func(idx int) {
|
|
|
|
if curr != 0 && idx > start {
|
|
|
|
if curr == -1 {
|
2015-03-22 07:05:54 +00:00
|
|
|
offsets = append(offsets, colorOffset{
|
2015-10-02 09:40:20 +00:00
|
|
|
offset: [2]int32{int32(start), int32(idx)}, color: color, bold: bold})
|
2015-03-18 16:59:14 +00:00
|
|
|
} else {
|
|
|
|
ansi := item.colors[curr-1]
|
2015-05-31 07:46:54 +00:00
|
|
|
fg := ansi.color.fg
|
|
|
|
if fg == -1 {
|
|
|
|
if current {
|
|
|
|
fg = curses.CurrentFG
|
|
|
|
} else {
|
|
|
|
fg = curses.FG
|
|
|
|
}
|
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
bg := ansi.color.bg
|
2015-05-31 07:46:54 +00:00
|
|
|
if bg == -1 {
|
|
|
|
if current {
|
|
|
|
bg = curses.DarkBG
|
|
|
|
} else {
|
|
|
|
bg = curses.BG
|
|
|
|
}
|
2015-03-18 16:59:14 +00:00
|
|
|
}
|
2015-03-22 07:05:54 +00:00
|
|
|
offsets = append(offsets, colorOffset{
|
2015-10-02 09:40:20 +00:00
|
|
|
offset: [2]int32{int32(start), int32(idx)},
|
2015-05-31 07:46:54 +00:00
|
|
|
color: curses.PairFor(fg, bg),
|
2015-03-18 16:59:14 +00:00
|
|
|
bold: ansi.color.bold || bold})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for idx, col := range cols {
|
|
|
|
if col != curr {
|
|
|
|
add(idx)
|
|
|
|
start = idx
|
|
|
|
curr = col
|
|
|
|
}
|
|
|
|
}
|
|
|
|
add(int(maxCol))
|
|
|
|
return offsets
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// ByOrder is for sorting substring offsets
|
2015-01-01 19:49:30 +00:00
|
|
|
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])
|
|
|
|
}
|
|
|
|
|
2015-01-11 18:01:24 +00:00
|
|
|
// ByRelevance is for sorting Items
|
2015-01-01 19:49:30 +00:00
|
|
|
type ByRelevance []*Item
|
|
|
|
|
|
|
|
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 {
|
2015-01-11 14:49:12 +00:00
|
|
|
irank := a[i].Rank(true)
|
|
|
|
jrank := a[j].Rank(true)
|
2015-01-01 19:49:30 +00:00
|
|
|
|
2015-02-25 16:42:15 +00:00
|
|
|
return compareRanks(irank, jrank, false)
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|
|
|
|
|
2015-02-25 16:42:15 +00:00
|
|
|
// ByRelevanceTac is for sorting Items
|
|
|
|
type ByRelevanceTac []*Item
|
|
|
|
|
|
|
|
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 {
|
|
|
|
irank := a[i].Rank(true)
|
|
|
|
jrank := a[j].Rank(true)
|
|
|
|
|
|
|
|
return compareRanks(irank, jrank, true)
|
|
|
|
}
|
|
|
|
|
2016-01-13 12:36:44 +00:00
|
|
|
func compareRanks(irank [5]int32, jrank [5]int32, tac bool) bool {
|
|
|
|
for idx := 0; idx < 4; idx++ {
|
|
|
|
left := irank[idx]
|
2016-01-12 18:07:42 +00:00
|
|
|
right := jrank[idx]
|
|
|
|
if left < right {
|
|
|
|
return true
|
|
|
|
} else if left > right {
|
|
|
|
return false
|
|
|
|
}
|
2015-01-08 17:37:08 +00:00
|
|
|
}
|
2016-01-13 12:36:44 +00:00
|
|
|
return (irank[4] <= jrank[4]) != tac
|
2015-01-01 19:49:30 +00:00
|
|
|
}
|