Performance fix - unnecessary rune convertion on --ansi
> time cat /tmp/list | fzf-0.10.1-darwin_amd64 --ansi -fqwerty > /dev/null real 0m4.364s user 0m8.231s sys 0m0.820s > time cat /tmp/list | fzf --ansi -fqwerty > /dev/null real 0m4.624s user 0m5.755s sys 0m0.732s
This commit is contained in:
parent
0ea66329b8
commit
e13bafc1ab
@ -7,7 +7,7 @@ type Chunk []*Item // >>> []Item
|
|||||||
|
|
||||||
// ItemBuilder is a closure type that builds Item object from a pointer to a
|
// ItemBuilder is a closure type that builds Item object from a pointer to a
|
||||||
// string and an integer
|
// string and an integer
|
||||||
type ItemBuilder func([]rune, int) *Item
|
type ItemBuilder func([]byte, int) *Item
|
||||||
|
|
||||||
// ChunkList is a list of Chunks
|
// ChunkList is a list of Chunks
|
||||||
type ChunkList struct {
|
type ChunkList struct {
|
||||||
@ -26,7 +26,7 @@ func NewChunkList(trans ItemBuilder) *ChunkList {
|
|||||||
trans: trans}
|
trans: trans}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chunk) push(trans ItemBuilder, data []rune, index int) bool {
|
func (c *Chunk) push(trans ItemBuilder, data []byte, index int) bool {
|
||||||
item := trans(data, index)
|
item := trans(data, index)
|
||||||
if item != nil {
|
if item != nil {
|
||||||
*c = append(*c, item)
|
*c = append(*c, item)
|
||||||
@ -53,7 +53,7 @@ func CountItems(cs []*Chunk) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push adds the item to the list
|
// Push adds the item to the list
|
||||||
func (cl *ChunkList) Push(data []rune) bool {
|
func (cl *ChunkList) Push(data []byte) bool {
|
||||||
cl.mutex.Lock()
|
cl.mutex.Lock()
|
||||||
defer cl.mutex.Unlock()
|
defer cl.mutex.Unlock()
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestChunkList(t *testing.T) {
|
func TestChunkList(t *testing.T) {
|
||||||
cl := NewChunkList(func(s []rune, i int) *Item {
|
cl := NewChunkList(func(s []byte, i int) *Item {
|
||||||
return &Item{text: s, rank: Rank{0, 0, uint32(i * 2)}}
|
return &Item{text: []rune(string(s)), rank: Rank{0, 0, uint32(i * 2)}}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
@ -17,8 +17,8 @@ func TestChunkList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add some data
|
// Add some data
|
||||||
cl.Push([]rune("hello"))
|
cl.Push([]byte("hello"))
|
||||||
cl.Push([]rune("world"))
|
cl.Push([]byte("world"))
|
||||||
|
|
||||||
// Previously created snapshot should remain the same
|
// Previously created snapshot should remain the same
|
||||||
if len(snapshot) > 0 {
|
if len(snapshot) > 0 {
|
||||||
@ -46,7 +46,7 @@ func TestChunkList(t *testing.T) {
|
|||||||
|
|
||||||
// Add more data
|
// Add more data
|
||||||
for i := 0; i < chunkSize*2; i++ {
|
for i := 0; i < chunkSize*2; i++ {
|
||||||
cl.Push([]rune(fmt.Sprintf("item %d", i)))
|
cl.Push([]byte(fmt.Sprintf("item %d", i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Previous snapshot should remain the same
|
// Previous snapshot should remain the same
|
||||||
@ -64,8 +64,8 @@ func TestChunkList(t *testing.T) {
|
|||||||
t.Error("Unexpected number of items")
|
t.Error("Unexpected number of items")
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.Push([]rune("hello"))
|
cl.Push([]byte("hello"))
|
||||||
cl.Push([]rune("world"))
|
cl.Push([]byte("world"))
|
||||||
|
|
||||||
lastChunkCount := len(*snapshot[len(snapshot)-1])
|
lastChunkCount := len(*snapshot[len(snapshot)-1])
|
||||||
if lastChunkCount != 2 {
|
if lastChunkCount != 2 {
|
||||||
|
38
src/core.go
38
src/core.go
@ -63,48 +63,54 @@ func Run(opts *Options) {
|
|||||||
eventBox := util.NewEventBox()
|
eventBox := util.NewEventBox()
|
||||||
|
|
||||||
// ANSI code processor
|
// ANSI code processor
|
||||||
ansiProcessor := func(runes []rune) ([]rune, []ansiOffset) {
|
ansiProcessor := func(data []byte) ([]rune, []ansiOffset) {
|
||||||
// By default, we do nothing
|
return util.BytesToRunes(data), nil
|
||||||
return runes, nil
|
}
|
||||||
|
ansiProcessorRunes := func(data []rune) ([]rune, []ansiOffset) {
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
if opts.Ansi {
|
if opts.Ansi {
|
||||||
if opts.Theme != nil {
|
if opts.Theme != nil {
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
ansiProcessor = func(runes []rune) ([]rune, []ansiOffset) {
|
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
|
||||||
trimmed, offsets, newState := extractColor(string(runes), state)
|
trimmed, offsets, newState := extractColor(string(data), state)
|
||||||
state = newState
|
state = newState
|
||||||
return []rune(trimmed), offsets
|
return []rune(trimmed), offsets
|
||||||
}
|
}
|
||||||
} 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(runes []rune) ([]rune, []ansiOffset) {
|
ansiProcessor = func(data []byte) ([]rune, []ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(string(runes), nil)
|
trimmed, _, _ := extractColor(string(data), nil)
|
||||||
return []rune(trimmed), nil
|
return []rune(trimmed), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ansiProcessorRunes = func(data []rune) ([]rune, []ansiOffset) {
|
||||||
|
return ansiProcessor([]byte(string(data)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunk list
|
// Chunk list
|
||||||
var chunkList *ChunkList
|
var chunkList *ChunkList
|
||||||
header := make([]string, 0, opts.HeaderLines)
|
header := make([]string, 0, opts.HeaderLines)
|
||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(data []rune, index int) *Item {
|
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, string(data))
|
header = append(header, string(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data, colors := ansiProcessor(data)
|
runes, colors := ansiProcessor(data)
|
||||||
return &Item{
|
return &Item{
|
||||||
text: data,
|
text: runes,
|
||||||
index: uint32(index),
|
index: uint32(index),
|
||||||
colors: colors,
|
colors: colors,
|
||||||
rank: Rank{0, 0, uint32(index)}}
|
rank: Rank{0, 0, uint32(index)}}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(data []rune, index int) *Item {
|
chunkList = NewChunkList(func(data []byte, index int) *Item {
|
||||||
tokens := Tokenize(data, opts.Delimiter)
|
runes := util.BytesToRunes(data)
|
||||||
|
tokens := Tokenize(runes, 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)))
|
||||||
@ -113,12 +119,12 @@ func Run(opts *Options) {
|
|||||||
}
|
}
|
||||||
item := Item{
|
item := Item{
|
||||||
text: joinTokens(trans),
|
text: joinTokens(trans),
|
||||||
origText: &data,
|
origText: &runes,
|
||||||
index: uint32(index),
|
index: uint32(index),
|
||||||
colors: nil,
|
colors: nil,
|
||||||
rank: Rank{0, 0, uint32(index)}}
|
rank: Rank{0, 0, uint32(index)}}
|
||||||
|
|
||||||
trimmed, colors := ansiProcessor(item.text)
|
trimmed, colors := ansiProcessorRunes(item.text)
|
||||||
item.text = trimmed
|
item.text = trimmed
|
||||||
item.colors = colors
|
item.colors = colors
|
||||||
return &item
|
return &item
|
||||||
@ -128,7 +134,7 @@ func Run(opts *Options) {
|
|||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader := Reader{func(data []rune) bool {
|
reader := Reader{func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero}
|
}, eventBox, opts.ReadZero}
|
||||||
go reader.ReadSource()
|
go reader.ReadSource()
|
||||||
@ -151,7 +157,7 @@ func Run(opts *Options) {
|
|||||||
|
|
||||||
if streamingFilter {
|
if streamingFilter {
|
||||||
reader := Reader{
|
reader := Reader{
|
||||||
func(runes []rune) 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) {
|
||||||
fmt.Println(string(item.text))
|
fmt.Println(string(item.text))
|
||||||
|
@ -5,14 +5,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reader reads from command or standard input
|
// Reader reads from command or standard input
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
pusher func([]rune) bool
|
pusher func([]byte) bool
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
delimNil bool
|
||||||
}
|
}
|
||||||
@ -42,21 +41,10 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
// end in delim.
|
// end in delim.
|
||||||
bytea, err := reader.ReadBytes(delim)
|
bytea, err := reader.ReadBytes(delim)
|
||||||
if len(bytea) > 0 {
|
if len(bytea) > 0 {
|
||||||
runes := make([]rune, 0, len(bytea))
|
|
||||||
for i := 0; i < len(bytea); {
|
|
||||||
if bytea[i] < utf8.RuneSelf {
|
|
||||||
runes = append(runes, rune(bytea[i]))
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
r, sz := utf8.DecodeRune(bytea[i:])
|
|
||||||
i += sz
|
|
||||||
runes = append(runes, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
runes = runes[:len(runes)-1]
|
bytea = bytea[:len(bytea)-1]
|
||||||
}
|
}
|
||||||
if r.pusher(runes) {
|
if r.pusher(bytea) {
|
||||||
r.eventBox.Set(EvtReadNew, nil)
|
r.eventBox.Set(EvtReadNew, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
strs := []string{}
|
strs := []string{}
|
||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
reader := Reader{
|
reader := Reader{
|
||||||
pusher: func(s []rune) bool { strs = append(strs, string(s)); return true },
|
pusher: func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eventBox: eb}
|
eventBox: eb}
|
||||||
|
|
||||||
// Check EventBox
|
// Check EventBox
|
||||||
|
@ -6,6 +6,7 @@ import "C"
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Max returns the largest integer
|
// Max returns the largest integer
|
||||||
@ -88,3 +89,18 @@ func TrimRight(runes []rune) []rune {
|
|||||||
}
|
}
|
||||||
return runes[0 : i+1]
|
return runes[0 : i+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BytesToRunes(bytea []byte) []rune {
|
||||||
|
runes := make([]rune, 0, len(bytea))
|
||||||
|
for i := 0; i < len(bytea); {
|
||||||
|
if bytea[i] < utf8.RuneSelf {
|
||||||
|
runes = append(runes, rune(bytea[i]))
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
r, sz := utf8.DecodeRune(bytea[i:])
|
||||||
|
i += sz
|
||||||
|
runes = append(runes, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return runes
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user