You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fzf/src/matcher.go

217 lines
4.5 KiB
Go

10 years ago
package fzf
import (
"fmt"
"runtime"
"sort"
"sync"
10 years ago
"time"
"github.com/junegunn/fzf/src/util"
10 years ago
)
10 years ago
// MatchRequest represents a search request
10 years ago
type MatchRequest struct {
chunks []*Chunk
pattern *Pattern
final bool
10 years ago
}
10 years ago
// Matcher is responsible for performing search
10 years ago
type Matcher struct {
patternBuilder func([]rune) *Pattern
sort bool
tac bool
eventBox *util.EventBox
reqBox *util.EventBox
10 years ago
partitions int
mergerCache map[string]*Merger
10 years ago
}
const (
reqRetry util.EventType = iota
10 years ago
reqReset
10 years ago
)
const (
10 years ago
progressMinDuration = 200 * time.Millisecond
10 years ago
)
10 years ago
// NewMatcher returns a new Matcher
10 years ago
func NewMatcher(patternBuilder func([]rune) *Pattern,
sort bool, tac bool, eventBox *util.EventBox) *Matcher {
10 years ago
return &Matcher{
patternBuilder: patternBuilder,
sort: sort,
tac: tac,
10 years ago
eventBox: eventBox,
reqBox: util.NewEventBox(),
10 years ago
partitions: runtime.NumCPU(),
mergerCache: make(map[string]*Merger)}
10 years ago
}
10 years ago
// Loop puts Matcher in action
10 years ago
func (m *Matcher) Loop() {
prevCount := 0
for {
var request MatchRequest
m.reqBox.Wait(func(events *util.Events) {
10 years ago
for _, val := range *events {
switch val := val.(type) {
case MatchRequest:
request = val
default:
panic(fmt.Sprintf("Unexpected type: %T", val))
}
}
events.Clear()
})
// Restart search
patternString := request.pattern.AsString()
var merger *Merger
10 years ago
cancelled := false
count := CountItems(request.chunks)
foundCache := false
if count == prevCount {
// Look up mergerCache
if cached, found := m.mergerCache[patternString]; found {
10 years ago
foundCache = true
merger = cached
10 years ago
}
} else {
// Invalidate mergerCache
10 years ago
prevCount = count
m.mergerCache = make(map[string]*Merger)
10 years ago
}
if !foundCache {
merger, cancelled = m.scan(request)
10 years ago
}
if !cancelled {
m.mergerCache[patternString] = merger
merger.final = request.final
10 years ago
m.eventBox.Set(EvtSearchFin, merger)
10 years ago
}
}
}
func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
perSlice := len(chunks) / m.partitions
// No need to parallelize
if perSlice == 0 {
return [][]*Chunk{chunks}
}
slices := make([][]*Chunk, m.partitions)
for i := 0; i < m.partitions; i++ {
start := i * perSlice
end := start + perSlice
if i == m.partitions-1 {
end = len(chunks)
}
slices[i] = chunks[start:end]
}
return slices
}
type partialResult struct {
index int
matches []*Item
}
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
10 years ago
startedAt := time.Now()
numChunks := len(request.chunks)
if numChunks == 0 {
return EmptyMerger, false
10 years ago
}
pattern := request.pattern
9 years ago
if pattern.IsEmpty() {
return PassMerger(&request.chunks, m.tac), false
}
cancelled := util.NewAtomicBool(false)
10 years ago
slices := m.sliceChunks(request.chunks)
numSlices := len(slices)
resultChan := make(chan partialResult, numSlices)
countChan := make(chan int, numChunks)
waitGroup := sync.WaitGroup{}
10 years ago
for idx, chunks := range slices {
waitGroup.Add(1)
10 years ago
go func(idx int, chunks []*Chunk) {
defer func() { waitGroup.Done() }()
10 years ago
sliceMatches := []*Item{}
for _, chunk := range chunks {
9 years ago
matches := request.pattern.Match(chunk)
10 years ago
sliceMatches = append(sliceMatches, matches...)
if cancelled.Get() {
return
}
countChan <- len(matches)
10 years ago
}
9 years ago
if m.sort {
if m.tac {
sort.Sort(ByRelevanceTac(sliceMatches))
} else {
sort.Sort(ByRelevance(sliceMatches))
}
10 years ago
}
resultChan <- partialResult{idx, sliceMatches}
}(idx, chunks)
}
wait := func() bool {
cancelled.Set(true)
waitGroup.Wait()
return true
}
10 years ago
count := 0
matchCount := 0
for matchesInChunk := range countChan {
10 years ago
count++
10 years ago
matchCount += matchesInChunk
if count == numChunks {
break
}
9 years ago
if m.reqBox.Peek(reqReset) {
return nil, wait()
10 years ago
}
10 years ago
if time.Now().Sub(startedAt) > progressMinDuration {
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
10 years ago
}
}
partialResults := make([][]*Item, numSlices)
for range slices {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
9 years ago
return NewMerger(partialResults, m.sort, m.tac), false
10 years ago
}
10 years ago
// Reset is called to interrupt/signal the ongoing search
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool) {
10 years ago
pattern := m.patternBuilder(patternRunes)
var event util.EventType
10 years ago
if cancel {
10 years ago
event = reqReset
10 years ago
} else {
10 years ago
event = reqRetry
10 years ago
}
m.reqBox.Set(event, MatchRequest{chunks, pattern, final})
10 years ago
}