diff --git a/_examples/single_select/main.go b/_examples/single_select/main.go index b7ce2f7..0caaebf 100644 --- a/_examples/single_select/main.go +++ b/_examples/single_select/main.go @@ -28,10 +28,19 @@ func main() { key.WithKeys("enter"), key.WithHelp("enter", "finish select"), ) + selectKeymap.NextPage = key.NewBinding( + key.WithKeys("q"), + key.WithHelp("q", "next page"), + ) + selectKeymap.PrevPage = key.NewBinding( + key.WithKeys("b"), + key.WithHelp("b", "prev page"), + ) selected, err := inf.NewSingleSelect( options, singleselect.WithDisableFilter(), singleselect.WithKeyBinding(selectKeymap), + singleselect.WithPageSize(5), ).Display("Hello world") if err == nil { diff --git a/components/components.go b/components/components.go index 9faf97d..b74dc50 100644 --- a/components/components.go +++ b/components/components.go @@ -3,8 +3,10 @@ package components import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/paginator" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/duke-git/lancet/v2/slice" "github.com/fzdwx/infinite/style" "github.com/fzdwx/infinite/theme" @@ -109,8 +111,13 @@ func NewSelection(choices []string) *Selection { items := slice.Map[string, SelectionItem](choices, func(idx int, item string) SelectionItem { return SelectionItem{idx, item} }) - + model := paginator.New() + p := paginator.New() + p.Type = paginator.Dots + p.ActiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}).Render("•") + p.InactiveDot = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "250", Dark: "238"}).Render("•") c := &Selection{ + Paginator: model, Choices: items, Selected: make(map[int]bool), CursorSymbol: SelectionDefaultCursorSymbol, @@ -161,6 +168,7 @@ func NewSpinner() *Spinner { return c } +// NewSelectionWithList WIP func NewSelectionWithList[T list.DefaultItem](items []T) *SelectionWithList[T] { keymap := &SelectionWithListKeyMap{ Choice: key.NewBinding( diff --git a/components/selection.go b/components/selection.go index 5f44348..58399ff 100644 --- a/components/selection.go +++ b/components/selection.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/paginator" tea "github.com/charmbracelet/bubbletea" "github.com/duke-git/lancet/v2/mathutil" "github.com/duke-git/lancet/v2/slice" @@ -135,15 +136,12 @@ type Selection struct { Selected map[int]bool // Current cursor index in currentChoices cursor int - // the offset of screen - scrollOffset int - // usually len(currentChoices) - availableChoices int // currently valid option currentChoices []SelectionItem program *tea.Program - Choices []SelectionItem + Paginator paginator.Model + Choices []SelectionItem Validators []Validator validatorsErrMsg []string @@ -241,17 +239,13 @@ func (s *Selection) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } if key.Matches(msg, s.Keymap.NextPage) { - for i := 0; i < s.PageSize; i++ { - s.moveDown() - } + s.Paginator.NextPage() shouldSkipFiler = true } str := msg.String() _ = str == "" if key.Matches(msg, s.Keymap.PrevPage) { - for i := 0; i < s.PageSize; i++ { - s.moveUp() - } + s.Paginator.PrevPage() shouldSkipFiler = true } @@ -327,8 +321,11 @@ func (s *Selection) View() string { msg.NewLine().Write(s.Header) } + s.Paginator.PerPage = s.PageSize + s.Paginator.SetTotalPages(len(s.currentChoices)) + start, end := s.Paginator.GetSliceBounds(len(s.currentChoices)) // Iterate over our Choices - for i, choice := range s.currentChoices { + for i, choice := range s.currentChoices[start:end] { val := choice.Val // Is the CursorSymbol pointing at this choice? @@ -348,6 +345,8 @@ func (s *Selection) View() string { msg.NewLine().Write(s.RowRender(cursorSymbol, hintSymbol, val)) } + msg.NewLine().Write(s.Paginator.View()) + if s.ShowHelp { msg.NewLine().Write(s.Help.View(s.Keymap)) } @@ -382,9 +381,7 @@ func (s *Selection) RenderColor() { // RefreshChoices refresh Choices func (s *Selection) RefreshChoices() { - var choices []SelectionItem var filterChoices []SelectionItem - var available, ignored int // filter choice if s.shouldFilter() && len(s.FilterInput.Value()) > 0 { @@ -394,24 +391,7 @@ func (s *Selection) RefreshChoices() { filterChoices = s.Choices } - for _, choice := range filterChoices { - available++ - - if s.PageSize > 0 && len(choices) >= s.PageSize { - break - } - - if (s.PageSize > 0) && (ignored < s.scrollOffset) { - ignored++ - - continue - } - - choices = append(choices, choice) - } - - s.currentChoices = choices - s.availableChoices = available + s.currentChoices = filterChoices } // viewResult get result @@ -519,12 +499,12 @@ func (s *Selection) finish() (tea.Model, tea.Cmd) { // shouldMoveToTop should move to top? func (s *Selection) shouldMoveToTop() bool { - return (s.cursor + s.scrollOffset) == (len(s.Choices) - 1) + return s.cursor == (s.PageSize-1) && s.Paginator.OnLastPage() } // shouldScrollDown should scroll down? func (s *Selection) shouldScrollDown() bool { - return s.cursor == len(s.currentChoices)-1 && s.canScrollDown() + return s.cursor == (s.PageSize-1) && s.canScrollDown() } // shouldScrollUp should scroll up? @@ -535,44 +515,30 @@ func (s *Selection) shouldScrollUp() bool { // moveToTop move cursor to top func (s *Selection) moveToTop() { s.cursor = 0 - s.scrollOffset = 0 + s.Paginator.Page = 0 s.RefreshChoices() } func (s *Selection) scrollUp() { - if s.PageSize <= 0 || s.scrollOffset <= 0 { - return - } - - s.cursor = mathutil.Min(len(s.currentChoices)-1, s.cursor+1) - s.scrollOffset-- + s.Paginator.PrevPage() s.RefreshChoices() } func (s *Selection) scrollDown() { - if s.PageSize <= 0 || s.scrollOffset+s.PageSize >= s.availableChoices { + if s.PageSize <= 0 { return } s.cursor = mathutil.Max(0, s.cursor-1) - s.scrollOffset++ s.RefreshChoices() } func (s *Selection) canScrollDown() bool { - if s.PageSize <= 0 || s.availableChoices <= s.PageSize { - return false - } - - if s.scrollOffset+s.PageSize >= len(s.Choices) { - return false - } - - return true + return s.Paginator.OnLastPage() == false } func (s *Selection) canScrollUp() bool { - return s.scrollOffset > 0 + return s.Paginator.Page != 0 } func (s *Selection) shouldFilter() bool {