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
12 "github.com/charmbracelet/crush/internal/agent"
13 "github.com/charmbracelet/crush/internal/config"
14 "github.com/charmbracelet/crush/internal/home"
15 "github.com/charmbracelet/crush/internal/ui/common"
16 "github.com/charmbracelet/crush/internal/ui/util"
17)
18
19// markProjectInitialized marks the current project as initialized in the config.
20func (m *UI) markProjectInitialized() tea.Msg {
21 // TODO: handle error so we show it in the tui footer
22 err := config.MarkProjectInitialized(m.com.Config())
23 if err != nil {
24 slog.Error(err.Error())
25 }
26 return nil
27}
28
29// updateInitializeView handles keyboard input for the project initialization prompt.
30func (m *UI) updateInitializeView(msg tea.KeyPressMsg) (cmds []tea.Cmd) {
31 switch {
32 case key.Matches(msg, m.keyMap.Initialize.Enter):
33 if m.onboarding.yesInitializeSelected {
34 cmds = append(cmds, m.initializeProject())
35 } else {
36 cmds = append(cmds, m.skipInitializeProject())
37 }
38 case key.Matches(msg, m.keyMap.Initialize.Switch):
39 m.onboarding.yesInitializeSelected = !m.onboarding.yesInitializeSelected
40 case key.Matches(msg, m.keyMap.Initialize.Yes):
41 cmds = append(cmds, m.initializeProject())
42 case key.Matches(msg, m.keyMap.Initialize.No):
43 cmds = append(cmds, m.skipInitializeProject())
44 }
45 return cmds
46}
47
48// initializeProject starts project initialization and transitions to the landing view.
49func (m *UI) initializeProject() tea.Cmd {
50 // clear the session
51 var cmds []tea.Cmd
52 if cmd := m.newSession(); cmd != nil {
53 cmds = append(cmds, cmd)
54 }
55 cfg := m.com.Config()
56
57 initialize := func() tea.Msg {
58 initPrompt, err := agent.InitializePrompt(*cfg)
59 if err != nil {
60 return util.InfoMsg{Type: util.InfoTypeError, Msg: err.Error()}
61 }
62 return sendMessageMsg{Content: initPrompt}
63 }
64 // Mark the project as initialized
65 cmds = append(cmds, initialize, m.markProjectInitialized)
66
67 return tea.Sequence(cmds...)
68}
69
70// skipInitializeProject skips project initialization and transitions to the landing view.
71func (m *UI) skipInitializeProject() tea.Cmd {
72 // TODO: initialize the project
73 m.setState(uiLanding, uiFocusEditor)
74 // mark the project as initialized
75 return m.markProjectInitialized
76}
77
78// initializeView renders the project initialization prompt with Yes/No buttons.
79func (m *UI) initializeView() string {
80 cfg := m.com.Config()
81 s := m.com.Styles.Initialize
82 cwd := home.Short(cfg.WorkingDir())
83 initFile := cfg.Options.InitializeAs
84
85 header := s.Header.Render("Would you like to initialize this project?")
86 path := s.Accent.PaddingLeft(2).Render(cwd)
87 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))
88 hint := s.Content.Render("You can also initialize anytime via ") + s.Accent.Render("ctrl+p") + s.Content.Render(".")
89 prompt := s.Content.Render("Would you like to initialize now?")
90
91 buttons := common.ButtonGroup(m.com.Styles, []common.ButtonOpts{
92 {Text: "Yep!", Selected: m.onboarding.yesInitializeSelected},
93 {Text: "Nope", Selected: !m.onboarding.yesInitializeSelected},
94 }, " ")
95
96 // max width 60 so the text is compact
97 width := min(m.layout.main.Dx(), 60)
98
99 return lipgloss.NewStyle().
100 Width(width).
101 Height(m.layout.main.Dy()).
102 PaddingBottom(1).
103 AlignVertical(lipgloss.Bottom).
104 Render(strings.Join(
105 []string{
106 header,
107 path,
108 desc,
109 hint,
110 prompt,
111 buttons,
112 },
113 "\n\n",
114 ))
115}