diff --git a/choose/choose.go b/choose/choose.go index a694a7a84..b32030b8a 100644 --- a/choose/choose.go +++ b/choose/choose.go @@ -12,6 +12,9 @@ package choose import ( "strings" + "time" + + "github.com/charmbracelet/gum/timeout" "github.com/charmbracelet/bubbles/paginator" tea "github.com/charmbracelet/bubbletea" @@ -39,6 +42,8 @@ type model struct { headerStyle lipgloss.Style itemStyle lipgloss.Style selectedItemStyle lipgloss.Style + hasTimeout bool + timeout time.Duration } type item struct { @@ -47,13 +52,27 @@ type item struct { order int } -func (m model) Init() tea.Cmd { return nil } +func (m model) Init() tea.Cmd { + return timeout.Init(m.timeout, nil) +} func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: return m, nil - + case timeout.TickTimeoutMsg: + if msg.TimeoutValue <= 0 { + m.quitting = true + // If the user hasn't selected any items in a multi-select. + // Then we select the item that they have pressed enter on. If they + // have selected items, then we simply return them. + if m.numSelected < 1 { + m.items[m.index].selected = true + } + return m, tea.Quit + } + m.timeout = msg.TimeoutValue + return m, timeout.Tick(msg.TimeoutValue, msg.Data) case tea.KeyMsg: start, end := m.paginator.GetSliceBounds(len(m.items)) switch keypress := msg.String(); keypress { @@ -151,6 +170,7 @@ func (m model) View() string { } var s strings.Builder + var timeoutStr string start, end := m.paginator.GetSliceBounds(len(m.items)) for i, item := range m.items[start:end] { @@ -161,7 +181,10 @@ func (m model) View() string { } if item.selected { - s.WriteString(m.selectedItemStyle.Render(m.selectedPrefix + item.text)) + if m.hasTimeout { + timeoutStr = timeout.Str(m.timeout) + } + s.WriteString(m.selectedItemStyle.Render(m.selectedPrefix + item.text + timeoutStr)) } else if i == m.index%m.height { s.WriteString(m.cursorStyle.Render(m.cursorPrefix + item.text)) } else { diff --git a/choose/command.go b/choose/command.go index 3c9322127..7407147aa 100644 --- a/choose/command.go +++ b/choose/command.go @@ -112,6 +112,8 @@ func (o Options) Run() error { itemStyle: o.ItemStyle.ToLipgloss(), selectedItemStyle: o.SelectedItemStyle.ToLipgloss(), numSelected: currentSelected, + hasTimeout: o.Timeout > 0, + timeout: o.Timeout, }, tea.WithOutput(os.Stderr)).Run() if err != nil { diff --git a/choose/options.go b/choose/options.go index 064718c1c..52e3a50d9 100644 --- a/choose/options.go +++ b/choose/options.go @@ -1,23 +1,27 @@ package choose -import "github.com/charmbracelet/gum/style" +import ( + "time" + + "github.com/charmbracelet/gum/style" +) // Options is the customization options for the choose command. type Options struct { - Options []string `arg:"" optional:"" help:"Options to choose from."` - - Limit int `help:"Maximum number of options to pick" default:"1" group:"Selection"` - NoLimit bool `help:"Pick unlimited number of options (ignores limit)" group:"Selection"` - Ordered bool `help:"Maintain the order of the selected options" env:"GUM_CHOOSE_ORDERED"` - Height int `help:"Height of the list" default:"10" env:"GUM_CHOOSE_HEIGHT"` - Cursor string `help:"Prefix to show on item that corresponds to the cursor position" default:"> " env:"GUM_CHOOSE_CURSOR"` - Header string `help:"Header value" default:"" env:"GUM_CHOOSE_HEADER"` - CursorPrefix string `help:"Prefix to show on the cursor item (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_CURSOR_PREFIX"` - SelectedPrefix string `help:"Prefix to show on selected items (hidden if limit is 1)" default:"◉ " env:"GUM_CHOOSE_SELECTED_PREFIX"` - UnselectedPrefix string `help:"Prefix to show on unselected items (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_UNSELECTED_PREFIX"` - Selected []string `help:"Options that should start as selected" default:"" env:"GUM_CHOOSE_SELECTED"` - CursorStyle style.Styles `embed:"" prefix:"cursor." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_CURSOR_"` - HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_CHOOSE_HEADER_"` - ItemStyle style.Styles `embed:"" prefix:"item." hidden:"" envprefix:"GUM_CHOOSE_ITEM_"` - SelectedItemStyle style.Styles `embed:"" prefix:"selected." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_SELECTED_"` + Options []string `arg:"" optional:"" help:"Options to choose from."` + Limit int `help:"Maximum number of options to pick" default:"1" group:"Selection"` + NoLimit bool `help:"Pick unlimited number of options (ignores limit)" group:"Selection"` + Ordered bool `help:"Maintain the order of the selected options" env:"GUM_CHOOSE_ORDERED"` + Height int `help:"Height of the list" default:"10" env:"GUM_CHOOSE_HEIGHT"` + Cursor string `help:"Prefix to show on item that corresponds to the cursor position" default:"> " env:"GUM_CHOOSE_CURSOR"` + Header string `help:"Header value" default:"" env:"GUM_CHOOSE_HEADER"` + CursorPrefix string `help:"Prefix to show on the cursor item (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_CURSOR_PREFIX"` + SelectedPrefix string `help:"Prefix to show on selected items (hidden if limit is 1)" default:"◉ " env:"GUM_CHOOSE_SELECTED_PREFIX"` + UnselectedPrefix string `help:"Prefix to show on unselected items (hidden if limit is 1)" default:"○ " env:"GUM_CHOOSE_UNSELECTED_PREFIX"` + Selected []string `help:"Options that should start as selected" default:"" env:"GUM_CHOOSE_SELECTED"` + CursorStyle style.Styles `embed:"" prefix:"cursor." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_CURSOR_"` + HeaderStyle style.Styles `embed:"" prefix:"header." set:"defaultForeground=240" envprefix:"GUM_CHOOSE_HEADER_"` + ItemStyle style.Styles `embed:"" prefix:"item." hidden:"" envprefix:"GUM_CHOOSE_ITEM_"` + SelectedItemStyle style.Styles `embed:"" prefix:"selected." set:"defaultForeground=212" envprefix:"GUM_CHOOSE_SELECTED_"` + Timeout time.Duration `help:"Timeout until choose returns selected element" default:"0" env:"GUM_CCHOOSE_TIMEOUT"` // including timeout command options [Timeout,...] }