jcloude/libs/fcrestore/tui/optionpicker.go

136 lines
2.6 KiB
Go

package tui
import (
"errors"
"strings"
"time"
tea "git.jingrow.com/charmbracelet/bubbletea"
)
func PickMultipleOptions(prompt string, choices []string) (selectedItems []string, err error) {
m := &multiSelectModel{
choices: choices,
selected: make(map[int]bool),
prompt: prompt,
cursor: 0,
selectedItems: []string{},
}
p := tea.NewProgram(m)
if _, err := p.Run(); err != nil {
return nil, err
}
return m.selectedItems, nil
}
// Internal model implementation
type multiSelectModel struct {
choices []string
selected map[int]bool
cursor int
selectedItems []string
quitting bool
err error
prompt string
}
func (m *multiSelectModel) Init() tea.Cmd {
return tea.Batch(
tea.EnterAltScreen,
tea.HideCursor,
clearErrorAfter(0),
)
}
func (m *multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+q":
m.quitting = true
return m, tea.Quit
case "enter":
// Check if any items are selected
if len(m.selected) == 0 {
m.err = errors.New("Please select at least one option")
return m, clearErrorAfter(2 * time.Second)
}
// Collect selected items
m.selectedItems = make([]string, 0, len(m.selected))
for i := range m.selected {
m.selectedItems = append(m.selectedItems, m.choices[i])
}
m.quitting = true
return m, tea.Quit
case " ":
// Toggle selection
if m.selected[m.cursor] {
delete(m.selected, m.cursor)
} else {
m.selected[m.cursor] = true
}
case "down", "j":
m.cursor++
if m.cursor >= len(m.choices) {
m.cursor = 0
}
case "up", "k":
m.cursor--
if m.cursor < 0 {
m.cursor = len(m.choices) - 1
}
}
case clearErrorMsg:
m.err = nil
}
return m, nil
}
func (m *multiSelectModel) View() string {
if m.quitting {
return ""
}
var s strings.Builder
s.WriteString("\n ")
// Show error or prompt
if m.err != nil {
s.WriteString(titleStyle.Render(BUBBLE_TEA_FILEPICKER_STYLE.DisabledFile.Render(m.err.Error())))
} else {
s.WriteString(titleStyle.Render(m.prompt))
}
s.WriteString("\n\n")
// Show choices
for i, choice := range m.choices {
cursor := " "
if m.cursor == i {
cursor = "> "
}
checkbox := "[ ]"
if m.selected[i] {
checkbox = "[✓]"
}
line := cursor + checkbox + " " + choice
if m.cursor == i {
s.WriteString(selectedItemStyle.Render(line))
} else {
s.WriteString(itemStyle.Render(line))
}
s.WriteString("\n")
}
s.WriteString("\n ↑/↓ to navigate • <space> to select • <enter> to submit • <ctrl+q> to quit\n")
return s.String()
}