diff --git a/confirm/command.go b/confirm/command.go index c14ab9e..81f3f56 100644 --- a/confirm/command.go +++ b/confirm/command.go @@ -4,38 +4,41 @@ import ( "fmt" "os" - "github.com/charmbracelet/gum/internal/exit" - - tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" ) // Run provides a shell script interface for prompting a user to confirm an // action with an affirmative or negative answer. func (o Options) Run() error { - m, err := tea.NewProgram(model{ - affirmative: o.Affirmative, - negative: o.Negative, - confirmation: o.Default, - defaultSelection: o.Default, - timeout: o.Timeout, - hasTimeout: o.Timeout > 0, - prompt: o.Prompt, - selectedStyle: o.SelectedStyle.ToLipgloss(), - unselectedStyle: o.UnselectedStyle.ToLipgloss(), - promptStyle: o.PromptStyle.ToLipgloss(), - }, tea.WithOutput(os.Stderr)).Run() + theme := huh.ThemeCharm() + theme.Focused.Base = lipgloss.NewStyle().Margin(0, 1) + theme.Focused.Title = o.PromptStyle.ToLipgloss() + theme.Focused.FocusedButton = o.SelectedStyle.ToLipgloss() + theme.Focused.BlurredButton = o.UnselectedStyle.ToLipgloss() + + choice := o.Default + + err := huh.NewForm( + huh.NewGroup( + huh.NewConfirm(). + Affirmative(o.Affirmative). + Negative(o.Negative). + Title(o.Prompt). + Value(&choice), + ), + ). + WithTheme(theme). + WithShowHelp(false). + Run() if err != nil { return fmt.Errorf("unable to run confirm: %w", err) } - if m.(model).aborted { - os.Exit(exit.StatusAborted) - } else if m.(model).confirmation { - os.Exit(0) + if !choice { + os.Exit(1) } - os.Exit(1) - return nil } diff --git a/confirm/confirm.go b/confirm/confirm.go deleted file mode 100644 index c148da3..0000000 --- a/confirm/confirm.go +++ /dev/null @@ -1,121 +0,0 @@ -// Package confirm provides an interface to ask a user to confirm an action. -// The user is provided with an interface to choose an affirmative or negative -// answer, which is then reflected in the exit code for use in scripting. -// -// If the user selects the affirmative answer, the program exits with 0. If the -// user selects the negative answer, the program exits with 1. -// -// I.e. confirm if the user wants to delete a file -// -// $ gum confirm "Are you sure?" && rm file.txt -package confirm - -import ( - "time" - - "github.com/charmbracelet/gum/timeout" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -type model struct { - prompt string - affirmative string - negative string - quitting bool - aborted bool - hasTimeout bool - timeout time.Duration - - confirmation bool - - defaultSelection bool - - // styles - promptStyle lipgloss.Style - selectedStyle lipgloss.Style - unselectedStyle lipgloss.Style -} - -func (m model) Init() tea.Cmd { - return timeout.Init(m.timeout, m.defaultSelection) -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.WindowSizeMsg: - return m, nil - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c": - m.confirmation = false - m.aborted = true - fallthrough - case "esc": - m.confirmation = false - m.quitting = true - return m, tea.Quit - case "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": - if m.negative == "" { - break - } - m.confirmation = !m.confirmation - case "enter": - m.quitting = true - return m, tea.Quit - case "y", "Y": - m.quitting = true - m.confirmation = true - return m, tea.Quit - } - case timeout.TickTimeoutMsg: - - if msg.TimeoutValue <= 0 { - m.quitting = true - m.confirmation = m.defaultSelection - return m, tea.Quit - } - - m.timeout = msg.TimeoutValue - return m, timeout.Tick(msg.TimeoutValue, msg.Data) - } - return m, nil -} - -func (m model) View() string { - if m.quitting { - return "" - } - - var aff, neg, timeoutStrYes, timeoutStrNo string - timeoutStrNo = "" - timeoutStrYes = "" - if m.hasTimeout { - if m.defaultSelection { - timeoutStrYes = timeout.Str(m.timeout) - } else { - timeoutStrNo = timeout.Str(m.timeout) - } - } - - if m.confirmation { - aff = m.selectedStyle.Render(m.affirmative + timeoutStrYes) - neg = m.unselectedStyle.Render(m.negative + timeoutStrNo) - } else { - aff = m.unselectedStyle.Render(m.affirmative + timeoutStrYes) - neg = m.selectedStyle.Render(m.negative + timeoutStrNo) - } - - // If the option is intentionally empty, do not show it. - if m.negative == "" { - neg = "" - } - - return lipgloss.JoinVertical(lipgloss.Center, m.promptStyle.Render(m.prompt), lipgloss.JoinHorizontal(lipgloss.Left, aff, neg)) -} diff --git a/confirm/options.go b/confirm/options.go index 672c646..eda9484 100644 --- a/confirm/options.go +++ b/confirm/options.go @@ -12,10 +12,10 @@ 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"` 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_"` + PromptStyle style.Styles `embed:"" prefix:"prompt." help:"The style of the prompt" set:"defaultMargin=1 0 0 1" 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_"` + 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=0 1" envprefix:"GUM_CONFIRM_SELECTED_"` //nolint:staticcheck - UnselectedStyle style.Styles `embed:"" prefix:"unselected." help:"The style of the unselected action" set:"defaultBackground=235" set:"defaultForeground=254" set:"defaultPadding=0 3" set:"defaultMargin=1 1" envprefix:"GUM_CONFIRM_UNSELECTED_"` + UnselectedStyle style.Styles `embed:"" prefix:"unselected." help:"The style of the unselected action" set:"defaultBackground=235" set:"defaultForeground=254" set:"defaultPadding=0 3" set:"defaultMargin=0 1" envprefix:"GUM_CONFIRM_UNSELECTED_"` Timeout time.Duration `help:"Timeout until confirm returns selected value or default if provided" default:"0" env:"GUM_CONFIRM_TIMEOUT"` }