mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-05 04:57:50 +00:00
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()
|
||||
matches, cancelled := matcher.scan(MatchRequest{
|
||||
merger, cancelled := matcher.scan(MatchRequest{
|
||||
chunks: snapshot,
|
||||
pattern: pattern}, limit)
|
||||
|
||||
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 {
|
||||
fmt.Println(patternString)
|
||||
}
|
||||
for _, item := range matches {
|
||||
item.Print()
|
||||
for i := 0; i < merger.Length(); i++ {
|
||||
merger.Get(i).Print()
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
@ -147,7 +148,7 @@ func Run(options *Options) {
|
||||
|
||||
case EVT_SEARCH_FIN:
|
||||
switch val := value.(type) {
|
||||
case []*Item:
|
||||
case *Merger:
|
||||
terminal.UpdateList(val)
|
||||
}
|
||||
}
|
||||
|
38
src/item.go
38
src/item.go
@ -104,41 +104,3 @@ func compareRanks(irank Rank, jrank Rank) bool {
|
||||
}
|
||||
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 {
|
||||
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
|
||||
reqBox *EventBox
|
||||
partitions int
|
||||
queryCache QueryCache
|
||||
mergerCache map[string]*Merger
|
||||
}
|
||||
|
||||
const (
|
||||
@ -44,7 +44,7 @@ func NewMatcher(patternBuilder func([]rune) *Pattern,
|
||||
eventBox: eventBox,
|
||||
reqBox: NewEventBox(),
|
||||
partitions: runtime.NumCPU(),
|
||||
queryCache: make(QueryCache)}
|
||||
mergerCache: make(map[string]*Merger)}
|
||||
}
|
||||
|
||||
func (m *Matcher) Loop() {
|
||||
@ -67,30 +67,30 @@ func (m *Matcher) Loop() {
|
||||
|
||||
// Restart search
|
||||
patternString := request.pattern.AsString()
|
||||
allMatches := []*Item{}
|
||||
var merger *Merger
|
||||
cancelled := false
|
||||
count := CountItems(request.chunks)
|
||||
|
||||
foundCache := false
|
||||
if count == prevCount {
|
||||
// Look up queryCache
|
||||
if cached, found := m.queryCache[patternString]; found {
|
||||
// Look up mergerCache
|
||||
if cached, found := m.mergerCache[patternString]; found {
|
||||
foundCache = true
|
||||
allMatches = cached
|
||||
merger = cached
|
||||
}
|
||||
} else {
|
||||
// Invalidate queryCache
|
||||
// Invalidate mergerCache
|
||||
prevCount = count
|
||||
m.queryCache = make(QueryCache)
|
||||
m.mergerCache = make(map[string]*Merger)
|
||||
}
|
||||
|
||||
if !foundCache {
|
||||
allMatches, cancelled = m.scan(request, 0)
|
||||
merger, cancelled = m.scan(request, 0)
|
||||
}
|
||||
|
||||
if !cancelled {
|
||||
m.queryCache[patternString] = allMatches
|
||||
m.eventBox.Set(EVT_SEARCH_FIN, allMatches)
|
||||
m.mergerCache[patternString] = merger
|
||||
m.eventBox.Set(EVT_SEARCH_FIN, merger)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,12 +120,12 @@ type partialResult struct {
|
||||
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()
|
||||
|
||||
numChunks := len(request.chunks)
|
||||
if numChunks == 0 {
|
||||
return []*Item{}, false
|
||||
return EmptyMerger, false
|
||||
}
|
||||
pattern := request.pattern
|
||||
empty := pattern.IsEmpty()
|
||||
@ -188,18 +188,7 @@ func (m *Matcher) scan(request MatchRequest, limit int) ([]*Item, bool) {
|
||||
partialResult := <-resultChan
|
||||
partialResults[partialResult.index] = partialResult.matches
|
||||
}
|
||||
|
||||
var allMatches []*Item
|
||||
if empty || !m.sort {
|
||||
allMatches = []*Item{}
|
||||
for _, matches := range partialResults {
|
||||
allMatches = append(allMatches, matches...)
|
||||
}
|
||||
} else {
|
||||
allMatches = SortMerge(partialResults)
|
||||
}
|
||||
|
||||
return allMatches, false
|
||||
return NewMerger(partialResults, !empty && m.sort), false
|
||||
}
|
||||
|
||||
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
|
||||
progress int
|
||||
reading bool
|
||||
list []*Item
|
||||
merger *Merger
|
||||
selected map[*string]*string
|
||||
reqBox *EventBox
|
||||
eventBox *EventBox
|
||||
@ -64,7 +64,7 @@ func NewTerminal(opts *Options, eventBox *EventBox) *Terminal {
|
||||
input: input,
|
||||
multi: opts.Multi,
|
||||
printQuery: opts.PrintQuery,
|
||||
list: []*Item{},
|
||||
merger: EmptyMerger,
|
||||
selected: make(map[*string]*string),
|
||||
reqBox: NewEventBox(),
|
||||
eventBox: eventBox,
|
||||
@ -99,10 +99,10 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
||||
t.reqBox.Set(REQ_INFO, nil)
|
||||
}
|
||||
|
||||
func (t *Terminal) UpdateList(list []*Item) {
|
||||
func (t *Terminal) UpdateList(merger *Merger) {
|
||||
t.mutex.Lock()
|
||||
t.progress = 100
|
||||
t.list = list
|
||||
t.merger = merger
|
||||
t.mutex.Unlock()
|
||||
t.reqBox.Set(REQ_INFO, nil)
|
||||
t.reqBox.Set(REQ_LIST, nil)
|
||||
@ -110,7 +110,7 @@ func (t *Terminal) UpdateList(list []*Item) {
|
||||
|
||||
func (t *Terminal) listIndex(y int) int {
|
||||
if t.tac {
|
||||
return len(t.list) - y - 1
|
||||
return t.merger.Length() - y - 1
|
||||
} else {
|
||||
return y
|
||||
}
|
||||
@ -121,8 +121,8 @@ func (t *Terminal) output() {
|
||||
fmt.Println(string(t.input))
|
||||
}
|
||||
if len(t.selected) == 0 {
|
||||
if len(t.list) > t.cy {
|
||||
t.list[t.listIndex(t.cy)].Print()
|
||||
if t.merger.Length() > t.cy {
|
||||
t.merger.Get(t.listIndex(t.cy)).Print()
|
||||
}
|
||||
} else {
|
||||
for ptr, orig := range t.selected {
|
||||
@ -175,7 +175,7 @@ func (t *Terminal) printInfo() {
|
||||
}
|
||||
|
||||
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 {
|
||||
output += fmt.Sprintf(" (%d)", len(t.selected))
|
||||
}
|
||||
@ -189,11 +189,11 @@ func (t *Terminal) printList() {
|
||||
t.constrain()
|
||||
|
||||
maxy := maxItems()
|
||||
count := len(t.list) - t.offset
|
||||
count := t.merger.Length() - t.offset
|
||||
for i := 0; i < maxy; i++ {
|
||||
t.move(i+2, 0, true)
|
||||
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
|
||||
events := []EventType{REQ_PROMPT}
|
||||
toggle := func() {
|
||||
item := t.list[t.listIndex(t.cy)]
|
||||
item := t.merger.Get(t.listIndex(t.cy))
|
||||
if _, found := t.selected[item.text]; !found {
|
||||
t.selected[item.text] = item.origText
|
||||
} else {
|
||||
@ -460,13 +460,13 @@ func (t *Terminal) Loop() {
|
||||
t.cx -= 1
|
||||
}
|
||||
case C.TAB:
|
||||
if t.multi && len(t.list) > 0 {
|
||||
if t.multi && t.merger.Length() > 0 {
|
||||
toggle()
|
||||
t.vmove(-1)
|
||||
req(REQ_LIST, REQ_INFO)
|
||||
}
|
||||
case C.BTAB:
|
||||
if t.multi && len(t.list) > 0 {
|
||||
if t.multi && t.merger.Length() > 0 {
|
||||
toggle()
|
||||
t.vmove(1)
|
||||
req(REQ_LIST, REQ_INFO)
|
||||
@ -567,7 +567,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
|
||||
func (t *Terminal) constrain() {
|
||||
count := len(t.list)
|
||||
count := t.merger.Length()
|
||||
height := C.MaxY() - 2
|
||||
diffpos := t.cy - t.offset
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user