mirror of
https://github.com/charmbracelet/gum
synced 2024-06-01 13:22:26 +02:00
feat: gum choose
, pick from a list of choices
gum choose allows the user to be prompted for a choice from a list of choices. For example, let's ask the user to pick a card from a deck. gum choose --height 15 {Ace,King,Queen,Jack,Ten,Nine,Eight,Seven,Six,Five,Four,Three,Two}" of "{Spades,Hearts,Clubs,Diamonds}
This commit is contained in:
parent
dab3792d5f
commit
46ddc28ae5
13
README.md
13
README.md
|
@ -28,7 +28,7 @@ gum spin --title "Taking a nap..." --color 212 -- sleep 5
|
||||||
find . -type f | gum filter
|
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).
|
||||||
|
|
||||||
<img src="https://stuff.charm.sh/gum/gum.gif" width="900" alt="Shell running the Gum examples/demo.sh script">
|
<img src="https://stuff.charm.sh/gum/gum.gif" width="900" alt="Shell running the Gum examples/demo.sh script">
|
||||||
|
|
||||||
|
@ -101,6 +101,17 @@ echo Cherry >> flavors.text
|
||||||
cat flavors.text | gum filter > selection.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
|
#### Progress
|
||||||
|
|
||||||
Display a progress bar while loading. The following command will display a
|
Display a progress bar while loading. The following command will display a
|
||||||
|
|
101
choose/choose.go
Normal file
101
choose/choose.go
Normal file
|
@ -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
|
||||||
|
}
|
47
choose/command.go
Normal file
47
choose/command.go
Normal file
|
@ -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)
|
||||||
|
}
|
13
choose/options.go
Normal file
13
choose/options.go
Normal file
|
@ -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:""`
|
||||||
|
}
|
14
gum.go
14
gum.go
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/charmbracelet/gum/choose"
|
||||||
"github.com/charmbracelet/gum/filter"
|
"github.com/charmbracelet/gum/filter"
|
||||||
"github.com/charmbracelet/gum/input"
|
"github.com/charmbracelet/gum/input"
|
||||||
"github.com/charmbracelet/gum/join"
|
"github.com/charmbracelet/gum/join"
|
||||||
|
@ -45,6 +46,19 @@ type Gum struct {
|
||||||
//
|
//
|
||||||
Filter filter.Options `cmd:"" help:"Filter options through fuzzy search."`
|
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.
|
// Spin provides a shell script interface for the spinner bubble.
|
||||||
// https://github.com/charmbracelet/bubbles/tree/master/spinner
|
// https://github.com/charmbracelet/bubbles/tree/master/spinner
|
||||||
//
|
//
|
||||||
|
|
8
main.go
8
main.go
|
@ -1,6 +1,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alecthomas/kong"
|
"github.com/alecthomas/kong"
|
||||||
"github.com/charmbracelet/gum/internal/stdin"
|
"github.com/charmbracelet/gum/internal/stdin"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
@ -25,6 +27,12 @@ func main() {
|
||||||
gum.Write.Run()
|
gum.Write.Run()
|
||||||
case "filter":
|
case "filter":
|
||||||
gum.Filter.Run()
|
gum.Filter.Run()
|
||||||
|
case "choose":
|
||||||
|
input, _ := stdin.Read()
|
||||||
|
gum.Choose.Options = strings.Split(input, "\n")
|
||||||
|
gum.Choose.Run()
|
||||||
|
case "choose <options>":
|
||||||
|
gum.Choose.Run()
|
||||||
case "spin <command>":
|
case "spin <command>":
|
||||||
gum.Spin.Run()
|
gum.Spin.Run()
|
||||||
case "progress":
|
case "progress":
|
||||||
|
|
Loading…
Reference in a new issue