package interactive import ( "fmt" "strings" "git.solsynth.dev/goatworks/turbine/pkg/launchpad/config" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) var ( titleStyle = lipgloss.NewStyle(). Background(lipgloss.Color("62")). Foreground(lipgloss.Color("230")). Padding(0, 1) helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241")) ) type model struct { services []config.Service cursor int selected map[int]struct{} startServices bool } func initialModel(services []config.Service) model { selected := make(map[int]struct{}) for i := range services { selected[i] = struct{}{} } return model{ services: services, selected: selected, } } func (m model) Init() tea.Cmd { return nil } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "up", "k": if m.cursor > 0 { m.cursor-- } case "down", "j": if m.cursor < len(m.services)-1 { m.cursor++ } case " ": if _, ok := m.selected[m.cursor]; ok { delete(m.selected, m.cursor) } else { m.selected[m.cursor] = struct{}{} } case "enter": m.startServices = true return m, tea.Quit } } return m, nil } func (m model) View() string { var b strings.Builder b.WriteString(titleStyle.Render("Select services to start")) b.WriteString("\n\n") for i, service := range m.services { cursor := " " if m.cursor == i { cursor = ">" } checked := " " if _, ok := m.selected[i]; ok { checked = "x" } b.WriteString(fmt.Sprintf("%s [%s] %s\n", cursor, checked, service.Name)) } b.WriteString("\n") b.WriteString(helpStyle.Render("Use up/down to navigate, space to select, enter to start, q to quit.")) return b.String() } func (m model) SelectedServices() []config.Service { var selectedServices []config.Service for i := range m.selected { selectedServices = append(selectedServices, m.services[i]) } return selectedServices } func SelectServices(services []config.Service) ([]config.Service, error) { if len(services) == 0 { return nil, fmt.Errorf("no services defined in launchpad.toml") } m := initialModel(services) p := tea.NewProgram(m) finalModel, err := p.Run() if err != nil { return nil, err } fm := finalModel.(model) if fm.startServices { return fm.SelectedServices(), nil } return nil, nil // User quit }