Implementing multiple choice selection in Go with promptui
This is just a short post about how I chose to implement multiple choice selection in Go with the promptui
CLI library.
promptui
doesn’t natively support multiple choice selection, so I’m using an instance of promptui.Select
to toggle selection status and re-run another selection prompt when an item is chosen via the CLI. When a “Done” item is selected, all the selected items are returned. You can see the code on GitHub, and below (see inline comments):
type item struct {
ID string
IsSelected bool
}
// selectItems() prompts user to select one or more items in the given slice
func selectItems(selectedPos int, allItems []*item) ([]*item, error) {
// Always prepend a "Done" item to the slice if it doesn't
// already exist.
const doneID = "Done"
if len(allItems) > 0 && allItems[0].ID != doneID {
var items = []*item{
{
ID: doneID,
},
}
allItems = append(items, allItems...)
}
// Define promptui template
templates := &promptui.SelectTemplates{
Label: `{{if .IsSelected}}
✔
{{end}} {{ .ID }} - label`,
Active: "→ {{if .IsSelected}}✔ {{end}}{{ .ID | cyan }}",
Inactive: "{{if .IsSelected}}✔ {{end}}{{ .ID | cyan }}",
}
prompt := promptui.Select{
Label: "Item",
Items: allItems,
Templates: templates,
Size: 5,
// Start the cursor at the currently selected index
CursorPos: selectedPos,
HideSelected: true,
}
selectionIdx, _, err := prompt.Run()
if err != nil {
return nil, fmt.Errorf("prompt failed: %w", err)
}
chosenItem := allItems[selectionIdx]
if chosenItem.ID != doneID {
// If the user selected something other than "Done",
// toggle selection on this item and run the function again.
chosenItem.IsSelected = !chosenItem.IsSelected
return selectItems(selectionIdx, allItems)
}
// If the user selected the "Done" item, return
// all selected items.
var selectedItems []*item
for _, i := range allItems {
if i.IsSelected {
selectedItems = append(selectedItems, i)
}
}
return selectedItems, nil
}
The result looks like this: