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