summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2016-08-19 02:39:32 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2016-08-19 02:39:32 +0900
commit37dc273148df0893053bf5cda0582a23f5c2b2d2 (patch)
treed90f5e96fa97de429265461268b1a6db2703cb4c
parentf7f01d109eb05c7eae82c243b6b6d5c5951ee707 (diff)
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
-rw-r--r--src/algo/algo.go22
-rw-r--r--src/algo/algo_test.go2
-rw-r--r--src/ansi.go7
-rw-r--r--src/ansi_test.go84
-rw-r--r--src/cache.go6
-rw-r--r--src/cache_test.go4
-rw-r--r--src/chunklist_test.go9
-rw-r--r--src/core.go28
-rw-r--r--src/item.go278
-rw-r--r--src/item_test.go108
-rw-r--r--src/matcher.go16
-rw-r--r--src/merger.go22
-rw-r--r--src/merger_test.go26
-rw-r--r--src/options.go5
-rw-r--r--src/pattern.go110
-rw-r--r--src/pattern_test.go8
-rw-r--r--src/terminal.go35
-rw-r--r--src/tokenizer.go22
-rw-r--r--src/util/util.go10
19 files changed, 234 insertions, 568 deletions
diff --git a/src/algo/algo.go b/src/algo/algo.go
index 8825a059..00265c60 100644
--- a/src/algo/algo.go
+++ b/src/algo/algo.go
@@ -24,12 +24,12 @@ func indexAt(index int, max int, forward bool) int {
// Result conatins the results of running a match function.
type Result struct {
- Start int32
- End int32
+ Start int
+ End int
// Items are basically sorted by the lengths of matched substrings.
// But we slightly adjust the score with bonus for better results.
- Bonus int32
+ Bonus int
}
type charClass int
@@ -42,8 +42,8 @@ const (
charNumber
)
-func evaluateBonus(caseSensitive bool, text util.Chars, pattern []rune, sidx int, eidx int) int32 {
- var bonus int32
+func evaluateBonus(caseSensitive bool, text util.Chars, pattern []rune, sidx int, eidx int) int {
+ var bonus int
pidx := 0
lenPattern := len(pattern)
consecutive := false
@@ -63,7 +63,7 @@ func evaluateBonus(caseSensitive bool, text util.Chars, pattern []rune, sidx int
class = charNonWord
}
- var point int32
+ var point int
if prevClass == charNonWord && class != charNonWord {
// Word boundary
point = 2
@@ -181,7 +181,7 @@ func FuzzyMatch(caseSensitive bool, forward bool, text util.Chars, pattern []run
sidx, eidx = lenRunes-eidx, lenRunes-sidx
}
- return Result{int32(sidx), int32(eidx),
+ return Result{sidx, eidx,
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
}
return Result{-1, -1, 0}
@@ -228,7 +228,7 @@ func ExactMatchNaive(caseSensitive bool, forward bool, text util.Chars, pattern
sidx = lenRunes - (index + 1)
eidx = lenRunes - (index - lenPattern + 1)
}
- return Result{int32(sidx), int32(eidx),
+ return Result{sidx, eidx,
evaluateBonus(caseSensitive, text, pattern, sidx, eidx)}
}
} else {
@@ -255,7 +255,7 @@ func PrefixMatch(caseSensitive bool, forward bool, text util.Chars, pattern []ru
}
}
lenPattern := len(pattern)
- return Result{0, int32(lenPattern),
+ return Result{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)
sidx := trimmedLen - lenPattern
eidx := trimmedLen
- return Result{int32(sidx), int32(eidx),
+ return Result{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)
}
if runesStr == string(pattern) {
- return Result{0, int32(len(pattern)), 0}
+ return Result{0, len(pattern), 0}
}
return Result{-1, -1, 0}
}
diff --git a/src/algo/algo_test.go b/src/algo/algo_test.go
index d6a5d487..7034dcef 100644
--- a/src/algo/algo_test.go
+++ b/src/algo/algo_test.go
@@ -7,7 +7,7 @@ import (
"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 {
pattern = strings.ToLower(pattern)
}
diff --git a/src/ansi.go b/src/ansi.go
index 56831eee..0a525671 100644
--- a/src/ansi.go
+++ b/src/ansi.go
@@ -36,7 +36,7 @@ func init() {
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 output bytes.Buffer
@@ -84,7 +84,10 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
if proc != nil {
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 {
diff --git a/src/ansi_test.go b/src/ansi_test.go
index 31803f38..a80e98ad 100644
--- a/src/ansi_test.go
+++ b/src/ansi_test.go
@@ -16,7 +16,7 @@ func TestExtractColor(t *testing.T) {
src := "hello world"
var state *ansiState
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)
state = newState
if output != "hello world" {
@@ -26,127 +26,127 @@ func TestExtractColor(t *testing.T) {
assertion(ansiOffsets, state)
}
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) > 0 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if offsets != nil {
t.Fail()
}
})
state = nil
src = "\x1b[0mhello world"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) > 0 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if offsets != nil {
t.Fail()
}
})
state = nil
src = "\x1b[1mhello world"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
- assert(offsets[0], 0, 11, -1, -1, true)
+ assert((*offsets)[0], 0, 11, -1, -1, true)
})
state = nil
src = "\x1b[1mhello \x1b[mworld"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
- assert(offsets[0], 0, 6, -1, -1, true)
+ assert((*offsets)[0], 0, 6, -1, -1, true)
})
state = nil
src = "\x1b[1mhello \x1b[Kworld"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
- assert(offsets[0], 0, 11, -1, -1, true)
+ assert((*offsets)[0], 0, 11, -1, -1, true)
})
state = nil
src = "hello \x1b[34;45;1mworld"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
- assert(offsets[0], 6, 11, 4, 5, true)
+ assert((*offsets)[0], 6, 11, 4, 5, true)
})
state = nil
src = "hello \x1b[34;45;1mwor\x1b[34;45;1mld"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
- assert(offsets[0], 6, 11, 4, 5, true)
+ assert((*offsets)[0], 6, 11, 4, 5, true)
})
state = nil
src = "hello \x1b[34;45;1mwor\x1b[0mld"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
- assert(offsets[0], 6, 9, 4, 5, true)
+ assert((*offsets)[0], 6, 9, 4, 5, true)
})
state = nil
src = "hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 3 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 3 {
t.Fail()
}
- assert(offsets[0], 6, 8, 4, 233, true)
- assert(offsets[1], 8, 9, 161, 233, true)
- assert(offsets[2], 10, 11, 161, -1, false)
+ assert((*offsets)[0], 6, 8, 4, 233, true)
+ assert((*offsets)[1], 8, 9, 161, 233, true)
+ assert((*offsets)[2], 10, 11, 161, -1, false)
})
// {38,48};5;{38,48}
state = nil
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) {
- if len(offsets) != 2 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 2 {
t.Fail()
}
- assert(offsets[0], 6, 9, 38, 48, true)
- assert(offsets[1], 9, 10, 48, 38, true)
+ assert((*offsets)[0], 6, 9, 38, 48, true)
+ assert((*offsets)[1], 9, 10, 48, 38, true)
})
src = "hello \x1b[32;1mworld"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
if state.fg != 2 || state.bg != -1 || !state.bold {
t.Fail()
}
- assert(offsets[0], 6, 11, 2, -1, true)
+ assert((*offsets)[0], 6, 11, 2, -1, true)
})
src = "hello world"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 1 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 1 {
t.Fail()
}
if state.fg != 2 || state.bg != -1 || !state.bold {
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"
- check(func(offsets []ansiOffset, state *ansiState) {
- if len(offsets) != 2 {
+ check(func(offsets *[]ansiOffset, state *ansiState) {
+ if len(*offsets) != 2 {
t.Fail()
}
if state.fg != 200 || state.bg != 100 || state.bold {
t.Fail()
}
- assert(offsets[0], 0, 6, 2, -1, true)
- assert(offsets[1], 6, 11, 200, 100, false)
+ assert((*offsets)[0], 0, 6, 2, -1, true)
+ assert((*offsets)[1], 6, 11, 200, 100, false)
})
}
diff --git a/src/cache.go b/src/cache.go
index d2ec00b4..baf88dd8 100644
--- a/src/cache.go
+++ b/src/cache.go
@@ -3,7 +3,7 @@ package fzf
import "sync"
// 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
type ChunkCache struct {
@@ -17,7 +17,7 @@ func NewChunkCache() ChunkCache {
}
// 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 {
return
}
@@ -34,7 +34,7 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Item) {
}
// 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() {
return nil, false
}
diff --git a/src/cache_test.go b/src/cache_test.go
index 05f904c9..8703fc41 100644
--- a/src/cache_test.go
+++ b/src/cache_test.go
@@ -7,8 +7,8 @@ func TestChunkCache(t *testing.T) {
chunk2 := make(Chunk, chunkSize)
chunk1p := &Chunk{}
chunk2p := &chunk2
- items1 := []*Item{&Item{}}
- items2 := []*Item{&Item{}, &Item{}}
+ items1 := []*Result{&Result{}}
+ items2 := []*Result{&Result{}, &Result{}}
cache.Add(chunk1p, "foo", items1)
cache.Add(chunk2p, "foo", items1)
cache.Add(chunk2p, "bar", items2)
diff --git a/src/chunklist_test.go b/src/chunklist_test.go
index 2523675a..594daebb 100644
--- a/src/chunklist_test.go
+++ b/src/chunklist_test.go
@@ -12,7 +12,7 @@ func TestChunkList(t *testing.T) {
sortCriteria = []criterion{byMatchLen, byLength}
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
@@ -41,11 +41,8 @@ func TestChunkList(t *testing.T) {
if len(*chunk1) != 2 {
t.Error("Snapshot should contain only two items")
}
- last := func(arr [5]int32) int32 {
- return arr[len(arr)-1]
- }
- if (*chunk1)[0].text.ToString() != "hello" || last((*chunk1)[0].rank) != 0 ||
- (*chunk1)[1].text.ToString() != "world" || last((*chunk1)[1].rank) != 2 {
+ if (*chunk1)[0].text.ToString() != "hello" || (*chunk1)[0].index != 0 ||
+ (*chunk1)[1].text.ToString() != "world" || (*chunk1)[1].index != 2 {
t.Error("Invalid data")
}
if chunk1.IsFull() {
diff --git a/src/core.go b/src/core.go
index 76d30d1a..290c0af0 100644
--- a/src/core.go
+++ b/src/core.go
@@ -56,16 +56,16 @@ func Run(opts *Options) {
eventBox := util.NewEventBox()
// ANSI code processor
- ansiProcessor := func(data []byte) (util.Chars, []ansiOffset) {
+ ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
return util.ToChars(data), nil
}
- ansiProcessorRunes := func(data []rune) (util.Chars, []ansiOffset) {
+ ansiProcessorRunes := func(data []rune) (util.Chars, *[]ansiOffset) {
return util.RunesToChars(data), nil
}
if opts.Ansi {
if opts.Theme != nil {
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)
state = newState
return util.RunesToChars([]rune(trimmed)), offsets
@@ -73,12 +73,12 @@ func Run(opts *Options) {
} else {
// When color is disabled but ansi option is given,
// 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)
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)))
}
}
@@ -95,14 +95,13 @@ func Run(opts *Options) {
}
chars, colors := ansiProcessor(data)
return &Item{
+ index: int32(index),
text: chars,
- colors: colors,
- rank: buildEmptyRank(int32(index))}
+ colors: colors}
})
} else {
chunkList = NewChunkList(func(data []byte, index int) *Item {
- chars := util.ToChars(data)
- tokens := Tokenize(chars, opts.Delimiter)
+ tokens := Tokenize(util.ToChars(data), opts.Delimiter)
trans := Transform(tokens, opts.WithNth)
if len(header) < opts.HeaderLines {
header = append(header, string(joinTokens(trans)))
@@ -111,10 +110,9 @@ func Run(opts *Options) {
}
textRunes := joinTokens(trans)
item := Item{
- text: util.RunesToChars(textRunes),
+ index: int32(index),
origText: &data,
- colors: nil,
- rank: buildEmptyRank(int32(index))}
+ colors: nil}
trimmed, colors := ansiProcessorRunes(textRunes)
item.text = trimmed
@@ -163,7 +161,7 @@ func Run(opts *Options) {
reader := Reader{
func(runes []byte) bool {
item := chunkList.trans(runes, 0)
- if item != nil && pattern.MatchItem(item) {
+ if item != nil && pattern.MatchItem(item) != nil {
fmt.Println(item.text.ToString())
found = true
}
@@ -179,7 +177,7 @@ func Run(opts *Options) {
chunks: snapshot,
pattern: pattern})
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
}
}
@@ -259,7 +257,7 @@ func Run(opts *Options) {
fmt.Println()
}
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 {
os.Exit(exitOk)
diff --git a/src/item.go b/src/item.go
index 584dfd85..4e60fafb 100644
--- a/src/item.go
+++ b/src/item.go
@@ -1,295 +1,39 @@
package fzf
import (
- "math"
-
- "github.com/junegunn/fzf/src/curses"
"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
type Item struct {
+ index int32
text util.Chars
origText *[]byte
+ colors *[]ansiOffset
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
func (item *Item) Index() int32 {
- return item.rank[4]
+ return item.index
}
-// Rank calculates rank of the Item
-func (item *Item) Rank(cache bool) [5]int32 {
- if cache && isRankValid(item.rank) {
- return item.rank
- }
- matchlen := 0
- 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
+// Colors returns ansiOffsets of the Item
+func (item *Item) Colors() []ansiOffset {
+ if item.colors == nil {
+ return []ansiOffset{}
}
- return rank
+ return *item.colors
}
// AsString returns the original 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 stripAnsi {
trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
- return &trimmed
- }
- orig := string(*item.origText)
- return &orig
- }
- str := 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 trimmed
}
+ return string(*item.origText)
}
- return (irank[4] <= jrank[4]) != tac
+ return item.text.ToString()
}
diff --git a/src/item_test.go b/src/item_test.go
index 36a436d3..1efb5f1d 100644
--- a/src/item_test.go
+++ b/src/item_test.go
@@ -1,109 +1,23 @@
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) {
- 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))
+func TestStringPtr(t *testing.T) {
+ orig := []byte("\x1b[34mfoo")
+ text := []byte("\x1b[34mbar")
+ item := Item{origText: &orig, text: util.ToChars(text)}
+ if item.AsString(true) != "foo" || item.AsString(false) != string(orig) {
+ t.Fail()
}
- // 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)
+ if item.AsString(true) != "foo" {
+ t.Fail()
}
-
- 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)
- }
+ item.origText = nil
+ if item.AsString(true) != string(text) || item.AsString(false) != string(text) {
+ t.Fail()
}
- 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)
}
diff --git a/src/matcher.go b/src/matcher.go
index 4c00db7e..8cd9a9f8 100644
--- a/src/matcher.go
+++ b/src/matcher.go
@@ -128,7 +128,7 @@ func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
type partialResult struct {
index int
- matches []*Item
+ matches []*Result
}
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
@@ -155,15 +155,21 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
waitGroup.Add(1)
go func(idx int, chunks []*Chunk) {
defer func() { waitGroup.Done() }()
- sliceMatches := []*Item{}
- for _, chunk := range chunks {
+ count := 0
+ allMatches := make([][]*Result, len(chunks))
+ for idx, chunk := range chunks {
matches := request.pattern.Match(chunk)
- sliceMatches = append(sliceMatches, matches...)
+ allMatches[idx] = matches
+ count += len(matches)
if cancelled.Get() {
return
}
countChan <- len(matches)
}
+ sliceMatches := make([]*Result, 0, count)
+ for _, matches := range allMatches {
+ sliceMatches = append(sliceMatches, matches...)
+ }
if m.sort {
if m.tac {
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 {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
diff --git a/src/merger.go b/src/merger.go
index 0d3fb801..3879ab7e 100644
--- a/src/merger.go
+++ b/src/merger.go
@@ -3,13 +3,13 @@ package fzf
import "fmt"
// 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