mirror of https://github.com/Llewellynvdm/fzf.git
Micro-optimizations
- Make structs smaller - Introduce Result struct and use it to represent matched items instead of reusing Item struct for that purpose - Avoid unnecessary memory allocation - Avoid growing slice from the initial capacity - Code cleanup
This commit is contained in:
parent
f7f01d109e
commit
37dc273148
|
@ -24,12 +24,12 @@ func indexAt(index int, max int, forward bool) int {
|
||||||
|
|
||||||
// Result conatins the results of running a match function.
|
// Result conatins the results of running a match function.
|
||||||
type Result struct {
|
type Result struct {
|
||||||
Start int32
|
Start int
|
||||||
End int32
|
End int
|
||||||
|
|
||||||
// Items are basically sorted by the lengths of matched substrings.
|
// Items are basically sorted by the lengths of matched substrings.
|
||||||
// But we slightly adjust the score with bonus for better results.
|
// But we slightly adjust the score with bonus for better results.
|
||||||
Bonus int32
|
Bonus int
|
||||||
}
|
}
|
||||||
|
|
||||||
type charClass int
|
type charClass int
|
||||||
|
@ -42,8 +42,8 @@ const (
|
||||||
charNumber
|
charNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
func evaluateBonus(caseSensitive bool, text util.Chars, pattern []rune, sidx int, eidx int) int32 {
|
func evaluateBonus(caseSensitive bool, text util.Chars, pattern []rune, sidx int, eidx int) int {
|
||||||
var bonus int32
|
var bonus int
|
||||||
pidx := 0
|
pidx := 0
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
consecutive := false
|
consecutive := false
|
||||||
|
@ -63,7 +63,7 @@ func evaluateBonus(caseSensitive bool, text util.Chars, pattern []rune, sidx int
|
||||||
class = charNonWord
|
class = charNonWord
|
||||||
}
|
}
|
||||||
|
|
||||||
var point int32
|
var point int
|
||||||
if prevClass == charNonWord && class != charNonWord {
|
if prevClass == charNonWord && class != charNonWord {
|
||||||
// Word boundary
|
// Word boundary
|
||||||
point = 2
|
point = 2
|
||||||
|
@ -181,7 +181,7 @@ func FuzzyMatch(caseSensitive bool, forward bool, text util.Chars, pattern []run
|
||||||
sidx, eidx = lenRunes-eidx, lenRunes-sidx
|
sidx, eidx = lenRunes-eidx, lenRunes-sidx
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result{int32(sidx), int32(eidx),
|
return Result{sidx, eidx,
|
||||||
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}
|
return Result{-1, -1, 0}
|
||||||
|
@ -228,7 +228,7 @@ func ExactMatchNaive(caseSensitive bool, forward bool, text util.Chars, pattern
|
||||||
sidx = lenRunes - (index + 1)
|
sidx = lenRunes - (index + 1)
|
||||||
eidx = lenRunes - (index - lenPattern + 1)
|
eidx = lenRunes - (index - lenPattern + 1)
|
||||||
}
|
}
|
||||||
return Result{int32(sidx), int32(eidx),
|
return Result{sidx, eidx,
|
||||||
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -255,7 +255,7 @@ func PrefixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []ru
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
return Result{0, int32(lenPattern),
|
return Result{0, lenPattern,
|
||||||
evaluateBonus(caseSensitive, text, pattern, 0, lenPattern)}
|
evaluateBonus(caseSensitive, text, pattern, 0, lenPattern)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ func SuffixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []ru
|
||||||
lenPattern := len(pattern)
|
lenPattern := len(pattern)
|
||||||
sidx := trimmedLen - lenPattern
|
sidx := trimmedLen - lenPattern
|
||||||
eidx := trimmedLen
|
eidx := trimmedLen
|
||||||
return Result{int32(sidx), int32(eidx),
|
return Result{sidx, eidx,
|
||||||
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +294,7 @@ func EqualMatch(caseSensitive bool, forward bool, text util.Chars, pattern []run
|
||||||
runesStr = strings.ToLower(runesStr)
|
runesStr = strings.ToLower(runesStr)
|
||||||
}
|
}
|
||||||
if runesStr == string(pattern) {
|
if runesStr == string(pattern) {
|
||||||
return Result{0, int32(len(pattern)), 0}
|
return Result{0, len(pattern), 0}
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}
|
return Result{-1, -1, 0}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertMatch(t *testing.T, fun func(bool, bool, util.Chars, []rune) Result, caseSensitive, forward bool, input, pattern string, sidx int32, eidx int32, bonus int32) {
|
func assertMatch(t *testing.T, fun func(bool, bool, util.Chars, []rune) Result, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, bonus int) {
|
||||||
if !caseSensitive {
|
if !caseSensitive {
|
||||||
pattern = strings.ToLower(pattern)
|
pattern = strings.ToLower(pattern)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ func init() {
|
||||||
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]")
|
ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[mK]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, []ansiOffset, *ansiState) {
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||||
var offsets []ansiOffset
|
var offsets []ansiOffset
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
|
|
||||||
|
@ -84,7 +84,10 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
||||||
if proc != nil {
|
if proc != nil {
|
||||||
proc(rest, state)
|
proc(rest, state)
|
||||||
}
|
}
|
||||||
return output.String(), offsets, state
|
if len(offsets) == 0 {
|
||||||
|
return output.String(), nil, state
|
||||||
|
}
|
||||||
|
return output.String(), &offsets, state
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestExtractColor(t *testing.T) {
|
||||||
src := "hello world"
|
src := "hello world"
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
clean := "\x1b[0m"
|
clean := "\x1b[0m"
|
||||||
check := func(assertion func(ansiOffsets []ansiOffset, state *ansiState)) {
|
check := func(assertion func(ansiOffsets *[]ansiOffset, state *ansiState)) {
|
||||||
output, ansiOffsets, newState := extractColor(src, state, nil)
|
output, ansiOffsets, newState := extractColor(src, state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
if output != "hello world" {
|
if output != "hello world" {
|
||||||
|
@ -26,127 +26,127 @@ func TestExtractColor(t *testing.T) {
|
||||||
assertion(ansiOffsets, state)
|
assertion(ansiOffsets, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) > 0 {
|
if offsets != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "\x1b[0mhello world"
|
src = "\x1b[0mhello world"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) > 0 {
|
if offsets != nil {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "\x1b[1mhello world"
|
src = "\x1b[1mhello world"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 0, 11, -1, -1, true)
|
assert((*offsets)[0], 0, 11, -1, -1, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "\x1b[1mhello \x1b[mworld"
|
src = "\x1b[1mhello \x1b[mworld"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 0, 6, -1, -1, true)
|
assert((*offsets)[0], 0, 6, -1, -1, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "\x1b[1mhello \x1b[Kworld"
|
src = "\x1b[1mhello \x1b[Kworld"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 0, 11, -1, -1, true)
|
assert((*offsets)[0], 0, 11, -1, -1, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "hello \x1b[34;45;1mworld"
|
src = "hello \x1b[34;45;1mworld"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 6, 11, 4, 5, true)
|
assert((*offsets)[0], 6, 11, 4, 5, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld"
|
src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 6, 11, 4, 5, true)
|
assert((*offsets)[0], 6, 11, 4, 5, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "hello \x1b[34;45;1mwor\x1b[0mld"
|
src = "hello \x1b[34;45;1mwor\x1b[0mld"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 6, 9, 4, 5, true)
|
assert((*offsets)[0], 6, 9, 4, 5, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
state = nil
|
state = nil
|
||||||
src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md"
|
src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 3 {
|
if len(*offsets) != 3 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 6, 8, 4, 233, true)
|
assert((*offsets)[0], 6, 8, 4, 233, true)
|
||||||
assert(offsets[1], 8, 9, 161, 233, true)
|
assert((*offsets)[1], 8, 9, 161, 233, true)
|
||||||
assert(offsets[2], 10, 11, 161, -1, false)
|
assert((*offsets)[2], 10, 11, 161, -1, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
// {38,48};5;{38,48}
|
// {38,48};5;{38,48}
|
||||||
state = nil
|
state = nil
|
||||||
src = "hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md"
|
src = "hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 2 {
|
if len(*offsets) != 2 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 6, 9, 38, 48, true)
|
assert((*offsets)[0], 6, 9, 38, 48, true)
|
||||||
assert(offsets[1], 9, 10, 48, 38, true)
|
assert((*offsets)[1], 9, 10, 48, 38, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
src = "hello \x1b[32;1mworld"
|
src = "hello \x1b[32;1mworld"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if state.fg != 2 || state.bg != -1 || !state.bold {
|
if state.fg != 2 || state.bg != -1 || !state.bold {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 6, 11, 2, -1, true)
|
assert((*offsets)[0], 6, 11, 2, -1, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
src = "hello world"
|
src = "hello world"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 1 {
|
if len(*offsets) != 1 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if state.fg != 2 || state.bg != -1 || !state.bold {
|
if state.fg != 2 || state.bg != -1 || !state.bold {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 0, 11, 2, -1, true)
|
assert((*offsets)[0], 0, 11, 2, -1, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
src = "hello \x1b[0;38;5;200;48;5;100mworld"
|
src = "hello \x1b[0;38;5;200;48;5;100mworld"
|
||||||
check(func(offsets []ansiOffset, state *ansiState) {
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
if len(offsets) != 2 {
|
if len(*offsets) != 2 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if state.fg != 200 || state.bg != 100 || state.bold {
|
if state.fg != 200 || state.bg != 100 || state.bold {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
assert(offsets[0], 0, 6, 2, -1, true)
|
assert((*offsets)[0], 0, 6, 2, -1, true)
|
||||||
assert(offsets[1], 6, 11, 200, 100, false)
|
assert((*offsets)[1], 6, 11, 200, 100, false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package fzf
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
// queryCache associates strings to lists of items
|
// queryCache associates strings to lists of items
|
||||||
type queryCache map[string][]*Item
|
type queryCache map[string][]*Result
|
||||||
|
|
||||||
// ChunkCache associates Chunk and query string to lists of items
|
// ChunkCache associates Chunk and query string to lists of items
|
||||||
type ChunkCache struct {
|
type ChunkCache struct {
|
||||||
|
@ -17,7 +17,7 @@ func NewChunkCache() ChunkCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the list to the cache
|
// Add adds the list to the cache
|
||||||
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Item) {
|
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) {
|
||||||
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find is called to lookup ChunkCache
|
// Find is called to lookup ChunkCache
|
||||||
func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Item, bool) {
|
func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Result, bool) {
|
||||||
if len(key) == 0 || !chunk.IsFull() {
|
if len(key) == 0 || !chunk.IsFull() {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ func TestChunkCache(t *testing.T) {
|
||||||
chunk2 := make(Chunk, chunkSize)
|
chunk2 := make(Chunk, chunkSize)
|
||||||
chunk1p := &Chunk{}
|
chunk1p := &Chunk{}
|
||||||
chunk2p := &chunk2
|
chunk2p := &chunk2
|
||||||
items1 := []*Item{&Item{}}
|
items1 := []*Result{&Result{}}
|
||||||
items2 := []*Item{&Item{}, &Item{}}
|
items2 := []*Result{&Result{}, &Result{}}
|
||||||
cache.Add(chunk1p, "foo", items1)
|
cache.Add(chunk1p, "foo", items1)
|
||||||
cache.Add(chunk2p, "foo", items1)
|
cache.Add(chunk2p, "foo", items1)
|
||||||
cache.Add(chunk2p, "bar", items2)
|
cache.Add(chunk2p, "bar", items2)
|
||||||
|
|
|
@ -12,7 +12,7 @@ func TestChunkList(t *testing.T) {
|
||||||
sortCriteria = []criterion{byMatchLen, byLength}
|
sortCriteria = []criterion{byMatchLen, byLength}
|
||||||
|
|
||||||
cl := NewChunkList(func(s []byte, i int) *Item {
|
cl := NewChunkList(func(s []byte, i int) *Item {
|
||||||
return &Item{text: util.ToChars(s), rank: buildEmptyRank(int32(i * 2))}
|
return &Item{text: util.ToChars(s), index: int32(i * 2)}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
|
@ -41,11 +41,8 @@ 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")
|
||||||
}
|
}
|
||||||
last := func(arr [5]int32) int32 {
|
if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].index != 0 ||
|
||||||
return arr[len(arr)-1]
|
(*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].index != 2 {
|
||||||
}
|
|
||||||
if (*chunk1)[0].text.ToString() != "hello" || last((*chunk1)[0].rank) != 0 ||
|
|
||||||
(*chunk1)[1].text.ToString() != "world" || last((*chunk1)[1].rank) != 2 {
|
|
||||||
t.Error("Invalid data")
|
t.Error("Invalid data")
|
||||||
}
|
}
|
||||||
if chunk1.IsFull() {
|
if chunk1.IsFull() {
|
||||||
|
|
28
src/core.go
28
src/core.go
|
@ -56,16 +56,16 @@ func Run(opts *Options) {
|
||||||
eventBox := util.NewEventBox()
|
eventBox := util.NewEventBox()
|
||||||
|
|
||||||
// ANSI code processor
|
// ANSI code processor
|
||||||
ansiProcessor := func(data []byte) (util.Chars, []ansiOffset) {
|
ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
return util.ToChars(data), nil
|
return util.ToChars(data), nil
|
||||||
}
|
}
|
||||||
ansiProcessorRunes := func(data []rune) (util.Chars, []ansiOffset) {
|
ansiProcessorRunes := func(data []rune) (util.Chars, *[]ansiOffset) {
|
||||||
return util.RunesToChars(data), nil
|
return util.RunesToChars(data), nil
|
||||||
}
|
}
|
||||||
if opts.Ansi {
|
if opts.Ansi {
|
||||||
if opts.Theme != nil {
|
if opts.Theme != nil {
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
ansiProcessor = func(data []byte) (util.Chars, []ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
trimmed, offsets, newState := extractColor(string(data), state, nil)
|
||||||
state = newState
|
state = newState
|
||||||
return util.RunesToChars([]rune(trimmed)), offsets
|
return util.RunesToChars([]rune(trimmed)), offsets
|
||||||
|
@ -73,12 +73,12 @@ func Run(opts *Options) {
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, []ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
trimmed, _, _ := extractColor(string(data), nil, nil)
|
||||||
return util.RunesToChars([]rune(trimmed)), nil
|
return util.RunesToChars([]rune(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ansiProcessorRunes = func(data []rune) (util.Chars, []ansiOffset) {
|
ansiProcessorRunes = func(data []rune) (util.Chars, *[]ansiOffset) {
|
||||||
return ansiProcessor([]byte(string(data)))
|
return ansiProcessor([]byte(string(data)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,14 +95,13 @@ func Run(opts *Options) {
|
||||||
}
|
}
|
||||||
chars, colors := ansiProcessor(data)
|
chars, colors := ansiProcessor(data)
|
||||||
return &Item{
|
return &Item{
|
||||||
|
index: int32(index),
|
||||||
text: chars,
|
text: chars,
|
||||||
colors: colors,
|
colors: colors}
|
||||||
rank: buildEmptyRank(int32(index))}
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
||||||
chars := util.ToChars(data)
|
tokens := Tokenize(util.ToChars(data), opts.Delimiter)
|
||||||
tokens := Tokenize(chars, opts.Delimiter)
|
|
||||||
trans := Transform(tokens, opts.WithNth)
|
trans := Transform(tokens, opts.WithNth)
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(joinTokens(trans)))
|
header = append(header, string(joinTokens(trans)))
|
||||||
|
@ -111,10 +110,9 @@ func Run(opts *Options) {
|
||||||
}
|
}
|
||||||
textRunes := joinTokens(trans)
|
textRunes := joinTokens(trans)
|
||||||
item := Item{
|
item := Item{
|
||||||
text: util.RunesToChars(textRunes),
|
index: int32(index),
|
||||||
origText: &data,
|
origText: &data,
|
||||||
colors: nil,
|
colors: nil}
|
||||||
rank: buildEmptyRank(int32(index))}
|
|
||||||
|
|
||||||
trimmed, colors := ansiProcessorRunes(textRunes)
|
trimmed, colors := ansiProcessorRunes(textRunes)
|
||||||
item.text = trimmed
|
item.text = trimmed
|
||||||
|
@ -163,7 +161,7 @@ func Run(opts *Options) {
|
||||||
reader := Reader{
|
reader := Reader{
|
||||||
func(runes []byte) bool {
|
func(runes []byte) bool {
|
||||||
item := chunkList.trans(runes, 0)
|
item := chunkList.trans(runes, 0)
|
||||||
if item != nil && pattern.MatchItem(item) {
|
if item != nil && pattern.MatchItem(item) != nil {
|
||||||
fmt.Println(item.text.ToString())
|
fmt.Println(item.text.ToString())
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
|
@ -179,7 +177,7 @@ func Run(opts *Options) {
|
||||||
chunks: snapshot,
|
chunks: snapshot,
|
||||||
pattern: pattern})
|
pattern: pattern})
|
||||||
for i := 0; i < merger.Length(); i++ {
|
for i := 0; i < merger.Length(); i++ {
|
||||||
fmt.Println(merger.Get(i).AsString(opts.Ansi))
|
fmt.Println(merger.Get(i).item.AsString(opts.Ansi))
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,7 +257,7 @@ func Run(opts *Options) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
fmt.Println(val.Get(i).AsString(opts.Ansi))
|
fmt.Println(val.Get(i).item.AsString(opts.Ansi))
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
os.Exit(exitOk)
|
os.Exit(exitOk)
|
||||||
|
|
278
src/item.go
278
src/item.go
|
@ -1,295 +1,39 @@
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Offset holds three 32-bit integers denoting the offsets of a matched substring
|
|
||||||
type Offset [3]int32
|
|
||||||
|
|
||||||
type colorOffset struct {
|
|
||||||
offset [2]int32
|
|
||||||
color int
|
|
||||||
bold bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Item represents each input line
|
// Item represents each input line
|
||||||
type Item struct {
|
type Item struct {
|
||||||
|
index int32
|
||||||
text util.Chars
|
text util.Chars
|
||||||
origText *[]byte
|
origText *[]byte
|
||||||
|
colors *[]ansiOffset
|
||||||
transformed []Token
|
transformed []Token
|
||||||
offsets []Offset
|
|
||||||
colors []ansiOffset
|
|
||||||
rank [5]int32
|
|
||||||
bonus int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort criteria to use. Never changes once fzf is started.
|
|
||||||
var sortCriteria []criterion
|
|
||||||
|
|
||||||
func isRankValid(rank [5]int32) bool {
|
|
||||||
// Exclude ordinal index
|
|
||||||
for _, r := range rank[:4] {
|
|
||||||
if r > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildEmptyRank(index int32) [5]int32 {
|
|
||||||
return [5]int32{0, 0, 0, 0, index}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index returns ordinal index of the Item
|
// Index returns ordinal index of the Item
|
||||||
func (item *Item) Index() int32 {
|
func (item *Item) Index() int32 {
|
||||||
return item.rank[4]
|
return item.index
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rank calculates rank of the Item
|
// Colors returns ansiOffsets of the Item
|
||||||
func (item *Item) Rank(cache bool) [5]int32 {
|
func (item *Item) Colors() []ansiOffset {
|
||||||
if cache && isRankValid(item.rank) {
|
if item.colors == nil {
|
||||||
return item.rank
|
return []ansiOffset{}
|
||||||
}
|
}
|
||||||
matchlen := 0
|
return *item.colors
|
||||||
prevEnd := 0
|
|
||||||
lenSum := 0
|
|
||||||
minBegin := math.MaxInt32
|
|
||||||
for _, offset := range item.offsets {
|
|
||||||
begin := int(offset[0])
|
|
||||||
end := int(offset[1])
|
|
||||||
trimLen := int(offset[2])
|
|
||||||
lenSum += trimLen
|
|
||||||
if prevEnd > begin {
|
|
||||||
begin = prevEnd
|
|
||||||
}
|
|
||||||
if end > prevEnd {
|
|
||||||
prevEnd = end
|
|
||||||
}
|
|
||||||
if end > begin {
|
|
||||||
if begin < minBegin {
|
|
||||||
minBegin = begin
|
|
||||||
}
|
|
||||||
matchlen += end - begin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rank := buildEmptyRank(item.Index())
|
|
||||||
for idx, criterion := range sortCriteria {
|
|
||||||
var val int32
|
|
||||||
switch criterion {
|
|
||||||
case byMatchLen:
|
|
||||||
if matchlen == 0 {
|
|
||||||
val = math.MaxInt32
|
|
||||||
} else {
|
|
||||||
// It is extremely unlikely that bonus exceeds 128
|
|
||||||
val = 128*int32(matchlen) - item.bonus
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
val = int32(item.text.Length())
|
|
||||||
}
|
|
||||||
case byBegin:
|
|
||||||
// We can't just look at item.offsets[0][0] because it can be an inverse term
|
|
||||||
whitePrefixLen := 0
|
|
||||||
numChars := item.text.Length()
|
|
||||||
for idx := 0; idx < numChars; idx++ {
|
|
||||||
r := item.text.Get(idx)
|
|
||||||
whitePrefixLen = idx
|
|
||||||
if idx == minBegin || r != ' ' && r != '\t' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val = int32(minBegin - whitePrefixLen)
|
|
||||||
case byEnd:
|
|
||||||
if prevEnd > 0 {
|
|
||||||
val = int32(1 + item.text.Length() - prevEnd)
|
|
||||||
} else {
|
|
||||||
// Empty offsets due to inverse terms.
|
|
||||||
val = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rank[idx] = val
|
|
||||||
}
|
|
||||||
if cache {
|
|
||||||
item.rank = rank
|
|
||||||
}
|
|
||||||
return rank
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsString returns the original string
|
// AsString returns the original string
|
||||||
func (item *Item) AsString(stripAnsi bool) string {
|
func (item *Item) AsString(stripAnsi bool) string {
|
||||||
return *item.StringPtr(stripAnsi)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringPtr returns the pointer to the original string
|
|
||||||
func (item *Item) StringPtr(stripAnsi bool) *string {
|
|
||||||
if item.origText != nil {
|
if item.origText != nil {
|
||||||
if stripAnsi {
|
if stripAnsi {
|
||||||
trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
|
trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
|
||||||
return &trimmed
|
return trimmed
|
||||||
}
|
}
|
||||||
orig := string(*item.origText)
|
return string(*item.origText)
|
||||||
return &orig
|
|
||||||
}
|
}
|
||||||
str := item.text.ToString()
|
return item.text.ToString()
|
||||||
return &str
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item *Item) colorOffsets(color int, bold bool, current bool) []colorOffset {
|
|
||||||
if len(item.colors) == 0 {
|
|
||||||
var offsets []colorOffset
|
|
||||||
for _, off := range item.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 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
|
|
||||||
var offsets []colorOffset
|
|
||||||
add := func(idx int) {
|
|
||||||
if curr != 0 && idx > start {
|
|
||||||
if curr == -1 {
|
|
||||||
offsets = append(offsets, colorOffset{
|
|
||||||
offset: [2]int32{int32(start), int32(idx)}, color: color, bold: bold})
|
|
||||||
} else {
|
|
||||||
ansi := item.colors[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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
offsets = append(offsets, 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 offsets
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 []*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 {
|
|
||||||
irank := a[i].Rank(true)
|
|
||||||
jrank := a[j].Rank(true)
|
|
||||||
|
|
||||||
return compareRanks(irank, jrank, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareRanks(irank [5]int32, jrank [5]int32, tac bool) bool {
|
|
||||||
for idx := 0; idx < 4; idx++ {
|
|
||||||
left := irank[idx]
|
|
||||||
right := jrank[idx]
|
|
||||||
if left < right {
|
|
||||||
return true
|
|
||||||
} else if left > right {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (irank[4] <= jrank[4]) != tac
|
|
||||||
}
|
}
|
||||||
|
|
112
src/item_test.go
112
src/item_test.go
|
@ -1,109 +1,23 @@
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/curses"
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestStringPtr(t *testing.T) {
|
||||||
offsets := []Offset{
|
orig := []byte("\x1b[34mfoo")
|
||||||
Offset{3, 5}, Offset{2, 7},
|
text := []byte("\x1b[34mbar")
|
||||||
Offset{1, 3}, Offset{2, 9}}
|
item := Item{origText: &orig, text: util.ToChars(text)}
|
||||||
sort.Sort(ByOrder(offsets))
|
if item.AsString(true) != "foo" || item.AsString(false) != string(orig) {
|
||||||
|
t.Fail()
|
||||||
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
|
}
|
||||||
offsets[1][0] != 2 || offsets[1][1] != 7 ||
|
if item.AsString(true) != "foo" {
|
||||||
offsets[2][0] != 2 || offsets[2][1] != 9 ||
|
t.Fail()
|
||||||
offsets[3][0] != 3 || offsets[3][1] != 5 {
|
}
|
||||||
t.Error("Invalid order:", offsets)
|
item.origText = nil
|
||||||
|
if item.AsString(true) != string(text) || item.AsString(false) != string(text) {
|
||||||
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRankComparison(t *testing.T) {
|
|
||||||
if compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{2, 0, 0, 0, 7}, false) ||
|
|
||||||
!compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{3, 0, 0, 0, 6}, false) ||
|
|
||||||
!compareRanks([5]int32{1, 2, 0, 0, 3}, [5]int32{1, 3, 0, 0, 2}, false) ||
|
|
||||||
!compareRanks([5]int32{0, 0, 0, 0, 0}, [5]int32{0, 0, 0, 0, 0}, false) {
|
|
||||||
t.Error("Invalid order")
|
|
||||||
}
|
|
||||||
|
|
||||||
if compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{2, 0, 0, 0, 7}, true) ||
|
|
||||||
!compareRanks([5]int32{3, 0, 0, 0, 5}, [5]int32{3, 0, 0, 0, 6}, false) ||
|
|
||||||
!compareRanks([5]int32{1, 2, 0, 0, 3}, [5]int32{1, 3, 0, 0, 2}, true) ||
|
|
||||||
!compareRanks([5]int32{0, 0, 0, 0, 0}, [5]int32{0, 0, 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}
|
|
||||||
|
|
||||||
strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")}
|
|
||||||
item1 := Item{text: util.RunesToChars(strs[0]), offsets: []Offset{}, rank: [5]int32{0, 0, 0, 0, 1}}
|
|
||||||
rank1 := item1.Rank(true)
|
|
||||||
if rank1[0] != math.MaxInt32 || rank1[1] != 3 || rank1[4] != 1 {
|
|
||||||
t.Error(item1.Rank(true))
|
|
||||||
}
|
|
||||||
// Only differ in index
|
|
||||||
item2 := Item{text: util.RunesToChars(strs[0]), offsets: []Offset{}}
|
|
||||||
|
|
||||||
items := []*Item{&item1, &item2}
|
|
||||||
sort.Sort(ByRelevance(items))
|
|
||||||
if items[0] != &item2 || items[1] != &item1 {
|
|
||||||
t.Error(items)
|
|
||||||
}
|
|
||||||
|
|
||||||
items = []*Item{&item2, &item1, &item1, &item2}
|
|
||||||
sort.Sort(ByRelevance(items))
|
|
||||||
if items[0] != &item2 || items[1] != &item2 ||
|
|
||||||
items[2] != &item1 || items[3] != &item1 {
|
|
||||||
t.Error(items)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by relevance
|
|
||||||
item3 := Item{text: util.RunesToChars(strs[1]), rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
|
||||||
item4 := Item{text: util.RunesToChars(strs[1]), rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 2}, Offset{6, 7}}}
|
|
||||||
item5 := Item{text: util.RunesToChars(strs[2]), rank: [5]int32{0, 0, 0, 0, 2}, offsets: []Offset{Offset{1, 3}, Offset{5, 7}}}
|
|
||||||
item6 := Item{text: util.RunesToChars(strs[2]), rank: [5]int32{0, 0, 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 ||
|
|
||||||
items[2] != &item5 || items[3] != &item3 ||
|
|
||||||
items[4] != &item2 || items[5] != &item1 {
|
|
||||||
t.Error(items)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestColorOffset(t *testing.T) {
|
|
||||||
// ------------ 20 ---- -- ----
|
|
||||||
// ++++++++ ++++++++++
|
|
||||||
// --++++++++-- --++++++++++---
|
|
||||||
item := Item{
|
|
||||||
offsets: []Offset{Offset{5, 15}, Offset{25, 35}},
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
|
||||||
|
|
||||||
type partialResult struct {
|
type partialResult struct {
|
||||||
index int
|
index int
|
||||||
matches []*Item
|
matches []*Result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||||
|
@ -155,15 +155,21 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||||
waitGroup.Add(1)
|
waitGroup.Add(1)
|
||||||
go func(idx int, chunks []*Chunk) {
|
go func(idx int, chunks []*Chunk) {
|
||||||
defer func() { waitGroup.Done() }()
|
defer func() { waitGroup.Done() }()
|
||||||
sliceMatches := []*Item{}
|
count := 0
|
||||||
for _, chunk := range chunks {
|
allMatches := make([][]*Result, len(chunks))
|
||||||
|
for idx, chunk := range chunks {
|
||||||
matches := request.pattern.Match(chunk)
|
matches := request.pattern.Match(chunk)
|
||||||
sliceMatches = append(sliceMatches, matches...)
|
allMatches[idx] = matches
|
||||||
|
count += len(matches)
|
||||||
if cancelled.Get() {
|
if cancelled.Get() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
countChan <- len(matches)
|
countChan <- len(matches)
|
||||||
}
|
}
|
||||||
|
sliceMatches := make([]*Result, 0, count)
|
||||||
|
for _, matches := range allMatches {
|
||||||
|
sliceMatches = append(sliceMatches, matches...)
|
||||||
|
}
|
||||||
if m.sort {
|
if m.sort {
|
||||||
if m.tac {
|
if m.tac {
|
||||||
sort.Sort(ByRelevanceTac(sliceMatches))
|
sort.Sort(ByRelevanceTac(sliceMatches))
|
||||||
|
@ -200,7 +206,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
partialResults := make([][]*Item, numSlices)
|
partialResults := make([][]*Result, numSlices)
|
||||||
for _ = range slices {
|
for _ = range slices {
|
||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
|
|
|
@ -3,13 +3,13 @@ package fzf
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
var EmptyMerger = NewMerger([][]*Item{}, false, false)
|
var EmptyMerger = NewMerger([][]*Result{}, false, false)
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
// a single, globally-sorted list
|
// a single, globally-sorted list
|
||||||
type Merger struct {
|
type Merger struct {
|
||||||
lists [][]*Item
|
lists [][]*Result
|
||||||
merged []*Item
|
merged []*Result
|
||||||
chunks *[]*Chunk
|
chunks *[]*Chunk
|
||||||
cursors []int
|
cursors []int
|
||||||
sorted bool
|
sorted bool
|
||||||
|
@ -33,10 +33,10 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(lists [][]*Item, sorted bool, tac bool) *Merger {
|
func NewMerger(lists [][]*Result, sorted bool, tac bool) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
lists: lists,
|
lists: lists,
|
||||||
merged: []*Item{},
|
merged: []*Result{},
|
||||||
chunks: nil,
|
chunks: nil,
|
||||||
cursors: make([]int, len(lists)),
|
cursors: make([]int, len(lists)),
|
||||||
sorted: sorted,
|
sorted: sorted,
|
||||||
|
@ -55,14 +55,14 @@ func (mg *Merger) Length() int {
|
||||||
return mg.count
|
return mg.count
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the pointer to the Item object indexed by the given integer
|
// Get returns the pointer to the Result object indexed by the given integer
|
||||||
func (mg *Merger) Get(idx int) *Item {
|
func (mg *Merger) Get(idx int) *Result {
|
||||||
if mg.chunks != nil {
|
if mg.chunks != nil {
|
||||||
if mg.tac {
|
if mg.tac {
|
||||||
idx = mg.count - idx - 1
|
idx = mg.count - idx - 1
|
||||||
}
|
}
|
||||||
chunk := (*mg.chunks)[idx/chunkSize]
|
chunk := (*mg.chunks)[idx/chunkSize]
|
||||||
return (*chunk)[idx%chunkSize]
|
return &Result{item: (*chunk)[idx%chunkSize]}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mg.sorted {
|
if mg.sorted {
|
||||||
|
@ -86,9 +86,9 @@ func (mg *Merger) cacheable() bool {
|
||||||
return mg.count < mergerCacheMax
|
return mg.count < mergerCacheMax
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mg *Merger) mergedGet(idx int) *Item {
|
func (mg *Merger) mergedGet(idx int) *Result {
|
||||||
for i := len(mg.merged); i <= idx; i++ {
|
for i := len(mg.merged); i <= idx; i++ {
|
||||||
minRank := buildEmptyRank(0)
|
minRank := minRank()
|
||||||
minIdx := -1
|
minIdx := -1
|
||||||
for listIdx, list := range mg.lists {
|
for listIdx, list := range mg.lists {
|
||||||
cursor := mg.cursors[listIdx]
|
cursor := mg.cursors[listIdx]
|
||||||
|
@ -97,7 +97,7 @@ func (mg *Merger) mergedGet(idx int) *Item {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if cursor >= 0 {
|
if cursor >= 0 {
|
||||||
rank := list[cursor].Rank(false)
|
rank := list[cursor].rank
|
||||||
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
||||||
minRank = rank
|
minRank = rank
|
||||||
minIdx = listIdx
|
minIdx = listIdx
|
||||||
|
|
|
@ -15,7 +15,7 @@ func assert(t *testing.T, cond bool, msg ...string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func randItem() *Item {
|
func randResult() *Result {
|
||||||
str := fmt.Sprintf("%d", rand.Uint32())
|
str := fmt.Sprintf("%d", rand.Uint32())
|
||||||
offsets := make([]Offset, rand.Int()%3)
|
offsets := make([]Offset, rand.Int()%3)
|
||||||
for idx := range offsets {
|
for idx := range offsets {
|
||||||
|
@ -23,10 +23,10 @@ func randItem() *Item {
|
||||||
eidx := sidx + int32(rand.Uint32()%20)
|
eidx := sidx + int32(rand.Uint32()%20)
|
||||||
offsets[idx] = Offset{sidx, eidx}
|
offsets[idx] = Offset{sidx, eidx}
|
||||||
}
|
}
|
||||||
return &Item{
|
return &Result{
|
||||||
text: util.RunesToChars([]rune(str)),
|
item: &Item{text: util.RunesToChars([]rune(str))},
|
||||||
rank: buildEmptyRank(rand.Int31()),
|
offsets: offsets,
|
||||||
offsets: offsets}
|
rank: rank{index: rand.Int31()}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyMerger(t *testing.T) {
|
func TestEmptyMerger(t *testing.T) {
|
||||||
|
@ -36,23 +36,23 @@ func TestEmptyMerger(t *testing.T) {
|
||||||
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildLists(partiallySorted bool) ([][]*Item, []*Item) {
|
func buildLists(partiallySorted bool) ([][]*Result, []*Result) {
|
||||||
numLists := 4
|
numLists := 4
|
||||||
lists := make([][]*Item, numLists)
|
lists := make([][]*Result, numLists)
|
||||||
cnt := 0
|
cnt := 0
|
||||||
for i := 0; i < numLists; i++ {
|
for i := 0; i < numLists; i++ {
|
||||||
numItems := rand.Int() % 20
|
numResults := rand.Int() % 20
|
||||||
cnt += numItems
|
cnt += numResults
|
||||||
lists[i] = make([]*Item, numItems)
|
lists[i] = make([]*Result, numResults)
|
||||||
for j := 0; j < numItems; j++ {
|
for j := 0; j < numResults; j++ {
|
||||||
item := randItem()
|
item := randResult()
|
||||||
lists[i][j] = item
|
lists[i][j] = item
|
||||||
}
|
}
|
||||||
if partiallySorted {
|
if partiallySorted {
|
||||||
sort.Sort(ByRelevance(lists[i]))
|
sort.Sort(ByRelevance(lists[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items := []*Item{}
|
items := []*Result{}
|
||||||
for _, list := range lists {
|
for _, list := range lists {
|
||||||
items = append(items, list...)
|
items = append(items, list...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ type criterion int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
byMatchLen criterion = iota
|
byMatchLen criterion = iota
|
||||||
|
byBonus
|
||||||
byLength
|
byLength
|
||||||
byBegin
|
byBegin
|
||||||
byEnd
|
byEnd
|
||||||
|
@ -178,7 +179,7 @@ func defaultOptions() *Options {
|
||||||
Delimiter: Delimiter{},
|
Delimiter: Delimiter{},
|
||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Tac: false,
|
Tac: false,
|
||||||
Criteria: []criterion{byMatchLen, byLength},
|
Criteria: []criterion{byMatchLen, byBonus, byLength},
|
||||||
Multi: false,
|
Multi: false,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
|
@ -406,7 +407,7 @@ func parseKeyChords(str string, message string) map[int]string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTiebreak(str string) []criterion {
|
func parseTiebreak(str string) []criterion {
|
||||||
criteria := []criterion{byMatchLen}
|
criteria := []criterion{byMatchLen, byBonus}
|
||||||
hasIndex := false
|
hasIndex := false
|
||||||
hasLength := false
|
hasLength := false
|
||||||
hasBegin := false
|
hasBegin := false
|
||||||
|
|
110
src/pattern.go
110
src/pattern.go
|
@ -2,7 +2,6 @@ package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
|
@ -235,9 +234,7 @@ func (p *Pattern) CacheKey() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match returns the list of matches Items in the given Chunk
|
// Match returns the list of matches Items in the given Chunk
|
||||||
func (p *Pattern) Match(chunk *Chunk) []*Item {
|
func (p *Pattern) Match(chunk *Chunk) []*Result {
|
||||||
space := chunk
|
|
||||||
|
|
||||||
// ChunkCache: Exact match
|
// ChunkCache: Exact match
|
||||||
cacheKey := p.CacheKey()
|
cacheKey := p.CacheKey()
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
|
@ -246,7 +243,8 @@ func (p *Pattern) Match(chunk *Chunk) []*Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChunkCache: Prefix/suffix match
|
// Prefix/suffix cache
|
||||||
|
var space []*Result
|
||||||
Loop:
|
Loop:
|
||||||
for idx := 1; idx < len(cacheKey); idx++ {
|
for idx := 1; idx < len(cacheKey); idx++ {
|
||||||
// [---------| ] | [ |---------]
|
// [---------| ] | [ |---------]
|
||||||
|
@ -256,14 +254,13 @@ Loop:
|
||||||
suffix := cacheKey[idx:]
|
suffix := cacheKey[idx:]
|
||||||
for _, substr := range [2]*string{&prefix, &suffix} {
|
for _, substr := range [2]*string{&prefix, &suffix} {
|
||||||
if cached, found := _cache.Find(chunk, *substr); found {
|
if cached, found := _cache.Find(chunk, *substr); found {
|
||||||
cachedChunk := Chunk(cached)
|
space = cached
|
||||||
space = &cachedChunk
|
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := p.matchChunk(space)
|
matches := p.matchChunk(chunk, space)
|
||||||
|
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
_cache.Add(chunk, cacheKey, matches)
|
_cache.Add(chunk, cacheKey, matches)
|
||||||
|
@ -271,20 +268,19 @@ Loop:
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) matchChunk(chunk *Chunk) []*Item {
|
func (p *Pattern) matchChunk(chunk *Chunk, space []*Result) []*Result {
|
||||||
matches := []*Item{}
|
matches := []*Result{}
|
||||||
if !p.extended {
|
|
||||||
|
if space == nil {
|
||||||
for _, item := range *chunk {
|
for _, item := range *chunk {
|
||||||
offset, bonus := p.basicMatch(item)
|
if match := p.MatchItem(item); match != nil {
|
||||||
if sidx := offset[0]; sidx >= 0 {
|
matches = append(matches, match)
|
||||||
matches = append(matches,
|
|
||||||
dupItem(item, []Offset{offset}, bonus))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, item := range *chunk {
|
for _, result := range space {
|
||||||
if offsets, bonus := p.extendedMatch(item); len(offsets) == len(p.termSets) {
|
if match := p.MatchItem(result.item); match != nil {
|
||||||
matches = append(matches, dupItem(item, offsets, bonus))
|
matches = append(matches, match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -292,29 +288,21 @@ func (p *Pattern) matchChunk(chunk *Chunk) []*Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchItem returns true if the Item is a match
|
// MatchItem returns true if the Item is a match
|
||||||
func (p *Pattern) MatchItem(item *Item) bool {
|
func (p *Pattern) MatchItem(item *Item) *Result {
|
||||||
if !p.extended {
|
if p.extended {
|
||||||
offset, _ := p.basicMatch(item)
|
if offsets, bonus, trimLen := p.extendedMatch(item); len(offsets) == len(p.termSets) {
|
||||||
sidx := offset[0]
|
return buildResult(item, offsets, bonus, trimLen)
|
||||||
return sidx >= 0
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
offsets, _ := p.extendedMatch(item)
|
offset, bonus, trimLen := p.basicMatch(item)
|
||||||
return len(offsets) == len(p.termSets)
|
if sidx := offset[0]; sidx >= 0 {
|
||||||
|
return buildResult(item, []Offset{offset}, bonus, trimLen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dupItem(item *Item, offsets []Offset, bonus int32) *Item {
|
func (p *Pattern) basicMatch(item *Item) (Offset, int, int) {
|
||||||
sort.Sort(ByOrder(offsets))
|
|
||||||
return &Item{
|
|
||||||
text: item.text,
|
|
||||||
origText: item.origText,
|
|
||||||
transformed: item.transformed,
|
|
||||||
offsets: offsets,
|
|
||||||
bonus: bonus,
|
|
||||||
colors: item.colors,
|
|
||||||
rank: buildEmptyRank(item.Index())}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pattern) basicMatch(item *Item) (Offset, int32) {
|
|
||||||
input := p.prepareInput(item)
|
input := p.prepareInput(item)
|
||||||
if p.fuzzy {
|
if p.fuzzy {
|
||||||
return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.forward, p.text)
|
return p.iter(algo.FuzzyMatch, input, p.caseSensitive, p.forward, p.text)
|
||||||
|
@ -322,33 +310,39 @@ func (p *Pattern) basicMatch(item *Item) (Offset, int32) {
|
||||||
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.forward, p.text)
|
return p.iter(algo.ExactMatchNaive, input, p.caseSensitive, p.forward, p.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) extendedMatch(item *Item) ([]Offset, int32) {
|
func (p *Pattern) extendedMatch(item *Item) ([]Offset, int, int) {
|
||||||
input := p.prepareInput(item)
|
input := p.prepareInput(item)
|
||||||
offsets := []Offset{}
|
offsets := []Offset{}
|
||||||
var totalBonus int32
|
var totalBonus int
|
||||||
|
var totalTrimLen int
|
||||||
for _, termSet := range p.termSets {
|
for _, termSet := range p.termSets {
|
||||||
var offset *Offset
|
var offset Offset
|
||||||
var bonus int32
|
var bonus int
|
||||||
|
var trimLen int
|
||||||
|
matched := false
|
||||||
for _, term := range termSet {
|
for _, term := range termSet {
|
||||||
pfun := p.procFun[term.typ]
|
pfun := p.procFun[term.typ]
|
||||||
off, pen := p.iter(pfun, input, term.caseSensitive, p.forward, term.text)
|
off, pen, tLen := p.iter(pfun, input, term.caseSensitive, p.forward, term.text)
|
||||||
if sidx := off[0]; sidx >= 0 {
|
if sidx := off[0]; sidx >= 0 {
|
||||||
if term.inv {
|
if term.inv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
offset, bonus = &off, pen
|
offset, bonus, trimLen = off, pen, tLen
|
||||||
|
matched = true
|
||||||
break
|
break
|
||||||
} else if term.inv {
|
} else if term.inv {
|
||||||
offset, bonus = &Offset{0, 0, 0}, 0
|
offset, bonus, trimLen = Offset{0, 0}, 0, 0
|
||||||
|
matched = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if offset != nil {
|
if matched {
|
||||||
offsets = append(offsets, *offset)
|
offsets = append(offsets, offset)
|
||||||
totalBonus += bonus
|
totalBonus += bonus
|
||||||
|
totalTrimLen += trimLen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return offsets, totalBonus
|
return offsets, totalBonus, totalTrimLen
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) prepareInput(item *Item) []Token {
|
func (p *Pattern) prepareInput(item *Item) []Token {
|
||||||
|
@ -357,26 +351,24 @@ func (p *Pattern) prepareInput(item *Item) []Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
var ret []Token
|
var ret []Token
|
||||||
if len(p.nth) > 0 {
|
if len(p.nth) == 0 {
|
||||||
|
ret = []Token{Token{text: &item.text, prefixLength: 0, trimLength: int32(item.text.TrimLength())}}
|
||||||
|
} else {
|
||||||
tokens := Tokenize(item.text, p.delimiter)
|
tokens := Tokenize(item.text, p.delimiter)
|
||||||
ret = Transform(tokens, p.nth)
|
ret = Transform(tokens, p.nth)
|
||||||
} else {
|
|
||||||
ret = []Token{Token{text: item.text, prefixLength: 0, trimLength: item.text.TrimLength()}}
|
|
||||||
}
|
}
|
||||||
item.transformed = ret
|
item.transformed = ret
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pattern) iter(pfun func(bool, bool, util.Chars, []rune) algo.Result,
|
func (p *Pattern) iter(pfun func(bool, bool, util.Chars, []rune) algo.Result,
|
||||||
tokens []Token, caseSensitive bool, forward bool, pattern []rune) (Offset, int32) {
|
tokens []Token, caseSensitive bool, forward bool, pattern []rune) (Offset, int, int) {
|
||||||
for _, part := range tokens {
|
for _, part := range tokens {
|
||||||
prefixLength := int32(part.prefixLength)
|
if res := pfun(caseSensitive, forward, *part.text, pattern); res.Start >= 0 {
|
||||||
if res := pfun(caseSensitive, forward, part.text, pattern); res.Start >= 0 {
|
sidx := int32(res.Start) + part.prefixLength
|
||||||
sidx := res.Start + prefixLength
|
eidx := int32(res.End) + part.prefixLength
|
||||||
eidx := res.End + prefixLength
|
return Offset{sidx, eidx}, res.Bonus, int(part.trimLength)
|
||||||
return Offset{sidx, eidx, int32(part.trimLength)}, res.Bonus
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: math.MaxUint16
|
return Offset{-1, -1}, 0, -1
|
||||||
return Offset{-1, -1, -1}, 0.0
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ func TestEqual(t *testing.T) {
|
||||||
clearPatternCache()
|
clearPatternCache()
|
||||||
pattern := BuildPattern(true, true, CaseSmart, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
pattern := BuildPattern(true, true, CaseSmart, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
|
|
||||||
match := func(str string, sidxExpected int32, eidxExpected int32) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
res := algo.EqualMatch(
|
res := algo.EqualMatch(
|
||||||
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text)
|
pattern.caseSensitive, pattern.forward, util.RunesToChars([]rune(str)), pattern.termSets[0][0].text)
|
||||||
if res.Start != sidxExpected || res.End != eidxExpected {
|
if res.Start != sidxExpected || res.End != eidxExpected {
|
||||||
|
@ -133,10 +133,10 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
transformed: trans},
|
transformed: trans},
|
||||||
}
|
}
|
||||||
pattern.extended = extended
|
pattern.extended = extended
|
||||||
matches := pattern.matchChunk(&chunk)
|
matches := pattern.matchChunk(&chunk, nil) // No cache
|
||||||
if matches[0].text.ToString() != "junegunn" || string(*matches[0].origText) != "junegunn.choi" ||
|
if matches[0].item.text.ToString() != "junegunn" || string(*matches[0].item.origText) != "junegunn.choi" ||
|
||||||
matches[0].offsets[0][0] != 0 || matches[0].offsets[0][1] != 5 ||
|
matches[0].offsets[0][0] != 0 || matches[0].offsets[0][1] != 5 ||
|
||||||
!reflect.DeepEqual(matches[0].transformed, trans) {
|
!reflect.DeepEqual(matches[0].item.transformed, trans) {
|
||||||
t.Error("Invalid match result", matches)
|
t.Error("Invalid match result", matches)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ type Terminal struct {
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
at time.Time
|
at time.Time
|
||||||
text *string
|
text string
|
||||||
}
|
}
|
||||||
|
|
||||||
type byTimeOrder []selectedItem
|
type byTimeOrder []selectedItem
|
||||||
|
@ -357,7 +357,7 @@ func (t *Terminal) output() bool {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, sel := range t.sortSelected() {
|
for _, sel := range t.sortSelected() {
|
||||||
fmt.Println(*sel.text)
|
fmt.Println(sel.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return found
|
return found
|
||||||
|
@ -565,11 +565,10 @@ func (t *Terminal) printHeader() {
|
||||||
state = newState
|
state = newState
|
||||||
item := &Item{
|
item := &Item{
|
||||||
text: util.RunesToChars([]rune(trimmed)),
|
text: util.RunesToChars([]rune(trimmed)),
|
||||||
colors: colors,
|
colors: colors}
|
||||||
rank: buildEmptyRank(0)}
|
|
||||||
|
|
||||||
t.move(line, 2, true)
|
t.move(line, 2, true)
|
||||||
t.printHighlighted(item, false, C.ColHeader, 0, false)
|
t.printHighlighted(&Result{item: item}, false, C.ColHeader, 0, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,7 +589,8 @@ func (t *Terminal) printList() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printItem(item *Item, i int, current bool) {
|
func (t *Terminal) printItem(result *Result, i int, current bool) {
|
||||||
|
item := result.item
|
||||||
_, selected := t.selected[item.Index()]
|
_, selected := t.selected[item.Index()]
|
||||||
label := " "
|
label := " "
|
||||||
if t.jumping != jumpDisabled {
|
if t.jumping != jumpDisabled {
|
||||||
|
@ -609,14 +609,14 @@ func (t *Terminal) printItem(item *Item, i int, current bool) {
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(C.ColCurrent, true, " ")
|
t.window.CPrint(C.ColCurrent, true, " ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch, true)
|
t.printHighlighted(result, true, C.ColCurrent, C.ColCurrentMatch, true)
|
||||||
} else {
|
} else {
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(C.ColSelected, true, ">")
|
t.window.CPrint(C.ColSelected, true, ">")
|
||||||
} else {
|
} else {
|
||||||
t.window.Print(" ")
|
t.window.Print(" ")
|
||||||
}
|
}
|
||||||
t.printHighlighted(item, false, 0, C.ColMatch, false)
|
t.printHighlighted(result, false, 0, C.ColMatch, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,16 +667,17 @@ func overflow(runes []rune, max int) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int, current bool) {
|
func (t *Terminal) printHighlighted(result *Result, bold bool, col1 int, col2 int, current bool) {
|
||||||
|
item := result.item
|
||||||
var maxe int
|
var maxe int
|
||||||
for _, offset := range item.offsets {
|
for _, offset := range result.offsets {
|
||||||
maxe = util.Max(maxe, int(offset[1]))
|
maxe = util.Max(maxe, int(offset[1]))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overflow
|
// Overflow
|
||||||
text := make([]rune, item.text.Length())
|
text := make([]rune, item.text.Length())
|
||||||
copy(text, item.text.ToRunes())
|
copy(text, item.text.ToRunes())
|
||||||
offsets := item.colorOffsets(col2, bold, current)
|
offsets := result.colorOffsets(col2, bold, current)
|
||||||
maxWidth := t.window.Width - 3
|
maxWidth := t.window.Width - 3
|
||||||
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
|
||||||
if overflow(text, maxWidth) {
|
if overflow(text, maxWidth) {
|
||||||
|
@ -866,7 +867,7 @@ func (t *Terminal) isPreviewEnabled() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) current() string {
|
func (t *Terminal) current() string {
|
||||||
return t.merger.Get(t.cy).AsString(t.ansi)
|
return t.merger.Get(t.cy).item.AsString(t.ansi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
|
@ -1037,13 +1038,13 @@ func (t *Terminal) Loop() {
|
||||||
}
|
}
|
||||||
selectItem := func(item *Item) bool {
|
selectItem := func(item *Item) bool {
|
||||||
if _, found := t.selected[item.Index()]; !found {
|
if _, found := t.selected[item.Index()]; !found {
|
||||||
t.selected[item.Index()] = selectedItem{time.Now(), item.StringPtr(t.ansi)}
|
t.selected[item.Index()] = selectedItem{time.Now(), item.AsString(t.ansi)}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
toggleY := func(y int) {
|
toggleY := func(y int) {
|
||||||
item := t.merger.Get(y)
|
item := t.merger.Get(y).item
|
||||||
if !selectItem(item) {
|
if !selectItem(item) {
|
||||||
delete(t.selected, item.Index())
|
delete(t.selected, item.Index())
|
||||||
}
|
}
|
||||||
|
@ -1068,14 +1069,14 @@ func (t *Terminal) Loop() {
|
||||||
case actIgnore:
|
case actIgnore:
|
||||||
case actExecute:
|
case actExecute:
|
||||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||||
item := t.merger.Get(t.cy)
|
item := t.merger.Get(t.cy).item
|
||||||
t.executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
|
t.executeCommand(t.execmap[mapkey], quoteEntry(item.AsString(t.ansi)))
|
||||||
}
|
}
|
||||||
case actExecuteMulti:
|
case actExecuteMulti:
|
||||||
if len(t.selected) > 0 {
|
if len(t.selected) > 0 {
|
||||||
sels := make([]string, len(t.selected))
|
sels := make([]string, len(t.selected))
|
||||||
for i, sel := range t.sortSelected() {
|
for i, sel := range t.sortSelected() {
|
||||||
sels[i] = quoteEntry(*sel.text)
|
sels[i] = quoteEntry(sel.text)
|
||||||
}
|
}
|
||||||
t.executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
|
t.executeCommand(t.execmap[mapkey], strings.Join(sels, " "))
|
||||||
} else {
|
} else {
|
||||||
|
@ -1137,7 +1138,7 @@ func (t *Terminal) Loop() {
|
||||||
case actSelectAll:
|
case actSelectAll:
|
||||||
if t.multi {
|
if t.multi {
|
||||||
for i := 0; i < t.merger.Length(); i++ {
|
for i := 0; i < t.merger.Length(); i++ {
|
||||||
item := t.merger.Get(i)
|
item := t.merger.Get(i).item
|
||||||
selectItem(item)
|
selectItem(item)
|
||||||
}
|
}
|
||||||
req(reqList, reqInfo)
|
req(reqList, reqInfo)
|
||||||
|
|
|
@ -18,9 +18,9 @@ type Range struct {
|
||||||
|
|
||||||
// Token contains the tokenized part of the strings and its prefix length
|
// Token contains the tokenized part of the strings and its prefix length
|
||||||
type Token struct {
|
type Token struct {
|
||||||
text util.Chars
|
text *util.Chars
|
||||||
prefixLength int
|
prefixLength int32
|
||||||
trimLength int
|
trimLength int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delimiter for tokenizing the input
|
// Delimiter for tokenizing the input
|
||||||
|
@ -80,9 +80,8 @@ func withPrefixLengths(tokens []util.Chars, begin int) []Token {
|
||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
for idx, token := range tokens {
|
for idx, token := range tokens {
|
||||||
// Need to define a new local variable instead of the reused token to take
|
// NOTE: &tokens[idx] instead of &tokens
|
||||||
// the pointer to it
|
ret[idx] = Token{&tokens[idx], int32(prefixLength), int32(token.TrimLength())}
|
||||||
ret[idx] = Token{token, prefixLength, token.TrimLength()}
|
|
||||||
prefixLength += token.Length()
|
prefixLength += token.Length()
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
|
@ -178,12 +177,13 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
||||||
transTokens := make([]Token, len(withNth))
|
transTokens := make([]Token, len(withNth))
|
||||||
numTokens := len(tokens)
|
numTokens := len(tokens)
|
||||||
for idx, r := range withNth {
|
for idx, r := range withNth {
|
||||||
parts := []util.Chars{}
|
parts := []*util.Chars{}
|
||||||
minIdx := 0
|
minIdx := 0
|
||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
parts = append(parts, util.RunesToChars(joinTokens(tokens)))
|
chars := util.RunesToChars(joinTokens(tokens))
|
||||||
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
idx += numTokens + 1
|
idx += numTokens + 1
|
||||||
|
@ -227,7 +227,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
||||||
case 0:
|
case 0:
|
||||||
merged = util.RunesToChars([]rune{})
|
merged = util.RunesToChars([]rune{})
|
||||||
case 1:
|
case 1:
|
||||||
merged = parts[0]
|
merged = *parts[0]
|
||||||
default:
|
default:
|
||||||
runes := []rune{}
|
runes := []rune{}
|
||||||
for _, part := range parts {
|
for _, part := range parts {
|
||||||
|
@ -236,13 +236,13 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
||||||
merged = util.RunesToChars(runes)
|
merged = util.RunesToChars(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
var prefixLength int
|
var prefixLength int32
|
||||||
if minIdx < numTokens {
|
if minIdx < numTokens {
|
||||||
prefixLength = tokens[minIdx].prefixLength
|
prefixLength = tokens[minIdx].prefixLength
|
||||||
} else {
|
} else {
|
||||||
prefixLength = 0
|
prefixLength = 0
|
||||||
}
|
}
|
||||||
transTokens[idx] = Token{merged, prefixLength, merged.TrimLength()}
|
transTokens[idx] = Token{&merged, prefixLength, int32(merged.TrimLength())}
|
||||||
}
|
}
|
||||||
return transTokens
|
return transTokens
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ package util
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
@ -63,6 +64,15 @@ func Constrain(val int, min int, max int) int {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AsUint16(val int) uint16 {
|
||||||
|
if val > math.MaxUint16 {
|
||||||
|
return math.MaxUint16
|
||||||
|
} else if val < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return uint16(val)
|
||||||
|
}
|
||||||
|
|
||||||
// DurWithin limits the given time.Duration with the upper and lower bounds
|
// DurWithin limits the given time.Duration with the upper and lower bounds
|
||||||
func DurWithin(
|
func DurWithin(
|
||||||
val time.Duration, min time.Duration, max time.Duration) time.Duration {
|
val time.Duration, min time.Duration, max time.Duration) time.Duration {
|
||||||
|
|
Loading…
Reference in New Issue