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 • to select • to submit • to quit\n") return s.String() }