diff --git a/confirm/command.go b/confirm/command.go new file mode 100644 index 0000000..7522b7b --- /dev/null +++ b/confirm/command.go @@ -0,0 +1,30 @@ +package confirm + +import ( + "os" + + tea "github.com/charmbracelet/bubbletea" +) + +// 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, + vertical: o.Vertical, + selected: 0, + prompt: o.Prompt, + selectedStyle: o.SelectedStyle.ToLipgloss(), + unselectedStyle: o.UnselectedStyle.ToLipgloss(), + promptStyle: o.PromptStyle.ToLipgloss(), + }, tea.WithOutput(os.Stderr)).StartReturningModel() + + if err != nil { + return err + } + + os.Exit(m.(model).selected) + + return nil +} diff --git a/confirm/confirm.go b/confirm/confirm.go new file mode 100644 index 0000000..022a5de --- /dev/null +++ b/confirm/confirm.go @@ -0,0 +1,84 @@ +// 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 ( + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type model struct { + prompt string + affirmative string + negative string + vertical bool + quitting bool + selected int + + // styles + promptStyle lipgloss.Style + selectedStyle lipgloss.Style + unselectedStyle lipgloss.Style +} + +func (m model) Init() tea.Cmd { return nil } + +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 "left", "h", "ctrl+p": + m.selected = m.selected - 1 + if m.selected < 0 { + m.selected = 1 + } + case "right", "l", "ctrl+n": + m.selected = m.selected + 1 + if m.selected > 1 { + m.selected = 0 + } + case "enter": + m.quitting = true + return m, tea.Quit + case "ctrl+c", "q": + m.selected = 1 + m.quitting = true + return m, tea.Quit + } + } + return m, nil +} + +func (m model) View() string { + if m.quitting { + return "" + } + + joinFunc := lipgloss.JoinHorizontal + if m.vertical { + joinFunc = lipgloss.JoinVertical + } + + var aff, neg string + + if m.selected == 0 { + aff = m.selectedStyle.Render(m.affirmative) + neg = m.unselectedStyle.Render(m.negative) + } else { + aff = m.unselectedStyle.Render(m.affirmative) + neg = m.selectedStyle.Render(m.negative) + } + + return lipgloss.JoinVertical(lipgloss.Center, m.promptStyle.Render(m.prompt), joinFunc(lipgloss.Left, aff, neg)) +} diff --git a/confirm/options.go b/confirm/options.go new file mode 100644 index 0000000..ceb9c7b --- /dev/null +++ b/confirm/options.go @@ -0,0 +1,14 @@ +package confirm + +import "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"` + Prompt string `arg:"" help:"Prompt to display." default:"Are you sure?"` + Vertical bool `help:"Whether to display the options vertically" default:"false"` + PromptStyle style.Styles `embed:"" prefix:"prompt." help:"The style of the prompt"` + UnselectedStyle style.Styles `embed:"" prefix:"unselected." help:"The style of the unselected action" set:"defaultBackground=0" set:"defaultPadding=0 3" set:"defaultMargin=1 1"` + SelectedStyle style.Styles `embed:"" prefix:"selected." help:"The style of the selected action" set:"defaultBackground=8" set:"defaultForeground=212" set:"defaultPadding=0 3" set:"defaultMargin=1 1"` +} diff --git a/gum.go b/gum.go index 93ec5b2..917c8d5 100644 --- a/gum.go +++ b/gum.go @@ -3,6 +3,7 @@ package main import ( "github.com/charmbracelet/gum/choose" "github.com/charmbracelet/gum/completion" + "github.com/charmbracelet/gum/confirm" "github.com/charmbracelet/gum/filter" "github.com/charmbracelet/gum/format" "github.com/charmbracelet/gum/input" @@ -34,6 +35,20 @@ type Gum struct { // Choose choose.Options `cmd:"" help:"Choose an option from a list of choices"` + // 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 + // + Confirm confirm.Options `cmd:"" help:"Ask a user to confirm an action"` + // Filter provides a fuzzy searching text input to allow filtering a list of // options to select one option. // diff --git a/main.go b/main.go index af1229f..6f581e4 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,9 @@ func main() { kong.Vars{ "defaultBackground": "", "defaultForeground": "", + "defaultMargin": "0 0", + "defaultPadding": "0 0", + "defaultUnderline": "false", }, ) ctx.Run() diff --git a/style/lipgloss.go b/style/lipgloss.go index ff1f9a0..d8d641a 100644 --- a/style/lipgloss.go +++ b/style/lipgloss.go @@ -22,5 +22,6 @@ func (s Styles) ToLipgloss() lipgloss.Style { Bold(s.Bold). Faint(s.Faint). Italic(s.Italic). - Strikethrough(s.Strikethrough) + Strikethrough(s.Strikethrough). + Underline(s.Underline) } diff --git a/style/options.go b/style/options.go index b757459..452095c 100644 --- a/style/options.go +++ b/style/options.go @@ -18,24 +18,25 @@ type Options struct { // components, through embedding and prefixing. type Styles struct { // Colors - Background string `help:"Background color of the ${name=element}" default:"${defaultBackground}" group:"Style Flags"` - Foreground string `help:"color of the ${name=element}" default:"${defaultForeground}" group:"Style Flags"` + Background string `help:"Background Color" default:"${defaultBackground}" group:"Style Flags"` + Foreground string `help:"Foreground Color" default:"${defaultForeground}" group:"Style Flags"` // Border - Border string `help:"Border style to apply" enum:"none,hidden,normal,rounded,thick,double" default:"none" group:"Style Flags"` - BorderBackground string `help:"Border background color" group:"Style Flags"` - BorderForeground string `help:"Border foreground color" group:"Style Flags"` + Border string `help:"Border Style" enum:"none,hidden,normal,rounded,thick,double" default:"none" group:"Style Flags"` + BorderBackground string `help:"Border Background Color" group:"Style Flags"` + BorderForeground string `help:"Border Foreground Color" group:"Style Flags"` // Layout - Align string `help:"Text alignment" enum:"left,center,right,bottom,middle,top" default:"left" group:"Style Flags"` - Height int `help:"Height of output" group:"Style Flags"` - Width int `help:"Width of output" group:"Style Flags"` - Margin string `help:"Margin to apply around the text." default:"0 0" group:"Style Flags"` - Padding string `help:"Padding to apply around the text." default:"0 0"` + Align string `help:"Text Alignment" enum:"left,center,right,bottom,middle,top" default:"left" group:"Style Flags"` + Height int `help:"Text height" group:"Style Flags"` + Width int `help:"Text width" group:"Style Flags"` + Margin string `help:"Text margin" default:"${defaultMargin}" group:"Style Flags"` + Padding string `help:"Text padding" default:"${defaultPadding}" group:"Style Flags"` // Format - Bold bool `help:"Apply bold formatting" group:"Style Flags"` - Faint bool `help:"Apply faint formatting" group:"Style Flags"` - Italic bool `help:"Apply italic formatting" group:"Style Flags"` - Strikethrough bool `help:"Apply strikethrough formatting" group:"Style Flags"` + Bold bool `help:"Bold text" group:"Style Flags"` + Faint bool `help:"Faint text" group:"Style Flags"` + Italic bool `help:"Italicize text" group:"Style Flags"` + Strikethrough bool `help:"Strikethrough text" group:"Style Flags"` + Underline bool `help:"Underline text" default:"${defaultUnderline}" group:"Style Flags"` }