mirror of
https://github.com/charmbracelet/gum
synced 2024-05-21 15:46:43 +02:00
Use Huh for Gum Confirm (#522)
* feat: gum confirm with huh Use `huh` for `gum confirm`. * fix: lint
This commit is contained in:
parent
44906e23b9
commit
f7572e387e
|
@ -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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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"`
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue