onboarding.go

  1package model
  2
  3import (
  4	"fmt"
  5	"log/slog"
  6	"strings"
  7
  8	"charm.land/bubbles/v2/key"
  9	tea "charm.land/bubbletea/v2"
 10	"charm.land/lipgloss/v2"
 11	"github.com/charmbracelet/crush/internal/config"
 12	"github.com/charmbracelet/crush/internal/home"
 13	"github.com/charmbracelet/crush/internal/ui/common"
 14)
 15
 16// markProjectInitialized marks the current project as initialized in the config.
 17func (m *UI) markProjectInitialized() tea.Msg {
 18	// TODO: handle error so we show it in the tui footer
 19	err := config.MarkProjectInitialized()
 20	if err != nil {
 21		slog.Error(err.Error())
 22	}
 23	return nil
 24}
 25
 26// updateInitializeView handles keyboard input for the project initialization prompt.
 27func (m *UI) updateInitializeView(msg tea.KeyPressMsg) (cmds []tea.Cmd) {
 28	switch {
 29	case key.Matches(msg, m.keyMap.Initialize.Enter):
 30		if m.onboarding.yesInitializeSelected {
 31			cmds = append(cmds, m.initializeProject())
 32		} else {
 33			cmds = append(cmds, m.skipInitializeProject())
 34		}
 35	case key.Matches(msg, m.keyMap.Initialize.Switch):
 36		m.onboarding.yesInitializeSelected = !m.onboarding.yesInitializeSelected
 37	case key.Matches(msg, m.keyMap.Initialize.Yes):
 38		cmds = append(cmds, m.initializeProject())
 39	case key.Matches(msg, m.keyMap.Initialize.No):
 40		cmds = append(cmds, m.skipInitializeProject())
 41	}
 42	return cmds
 43}
 44
 45// initializeProject starts project initialization and transitions to the landing view.
 46func (m *UI) initializeProject() tea.Cmd {
 47	// TODO: initialize the project
 48	// for now we just go to the landing page
 49	m.state = uiLanding
 50	m.focus = uiFocusEditor
 51	// TODO: actually send a message to the agent
 52	return m.markProjectInitialized
 53}
 54
 55// skipInitializeProject skips project initialization and transitions to the landing view.
 56func (m *UI) skipInitializeProject() tea.Cmd {
 57	// TODO: initialize the project
 58	m.state = uiLanding
 59	m.focus = uiFocusEditor
 60	// mark the project as initialized
 61	return m.markProjectInitialized
 62}
 63
 64// initializeView renders the project initialization prompt with Yes/No buttons.
 65func (m *UI) initializeView() string {
 66	cfg := m.com.Config()
 67	s := m.com.Styles.Initialize
 68	cwd := home.Short(cfg.WorkingDir())
 69	initFile := cfg.Options.InitializeAs
 70
 71	header := s.Header.Render("Would you like to initialize this project?")
 72	path := s.Accent.PaddingLeft(2).Render(cwd)
 73	desc := s.Content.Render(fmt.Sprintf("When I initialize your codebase I examine the project and put the result into an %s file which serves as general context.", initFile))
 74	hint := s.Content.Render("You can also initialize anytime via ") + s.Accent.Render("ctrl+p") + s.Content.Render(".")
 75	prompt := s.Content.Render("Would you like to initialize now?")
 76
 77	buttons := common.ButtonGroup(m.com.Styles, []common.ButtonOpts{
 78		{Text: "Yep!", Selected: m.onboarding.yesInitializeSelected},
 79		{Text: "Nope", Selected: !m.onboarding.yesInitializeSelected},
 80	}, " ")
 81
 82	// max width 60 so the text is compact
 83	width := min(m.layout.main.Dx(), 60)
 84
 85	return lipgloss.NewStyle().
 86		Width(width).
 87		Height(m.layout.main.Dy()).
 88		PaddingBottom(1).
 89		AlignVertical(lipgloss.Bottom).
 90		Render(strings.Join(
 91			[]string{
 92				header,
 93				path,
 94				desc,
 95				hint,
 96				prompt,
 97				buttons,
 98			},
 99			"\n\n",
100		))
101}