doing 2.0.20 → 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/doing.rdoc +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/helpers/fzf/.goreleaser.yml +119 -0
- data/lib/helpers/fzf/.rubocop.yml +28 -0
- data/lib/helpers/fzf/ADVANCED.md +565 -0
- data/lib/helpers/fzf/BUILD.md +49 -0
- data/lib/helpers/fzf/CHANGELOG.md +1193 -0
- data/lib/helpers/fzf/Dockerfile +11 -0
- data/lib/helpers/fzf/LICENSE +21 -0
- data/lib/helpers/fzf/Makefile +166 -0
- data/lib/helpers/fzf/README-VIM.md +486 -0
- data/lib/helpers/fzf/README.md +712 -0
- data/lib/helpers/fzf/bin/fzf-tmux +233 -0
- data/lib/helpers/fzf/doc/fzf.txt +512 -0
- data/lib/helpers/fzf/go.mod +17 -0
- data/lib/helpers/fzf/go.sum +31 -0
- data/lib/helpers/fzf/install +382 -0
- data/lib/helpers/fzf/install.ps1 +65 -0
- data/lib/helpers/fzf/main.go +14 -0
- data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
- data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
- data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
- data/lib/helpers/fzf/shell/completion.bash +381 -0
- data/lib/helpers/fzf/shell/completion.zsh +329 -0
- data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
- data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
- data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
- data/lib/helpers/fzf/src/LICENSE +21 -0
- data/lib/helpers/fzf/src/algo/algo.go +884 -0
- data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
- data/lib/helpers/fzf/src/algo/normalize.go +492 -0
- data/lib/helpers/fzf/src/ansi.go +409 -0
- data/lib/helpers/fzf/src/ansi_test.go +427 -0
- data/lib/helpers/fzf/src/cache.go +81 -0
- data/lib/helpers/fzf/src/cache_test.go +39 -0
- data/lib/helpers/fzf/src/chunklist.go +89 -0
- data/lib/helpers/fzf/src/chunklist_test.go +80 -0
- data/lib/helpers/fzf/src/constants.go +85 -0
- data/lib/helpers/fzf/src/core.go +351 -0
- data/lib/helpers/fzf/src/history.go +96 -0
- data/lib/helpers/fzf/src/history_test.go +68 -0
- data/lib/helpers/fzf/src/item.go +44 -0
- data/lib/helpers/fzf/src/item_test.go +23 -0
- data/lib/helpers/fzf/src/matcher.go +235 -0
- data/lib/helpers/fzf/src/merger.go +120 -0
- data/lib/helpers/fzf/src/merger_test.go +88 -0
- data/lib/helpers/fzf/src/options.go +1691 -0
- data/lib/helpers/fzf/src/options_test.go +457 -0
- data/lib/helpers/fzf/src/pattern.go +425 -0
- data/lib/helpers/fzf/src/pattern_test.go +209 -0
- data/lib/helpers/fzf/src/protector/protector.go +8 -0
- data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
- data/lib/helpers/fzf/src/reader.go +201 -0
- data/lib/helpers/fzf/src/reader_test.go +63 -0
- data/lib/helpers/fzf/src/result.go +243 -0
- data/lib/helpers/fzf/src/result_others.go +16 -0
- data/lib/helpers/fzf/src/result_test.go +159 -0
- data/lib/helpers/fzf/src/result_x86.go +16 -0
- data/lib/helpers/fzf/src/terminal.go +2832 -0
- data/lib/helpers/fzf/src/terminal_test.go +638 -0
- data/lib/helpers/fzf/src/terminal_unix.go +26 -0
- data/lib/helpers/fzf/src/terminal_windows.go +45 -0
- data/lib/helpers/fzf/src/tokenizer.go +253 -0
- data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
- data/lib/helpers/fzf/src/tui/dummy.go +46 -0
- data/lib/helpers/fzf/src/tui/light.go +987 -0
- data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
- data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
- data/lib/helpers/fzf/src/tui/tcell.go +721 -0
- data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
- data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
- data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
- data/lib/helpers/fzf/src/tui/tui.go +625 -0
- data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
- data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
- data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
- data/lib/helpers/fzf/src/util/chars.go +198 -0
- data/lib/helpers/fzf/src/util/chars_test.go +46 -0
- data/lib/helpers/fzf/src/util/eventbox.go +96 -0
- data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
- data/lib/helpers/fzf/src/util/slab.go +12 -0
- data/lib/helpers/fzf/src/util/util.go +138 -0
- data/lib/helpers/fzf/src/util/util_test.go +40 -0
- data/lib/helpers/fzf/src/util/util_unix.go +47 -0
- data/lib/helpers/fzf/src/util/util_windows.go +83 -0
- data/lib/helpers/fzf/test/fzf.vader +175 -0
- data/lib/helpers/fzf/test/test_go.rb +2626 -0
- data/lib/helpers/fzf/uninstall +117 -0
- metadata +87 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
package fzf
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"runtime"
|
|
6
|
+
"sort"
|
|
7
|
+
"sync"
|
|
8
|
+
"time"
|
|
9
|
+
|
|
10
|
+
"github.com/junegunn/fzf/src/util"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// MatchRequest represents a search request
|
|
14
|
+
type MatchRequest struct {
|
|
15
|
+
chunks []*Chunk
|
|
16
|
+
pattern *Pattern
|
|
17
|
+
final bool
|
|
18
|
+
sort bool
|
|
19
|
+
clearCache bool
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Matcher is responsible for performing search
|
|
23
|
+
type Matcher struct {
|
|
24
|
+
patternBuilder func([]rune) *Pattern
|
|
25
|
+
sort bool
|
|
26
|
+
tac bool
|
|
27
|
+
eventBox *util.EventBox
|
|
28
|
+
reqBox *util.EventBox
|
|
29
|
+
partitions int
|
|
30
|
+
slab []*util.Slab
|
|
31
|
+
mergerCache map[string]*Merger
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const (
|
|
35
|
+
reqRetry util.EventType = iota
|
|
36
|
+
reqReset
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// NewMatcher returns a new Matcher
|
|
40
|
+
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
|
41
|
+
sort bool, tac bool, eventBox *util.EventBox) *Matcher {
|
|
42
|
+
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
|
43
|
+
return &Matcher{
|
|
44
|
+
patternBuilder: patternBuilder,
|
|
45
|
+
sort: sort,
|
|
46
|
+
tac: tac,
|
|
47
|
+
eventBox: eventBox,
|
|
48
|
+
reqBox: util.NewEventBox(),
|
|
49
|
+
partitions: partitions,
|
|
50
|
+
slab: make([]*util.Slab, partitions),
|
|
51
|
+
mergerCache: make(map[string]*Merger)}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Loop puts Matcher in action
|
|
55
|
+
func (m *Matcher) Loop() {
|
|
56
|
+
prevCount := 0
|
|
57
|
+
|
|
58
|
+
for {
|
|
59
|
+
var request MatchRequest
|
|
60
|
+
|
|
61
|
+
m.reqBox.Wait(func(events *util.Events) {
|
|
62
|
+
for _, val := range *events {
|
|
63
|
+
switch val := val.(type) {
|
|
64
|
+
case MatchRequest:
|
|
65
|
+
request = val
|
|
66
|
+
default:
|
|
67
|
+
panic(fmt.Sprintf("Unexpected type: %T", val))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
events.Clear()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
if request.sort != m.sort || request.clearCache {
|
|
74
|
+
m.sort = request.sort
|
|
75
|
+
m.mergerCache = make(map[string]*Merger)
|
|
76
|
+
clearChunkCache()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Restart search
|
|
80
|
+
patternString := request.pattern.AsString()
|
|
81
|
+
var merger *Merger
|
|
82
|
+
cancelled := false
|
|
83
|
+
count := CountItems(request.chunks)
|
|
84
|
+
|
|
85
|
+
foundCache := false
|
|
86
|
+
if count == prevCount {
|
|
87
|
+
// Look up mergerCache
|
|
88
|
+
if cached, found := m.mergerCache[patternString]; found {
|
|
89
|
+
foundCache = true
|
|
90
|
+
merger = cached
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// Invalidate mergerCache
|
|
94
|
+
prevCount = count
|
|
95
|
+
m.mergerCache = make(map[string]*Merger)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if !foundCache {
|
|
99
|
+
merger, cancelled = m.scan(request)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if !cancelled {
|
|
103
|
+
if merger.cacheable() {
|
|
104
|
+
m.mergerCache[patternString] = merger
|
|
105
|
+
}
|
|
106
|
+
merger.final = request.final
|
|
107
|
+
m.eventBox.Set(EvtSearchFin, merger)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk {
|
|
113
|
+
partitions := m.partitions
|
|
114
|
+
perSlice := len(chunks) / partitions
|
|
115
|
+
|
|
116
|
+
if perSlice == 0 {
|
|
117
|
+
partitions = len(chunks)
|
|
118
|
+
perSlice = 1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
slices := make([][]*Chunk, partitions)
|
|
122
|
+
for i := 0; i < partitions; i++ {
|
|
123
|
+
start := i * perSlice
|
|
124
|
+
end := start + perSlice
|
|
125
|
+
if i == partitions-1 {
|
|
126
|
+
end = len(chunks)
|
|
127
|
+
}
|
|
128
|
+
slices[i] = chunks[start:end]
|
|
129
|
+
}
|
|
130
|
+
return slices
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type partialResult struct {
|
|
134
|
+
index int
|
|
135
|
+
matches []Result
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|
139
|
+
startedAt := time.Now()
|
|
140
|
+
|
|
141
|
+
numChunks := len(request.chunks)
|
|
142
|
+
if numChunks == 0 {
|
|
143
|
+
return EmptyMerger, false
|
|
144
|
+
}
|
|
145
|
+
pattern := request.pattern
|
|
146
|
+
if pattern.IsEmpty() {
|
|
147
|
+
return PassMerger(&request.chunks, m.tac), false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
cancelled := util.NewAtomicBool(false)
|
|
151
|
+
|
|
152
|
+
slices := m.sliceChunks(request.chunks)
|
|
153
|
+
numSlices := len(slices)
|
|
154
|
+
resultChan := make(chan partialResult, numSlices)
|
|
155
|
+
countChan := make(chan int, numChunks)
|
|
156
|
+
waitGroup := sync.WaitGroup{}
|
|
157
|
+
|
|
158
|
+
for idx, chunks := range slices {
|
|
159
|
+
waitGroup.Add(1)
|
|
160
|
+
if m.slab[idx] == nil {
|
|
161
|
+
m.slab[idx] = util.MakeSlab(slab16Size, slab32Size)
|
|
162
|
+
}
|
|
163
|
+
go func(idx int, slab *util.Slab, chunks []*Chunk) {
|
|
164
|
+
defer func() { waitGroup.Done() }()
|
|
165
|
+
count := 0
|
|
166
|
+
allMatches := make([][]Result, len(chunks))
|
|
167
|
+
for idx, chunk := range chunks {
|
|
168
|
+
matches := request.pattern.Match(chunk, slab)
|
|
169
|
+
allMatches[idx] = matches
|
|
170
|
+
count += len(matches)
|
|
171
|
+
if cancelled.Get() {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
countChan <- len(matches)
|
|
175
|
+
}
|
|
176
|
+
sliceMatches := make([]Result, 0, count)
|
|
177
|
+
for _, matches := range allMatches {
|
|
178
|
+
sliceMatches = append(sliceMatches, matches...)
|
|
179
|
+
}
|
|
180
|
+
if m.sort {
|
|
181
|
+
if m.tac {
|
|
182
|
+
sort.Sort(ByRelevanceTac(sliceMatches))
|
|
183
|
+
} else {
|
|
184
|
+
sort.Sort(ByRelevance(sliceMatches))
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
resultChan <- partialResult{idx, sliceMatches}
|
|
188
|
+
}(idx, m.slab[idx], chunks)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
wait := func() bool {
|
|
192
|
+
cancelled.Set(true)
|
|
193
|
+
waitGroup.Wait()
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
count := 0
|
|
198
|
+
matchCount := 0
|
|
199
|
+
for matchesInChunk := range countChan {
|
|
200
|
+
count++
|
|
201
|
+
matchCount += matchesInChunk
|
|
202
|
+
|
|
203
|
+
if count == numChunks {
|
|
204
|
+
break
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if m.reqBox.Peek(reqReset) {
|
|
208
|
+
return nil, wait()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if time.Since(startedAt) > progressMinDuration {
|
|
212
|
+
m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
partialResults := make([][]Result, numSlices)
|
|
217
|
+
for range slices {
|
|
218
|
+
partialResult := <-resultChan
|
|
219
|
+
partialResults[partialResult.index] = partialResult.matches
|
|
220
|
+
}
|
|
221
|
+
return NewMerger(pattern, partialResults, m.sort, m.tac), false
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Reset is called to interrupt/signal the ongoing search
|
|
225
|
+
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool, clearCache bool) {
|
|
226
|
+
pattern := m.patternBuilder(patternRunes)
|
|
227
|
+
|
|
228
|
+
var event util.EventType
|
|
229
|
+
if cancel {
|
|
230
|
+
event = reqReset
|
|
231
|
+
} else {
|
|
232
|
+
event = reqRetry
|
|
233
|
+
}
|
|
234
|
+
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, clearCache})
|
|
235
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
package fzf
|
|
2
|
+
|
|
3
|
+
import "fmt"
|
|
4
|
+
|
|
5
|
+
// EmptyMerger is a Merger with no data
|
|
6
|
+
var EmptyMerger = NewMerger(nil, [][]Result{}, false, false)
|
|
7
|
+
|
|
8
|
+
// Merger holds a set of locally sorted lists of items and provides the view of
|
|
9
|
+
// a single, globally-sorted list
|
|
10
|
+
type Merger struct {
|
|
11
|
+
pattern *Pattern
|
|
12
|
+
lists [][]Result
|
|
13
|
+
merged []Result
|
|
14
|
+
chunks *[]*Chunk
|
|
15
|
+
cursors []int
|
|
16
|
+
sorted bool
|
|
17
|
+
tac bool
|
|
18
|
+
final bool
|
|
19
|
+
count int
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// PassMerger returns a new Merger that simply returns the items in the
|
|
23
|
+
// original order
|
|
24
|
+
func PassMerger(chunks *[]*Chunk, tac bool) *Merger {
|
|
25
|
+
mg := Merger{
|
|
26
|
+
pattern: nil,
|
|
27
|
+
chunks: chunks,
|
|
28
|
+
tac: tac,
|
|
29
|
+
count: 0}
|
|
30
|
+
|
|
31
|
+
for _, chunk := range *mg.chunks {
|
|
32
|
+
mg.count += chunk.count
|
|
33
|
+
}
|
|
34
|
+
return &mg
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// NewMerger returns a new Merger
|
|
38
|
+
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger {
|
|
39
|
+
mg := Merger{
|
|
40
|
+
pattern: pattern,
|
|
41
|
+
lists: lists,
|
|
42
|
+
merged: []Result{},
|
|
43
|
+
chunks: nil,
|
|
44
|
+
cursors: make([]int, len(lists)),
|
|
45
|
+
sorted: sorted,
|
|
46
|
+
tac: tac,
|
|
47
|
+
final: false,
|
|
48
|
+
count: 0}
|
|
49
|
+
|
|
50
|
+
for _, list := range mg.lists {
|
|
51
|
+
mg.count += len(list)
|
|
52
|
+
}
|
|
53
|
+
return &mg
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Length returns the number of items
|
|
57
|
+
func (mg *Merger) Length() int {
|
|
58
|
+
return mg.count
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Get returns the pointer to the Result object indexed by the given integer
|
|
62
|
+
func (mg *Merger) Get(idx int) Result {
|
|
63
|
+
if mg.chunks != nil {
|
|
64
|
+
if mg.tac {
|
|
65
|
+
idx = mg.count - idx - 1
|
|
66
|
+
}
|
|
67
|
+
chunk := (*mg.chunks)[idx/chunkSize]
|
|
68
|
+
return Result{item: &chunk.items[idx%chunkSize]}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if mg.sorted {
|
|
72
|
+
return mg.mergedGet(idx)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if mg.tac {
|
|
76
|
+
idx = mg.count - idx - 1
|
|
77
|
+
}
|
|
78
|
+
for _, list := range mg.lists {
|
|
79
|
+
numItems := len(list)
|
|
80
|
+
if idx < numItems {
|
|
81
|
+
return list[idx]
|
|
82
|
+
}
|
|
83
|
+
idx -= numItems
|
|
84
|
+
}
|
|
85
|
+
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func (mg *Merger) cacheable() bool {
|
|
89
|
+
return mg.count < mergerCacheMax
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func (mg *Merger) mergedGet(idx int) Result {
|
|
93
|
+
for i := len(mg.merged); i <= idx; i++ {
|
|
94
|
+
minRank := minRank()
|
|
95
|
+
minIdx := -1
|
|
96
|
+
for listIdx, list := range mg.lists {
|
|
97
|
+
cursor := mg.cursors[listIdx]
|
|
98
|
+
if cursor < 0 || cursor == len(list) {
|
|
99
|
+
mg.cursors[listIdx] = -1
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
if cursor >= 0 {
|
|
103
|
+
rank := list[cursor]
|
|
104
|
+
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
|
105
|
+
minRank = rank
|
|
106
|
+
minIdx = listIdx
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if minIdx >= 0 {
|
|
112
|
+
chosen := mg.lists[minIdx]
|
|
113
|
+
mg.merged = append(mg.merged, chosen[mg.cursors[minIdx]])
|
|
114
|
+
mg.cursors[minIdx]++
|
|
115
|
+
} else {
|
|
116
|
+
panic(fmt.Sprintf("Index out of bounds (sorted, %d/%d)", i, mg.count))
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return mg.merged[idx]
|
|
120
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package fzf
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"math/rand"
|
|
6
|
+
"sort"
|
|
7
|
+
"testing"
|
|
8
|
+
|
|
9
|
+
"github.com/junegunn/fzf/src/util"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func assert(t *testing.T, cond bool, msg ...string) {
|
|
13
|
+
if !cond {
|
|
14
|
+
t.Error(msg)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
func randResult() Result {
|
|
19
|
+
str := fmt.Sprintf("%d", rand.Uint32())
|
|
20
|
+
chars := util.ToChars([]byte(str))
|
|
21
|
+
chars.Index = rand.Int31()
|
|
22
|
+
return Result{item: &Item{text: chars}}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func TestEmptyMerger(t *testing.T) {
|
|
26
|
+
assert(t, EmptyMerger.Length() == 0, "Not empty")
|
|
27
|
+
assert(t, EmptyMerger.count == 0, "Invalid count")
|
|
28
|
+
assert(t, len(EmptyMerger.lists) == 0, "Invalid lists")
|
|
29
|
+
assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func buildLists(partiallySorted bool) ([][]Result, []Result) {
|
|
33
|
+
numLists := 4
|
|
34
|
+
lists := make([][]Result, numLists)
|
|
35
|
+
cnt := 0
|
|
36
|
+
for i := 0; i < numLists; i++ {
|
|
37
|
+
numResults := rand.Int() % 20
|
|
38
|
+
cnt += numResults
|
|
39
|
+
lists[i] = make([]Result, numResults)
|
|
40
|
+
for j := 0; j < numResults; j++ {
|
|
41
|
+
item := randResult()
|
|
42
|
+
lists[i][j] = item
|
|
43
|
+
}
|
|
44
|
+
if partiallySorted {
|
|
45
|
+
sort.Sort(ByRelevance(lists[i]))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
items := []Result{}
|
|
49
|
+
for _, list := range lists {
|
|
50
|
+
items = append(items, list...)
|
|
51
|
+
}
|
|
52
|
+
return lists, items
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
func TestMergerUnsorted(t *testing.T) {
|
|
56
|
+
lists, items := buildLists(false)
|
|
57
|
+
cnt := len(items)
|
|
58
|
+
|
|
59
|
+
// Not sorted: same order
|
|
60
|
+
mg := NewMerger(nil, lists, false, false)
|
|
61
|
+
assert(t, cnt == mg.Length(), "Invalid Length")
|
|
62
|
+
for i := 0; i < cnt; i++ {
|
|
63
|
+
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func TestMergerSorted(t *testing.T) {
|
|
68
|
+
lists, items := buildLists(true)
|
|
69
|
+
cnt := len(items)
|
|
70
|
+
|
|
71
|
+
// Sorted sorted order
|
|
72
|
+
mg := NewMerger(nil, lists, true, false)
|
|
73
|
+
assert(t, cnt == mg.Length(), "Invalid Length")
|
|
74
|
+
sort.Sort(ByRelevance(items))
|
|
75
|
+
for i := 0; i < cnt; i++ {
|
|
76
|
+
if items[i] != mg.Get(i) {
|
|
77
|
+
t.Error("Not sorted", items[i], mg.Get(i))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Inverse order
|
|
82
|
+
mg2 := NewMerger(nil, lists, true, false)
|
|
83
|
+
for i := cnt - 1; i >= 0; i-- {
|
|
84
|
+
if items[i] != mg2.Get(i) {
|
|
85
|
+
t.Error("Not sorted", items[i], mg2.Get(i))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|