Improve response time by only looking at top-N items
This commit is contained in:
parent
aa05bf5206
commit
b7bb100810
11
src/core.go
11
src/core.go
@ -93,17 +93,18 @@ func Run(options *Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
snapshot, _ := chunkList.Snapshot()
|
snapshot, _ := chunkList.Snapshot()
|
||||||
matches, cancelled := matcher.scan(MatchRequest{
|
merger, cancelled := matcher.scan(MatchRequest{
|
||||||
chunks: snapshot,
|
chunks: snapshot,
|
||||||
pattern: pattern}, limit)
|
pattern: pattern}, limit)
|
||||||
|
|
||||||
if !cancelled && (filtering ||
|
if !cancelled && (filtering ||
|
||||||
opts.Exit0 && len(matches) == 0 || opts.Select1 && len(matches) == 1) {
|
opts.Exit0 && merger.Length() == 0 ||
|
||||||
|
opts.Select1 && merger.Length() == 1) {
|
||||||
if opts.PrintQuery {
|
if opts.PrintQuery {
|
||||||
fmt.Println(patternString)
|
fmt.Println(patternString)
|
||||||
}
|
}
|
||||||
for _, item := range matches {
|
for i := 0; i < merger.Length(); i++ {
|
||||||
item.Print()
|
merger.Get(i).Print()
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
@ -147,7 +148,7 @@ func Run(options *Options) {
|
|||||||
|
|
||||||
case EVT_SEARCH_FIN:
|
case EVT_SEARCH_FIN:
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
case []*Item:
|
case *Merger:
|
||||||
terminal.UpdateList(val)
|
terminal.UpdateList(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
src/item.go
38
src/item.go
@ -104,41 +104,3 @@ func compareRanks(irank Rank, jrank Rank) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func SortMerge(partialResults [][]*Item) []*Item {
|
|
||||||
if len(partialResults) == 1 {
|
|
||||||
return partialResults[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
merged := []*Item{}
|
|
||||||
|
|
||||||
for len(partialResults) > 0 {
|
|
||||||
minRank := Rank{0, 0, 0}
|
|
||||||
minIdx := -1
|
|
||||||
|
|
||||||
for idx, partialResult := range partialResults {
|
|
||||||
if len(partialResult) > 0 {
|
|
||||||
rank := partialResult[0].Rank()
|
|
||||||
if minIdx < 0 || compareRanks(rank, minRank) {
|
|
||||||
minRank = rank
|
|
||||||
minIdx = idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if minIdx >= 0 {
|
|
||||||
merged = append(merged, partialResults[minIdx][0])
|
|
||||||
partialResults[minIdx] = partialResults[minIdx][1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
nonEmptyPartialResults := make([][]*Item, 0, len(partialResults))
|
|
||||||
for _, partialResult := range partialResults {
|
|
||||||
if len(partialResult) > 0 {
|
|
||||||
nonEmptyPartialResults = append(nonEmptyPartialResults, partialResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
partialResults = nonEmptyPartialResults
|
|
||||||
}
|
|
||||||
|
|
||||||
return merged
|
|
||||||
}
|
|
||||||
|
@ -64,14 +64,4 @@ func TestItemRank(t *testing.T) {
|
|||||||
items[4] != &item5 || items[5] != &item3 {
|
items[4] != &item5 || items[5] != &item3 {
|
||||||
t.Error(items)
|
t.Error(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort merged lists
|
|
||||||
lists := [][]*Item{
|
|
||||||
[]*Item{&item2, &item4, &item5}, []*Item{&item1, &item6}, []*Item{&item3}}
|
|
||||||
items = SortMerge(lists)
|
|
||||||
if items[0] != &item2 || items[1] != &item1 ||
|
|
||||||
items[2] != &item6 || items[3] != &item4 ||
|
|
||||||
items[4] != &item5 || items[5] != &item3 {
|
|
||||||
t.Error(items)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ type Matcher struct {
|
|||||||
eventBox *EventBox
|
eventBox *EventBox
|
||||||
reqBox *EventBox
|
reqBox *EventBox
|
||||||
partitions int
|
partitions int
|
||||||
queryCache QueryCache
|
mergerCache map[string]*Merger
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -44,7 +44,7 @@ func NewMatcher(patternBuilder func([]rune) *Pattern,
|
|||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
reqBox: NewEventBox(),
|
reqBox: NewEventBox(),
|
||||||
partitions: runtime.NumCPU(),
|
partitions: runtime.NumCPU(),
|
||||||
queryCache: make(QueryCache)}
|
mergerCache: make(map[string]*Merger)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matcher) Loop() {
|
func (m *Matcher) Loop() {
|
||||||
@ -67,30 +67,30 @@ func (m *Matcher) Loop() {
|
|||||||
|
|
||||||
// Restart search
|
// Restart search
|
||||||
patternString := request.pattern.AsString()
|
patternString := request.pattern.AsString()
|
||||||
allMatches := []*Item{}
|
var merger *Merger
|
||||||
cancelled := false
|
cancelled := false
|
||||||
count := CountItems(request.chunks)
|
count := CountItems(request.chunks)
|
||||||
|
|
||||||
foundCache := false
|
foundCache := false
|
||||||
if count == prevCount {
|
if count == prevCount {
|
||||||
// Look up queryCache
|
// Look up mergerCache
|
||||||
if cached, found := m.queryCache[patternString]; found {
|
if cached, found := m.mergerCache[patternString]; found {
|
||||||
foundCache = true
|
foundCache = true
|
||||||
allMatches = cached
|
merger = cached
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Invalidate queryCache
|
// Invalidate mergerCache
|
||||||
prevCount = count
|
prevCount = count
|
||||||
m.queryCache = make(QueryCache)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundCache {
|
if !foundCache {
|
||||||
allMatches, cancelled = m.scan(request, 0)
|
merger, cancelled = m.scan(request, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cancelled {
|
if !cancelled {
|
||||||
m.queryCache[patternString] = allMatches
|
m.mergerCache[patternString] = merger
|
||||||
m.eventBox.Set(EVT_SEARCH_FIN, allMatches)
|
m.eventBox.Set(EVT_SEARCH_FIN, merger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,12 +120,12 @@ type partialResult struct {
|
|||||||
matches []*Item
|
matches []*Item
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matcher) scan(request MatchRequest, limit int) ([]*Item, bool) {
|
func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
|
||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
|
|
||||||
numChunks := len(request.chunks)
|
numChunks := len(request.chunks)
|
||||||
if numChunks == 0 {
|
if numChunks == 0 {
|
||||||
return []*Item{}, false
|
return EmptyMerger, false
|
||||||
}
|
}
|
||||||
pattern := request.pattern
|
pattern := request.pattern
|
||||||
empty := pattern.IsEmpty()
|
empty := pattern.IsEmpty()
|
||||||
@ -188,18 +188,7 @@ func (m *Matcher) scan(request MatchRequest, limit int) ([]*Item, bool) {
|
|||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
|
return NewMerger(partialResults, !empty && m.sort), false
|
||||||
var allMatches []*Item
|
|
||||||
if empty || !m.sort {
|
|
||||||
allMatches = []*Item{}
|
|
||||||
for _, matches := range partialResults {
|
|
||||||
allMatches = append(allMatches, matches...)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
allMatches = SortMerge(partialResults)
|
|
||||||
}
|
|
||||||
|
|
||||||
return allMatches, false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool) {
|
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool) {
|
||||||
|
79
src/merger.go
Normal file
79
src/merger.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
var EmptyMerger *Merger = NewMerger([][]*Item{}, false)
|
||||||
|
|
||||||
|
type Merger struct {
|
||||||
|
lists [][]*Item
|
||||||
|
merged []*Item
|
||||||
|
cursors []int
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMerger(lists [][]*Item, sorted bool) *Merger {
|
||||||
|
mg := Merger{
|
||||||
|
lists: lists,
|
||||||
|
merged: []*Item{},
|
||||||
|
cursors: make([]int, len(lists)),
|
||||||
|
done: false}
|
||||||
|
if !sorted {
|
||||||
|
for _, list := range lists {
|
||||||
|
mg.merged = append(mg.merged, list...)
|
||||||
|
}
|
||||||
|
mg.done = true
|
||||||
|
}
|
||||||
|
return &mg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Merger) Length() int {
|
||||||
|
cnt := 0
|
||||||
|
for _, list := range mg.lists {
|
||||||
|
cnt += len(list)
|
||||||
|
}
|
||||||
|
return cnt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Merger) Get(idx int) *Item {
|
||||||
|
if mg.done {
|
||||||
|
return mg.merged[idx]
|
||||||
|
} else if len(mg.lists) == 1 {
|
||||||
|
return mg.lists[0][idx]
|
||||||
|
}
|
||||||
|
mg.buildUpto(idx)
|
||||||
|
return mg.merged[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mg *Merger) buildUpto(upto int) {
|
||||||
|
numBuilt := len(mg.merged)
|
||||||
|
if numBuilt > upto {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := numBuilt; i <= upto; i++ {
|
||||||
|
minRank := Rank{0, 0, 0}
|
||||||
|
minIdx := -1
|
||||||
|
for listIdx, list := range mg.lists {
|
||||||
|
cursor := mg.cursors[listIdx]
|
||||||
|
if cursor < 0 || cursor == len(list) {
|
||||||
|
mg.cursors[listIdx] = -1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if cursor >= 0 {
|
||||||
|
rank := list[cursor].Rank()
|
||||||
|
if minIdx < 0 || compareRanks(rank, minRank) {
|
||||||
|
minRank = rank
|
||||||
|
minIdx = listIdx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mg.cursors[listIdx] = cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
if minIdx >= 0 {
|
||||||
|
chosen := mg.lists[minIdx]
|
||||||
|
mg.merged = append(mg.merged, chosen[mg.cursors[minIdx]])
|
||||||
|
mg.cursors[minIdx] += 1
|
||||||
|
} else {
|
||||||
|
mg.done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
src/merger_test.go
Normal file
80
src/merger_test.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assert(t *testing.T, cond bool, msg ...string) {
|
||||||
|
if !cond {
|
||||||
|
t.Error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randItem() *Item {
|
||||||
|
return &Item{
|
||||||
|
rank: Rank{uint16(rand.Uint32()), uint16(rand.Uint32()), rand.Uint32()}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyMerger(t *testing.T) {
|
||||||
|
assert(t, EmptyMerger.Length() == 0, "Not empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildLists(partiallySorted bool) ([][]*Item, []*Item) {
|
||||||
|
numLists := 4
|
||||||
|
lists := make([][]*Item, numLists)
|
||||||
|
cnt := 0
|
||||||
|
for i := 0; i < numLists; i++ {
|
||||||
|
numItems := rand.Int() % 20
|
||||||
|
cnt += numItems
|
||||||
|
lists[i] = make([]*Item, numItems)
|
||||||
|
for j := 0; j < numItems; j++ {
|
||||||
|
item := randItem()
|
||||||
|
lists[i][j] = item
|
||||||
|
}
|
||||||
|
if partiallySorted {
|
||||||
|
sort.Sort(ByRelevance(lists[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items := []*Item{}
|
||||||
|
for _, list := range lists {
|
||||||
|
items = append(items, list...)
|
||||||
|
}
|
||||||
|
return lists, items
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergerUnsorted(t *testing.T) {
|
||||||
|
lists, items := buildLists(false)
|
||||||
|
cnt := len(items)
|
||||||
|
|
||||||
|
// Not sorted: same order
|
||||||
|
mg := NewMerger(lists, false)
|
||||||
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
|
for i := 0; i < cnt; i++ {
|
||||||
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMergerSorted(t *testing.T) {
|
||||||
|
lists, items := buildLists(true)
|
||||||
|
cnt := len(items)
|
||||||
|
|
||||||
|
// Sorted sorted order
|
||||||
|
mg := NewMerger(lists, true)
|
||||||
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
|
sort.Sort(ByRelevance(items))
|
||||||
|
for i := 0; i < cnt; i++ {
|
||||||
|
if items[i] != mg.Get(i) {
|
||||||
|
t.Error("Not sorted", items[i], mg.Get(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse order
|
||||||
|
mg2 := NewMerger(lists, true)
|
||||||
|
for i := cnt - 1; i >= cnt; i-- {
|
||||||
|
if items[i] != mg2.Get(i) {
|
||||||
|
t.Error("Not sorted", items[i], mg2.Get(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,7 +25,7 @@ type Terminal struct {
|
|||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
list []*Item
|
merger *Merger
|
||||||
selected map[*string]*string
|
selected map[*string]*string
|
||||||
reqBox *EventBox
|
reqBox *EventBox
|
||||||
eventBox *EventBox
|
eventBox *EventBox
|
||||||
@ -64,7 +64,7 @@ func NewTerminal(opts *Options, eventBox *EventBox) *Terminal {
|
|||||||
input: input,
|
input: input,
|
||||||
multi: opts.Multi,
|
multi: opts.Multi,
|
||||||
printQuery: opts.PrintQuery,
|
printQuery: opts.PrintQuery,
|
||||||
list: []*Item{},
|
merger: EmptyMerger,
|
||||||
selected: make(map[*string]*string),
|
selected: make(map[*string]*string),
|
||||||
reqBox: NewEventBox(),
|
reqBox: NewEventBox(),
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
@ -99,10 +99,10 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
|||||||
t.reqBox.Set(REQ_INFO, nil)
|
t.reqBox.Set(REQ_INFO, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) UpdateList(list []*Item) {
|
func (t *Terminal) UpdateList(merger *Merger) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.progress = 100
|
t.progress = 100
|
||||||
t.list = list
|
t.merger = merger
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
t.reqBox.Set(REQ_INFO, nil)
|
t.reqBox.Set(REQ_INFO, nil)
|
||||||
t.reqBox.Set(REQ_LIST, nil)
|
t.reqBox.Set(REQ_LIST, nil)
|
||||||
@ -110,7 +110,7 @@ func (t *Terminal) UpdateList(list []*Item) {
|
|||||||
|
|
||||||
func (t *Terminal) listIndex(y int) int {
|
func (t *Terminal) listIndex(y int) int {
|
||||||
if t.tac {
|
if t.tac {
|
||||||
return len(t.list) - y - 1
|
return t.merger.Length() - y - 1
|
||||||
} else {
|
} else {
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
@ -121,8 +121,8 @@ func (t *Terminal) output() {
|
|||||||
fmt.Println(string(t.input))
|
fmt.Println(string(t.input))
|
||||||
}
|
}
|
||||||
if len(t.selected) == 0 {
|
if len(t.selected) == 0 {
|
||||||
if len(t.list) > t.cy {
|
if t.merger.Length() > t.cy {
|
||||||
t.list[t.listIndex(t.cy)].Print()
|
t.merger.Get(t.listIndex(t.cy)).Print()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for ptr, orig := range t.selected {
|
for ptr, orig := range t.selected {
|
||||||
@ -175,7 +175,7 @@ func (t *Terminal) printInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.move(1, 2, false)
|
t.move(1, 2, false)
|
||||||
output := fmt.Sprintf("%d/%d", len(t.list), t.count)
|
output := fmt.Sprintf("%d/%d", t.merger.Length(), t.count)
|
||||||
if t.multi && len(t.selected) > 0 {
|
if t.multi && len(t.selected) > 0 {
|
||||||
output += fmt.Sprintf(" (%d)", len(t.selected))
|
output += fmt.Sprintf(" (%d)", len(t.selected))
|
||||||
}
|
}
|
||||||
@ -189,11 +189,11 @@ func (t *Terminal) printList() {
|
|||||||
t.constrain()
|
t.constrain()
|
||||||
|
|
||||||
maxy := maxItems()
|
maxy := maxItems()
|
||||||
count := len(t.list) - t.offset
|
count := t.merger.Length() - t.offset
|
||||||
for i := 0; i < maxy; i++ {
|
for i := 0; i < maxy; i++ {
|
||||||
t.move(i+2, 0, true)
|
t.move(i+2, 0, true)
|
||||||
if i < count {
|
if i < count {
|
||||||
t.printItem(t.list[t.listIndex(i+t.offset)], i == t.cy-t.offset)
|
t.printItem(t.merger.Get(t.listIndex(i+t.offset)), i == t.cy-t.offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -417,7 +417,7 @@ func (t *Terminal) Loop() {
|
|||||||
previousInput := t.input
|
previousInput := t.input
|
||||||
events := []EventType{REQ_PROMPT}
|
events := []EventType{REQ_PROMPT}
|
||||||
toggle := func() {
|
toggle := func() {
|
||||||
item := t.list[t.listIndex(t.cy)]
|
item := t.merger.Get(t.listIndex(t.cy))
|
||||||
if _, found := t.selected[item.text]; !found {
|
if _, found := t.selected[item.text]; !found {
|
||||||
t.selected[item.text] = item.origText
|
t.selected[item.text] = item.origText
|
||||||
} else {
|
} else {
|
||||||
@ -460,13 +460,13 @@ func (t *Terminal) Loop() {
|
|||||||
t.cx -= 1
|
t.cx -= 1
|
||||||
}
|
}
|
||||||
case C.TAB:
|
case C.TAB:
|
||||||
if t.multi && len(t.list) > 0 {
|
if t.multi && t.merger.Length() > 0 {
|
||||||
toggle()
|
toggle()
|
||||||
t.vmove(-1)
|
t.vmove(-1)
|
||||||
req(REQ_LIST, REQ_INFO)
|
req(REQ_LIST, REQ_INFO)
|
||||||
}
|
}
|
||||||
case C.BTAB:
|
case C.BTAB:
|
||||||
if t.multi && len(t.list) > 0 {
|
if t.multi && t.merger.Length() > 0 {
|
||||||
toggle()
|
toggle()
|
||||||
t.vmove(1)
|
t.vmove(1)
|
||||||
req(REQ_LIST, REQ_INFO)
|
req(REQ_LIST, REQ_INFO)
|
||||||
@ -567,7 +567,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) constrain() {
|
func (t *Terminal) constrain() {
|
||||||
count := len(t.list)
|
count := t.merger.Length()
|
||||||
height := C.MaxY() - 2
|
height := C.MaxY() - 2
|
||||||
diffpos := t.cy - t.offset
|
diffpos := t.cy - t.offset
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user