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