diff --git a/action.go b/action.go index a14b987..c9893e6 100644 --- a/action.go +++ b/action.go @@ -11,12 +11,12 @@ import ( "context" - "github.com/google/btree" "github.com/lestrrat-go/pdebug" "github.com/peco/peco/hub" "github.com/peco/peco/internal/keyseq" "github.com/peco/peco/internal/util" "github.com/peco/peco/line" + "github.com/peco/peco/selection" ) // Action describes an action that can be executed upon receiving user input. @@ -196,7 +196,7 @@ func init() { } // selectLine marks the line as dirty and adds it to the selection. -func selectLine(l line.Line, s *Selection) { +func selectLine(l line.Line, s *selection.Set) { l.SetDirty(true) s.Add(l) } @@ -368,7 +368,7 @@ func doFinish(ctx context.Context, state *Peco, _ Event) { return } - sel := NewSelection() + sel := selection.New() state.Selection().Copy(sel) if sel.Len() == 0 { if l, err := state.CurrentLineBuffer().LineAt(state.Location().LineNumber()); err == nil { @@ -377,12 +377,8 @@ func doFinish(ctx context.Context, state *Peco, _ Event) { } var stdin bytes.Buffer - sel.Ascend(func(it btree.Item) bool { - line, ok := it.(line.Line) - if !ok { - return true - } - stdin.WriteString(line.Buffer()) + sel.Ascend(func(l line.Line) bool { + stdin.WriteString(l.Buffer()) stdin.WriteRune('\n') return true }) @@ -870,11 +866,7 @@ func doGoToAdjacentSelection(ctx context.Context, state *Peco, forward bool) { } found := false - selection.Ascend(func(it btree.Item) bool { - l, ok := it.(line.Line) - if !ok { - return true - } + selection.Ascend(func(l line.Line) bool { id := l.ID() if forward { if id > currentLine && id < target { diff --git a/action_test.go b/action_test.go index 3d7e7e3..d20435c 100644 --- a/action_test.go +++ b/action_test.go @@ -13,6 +13,7 @@ import ( "github.com/peco/peco/hub" "github.com/peco/peco/internal/keyseq" "github.com/peco/peco/line" + "github.com/peco/peco/selection" "github.com/stretchr/testify/require" ) @@ -201,7 +202,7 @@ func TestPagingActions(t *testing.T) { rHub := &recordingHub{} state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() state.currentLineBuffer = NewMemoryBuffer(0) action, ok := nameToActions[tt.action] @@ -549,7 +550,7 @@ func TestGHIssue574_PreviousSelectionLastLineNotUpdated(t *testing.T) { state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() // Set the current line buffer to our prepared buffer. state.currentLineBuffer = mb @@ -648,7 +649,7 @@ func TestNextSelectionNavigation(t *testing.T) { state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() state.currentLineBuffer = mb state.Selection().Add(lines[1]) // ID=20 @@ -727,7 +728,7 @@ func TestGHIssue428_PgUpPgDnDefaultBindings(t *testing.T) { state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() state.currentLineBuffer = NewMemoryBuffer(0) // Populate the keymap with defaults (no custom config). @@ -772,7 +773,7 @@ func TestGHIssue455_RefreshScreenSendsForceSync(t *testing.T) { state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() state.currentLineBuffer = NewMemoryBuffer(0) doRefreshScreen(ctx, state, Event{}) @@ -801,7 +802,7 @@ func TestDoFreezeResults(t *testing.T) { rHub := &recordingHub{} state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() lines := makeLines("alpha", "beta", "gamma") mb := NewMemoryBuffer(0) @@ -834,7 +835,7 @@ func TestDoFreezeResults(t *testing.T) { rHub := &recordingHub{} state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() state.source = &Source{} lines := makeLines("frozen1", "frozen2") @@ -856,7 +857,7 @@ func TestDoFreezeResults(t *testing.T) { rHub := &recordingHub{} state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() origLines := makeLines("orig1", "orig2", "orig3") origSource := &Source{} @@ -886,7 +887,7 @@ func TestDoFreezeResults(t *testing.T) { rHub := &recordingHub{} state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() state.currentLineBuffer = NewMemoryBuffer(0) doFreezeResults(ctx, state, Event{}) @@ -901,7 +902,7 @@ func TestDoFreezeResults(t *testing.T) { rHub := &recordingHub{} state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() doUnfreezeResults(ctx, state, Event{}) @@ -1068,7 +1069,7 @@ func TestDoZoomInOut(t *testing.T) { rHub := &recordingHub{} state := New() state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() state.source = &Source{} state.source.lines = source.lines state.currentLineBuffer = filtered diff --git a/filter_incremental_test.go b/filter_incremental_test.go index 5c5fa20..82bb9a5 100644 --- a/filter_incremental_test.go +++ b/filter_incremental_test.go @@ -12,6 +12,7 @@ import ( "github.com/peco/peco/hub" "github.com/peco/peco/line" "github.com/peco/peco/pipeline" + "github.com/peco/peco/selection" "github.com/stretchr/testify/require" ) @@ -277,7 +278,7 @@ func TestFrozenCacheInvalidation(t *testing.T) { state := New() rHub := &recordingHub{} state.hub = rHub - state.selection = NewSelection() + state.selection = selection.New() // Populate filters (needed by Filter.Work) state.filters.Add(filter.NewIgnoreCase()) diff --git a/peco.go b/peco.go index 5563f13..ed23df6 100644 --- a/peco.go +++ b/peco.go @@ -16,13 +16,13 @@ import ( "context" - "github.com/google/btree" "github.com/lestrrat-go/pdebug" "github.com/peco/peco/filter" "github.com/peco/peco/hub" "github.com/peco/peco/internal/util" "github.com/peco/peco/line" "github.com/peco/peco/pipeline" + "github.com/peco/peco/selection" "github.com/peco/peco/sig" ) @@ -74,9 +74,9 @@ type Peco struct { readyCh chan struct{} resultCh chan line.Line screen Screen - selection *Selection + selection *selection.Set selectionPrefix string - selectionRangeStart RangeStart + selectionRangeStart selection.RangeStart exitZeroAndExit bool // True if --exit-0 is enabled selectOneAndExit bool // True if --select-1 is enabled selectOneTriggered atomic.Bool @@ -211,7 +211,7 @@ func New() *Peco { readyCh: make(chan struct{}), configReader: defaultConfigReader, screen: NewTcellScreen(), - selection: NewSelection(), + selection: selection.New(), maxScanBufferSize: bufio.MaxScanTokenSize, } } @@ -264,28 +264,11 @@ func (p *Peco) SetResultCh(ch chan line.Line) { p.resultCh = ch } -func (p *Peco) Selection() *Selection { +func (p *Peco) Selection() *selection.Set { return p.selection } -func (s RangeStart) Valid() bool { - return s.valid -} - -func (s RangeStart) Value() int { - return s.val -} - -func (s *RangeStart) SetValue(n int) { - s.val = n - s.valid = true -} - -func (s *RangeStart) Reset() { - s.valid = false -} - -func (p *Peco) SelectionRangeStart() *RangeStart { +func (p *Peco) SelectionRangeStart() *selection.RangeStart { return &p.selectionRangeStart } @@ -1086,11 +1069,7 @@ func (p *Peco) PrintResults() { p.SetResultCh(make(chan line.Line)) go func() { defer close(p.resultCh) - p.selection.Ascend(func(it btree.Item) bool { - l, ok := it.(line.Line) - if !ok { - return true - } + p.selection.Ascend(func(l line.Line) bool { p.ResultCh() <- l return true }) diff --git a/peco_test.go b/peco_test.go index d93700c..4959499 100644 --- a/peco_test.go +++ b/peco_test.go @@ -19,6 +19,7 @@ import ( "github.com/peco/peco/internal/keyseq" "github.com/peco/peco/internal/util" "github.com/peco/peco/line" + "github.com/peco/peco/selection" "github.com/stretchr/testify/require" ) @@ -302,7 +303,7 @@ func TestIDGen(t *testing.T) { lines = append(lines, line.NewRaw(idgen.Next(), fmt.Sprintf("%d", i), false, false)) } - sel := NewSelection() + sel := selection.New() for _, l := range lines { if sel.Has(l) { t.Errorf("Collision detected %d", l.ID()) diff --git a/selection.go b/selection.go deleted file mode 100644 index 7286369..0000000 --- a/selection.go +++ /dev/null @@ -1,84 +0,0 @@ -package peco - -import ( - "sync" - - "github.com/google/btree" - "github.com/peco/peco/line" -) - -// Selection stores the line ids that were selected by the user. -// The contents of the Selection is always sorted from smallest to -// largest line ID -type Selection struct { - mutex sync.RWMutex - tree *btree.BTree -} - -// RangeStart tracks the starting position of a range selection. -type RangeStart struct { - val int - valid bool -} - -// NewSelection creates a new empty Selection -func NewSelection() *Selection { - s := &Selection{} - 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 *Selection) Add(l line.Line) { - s.mutex.Lock() - defer s.mutex.Unlock() - s.tree.ReplaceOrInsert(l) -} - -// Copy copies all selected lines from s into dst. -func (s *Selection) Copy(dst *Selection) { - s.Ascend(func(it btree.Item) bool { - l, ok := it.(line.Line) - if !ok { - return true - } - dst.Add(l) - return true - }) -} - -// Remove removes the specified line from the selection -func (s *Selection) 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 *Selection) 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 *Selection) 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 *Selection) Len() int { - s.mutex.RLock() - defer s.mutex.RUnlock() - return s.tree.Len() -} - -// Ascend iterates over selected lines in ascending order, calling i for each. -func (s *Selection) Ascend(i btree.ItemIterator) { - s.mutex.RLock() - defer s.mutex.RUnlock() - s.tree.Ascend(i) -} diff --git a/selection/selection.go b/selection/selection.go new file mode 100644 index 0000000..c537dda --- /dev/null +++ b/selection/selection.go @@ -0,0 +1,107 @@ +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. +func (s *Set) Copy(dst *Set) { + s.Ascend(func(l line.Line) bool { + dst.Add(l) + return true + }) +} + +// 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 +} diff --git a/selection_test.go b/selection/selection_test.go similarity index 92% rename from selection_test.go rename to selection/selection_test.go index 4cc8a3b..e7b8b7c 100644 --- a/selection_test.go +++ b/selection/selection_test.go @@ -1,4 +1,4 @@ -package peco +package selection import ( "testing" @@ -8,7 +8,7 @@ import ( ) func TestSelection(t *testing.T) { - s := NewSelection() + s := New() var i uint64 alice := line.NewRaw(i, "Alice", false, false)