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