quit.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package dialog
  2. import (
  3. "strings"
  4. "github.com/charmbracelet/bubbles/key"
  5. tea "github.com/charmbracelet/bubbletea"
  6. "github.com/charmbracelet/lipgloss"
  7. "github.com/sst/opencode/internal/layout"
  8. "github.com/sst/opencode/internal/styles"
  9. "github.com/sst/opencode/internal/theme"
  10. "github.com/sst/opencode/internal/util"
  11. )
  12. const question = "Are you sure you want to quit?"
  13. type CloseQuitMsg struct{}
  14. type QuitDialog interface {
  15. tea.Model
  16. layout.Bindings
  17. }
  18. type quitDialogCmp struct {
  19. selectedNo bool
  20. }
  21. type helpMapping struct {
  22. LeftRight key.Binding
  23. EnterSpace key.Binding
  24. Yes key.Binding
  25. No key.Binding
  26. Tab key.Binding
  27. }
  28. var helpKeys = helpMapping{
  29. LeftRight: key.NewBinding(
  30. key.WithKeys("left", "right"),
  31. key.WithHelp("←/→", "switch options"),
  32. ),
  33. EnterSpace: key.NewBinding(
  34. key.WithKeys("enter", " "),
  35. key.WithHelp("enter/space", "confirm"),
  36. ),
  37. Yes: key.NewBinding(
  38. key.WithKeys("y", "Y"),
  39. key.WithHelp("y/Y", "yes"),
  40. ),
  41. No: key.NewBinding(
  42. key.WithKeys("n", "N"),
  43. key.WithHelp("n/N", "no"),
  44. ),
  45. Tab: key.NewBinding(
  46. key.WithKeys("tab"),
  47. key.WithHelp("tab", "switch options"),
  48. ),
  49. }
  50. func (q *quitDialogCmp) Init() tea.Cmd {
  51. return nil
  52. }
  53. func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  54. switch msg := msg.(type) {
  55. case tea.KeyMsg:
  56. switch {
  57. case key.Matches(msg, helpKeys.LeftRight) || key.Matches(msg, helpKeys.Tab):
  58. q.selectedNo = !q.selectedNo
  59. return q, nil
  60. case key.Matches(msg, helpKeys.EnterSpace):
  61. if !q.selectedNo {
  62. return q, tea.Quit
  63. }
  64. return q, util.CmdHandler(CloseQuitMsg{})
  65. case key.Matches(msg, helpKeys.Yes):
  66. return q, tea.Quit
  67. case key.Matches(msg, helpKeys.No):
  68. return q, util.CmdHandler(CloseQuitMsg{})
  69. }
  70. }
  71. return q, nil
  72. }
  73. func (q *quitDialogCmp) View() string {
  74. t := theme.CurrentTheme()
  75. baseStyle := styles.BaseStyle()
  76. yesStyle := baseStyle
  77. noStyle := baseStyle
  78. spacerStyle := baseStyle.Background(t.Background())
  79. if q.selectedNo {
  80. noStyle = noStyle.Background(t.Primary()).Foreground(t.Background())
  81. yesStyle = yesStyle.Background(t.Background()).Foreground(t.Primary())
  82. } else {
  83. yesStyle = yesStyle.Background(t.Primary()).Foreground(t.Background())
  84. noStyle = noStyle.Background(t.Background()).Foreground(t.Primary())
  85. }
  86. yesButton := yesStyle.Padding(0, 1).Render("Yes")
  87. noButton := noStyle.Padding(0, 1).Render("No")
  88. buttons := lipgloss.JoinHorizontal(lipgloss.Left, yesButton, spacerStyle.Render(" "), noButton)
  89. width := lipgloss.Width(question)
  90. remainingWidth := width - lipgloss.Width(buttons)
  91. if remainingWidth > 0 {
  92. buttons = spacerStyle.Render(strings.Repeat(" ", remainingWidth)) + buttons
  93. }
  94. content := baseStyle.Render(
  95. lipgloss.JoinVertical(
  96. lipgloss.Center,
  97. question,
  98. "",
  99. buttons,
  100. ),
  101. )
  102. return baseStyle.Padding(1, 2).
  103. Border(lipgloss.RoundedBorder()).
  104. BorderBackground(t.Background()).
  105. BorderForeground(t.TextMuted()).
  106. Width(lipgloss.Width(content) + 4).
  107. Render(content)
  108. }
  109. func (q *quitDialogCmp) BindingKeys() []key.Binding {
  110. return layout.KeyMapToSlice(helpKeys)
  111. }
  112. func NewQuitCmp() QuitDialog {
  113. return &quitDialogCmp{
  114. selectedNo: true,
  115. }
  116. }