feat(choose): Add paginator to chooser

Display paginator only if number of options > height specified
This commit is contained in:
Maas Lalani 2022-07-13 15:49:14 -04:00
parent a26995e17f
commit 4d98a8fa6f
No known key found for this signature in database
GPG key ID: 5A6ED5CBF1A0A000
2 changed files with 53 additions and 20 deletions

View file

@ -14,6 +14,7 @@ package choose
import ( import (
"strings" "strings"
"github.com/charmbracelet/bubbles/paginator"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
@ -21,7 +22,6 @@ import (
type model struct { type model struct {
height int height int
page int
cursor string cursor string
selectedPrefix string selectedPrefix string
unselectedPrefix string unselectedPrefix string
@ -31,6 +31,7 @@ type model struct {
index int index int
limit int limit int
numSelected int numSelected int
paginator paginator.Model
// styles // styles
cursorStyle lipgloss.Style cursorStyle lipgloss.Style
@ -51,27 +52,24 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
case tea.KeyMsg: case tea.KeyMsg:
start, end := m.paginator.GetSliceBounds(len(m.items))
switch keypress := msg.String(); keypress { switch keypress := msg.String(); keypress {
case "down", "j", "ctrl+n": case "down", "j", "ctrl+n":
m.index = (m.index + 1) % len(m.items) m.index = clamp(m.index+1, 0, len(m.items)-1)
m.page = m.index / m.height if m.index >= end {
m.paginator.NextPage()
}
case "up", "k", "ctrl+p": case "up", "k", "ctrl+p":
m.index = (m.index - 1 + len(m.items)) % len(m.items) m.index = clamp(m.index-1, 0, len(m.items)-1)
m.page = m.index / m.height if m.index <= start {
m.paginator.PrevPage()
}
case "right", "l", "ctrl+f": case "right", "l", "ctrl+f":
if m.index+m.height < len(m.items) { m.index = clamp(m.index+m.height, 0, len(m.items)-1)
m.index += m.height m.paginator.NextPage()
} else {
if m.page < len(m.items)/m.height {
m.index = len(m.items) - 1
}
}
m.page = m.index / m.height
case "left", "h", "ctrl+b": case "left", "h", "ctrl+b":
if m.index-m.height >= 0 { m.index = clamp(m.index-m.height, 0, len(m.items)-1)
m.index -= m.height m.paginator.PrevPage()
}
m.page = m.index / m.height
case "ctrl+c": case "ctrl+c":
m.quitting = true m.quitting = true
return m, tea.Quit return m, tea.Quit
@ -98,7 +96,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
} }
return m, nil var cmd tea.Cmd
m.paginator, cmd = m.paginator.Update(msg)
return m, cmd
} }
func (m model) View() string { func (m model) View() string {
@ -108,7 +108,8 @@ func (m model) View() string {
var s strings.Builder var s strings.Builder
for i, item := range m.items[clamp(m.page*m.height, 0, len(m.items)):clamp((m.page+1)*m.height, 0, len(m.items))] { start, end := m.paginator.GetSliceBounds(len(m.items))
for i, item := range m.items[start:end] {
if i == m.index%m.height { if i == m.index%m.height {
s.WriteString(m.cursorStyle.Render(m.cursor)) s.WriteString(m.cursorStyle.Render(m.cursor))
} else { } else {
@ -122,9 +123,18 @@ func (m model) View() string {
} else { } else {
s.WriteString(m.itemStyle.Render(m.unselectedPrefix + item.text)) s.WriteString(m.itemStyle.Render(m.unselectedPrefix + item.text))
} }
s.WriteRune('\n') if i != m.height {
s.WriteRune('\n')
}
} }
if m.paginator.TotalPages <= 1 {
return s.String()
}
s.WriteString(strings.Repeat("\n", m.height-m.paginator.ItemsOnPage(len(m.items))+1))
s.WriteString(" " + m.paginator.View())
return s.String() return s.String()
} }

View file

@ -6,8 +6,15 @@ import (
"os" "os"
"strings" "strings"
"github.com/charmbracelet/bubbles/paginator"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/gum/internal/stdin" "github.com/charmbracelet/gum/internal/stdin"
"github.com/charmbracelet/lipgloss"
)
var (
subduedStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#847A85", Dark: "#979797"})
verySubduedStyle = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"})
) )
// Run provides a shell script interface for choosing between different through // Run provides a shell script interface for choosing between different through
@ -40,6 +47,21 @@ func (o Options) Run() error {
o.Limit = len(o.Options) o.Limit = len(o.Options)
} }
// Use the pagination model to display the current and total number of
// pages.
pager := paginator.New()
pager.SetTotalPages((len(items) + o.Height - 1) / o.Height)
pager.PerPage = o.Height
pager.Type = paginator.Dots
pager.ActiveDot = subduedStyle.Render("•")
pager.InactiveDot = verySubduedStyle.Render("•")
// Disable Keybindings since we will control it ourselves.
pager.UseHLKeys = false
pager.UseLeftRightKeys = false
pager.UseJKKeys = false
pager.UsePgUpPgDownKeys = false
m, err := tea.NewProgram(model{ m, err := tea.NewProgram(model{
height: o.Height, height: o.Height,
cursor: o.Cursor, cursor: o.Cursor,
@ -48,6 +70,7 @@ func (o Options) Run() error {
cursorPrefix: o.CursorPrefix, cursorPrefix: o.CursorPrefix,
items: items, items: items,
limit: o.Limit, limit: o.Limit,
paginator: pager,
cursorStyle: o.CursorStyle.ToLipgloss(), cursorStyle: o.CursorStyle.ToLipgloss(),
itemStyle: o.ItemStyle.ToLipgloss(), itemStyle: o.ItemStyle.ToLipgloss(),
selectedItemStyle: o.SelectedItemStyle.ToLipgloss(), selectedItemStyle: o.SelectedItemStyle.ToLipgloss(),