quit.go

  1package dialog
  2
  3import (
  4	"github.com/charmbracelet/bubbles/v2/key"
  5	tea "github.com/charmbracelet/bubbletea/v2"
  6	"github.com/charmbracelet/crush/internal/ui/common"
  7	"github.com/charmbracelet/lipgloss/v2"
  8)
  9
 10// QuitDialogKeyMap represents key bindings for the quit dialog.
 11type QuitDialogKeyMap struct {
 12	LeftRight,
 13	EnterSpace,
 14	Yes,
 15	No,
 16	Tab,
 17	Close key.Binding
 18}
 19
 20// DefaultQuitKeyMap returns the default key bindings for the quit dialog.
 21func DefaultQuitKeyMap() QuitDialogKeyMap {
 22	return QuitDialogKeyMap{
 23		LeftRight: key.NewBinding(
 24			key.WithKeys("left", "right"),
 25			key.WithHelp("←/→", "switch options"),
 26		),
 27		EnterSpace: key.NewBinding(
 28			key.WithKeys("enter", " "),
 29			key.WithHelp("enter/space", "confirm"),
 30		),
 31		Yes: key.NewBinding(
 32			key.WithKeys("y", "Y", "ctrl+c"),
 33			key.WithHelp("y/Y/ctrl+c", "yes"),
 34		),
 35		No: key.NewBinding(
 36			key.WithKeys("n", "N"),
 37			key.WithHelp("n/N", "no"),
 38		),
 39		Tab: key.NewBinding(
 40			key.WithKeys("tab"),
 41			key.WithHelp("tab", "switch options"),
 42		),
 43		Close: key.NewBinding(
 44			key.WithKeys("esc", "alt+esc"),
 45			key.WithHelp("esc", "cancel"),
 46		),
 47	}
 48}
 49
 50// Quit represents a confirmation dialog for quitting the application.
 51type Quit struct {
 52	com        *common.Common
 53	keyMap     QuitDialogKeyMap
 54	selectedNo bool // true if "No" button is selected
 55}
 56
 57// NewQuit creates a new quit confirmation dialog.
 58func NewQuit(com *common.Common) *Quit {
 59	q := &Quit{
 60		com:    com,
 61		keyMap: DefaultQuitKeyMap(),
 62	}
 63	return q
 64}
 65
 66// ID implements [Model].
 67func (*Quit) ID() string {
 68	return "quit"
 69}
 70
 71// Update implements [Model].
 72func (q *Quit) Update(msg tea.Msg) (Dialog, tea.Cmd) {
 73	switch msg := msg.(type) {
 74	case tea.KeyPressMsg:
 75		switch {
 76		case key.Matches(msg, q.keyMap.LeftRight, q.keyMap.Tab):
 77			q.selectedNo = !q.selectedNo
 78			return q, nil
 79		case key.Matches(msg, q.keyMap.EnterSpace):
 80			if !q.selectedNo {
 81				return q, tea.Quit
 82			}
 83			return nil, nil
 84		case key.Matches(msg, q.keyMap.Yes):
 85			return q, tea.Quit
 86		case key.Matches(msg, q.keyMap.No, q.keyMap.Close):
 87			return nil, nil
 88		}
 89	}
 90
 91	return q, nil
 92}
 93
 94// View implements [Model].
 95func (q *Quit) View() string {
 96	const question = "Are you sure you want to quit?"
 97	baseStyle := q.com.Styles.Base
 98	yesStyle := q.com.Styles.ButtonSelected
 99	noStyle := q.com.Styles.ButtonUnselected
100
101	if q.selectedNo {
102		noStyle = q.com.Styles.ButtonSelected
103		yesStyle = q.com.Styles.ButtonUnselected
104	}
105
106	const horizontalPadding = 3
107	yesButton := yesStyle.PaddingLeft(horizontalPadding).Underline(true).Render("Y") +
108		yesStyle.PaddingRight(horizontalPadding).Render("ep!")
109	noButton := noStyle.PaddingLeft(horizontalPadding).Underline(true).Render("N") +
110		noStyle.PaddingRight(horizontalPadding).Render("ope")
111
112	buttons := baseStyle.Width(lipgloss.Width(question)).Align(lipgloss.Right).Render(
113		lipgloss.JoinHorizontal(lipgloss.Center, yesButton, "  ", noButton),
114	)
115
116	content := baseStyle.Render(
117		lipgloss.JoinVertical(
118			lipgloss.Center,
119			question,
120			"",
121			buttons,
122		),
123	)
124
125	return q.com.Styles.BorderFocus.Render(content)
126}
127
128// ShortHelp implements [help.KeyMap].
129func (q *Quit) ShortHelp() []key.Binding {
130	return []key.Binding{
131		q.keyMap.LeftRight,
132		q.keyMap.EnterSpace,
133	}
134}
135
136// FullHelp implements [help.KeyMap].
137func (q *Quit) FullHelp() [][]key.Binding {
138	return [][]key.Binding{
139		{q.keyMap.LeftRight, q.keyMap.EnterSpace, q.keyMap.Yes, q.keyMap.No},
140		{q.keyMap.Tab, q.keyMap.Close},
141	}
142}