mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
403 lines
9.3 KiB
Go
403 lines
9.3 KiB
Go
package choose_files
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"slices"
|
|
"sync"
|
|
|
|
"github.com/kovidgoyal/kitty/tools/utils"
|
|
)
|
|
|
|
var _ = fmt.Print
|
|
|
|
type CollectionIndex struct {
|
|
Slice, Pos int
|
|
}
|
|
|
|
func (c CollectionIndex) Compare(o CollectionIndex) int {
|
|
if c.Slice == o.Slice {
|
|
return c.Pos - o.Pos
|
|
}
|
|
return c.Slice - o.Slice
|
|
}
|
|
|
|
func (c CollectionIndex) Less(o CollectionIndex) bool {
|
|
return c.Slice < o.Slice || (c.Slice == o.Slice && c.Pos < o.Pos)
|
|
}
|
|
|
|
func (c *CollectionIndex) NextSlice() {
|
|
c.Slice++
|
|
c.Pos = 0
|
|
}
|
|
|
|
type ResultCollection struct {
|
|
slices [][]ResultItem
|
|
append_idx CollectionIndex
|
|
batch_size int
|
|
}
|
|
|
|
func NewResultCollection(batch_size int) (ans *ResultCollection) {
|
|
batch_size = max(1, batch_size)
|
|
return &ResultCollection{
|
|
batch_size: batch_size,
|
|
slices: [][]ResultItem{make([]ResultItem, batch_size)},
|
|
}
|
|
}
|
|
|
|
func (c *ResultCollection) Len() int {
|
|
return c.batch_size*(len(c.slices)-1) + c.append_idx.Pos
|
|
}
|
|
|
|
func (c *ResultCollection) NextAppendPointer() (ans *ResultItem) {
|
|
s := c.slices[c.append_idx.Slice]
|
|
ans = &s[c.append_idx.Pos]
|
|
if c.append_idx.Pos+1 < len(s) {
|
|
c.append_idx.Pos++
|
|
} else if c.append_idx.Slice+1 < len(c.slices) {
|
|
c.append_idx.NextSlice()
|
|
} else {
|
|
c.slices = append(c.slices, make([]ResultItem, 4096))
|
|
c.append_idx.NextSlice()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *ResultCollection) Batch(offset *CollectionIndex) (ans []ResultItem) {
|
|
if offset.Slice == c.append_idx.Slice {
|
|
if offset.Pos < c.append_idx.Pos {
|
|
ans = c.slices[offset.Slice][offset.Pos:c.append_idx.Pos]
|
|
offset.Pos = c.append_idx.Pos
|
|
}
|
|
} else if offset.Slice < c.append_idx.Slice {
|
|
ans = c.slices[offset.Slice][offset.Pos:]
|
|
offset.NextSlice()
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *ResultCollection) NextDir(offset *CollectionIndex) (ans string, ignore_files []ignore_file_with_prefix) {
|
|
for ans == "" && offset.Compare(c.append_idx) < 0 {
|
|
if c.slices[offset.Slice][offset.Pos].ftype&fs.ModeDir != 0 {
|
|
ans = c.slices[offset.Slice][offset.Pos].text
|
|
ignore_files = c.slices[offset.Slice][offset.Pos].ignore_files
|
|
}
|
|
offset.Pos++
|
|
if offset.Pos >= len(c.slices[offset.Slice]) {
|
|
offset.NextSlice()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type SortedResults struct {
|
|
slices [][]*ResultItem
|
|
mutex sync.Mutex
|
|
len int
|
|
}
|
|
|
|
func NewSortedResults() *SortedResults { return &SortedResults{} }
|
|
func (s *SortedResults) lock() { s.mutex.Lock() }
|
|
func (s *SortedResults) unlock() { s.mutex.Unlock() }
|
|
|
|
func (s *SortedResults) Len() int {
|
|
s.lock()
|
|
defer s.unlock()
|
|
return s.len
|
|
}
|
|
|
|
func (s *SortedResults) Clear() {
|
|
s.lock()
|
|
defer s.unlock()
|
|
s.slices = nil
|
|
s.len = 0
|
|
}
|
|
|
|
func (s *SortedResults) At(pos CollectionIndex) (ans *ResultItem) {
|
|
s.lock()
|
|
defer s.unlock()
|
|
if pos.Slice < len(s.slices) {
|
|
s := s.slices[pos.Slice]
|
|
if pos.Pos < len(s) {
|
|
ans = s[pos.Pos]
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *SortedResults) RenderedMatches(pos CollectionIndex, max_num int) (ans []*ResultItem) {
|
|
s.lock()
|
|
defer s.unlock()
|
|
if pos.Slice >= len(s.slices) {
|
|
return
|
|
}
|
|
if max_num < 0 {
|
|
max_num = s.len
|
|
}
|
|
ans = make([]*ResultItem, 0, max_num)
|
|
for ; pos.Slice < len(s.slices) && max_num > 0; pos.NextSlice() {
|
|
sl := s.slices[pos.Slice]
|
|
if pos.Pos >= len(sl) {
|
|
continue
|
|
}
|
|
sl = sl[pos.Pos:min(len(sl), pos.Pos+max_num)]
|
|
ans = append(ans, sl...)
|
|
max_num -= len(sl)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *SortedResults) merge_slice(idx int, sl []*ResultItem) {
|
|
sz := len(s.slices[idx])
|
|
maxs := sl[len(sl)-1].score
|
|
limit := idx + 1
|
|
for limit < len(s.slices) {
|
|
q := s.slices[limit]
|
|
if q[0].score > maxs {
|
|
break
|
|
}
|
|
sz += len(q)
|
|
limit++
|
|
}
|
|
ans := make([]*ResultItem, 0, sz)
|
|
a := 0
|
|
b := CollectionIndex{Slice: idx}
|
|
ss := s.slices[b.Slice]
|
|
for a < len(sl) {
|
|
if sl[a].score <= ss[b.Pos].score {
|
|
ans = append(ans, sl[a])
|
|
a++
|
|
} else {
|
|
ans = append(ans, ss[b.Pos])
|
|
b.Pos++
|
|
if b.Pos >= len(ss) {
|
|
b.NextSlice()
|
|
if b.Slice >= limit {
|
|
break
|
|
}
|
|
ss = s.slices[b.Slice]
|
|
}
|
|
}
|
|
}
|
|
ans = append(ans, sl[a:]...)
|
|
for ; b.Slice < limit; b.NextSlice() {
|
|
ans = append(ans, s.slices[b.Slice][b.Pos:]...)
|
|
}
|
|
s.slices = slices.Replace(s.slices, idx, limit, ans)
|
|
}
|
|
|
|
func (s *SortedResults) AddSortedSlice(sl []*ResultItem) {
|
|
if len(sl) == 0 {
|
|
return
|
|
}
|
|
s.lock()
|
|
defer s.unlock()
|
|
s.len += len(sl)
|
|
if len(s.slices) == 0 {
|
|
s.slices = append(s.slices, sl)
|
|
return
|
|
}
|
|
sl_min, sl_max := sl[0].score, sl[len(sl)-1].score
|
|
for i, q := range s.slices {
|
|
switch {
|
|
case sl_max <= q[0].score:
|
|
s.slices = slices.Insert(s.slices, i, sl)
|
|
return
|
|
case sl_min >= q[len(q)-1].score:
|
|
continue
|
|
default:
|
|
s.merge_slice(i, sl)
|
|
return
|
|
}
|
|
}
|
|
s.slices = append(s.slices, sl)
|
|
}
|
|
|
|
func (s *SortedResults) Apply(first, last CollectionIndex, action func(*ResultItem) (keep_going bool)) {
|
|
s.lock()
|
|
defer s.unlock()
|
|
if first.Slice >= len(s.slices) || first.Pos >= len(s.slices[first.Slice]) {
|
|
return
|
|
}
|
|
amt := utils.IfElse(first.Less(last), 1, -1)
|
|
var did_wrap bool
|
|
for {
|
|
if !action(s.slices[first.Slice][first.Pos]) {
|
|
break
|
|
}
|
|
if first.Compare(last) == 0 {
|
|
break
|
|
}
|
|
first, did_wrap = s.increment_with_wrap_around(first, amt)
|
|
if did_wrap {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *SortedResults) Closest(idx CollectionIndex, matches func(*ResultItem) bool) *CollectionIndex {
|
|
s.lock()
|
|
defer s.unlock()
|
|
if idx.Slice >= len(s.slices) || idx.Pos >= len(s.slices[idx.Slice]) {
|
|
return nil
|
|
}
|
|
|
|
type result struct {
|
|
idx CollectionIndex
|
|
count int
|
|
}
|
|
var a, b result
|
|
iterate := func(idx CollectionIndex, amt int, result *result) {
|
|
var did_wrap bool
|
|
var count int
|
|
result.count = -1
|
|
for {
|
|
idx, did_wrap = s.increment_with_wrap_around(idx, amt)
|
|
if did_wrap {
|
|
break
|
|
}
|
|
count++
|
|
if matches(s.slices[idx.Slice][idx.Pos]) {
|
|
result.idx = idx
|
|
result.count = count
|
|
break
|
|
}
|
|
}
|
|
}
|
|
go func() { iterate(idx, 1, &a) }()
|
|
go func() { iterate(idx, -1, &a) }()
|
|
if a.count < 0 && b.count < 0 {
|
|
return nil
|
|
}
|
|
return utils.IfElse(a.count < b.count, &b.idx, &a.idx)
|
|
}
|
|
|
|
func (s *SortedResults) IncrementIndexWithWrapAroundAndCheck(idx CollectionIndex, amt int) (ans CollectionIndex, did_wrap bool) {
|
|
s.lock()
|
|
defer s.unlock()
|
|
return s.increment_with_wrap_around(idx, amt)
|
|
}
|
|
|
|
func (s *SortedResults) IncrementIndexWithWrapAround(idx CollectionIndex, amt int) CollectionIndex {
|
|
s.lock()
|
|
defer s.unlock()
|
|
ans, _ := s.increment_with_wrap_around(idx, amt)
|
|
return ans
|
|
}
|
|
|
|
func (s *SortedResults) increment_with_wrap_around(idx CollectionIndex, amt int) (CollectionIndex, bool) {
|
|
did_wrap := false
|
|
if amt > 0 {
|
|
for amt > 0 {
|
|
if delta := min(amt, len(s.slices[idx.Slice])-1-idx.Pos); delta > 0 {
|
|
idx.Pos += delta
|
|
amt -= delta
|
|
} else {
|
|
idx.NextSlice()
|
|
if idx.Slice >= len(s.slices) {
|
|
idx = CollectionIndex{} // wraparound
|
|
did_wrap = true
|
|
}
|
|
amt--
|
|
}
|
|
}
|
|
} else {
|
|
// we use separate code for negative increment instead of doing
|
|
// increment = len - increment as it is faster in the common case of
|
|
// increment much smaller than len
|
|
amt *= -1
|
|
for amt > 0 {
|
|
if idx.Pos > 0 {
|
|
delta := min(amt, idx.Pos)
|
|
amt -= delta
|
|
idx.Pos -= delta
|
|
} else {
|
|
if idx.Slice == 0 {
|
|
idx = CollectionIndex{Slice: len(s.slices) - 1, Pos: len(s.slices[len(s.slices)-1]) - 1}
|
|
did_wrap = true
|
|
} else {
|
|
idx.Slice--
|
|
idx.Pos = len(s.slices[idx.Slice]) - 1
|
|
}
|
|
amt--
|
|
}
|
|
}
|
|
}
|
|
return idx, did_wrap
|
|
}
|
|
|
|
// Return a - b
|
|
func (s *SortedResults) SignedDistance(a, b CollectionIndex) (ans int) {
|
|
s.lock()
|
|
defer s.unlock()
|
|
return s.signed_distance(a, b)
|
|
}
|
|
|
|
// Return a - b
|
|
func (s *SortedResults) signed_distance(a, b CollectionIndex) (ans int) {
|
|
mult := -1
|
|
if b.Less(a) {
|
|
a, b = b, a
|
|
mult = 1
|
|
}
|
|
limit := min(b.Slice, len(s.slices))
|
|
for ; a.Slice < limit; a.NextSlice() {
|
|
ans += len(s.slices[a.Slice]) - a.Pos
|
|
}
|
|
return mult * (ans + (b.Pos - a.Pos))
|
|
}
|
|
|
|
// Return |a - b|
|
|
func (s *SortedResults) distance(a, b CollectionIndex) (ans int) {
|
|
if b.Less(a) {
|
|
a, b = b, a
|
|
}
|
|
limit := min(b.Slice, len(s.slices))
|
|
for ; a.Slice < limit; a.NextSlice() {
|
|
ans += len(s.slices[a.Slice]) - a.Pos
|
|
}
|
|
return ans + (b.Pos - a.Pos)
|
|
}
|
|
|
|
func (s *SortedResults) SplitIntoColumns(calc_num_cols func(int) int, num_per_column, num_before_current int, current CollectionIndex) (ans [][]*ResultItem, num_before int, first_idx CollectionIndex) {
|
|
s.lock()
|
|
defer s.unlock()
|
|
num_cols := calc_num_cols(s.len)
|
|
total := num_cols * num_per_column
|
|
if total < 1 {
|
|
return nil, 0, CollectionIndex{}
|
|
}
|
|
num_before = min(total-1, num_before_current)
|
|
idx, did_wrap := s.increment_with_wrap_around(current, -num_before)
|
|
last_slice := s.slices[len(s.slices)-1]
|
|
last := CollectionIndex{Slice: len(s.slices) - 1, Pos: len(last_slice) - 1}
|
|
if did_wrap {
|
|
idx = CollectionIndex{}
|
|
} else if s.distance(idx, last) < total-1 {
|
|
if idx, did_wrap = s.increment_with_wrap_around(last, 1-total); did_wrap {
|
|
idx = CollectionIndex{}
|
|
}
|
|
}
|
|
first_idx = idx
|
|
num_before = s.distance(idx, current)
|
|
// fmt.Printf("111111 idx: %v current: %v num_before: %d\n", idx, current, num_before)
|
|
ans = make([][]*ResultItem, num_cols)
|
|
for colidx := range len(ans) {
|
|
col := make([]*ResultItem, 0, num_per_column)
|
|
for len(col) < num_per_column && idx.Slice < len(s.slices) {
|
|
ss := s.slices[idx.Slice]
|
|
limit := min(len(ss), idx.Pos+num_per_column-len(col))
|
|
col = append(col, ss[idx.Pos:limit]...)
|
|
idx.Pos = limit
|
|
if idx.Pos >= len(ss) {
|
|
idx.NextSlice()
|
|
if idx.Slice >= len(s.slices) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
ans[colidx] = col
|
|
}
|
|
return
|
|
}
|