peco.peco/selection/selection.go
2026-02-20 21:06:46 +09:00

121 lines
2.6 KiB
Go

package selection
import (
"sync"
"github.com/google/btree"
"github.com/peco/peco/line"
)
// Set stores the line ids that were selected by the user.
// The contents of the Set is always sorted from smallest to
// largest line ID.
type Set struct {
mutex sync.RWMutex
tree *btree.BTree
}
// RangeStart tracks the starting position of a range selection.
type RangeStart struct {
val int
valid bool
}
// New creates a new empty Set.
func New() *Set {
s := &Set{}
s.Reset()
return s
}
// Add adds a new line to the selection. If the line already
// exists in the selection, it is silently ignored.
func (s *Set) Add(l line.Line) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.tree.ReplaceOrInsert(l)
}
// Copy copies all selected lines from s into dst.
//
// Items are collected under the source read lock first, then added to dst
// after releasing that lock. This avoids deadlocks when dst == s and
// prevents ABBA deadlocks during concurrent bidirectional copies.
func (s *Set) Copy(dst *Set) {
s.mutex.RLock()
items := make([]line.Line, 0, s.tree.Len())
s.tree.Ascend(func(it btree.Item) bool {
if l, ok := it.(line.Line); ok {
items = append(items, l)
}
return true
})
s.mutex.RUnlock()
for _, l := range items {
dst.Add(l)
}
}
// Remove removes the specified line from the selection.
func (s *Set) Remove(l line.Line) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.tree.Delete(l)
}
// Reset clears all selected indices from the selection.
func (s *Set) Reset() {
s.mutex.Lock()
defer s.mutex.Unlock()
s.tree = btree.New(32)
}
// Has reports whether the given line is in the selection.
func (s *Set) Has(x line.Line) bool {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.tree.Has(x)
}
// Len returns the number of selected lines.
func (s *Set) Len() int {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.tree.Len()
}
// Ascend iterates over selected lines in ascending order, calling fn for each.
func (s *Set) Ascend(fn func(line.Line) bool) {
s.mutex.RLock()
defer s.mutex.RUnlock()
s.tree.Ascend(func(it btree.Item) bool {
l, ok := it.(line.Line)
if !ok {
return true
}
return fn(l)
})
}
// Valid reports whether the RangeStart has been set.
func (s RangeStart) Valid() bool {
return s.valid
}
// Value returns the starting line index of the range.
func (s RangeStart) Value() int {
return s.val
}
// SetValue sets the starting position and marks it as valid.
func (s *RangeStart) SetValue(n int) {
s.val = n
s.valid = true
}
// Reset clears the range start, marking it as invalid.
func (s *RangeStart) Reset() {
s.valid = false
}