summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaas Lalani <maas@lalani.dev>2024-03-28 14:41:06 -0400
committerGitHub <noreply@github.com>2024-03-28 14:41:06 -0400
commitf7572e387ebc7e9c52cd576b244535a89496860e (patch)
tree2317b0164542463dd5094612a1f1fabd0f685181
parent44906e23b952f28f7315d54516993377d922049b (diff)
Use Huh for Gum Confirm (#522)
* feat: gum confirm with huh Use `huh` for `gum confirm`. * fix: lint
-rw-r--r--confirm/command.go45
-rw-r--r--confirm/confirm.go121
-rw-r--r--confirm/options.go6
3 files changed, 27 insertions, 145 deletions
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"`
}