diff --git a/confirm/command.go b/confirm/command.go index 39bf2d6..f26e52a 100644 --- a/confirm/command.go +++ b/confirm/command.go @@ -17,6 +17,8 @@ func (o Options) Run() error { affirmative: o.Affirmative, negative: o.Negative, confirmation: o.Default, + timeout: o.Timeout, + hasTimeout: o.Timeout > 0, prompt: o.Prompt, selectedStyle: o.SelectedStyle.ToLipgloss(), unselectedStyle: o.UnselectedStyle.ToLipgloss(), diff --git a/confirm/confirm.go b/confirm/confirm.go index 128f4a3..90a7c9c 100644 --- a/confirm/confirm.go +++ b/confirm/confirm.go @@ -11,6 +11,9 @@ package confirm import ( + "fmt" + "time" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) @@ -20,6 +23,8 @@ type model struct { affirmative string negative string quitting bool + hasTimeout bool + timeout time.Duration confirmation bool @@ -29,7 +34,22 @@ type model struct { unselectedStyle lipgloss.Style } -func (m model) Init() tea.Cmd { return nil } +const tickInterval = time.Second + +type tickMsg struct{} + +func tick() tea.Cmd { + return tea.Tick(tickInterval, func(time.Time) tea.Msg { + return tickMsg{} + }) +} + +func (m model) Init() tea.Cmd { + if m.timeout > 0 { + return tick() + } + return nil +} func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { @@ -37,6 +57,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case tea.KeyMsg: switch msg.String() { + case "ctrl+c", "esc", "q", "n", "N": + m.confirmation = false + m.quitting = true + return m, tea.Quit case "left", "h", "ctrl+p", "tab", "right", "l", "ctrl+n", "shift+tab": m.confirmation = !m.confirmation @@ -47,11 +71,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.quitting = true m.confirmation = true return m, tea.Quit - case "ctrl+c", "esc", "q", "n", "N": - m.confirmation = false + } + case tickMsg: + if m.timeout <= 0 { m.quitting = true + m.confirmation = false return m, tea.Quit } + m.timeout -= tickInterval + return m, tick() } return m, nil } @@ -61,15 +89,26 @@ func (m model) View() string { return "" } - var aff, neg string + var aff, neg, timeout string + + if m.hasTimeout { + timeout = fmt.Sprintf(" (%d)", max(0, int(m.timeout.Seconds()))) + } if m.confirmation { aff = m.selectedStyle.Render(m.affirmative) - neg = m.unselectedStyle.Render(m.negative) + neg = m.unselectedStyle.Render(m.negative + timeout) } else { aff = m.unselectedStyle.Render(m.affirmative) - neg = m.selectedStyle.Render(m.negative) + neg = m.selectedStyle.Render(m.negative + timeout) } return lipgloss.JoinVertical(lipgloss.Center, m.promptStyle.Render(m.prompt), lipgloss.JoinHorizontal(lipgloss.Left, aff, neg)) } + +func max(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/confirm/options.go b/confirm/options.go index cca2785..3c0881c 100644 --- a/confirm/options.go +++ b/confirm/options.go @@ -1,14 +1,19 @@ package confirm -import "github.com/charmbracelet/gum/style" +import ( + "time" + + "github.com/charmbracelet/gum/style" +) // Options is the customization options for the confirm command. type Options struct { - Affirmative string `help:"The title of the affirmative action" default:"Yes"` - Negative string `help:"The title of the negative action" default:"No"` - Default bool `help:"Default confirmation action" default:"true"` - Prompt string `arg:"" help:"Prompt to display." default:"Are you sure?"` - PromptStyle style.Styles `embed:"" prefix:"prompt." help:"The style of the prompt" set:"defaultMargin=1 0 0 0" envprefix:"GUM_CONFIRM_PROMPT_"` + Affirmative string `help:"The title of the affirmative action" default:"Yes"` + Negative string `help:"The title of the negative action" default:"No"` + Default bool `help:"Default confirmation action" default:"true"` + Timeout time.Duration `help:"Timeout for confirmation" default:"0" env:"GUM_CONFIRM_TIMEOUT"` + Prompt string `arg:"" help:"Prompt to display." default:"Are you sure?"` + PromptStyle style.Styles `embed:"" prefix:"prompt." help:"The style of the prompt" set:"defaultMargin=1 0 0 0" envprefix:"GUM_CONFIRM_PROMPT_"` //nolint:staticcheck SelectedStyle style.Styles `embed:"" prefix:"selected." help:"The style of the selected action" set:"defaultBackground=212" set:"defaultForeground=230" set:"defaultPadding=0 3" set:"defaultMargin=1 1" envprefix:"GUM_CONFIRM_SELECTED_"` //nolint:staticcheck