diff --git a/README.md b/README.md
index 14f7e9e..92f6d78 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ gum spin --title "Taking a nap..." --color 212 -- sleep 5
find . -type f | gum filter
```
-The following example is running from a [single Bash script](./examples/demo.sh).
+The following example is running from a [single bash script](./examples/demo.sh).
@@ -101,6 +101,17 @@ echo Cherry >> flavors.text
cat flavors.text | gum filter > selection.text
```
+#### Choose
+
+Ask your users to choose an option from a list of choices.
+
+
+```bash
+echo "Pick a card, any card..."
+CARD=$(gum choose --height 15 {{A,K,Q,J},{10..2}}" "{♠,♥,♣,♦})
+echo "Was your card the $CARD?"
+```
+
#### Progress
Display a progress bar while loading. The following command will display a
diff --git a/choose/choose.go b/choose/choose.go
new file mode 100644
index 0000000..46f5b95
--- /dev/null
+++ b/choose/choose.go
@@ -0,0 +1,101 @@
+package choose
+
+import (
+ "fmt"
+ "io"
+
+ "github.com/charmbracelet/bubbles/list"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+)
+
+type model struct {
+ choice string
+ height int
+ indicator string
+ indicatorStyle lipgloss.Style
+ itemStyle lipgloss.Style
+ items []item
+ list list.Model
+ options []string
+ quitting bool
+ selectedItemStyle lipgloss.Style
+}
+
+type item string
+
+func (i item) FilterValue() string { return "" }
+
+type itemDelegate struct {
+ indicator string
+ indicatorStyle lipgloss.Style
+ itemStyle lipgloss.Style
+ selectedItemStyle lipgloss.Style
+}
+
+func (d itemDelegate) Height() int { return 1 }
+func (d itemDelegate) Spacing() int { return 0 }
+func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
+func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
+ i, ok := listItem.(item)
+ if !ok {
+ return
+ }
+
+ str := fmt.Sprintf("%s", i)
+
+ fn := d.itemStyle.Render
+ if index == m.Index() {
+ fn = func(s string) string {
+ return d.indicatorStyle.Render(d.indicator) + d.selectedItemStyle.Render(s)
+ }
+ }
+
+ fmt.Fprintf(w, fn(str))
+}
+
+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:
+ m.list.SetWidth(msg.Width)
+ return m, nil
+
+ case tea.KeyMsg:
+ switch keypress := msg.String(); keypress {
+ case "ctrl+c":
+ m.quitting = true
+ return m, tea.Quit
+
+ case "enter":
+ m.quitting = true
+ i, ok := m.list.SelectedItem().(item)
+ if ok {
+ m.choice = string(i)
+ }
+ return m, tea.Quit
+ }
+ }
+
+ var cmd tea.Cmd
+ m.list, cmd = m.list.Update(msg)
+ return m, cmd
+}
+
+func (m model) View() string {
+ if m.quitting {
+ return ""
+ }
+ return m.list.View()
+}
+
+func clamp(min, max, val int) int {
+ if val < min {
+ return min
+ }
+ if val > max {
+ return max
+ }
+ return val
+}
diff --git a/choose/command.go b/choose/command.go
new file mode 100644
index 0000000..dfd4fdc
--- /dev/null
+++ b/choose/command.go
@@ -0,0 +1,47 @@
+package choose
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/charmbracelet/bubbles/list"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/mattn/go-runewidth"
+)
+
+// Run provides a shell script interface for choosing between different through
+// options.
+func (o Options) Run() {
+ items := []list.Item{}
+ for _, option := range o.Options {
+ if option == "" {
+ continue
+ }
+ items = append(items, item(option))
+ }
+
+ const defaultWidth = 20
+
+ id := itemDelegate{
+ indicator: o.Indicator,
+ indicatorStyle: lipgloss.NewStyle().Foreground(lipgloss.Color(o.IndicatorColor)),
+ itemStyle: lipgloss.NewStyle().Padding(0, runewidth.StringWidth(o.Indicator)).Foreground(lipgloss.Color(o.UnselectedColor)),
+ selectedItemStyle: lipgloss.NewStyle().Foreground(lipgloss.Color(o.SelectedColor)),
+ }
+
+ l := list.New(items, id, defaultWidth, o.Height)
+ l.SetShowTitle(false)
+ l.SetShowStatusBar(false)
+ l.SetFilteringEnabled(false)
+ l.SetShowHelp(false)
+ l.SetShowPagination(!o.HidePagination)
+
+ m, err := tea.NewProgram(model{list: l}, tea.WithOutput(os.Stderr)).StartReturningModel()
+
+ if err != nil {
+ fmt.Println("Error running program:", err)
+ os.Exit(1)
+ }
+ fmt.Println(m.(model).choice)
+}
diff --git a/choose/options.go b/choose/options.go
new file mode 100644
index 0000000..8011ebb
--- /dev/null
+++ b/choose/options.go
@@ -0,0 +1,13 @@
+package choose
+
+// Options is the customization options for the choose command.
+type Options struct {
+ Options []string `arg:"" optional:"" help:"Options to choose from."`
+
+ Height int `help:"Height of the list." default:"10"`
+ HidePagination bool `help:"Hide pagination." default:"false"`
+ Indicator string `help:"Prefix to show on selected item" default:"> "`
+ IndicatorColor string `help:"Indicator foreground color" default:"212"`
+ SelectedColor string `help:"Selected item foreground color" default:"212"`
+ UnselectedColor string `help:"Unselected item foreground color" default:""`
+}
diff --git a/gum.go b/gum.go
index 669fec2..3d5abdd 100644
--- a/gum.go
+++ b/gum.go
@@ -1,6 +1,7 @@
package main
import (
+ "github.com/charmbracelet/gum/choose"
"github.com/charmbracelet/gum/filter"
"github.com/charmbracelet/gum/input"
"github.com/charmbracelet/gum/join"
@@ -45,6 +46,19 @@ type Gum struct {
//
Filter filter.Options `cmd:"" help:"Filter options through fuzzy search."`
+ // Choose provides an interface to choose one option from a given list of
+ // options. The options can be provided as (new-line separated) stdin or a
+ // list of arguments.
+ //
+ // It is different from the filter command as it does not provide a fuzzy
+ // finding input, so it is best used for smaller lists of options.
+ //
+ // Let's pick from a list of gum flavors:
+ //
+ // $ gum choose "Strawberry" "Banana" "Cherry"
+ //
+ Choose choose.Options `cmd:"" help:"Choose from a list of options."`
+
// Spin provides a shell script interface for the spinner bubble.
// https://github.com/charmbracelet/bubbles/tree/master/spinner
//
diff --git a/main.go b/main.go
index e4306d2..8b7e01a 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,8 @@
package main
import (
+ "strings"
+
"github.com/alecthomas/kong"
"github.com/charmbracelet/gum/internal/stdin"
"github.com/charmbracelet/lipgloss"
@@ -25,6 +27,12 @@ func main() {
gum.Write.Run()
case "filter":
gum.Filter.Run()
+ case "choose":
+ input, _ := stdin.Read()
+ gum.Choose.Options = strings.Split(input, "\n")
+ gum.Choose.Run()
+ case "choose ":
+ gum.Choose.Run()
case "spin ":
gum.Spin.Run()
case "progress":