fzf/src/matcher.go

258 lines
5.7 KiB
Go
Raw Normal View History

2015-01-01 19:49:30 +00:00
package fzf
import (
"fmt"
"runtime"
"sort"
"sync"
2015-01-01 19:49:30 +00:00
"time"
2015-01-12 03:56:17 +00:00
"github.com/junegunn/fzf/src/util"
2015-01-01 19:49:30 +00:00
)
2015-01-11 18:01:24 +00:00
// MatchRequest represents a search request
2015-01-01 19:49:30 +00:00
type MatchRequest struct {
chunks []*Chunk
pattern *Pattern
final bool
sort bool
revision revision
2015-01-01 19:49:30 +00:00
}
2015-01-11 18:01:24 +00:00
// Matcher is responsible for performing search
2015-01-01 19:49:30 +00:00
type Matcher struct {
cache *ChunkCache
2015-01-01 19:49:30 +00:00
patternBuilder func([]rune) *Pattern
sort bool
tac bool
2015-01-12 03:56:17 +00:00
eventBox *util.EventBox
reqBox *util.EventBox
2015-01-01 19:49:30 +00:00
partitions int
2016-09-07 00:58:18 +00:00
slab []*util.Slab
mergerCache map[string]*Merger
revision revision
2015-01-01 19:49:30 +00:00
}
const (
2015-01-12 03:56:17 +00:00
reqRetry util.EventType = iota
2015-01-11 18:01:24 +00:00
reqReset
2015-01-01 19:49:30 +00:00
)
2015-01-11 18:01:24 +00:00
// NewMatcher returns a new Matcher
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
sort bool, tac bool, eventBox *util.EventBox, revision revision) *Matcher {
2016-09-07 00:58:18 +00:00
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
2015-01-01 19:49:30 +00:00
return &Matcher{
cache: cache,
2015-01-01 19:49:30 +00:00
patternBuilder: patternBuilder,
sort: sort,
tac: tac,
2015-01-01 19:49:30 +00:00
eventBox: eventBox,
2015-01-12 03:56:17 +00:00
reqBox: util.NewEventBox(),
2016-09-07 00:58:18 +00:00
partitions: partitions,
slab: make([]*util.Slab, partitions),
mergerCache: make(map[string]*Merger),
revision: revision}
2015-01-01 19:49:30 +00:00
}
2015-01-11 18:01:24 +00:00
// Loop puts Matcher in action
2015-01-01 19:49:30 +00:00
func (m *Matcher) Loop() {
prevCount := 0
for {
var request MatchRequest
stop := false
2015-01-12 03:56:17 +00:00
m.reqBox.Wait(func(events *util.Events) {
for t, val := range *events {
if t == reqQuit {
stop = true
return
}
2015-01-01 19:49:30 +00:00
switch val := val.(type) {
case MatchRequest:
request = val
default:
panic(fmt.Sprintf("Unexpected type: %T", val))
}
}
events.Clear()
})
if stop {
break
}
2015-01-01 19:49:30 +00:00
cacheCleared := false
if request.sort != m.sort || request.revision != m.revision {
2015-03-31 13:05:02 +00:00
m.sort = request.sort
m.revision = request.revision
2015-03-31 13:05:02 +00:00
m.mergerCache = make(map[string]*Merger)
if !request.revision.compatible(m.revision) {
m.cache.Clear()
}
cacheCleared = true
2015-03-31 13:05:02 +00:00
}
2015-01-01 19:49:30 +00:00
// Restart search
patternString := request.pattern.AsString()
var merger *Merger
2015-01-01 19:49:30 +00:00
cancelled := false
count := CountItems(request.chunks)
if !cacheCleared {
if count == prevCount {
// Look up mergerCache
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final {
merger = cached
}
} else {
// Invalidate mergerCache
prevCount = count
m.mergerCache = make(map[string]*Merger)
2015-01-01 19:49:30 +00:00
}
}
if merger == nil {
merger, cancelled = m.scan(request)
2015-01-01 19:49:30 +00:00
}
if !cancelled {
2015-08-02 04:06:15 +00:00
if merger.cacheable() {
m.mergerCache[patternString] = merger
}
merger.final = request.final
2015-01-11 18:01:24 +00:00
m.eventBox.Set(EvtSearchFin, merger)
2015-01-01 19:49:30 +00:00
}
}
}
func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
partitions := m.partitions
perSlice := len(chunks) / partitions
2015-01-01 19:49:30 +00:00
if perSlice == 0 {
partitions = len(chunks)
perSlice = 1
2015-01-01 19:49:30 +00:00
}
slices := make([][]*Chunk, partitions)
for i := 0; i < partitions; i++ {
2015-01-01 19:49:30 +00:00
start := i * perSlice
end := start + perSlice
if i == partitions-1 {
2015-01-01 19:49:30 +00:00
end = len(chunks)
}
slices[i] = chunks[start:end]
}
return slices
}
type partialResult struct {
index int
matches []Result
2015-01-01 19:49:30 +00:00
}
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
2015-01-01 19:49:30 +00:00
startedAt := time.Now()
numChunks := len(request.chunks)
if numChunks == 0 {
return EmptyMerger(request.revision), false
2015-01-01 19:49:30 +00:00
}
pattern := request.pattern
2015-03-22 07:05:54 +00:00
if pattern.IsEmpty() {
return PassMerger(&request.chunks, m.tac, request.revision), false
}
minIndex := request.chunks[0].items[0].Index()
2015-01-12 03:56:17 +00:00
cancelled := util.NewAtomicBool(false)
2015-01-01 19:49:30 +00:00
slices := m.sliceChunks(request.chunks)
numSlices := len(slices)
resultChan := make(chan partialResult, numSlices)
countChan := make(chan int, numChunks)
waitGroup := sync.WaitGroup{}
2015-01-01 19:49:30 +00:00
for idx, chunks := range slices {
waitGroup.Add(1)
2016-09-07 00:58:18 +00:00
if m.slab[idx] == nil {
m.slab[idx] = util.MakeSlab(slab16Size, slab32Size)
}
go func(idx int, slab *util.Slab, chunks []*Chunk) {
defer func() { waitGroup.Done() }()
count := 0
allMatches := make([][]Result, len(chunks))
for idx, chunk := range chunks {
2016-09-07 00:58:18 +00:00
matches := request.pattern.Match(chunk, slab)
allMatches[idx] = matches
count += len(matches)
2015-01-01 19:49:30 +00:00
if cancelled.Get() {
return
}
countChan <- len(matches)
2015-01-01 19:49:30 +00:00
}
sliceMatches := make([]Result, 0, count)
for _, matches := range allMatches {
sliceMatches = append(sliceMatches, matches...)
}
if m.sort && request.pattern.sortable {
if m.tac {
sort.Sort(ByRelevanceTac(sliceMatches))
} else {
sort.Sort(ByRelevance(sliceMatches))
}
2015-01-01 19:49:30 +00:00
}
resultChan <- partialResult{idx, sliceMatches}
2016-09-07 00:58:18 +00:00
}(idx, m.slab[idx], chunks)
2015-01-01 19:49:30 +00:00
}
wait := func() bool {
cancelled.Set(true)
waitGroup.Wait()
return true
}
2015-01-01 19:49:30 +00:00
count := 0
matchCount := 0
for matchesInChunk := range countChan {
2015-01-11 18:01:24 +00:00
count++
2015-01-01 19:49:30 +00:00
matchCount += matchesInChunk
if count == numChunks {
break
}
2015-03-22 07:05:54 +00:00
if m.reqBox.Peek(reqReset) {
return nil, wait()
2015-01-01 19:49:30 +00:00
}
if time.Since(startedAt) > progressMinDuration {
2015-01-11 18:01:24 +00:00
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
2015-01-01 19:49:30 +00:00
}
}
partialResults := make([][]Result, numSlices)
for range slices {
2015-01-01 19:49:30 +00:00
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex), false
2015-01-01 19:49:30 +00:00
}
2015-01-11 18:01:24 +00:00
// Reset is called to interrupt/signal the ongoing search
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, revision revision) {
2015-01-01 19:49:30 +00:00
pattern := m.patternBuilder(patternRunes)
2015-01-12 03:56:17 +00:00
var event util.EventType
2015-01-01 19:49:30 +00:00
if cancel {
2015-01-11 18:01:24 +00:00
event = reqReset
2015-01-01 19:49:30 +00:00
} else {
2015-01-11 18:01:24 +00:00
event = reqRetry
2015-01-01 19:49:30 +00:00
}
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort, revision})
2015-01-01 19:49:30 +00:00
}
func (m *Matcher) Stop() {
m.reqBox.Set(reqQuit, nil)
}