quit.go

  1package dialog
  2
  3import (
  4	"charm.land/bubbles/v2/key"
  5	tea "charm.land/bubbletea/v2"
  6	"charm.land/lipgloss/v2"
  7	"github.com/charmbracelet/crush/internal/ui/common"
  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		selectedNo: true,
 66	}
 67	return q
 68}
 69
 70// ID implements [Model].
 71func (*Quit) ID() string {
 72	return QuitDialogID
 73}
 74
 75// Update implements [Model].
 76func (q *Quit) Update(msg tea.Msg) (Dialog, tea.Cmd) {
 77	switch msg := msg.(type) {
 78	case tea.KeyPressMsg:
 79		switch {
 80		case key.Matches(msg, q.keyMap.LeftRight, q.keyMap.Tab):
 81			q.selectedNo = !q.selectedNo
 82			return q, nil
 83		case key.Matches(msg, q.keyMap.EnterSpace):
 84			if !q.selectedNo {
 85				return q, tea.Quit
 86			}
 87			return nil, nil
 88		case key.Matches(msg, q.keyMap.Yes):
 89			return q, tea.Quit
 90		case key.Matches(msg, q.keyMap.No, q.keyMap.Close):
 91			return nil, nil
 92		}
 93	}
 94
 95	return q, nil
 96}
 97
 98// View implements [Model].
 99func (q *Quit) View() string {
100	const question = "Are you sure you want to quit?"
101	baseStyle := q.com.Styles.Base
102	buttonOpts := []common.ButtonOpts{
103		{Text: "Yep!", Selected: !q.selectedNo, Padding: 3},
104		{Text: "Nope", Selected: q.selectedNo, Padding: 3},
105	}
106	buttons := common.ButtonGroup(q.com.Styles, buttonOpts, " ")
107	content := baseStyle.Render(
108		lipgloss.JoinVertical(
109			lipgloss.Center,
110			question,
111			"",
112			buttons,
113		),
114	)
115
116	return q.com.Styles.BorderFocus.Render(content)
117}
118
119// ShortHelp implements [help.KeyMap].
120func (q *Quit) ShortHelp() []key.Binding {
121	return []key.Binding{
122		q.keyMap.LeftRight,
123		q.keyMap.EnterSpace,
124	}
125}
126
127// FullHelp implements [help.KeyMap].
128func (q *Quit) FullHelp() [][]key.Binding {
129	return [][]key.Binding{
130		{q.keyMap.LeftRight, q.keyMap.EnterSpace, q.keyMap.Yes, q.keyMap.No},
131		{q.keyMap.Tab, q.keyMap.Close},
132	}
133}