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"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/charmbracelet/gum/internal/exit"
|
"github.com/charmbracelet/huh"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run provides a shell script interface for prompting a user to confirm an
|
// Run provides a shell script interface for prompting a user to confirm an
|
||||||
// action with an affirmative or negative answer.
|
// action with an affirmative or negative answer.
|
||||||
func (o Options) Run() error {
|
func (o Options) Run() error {
|
||||||
m, err := tea.NewProgram(model{
|
theme := huh.ThemeCharm()
|
||||||
affirmative: o.Affirmative,
|
theme.Focused.Base = lipgloss.NewStyle().Margin(0, 1)
|
||||||
negative: o.Negative,
|
theme.Focused.Title = o.PromptStyle.ToLipgloss()
|
||||||
confirmation: o.Default,
|
theme.Focused.FocusedButton = o.SelectedStyle.ToLipgloss()
|
||||||
defaultSelection: o.Default,
|
theme.Focused.BlurredButton = o.UnselectedStyle.ToLipgloss()
|
||||||
timeout: o.Timeout,
|
|
||||||
hasTimeout: o.Timeout > 0,
|
choice := o.Default
|
||||||
prompt: o.Prompt,
|
|
||||||
selectedStyle: o.SelectedStyle.ToLipgloss(),
|
err := huh.NewForm(
|
||||||
unselectedStyle: o.UnselectedStyle.ToLipgloss(),
|
huh.NewGroup(
|
||||||
promptStyle: o.PromptStyle.ToLipgloss(),
|
huh.NewConfirm().
|
||||||
}, tea.WithOutput(os.Stderr)).Run()
|
Affirmative(o.Affirmative).
|
||||||
|
Negative(o.Negative).
|
||||||
|
Title(o.Prompt).
|
||||||
|
Value(&choice),
|
||||||
|
),
|
||||||
|
).
|
||||||
|
WithTheme(theme).
|
||||||
|
WithShowHelp(false).
|
||||||
|
Run()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to run confirm: %w", err)
|
return fmt.Errorf("unable to run confirm: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.(model).aborted {
|
if !choice {
|
||||||
os.Exit(exit.StatusAborted)
|
os.Exit(1)
|
||||||
} else if m.(model).confirmation {
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
|
|
||||||
return nil
|
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"`
|
Affirmative string `help:"The title of the affirmative action" default:"Yes"`
|
||||||
Negative string `help:"The title of the negative action" default:"No"`
|
Negative string `help:"The title of the negative action" default:"No"`
|
||||||
Prompt string `arg:"" help:"Prompt to display." default:"Are you sure?"`
|
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
|
//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
|
//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"`
|
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