mirror of
https://github.com/junegunn/fzf
synced 2026-06-09 10:03:17 +00:00
b9804f5873
All input lines now enter the chunklist with sequential indices, and header lines are excluded from matching via Pattern.startIndex and PassMerger offset. This allows the number of header lines to be changed at runtime with change-header-lines(N), transform-header-lines, and bg-transform-header-lines actions. - Remove EvtHeader event; header items are read directly from chunks - Add startIndex to Pattern and PassMerger for skipping header items - Add targetIndex field to Terminal for cursor repositioning across header-lines changes Close #4659
191 lines
4.2 KiB
Go
191 lines
4.2 KiB
Go
package fzf
|
|
|
|
import "fmt"
|
|
|
|
// EmptyMerger is a Merger with no data
|
|
func EmptyMerger(revision revision) *Merger {
|
|
return NewMerger(nil, [][]Result{}, false, false, revision, 0, 0)
|
|
}
|
|
|
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
|
// a single, globally-sorted list
|
|
type Merger struct {
|
|
pattern *Pattern
|
|
lists [][]Result
|
|
merged []Result
|
|
chunks *[]*Chunk
|
|
cursors []int
|
|
sorted bool
|
|
tac bool
|
|
final bool
|
|
count int
|
|
pass bool
|
|
startIndex int
|
|
revision revision
|
|
minIndex int32
|
|
maxIndex int32
|
|
}
|
|
|
|
// PassMerger returns a new Merger that simply returns the items in the
|
|
// original order. startIndex items are skipped from the beginning.
|
|
func PassMerger(chunks *[]*Chunk, tac bool, revision revision, startIndex int32) *Merger {
|
|
var minIndex, maxIndex int32
|
|
if len(*chunks) > 0 {
|
|
minIndex = (*chunks)[0].items[0].Index()
|
|
maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)
|
|
}
|
|
si := int(startIndex)
|
|
mg := Merger{
|
|
pattern: nil,
|
|
chunks: chunks,
|
|
tac: tac,
|
|
count: 0,
|
|
pass: true,
|
|
startIndex: si,
|
|
revision: revision,
|
|
minIndex: minIndex + startIndex,
|
|
maxIndex: maxIndex}
|
|
|
|
for _, chunk := range *mg.chunks {
|
|
mg.count += chunk.count
|
|
}
|
|
mg.count = max(0, mg.count-si)
|
|
return &mg
|
|
}
|
|
|
|
// NewMerger returns a new Merger
|
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32, maxIndex int32) *Merger {
|
|
mg := Merger{
|
|
pattern: pattern,
|
|
lists: lists,
|
|
merged: []Result{},
|
|
chunks: nil,
|
|
cursors: make([]int, len(lists)),
|
|
sorted: sorted,
|
|
tac: tac,
|
|
final: false,
|
|
count: 0,
|
|
revision: revision,
|
|
minIndex: minIndex,
|
|
maxIndex: maxIndex}
|
|
|
|
for _, list := range mg.lists {
|
|
mg.count += len(list)
|
|
}
|
|
return &mg
|
|
}
|
|
|
|
// Revision returns revision number
|
|
func (mg *Merger) Revision() revision {
|
|
return mg.revision
|
|
}
|
|
|
|
// Length returns the number of items
|
|
func (mg *Merger) Length() int {
|
|
return mg.count
|
|
}
|
|
|
|
func (mg *Merger) First() Result {
|
|
if mg.tac && !mg.sorted {
|
|
return mg.Get(mg.count - 1)
|
|
}
|
|
return mg.Get(0)
|
|
}
|
|
|
|
// FindIndex returns the index of the item with the given item index
|
|
func (mg *Merger) FindIndex(itemIndex int32) int {
|
|
index := -1
|
|
if mg.pass {
|
|
index = int(itemIndex - mg.minIndex)
|
|
if mg.tac {
|
|
index = mg.count - index - 1
|
|
}
|
|
} else {
|
|
for i := 0; i < mg.count; i++ {
|
|
if mg.Get(i).item.Index() == itemIndex {
|
|
index = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return index
|
|
}
|
|
|
|
// Get returns the pointer to the Result object indexed by the given integer
|
|
func (mg *Merger) Get(idx int) Result {
|
|
if mg.chunks != nil {
|
|
if mg.tac {
|
|
idx = mg.count - idx - 1
|
|
}
|
|
idx += mg.startIndex
|
|
firstChunk := (*mg.chunks)[0]
|
|
if firstChunk.count < chunkSize && idx >= firstChunk.count {
|
|
idx -= firstChunk.count
|
|
|
|
chunk := (*mg.chunks)[idx/chunkSize+1]
|
|
return Result{item: &chunk.items[idx%chunkSize]}
|
|
}
|
|
chunk := (*mg.chunks)[idx/chunkSize]
|
|
return Result{item: &chunk.items[idx%chunkSize]}
|
|
}
|
|
|
|
if mg.sorted {
|
|
return mg.mergedGet(idx)
|
|
}
|
|
|
|
if mg.tac {
|
|
idx = mg.count - idx - 1
|
|
}
|
|
for _, list := range mg.lists {
|
|
numItems := len(list)
|
|
if idx < numItems {
|
|
return list[idx]
|
|
}
|
|
idx -= numItems
|
|
}
|
|
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
|
|
}
|
|
|
|
func (mg *Merger) ToMap() map[int32]Result {
|
|
ret := make(map[int32]Result, mg.count)
|
|
for i := 0; i < mg.count; i++ {
|
|
result := mg.Get(i)
|
|
ret[result.Index()] = result
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (mg *Merger) cacheable() bool {
|
|
return mg.count < mergerCacheMax
|
|
}
|
|
|
|
func (mg *Merger) mergedGet(idx int) Result {
|
|
for i := len(mg.merged); i <= idx; i++ {
|
|
minRank := minRank()
|
|
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]
|
|
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
|
minRank = rank
|
|
minIdx = listIdx
|
|
}
|
|
}
|
|
}
|
|
|
|
if minIdx >= 0 {
|
|
chosen := mg.lists[minIdx]
|
|
mg.merged = append(mg.merged, chosen[mg.cursors[minIdx]])
|
|
mg.cursors[minIdx]++
|
|
} else {
|
|
panic(fmt.Sprintf("Index out of bounds (sorted, %d/%d)", i, mg.count))
|
|
}
|
|
}
|
|
return mg.merged[idx]
|
|
}
|