1package dialog
2
3import (
4 "strings"
5
6 "github.com/charmbracelet/bubbles/key"
7 tea "github.com/charmbracelet/bubbletea"
8 "github.com/charmbracelet/lipgloss"
9 "github.com/kujtimiihoxha/opencode/internal/tui/layout"
10 "github.com/kujtimiihoxha/opencode/internal/tui/styles"
11 "github.com/kujtimiihoxha/opencode/internal/tui/util"
12)
13
14const question = "Are you sure you want to quit?"
15
16type CloseQuitMsg struct{}
17
18type QuitDialog interface {
19 tea.Model
20 layout.Bindings
21}
22
23type quitDialogCmp struct {
24 selectedNo bool
25}
26
27type helpMapping struct {
28 LeftRight key.Binding
29 EnterSpace key.Binding
30 Yes key.Binding
31 No key.Binding
32 Tab key.Binding
33}
34
35var helpKeys = helpMapping{
36 LeftRight: key.NewBinding(
37 key.WithKeys("left", "right"),
38 key.WithHelp("←/→", "switch options"),
39 ),
40 EnterSpace: key.NewBinding(
41 key.WithKeys("enter", " "),
42 key.WithHelp("enter/space", "confirm"),
43 ),
44 Yes: key.NewBinding(
45 key.WithKeys("y", "Y"),
46 key.WithHelp("y/Y", "yes"),
47 ),
48 No: key.NewBinding(
49 key.WithKeys("n", "N"),
50 key.WithHelp("n/N", "no"),
51 ),
52 Tab: key.NewBinding(
53 key.WithKeys("tab"),
54 key.WithHelp("tab", "switch options"),
55 ),
56}
57
58func (q *quitDialogCmp) Init() tea.Cmd {
59 return nil
60}
61
62func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
63 switch msg := msg.(type) {
64 case tea.KeyMsg:
65 switch {
66 case key.Matches(msg, helpKeys.LeftRight) || key.Matches(msg, helpKeys.Tab):
67 q.selectedNo = !q.selectedNo
68 return q, nil
69 case key.Matches(msg, helpKeys.EnterSpace):
70 if !q.selectedNo {
71 return q, tea.Quit
72 }
73 return q, util.CmdHandler(CloseQuitMsg{})
74 case key.Matches(msg, helpKeys.Yes):
75 return q, tea.Quit
76 case key.Matches(msg, helpKeys.No):
77 return q, util.CmdHandler(CloseQuitMsg{})
78 }
79 }
80 return q, nil
81}
82
83func (q *quitDialogCmp) View() string {
84 yesStyle := styles.BaseStyle
85 noStyle := styles.BaseStyle
86 spacerStyle := styles.BaseStyle.Background(styles.Background)
87
88 if q.selectedNo {
89 noStyle = noStyle.Background(styles.PrimaryColor).Foreground(styles.Background)
90 yesStyle = yesStyle.Background(styles.Background).Foreground(styles.PrimaryColor)
91 } else {
92 yesStyle = yesStyle.Background(styles.PrimaryColor).Foreground(styles.Background)
93 noStyle = noStyle.Background(styles.Background).Foreground(styles.PrimaryColor)
94 }
95
96 yesButton := yesStyle.Padding(0, 1).Render("Yes")
97 noButton := noStyle.Padding(0, 1).Render("No")
98
99 buttons := lipgloss.JoinHorizontal(lipgloss.Left, yesButton, spacerStyle.Render(" "), noButton)
100
101 width := lipgloss.Width(question)
102 remainingWidth := width - lipgloss.Width(buttons)
103 if remainingWidth > 0 {
104 buttons = spacerStyle.Render(strings.Repeat(" ", remainingWidth)) + buttons
105 }
106
107 content := styles.BaseStyle.Render(
108 lipgloss.JoinVertical(
109 lipgloss.Center,
110 question,
111 "",
112 buttons,
113 ),
114 )
115
116 return styles.BaseStyle.Padding(1, 2).
117 Border(lipgloss.RoundedBorder()).
118 BorderBackground(styles.Background).
119 BorderForeground(styles.ForgroundDim).
120 Width(lipgloss.Width(content) + 4).
121 Render(content)
122}
123
124func (q *quitDialogCmp) BindingKeys() []key.Binding {
125 return layout.KeyMapToSlice(helpKeys)
126}
127
128func NewQuitCmp() QuitDialog {
129 return &quitDialogCmp{
130 selectedNo: true,
131 }
132}