quit.go

  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}